blob: 13d8382f2badda0a7eba75781fab51654cc6b60c [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is hand-formatted.
// This file must not import `dart:ui`, directly or indirectly, as it is
// intended to function even in pure Dart server or CLI environments.
import 'package:meta/meta.dart';
/// A map whose keys are strings and whose values are [DynamicMap],
/// [DynamicList], int, double, bool, string, and [BlobNode] objects.
///
/// Part of the data type for [DynamicContent] objects.
typedef DynamicMap = Map<String, Object?>;
/// A list whose values are [DynamicMap], [DynamicList], int, double, bool,
/// string, and [BlobNode] objects.
///
/// Part of the data type for [DynamicContent] objects.
typedef DynamicList = List<Object?>;
/// Base class of nodes that appear in the output of [decodeDataBlob] and
/// [decodeLibraryBlob].
///
/// In addition to this, the following types can be found in that output:
///
/// * [DynamicMap]
/// * [DynamicList]
/// * [int]
/// * [double]
/// * [bool]
/// * [String]
abstract class BlobNode {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const BlobNode();
}
bool _listEquals<T>(List<T>? a, List<T>? b) {
if (identical(a, b)) {
return true;
}
if (a == null || b == null || a.length != b.length) {
return false;
}
for (int index = 0; index < a.length; index += 1) {
if (a[index] != b[index]) {
return false;
}
}
return true;
}
/// The name of a widgets library in the RFW package.
///
/// Libraries are typically referred to with names like "core.widgets" or
/// "com.example.shopping.cart". This class represents these names as lists of
/// tokens, in those cases `['core', 'widgets']` and `['com', 'example',
/// 'shopping', 'cart']`, for example.
@immutable
class LibraryName implements Comparable<LibraryName> {
/// Wrap the given list as a [LibraryName].
///
/// The given list is not copied; it is an error to modify it after creating
/// the [LibraryName].
const LibraryName(this.parts);
/// The components of the structured library name.
final List<String> parts;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is LibraryName
&& _listEquals<String>(parts, other.parts);
}
@override
int get hashCode => Object.hashAll(parts);
@override
String toString() => parts.join('.');
@override
int compareTo(LibraryName other) {
for (int index = 0; index < parts.length; index += 1) {
if (other.parts.length <= index) {
return 1;
}
final int result = parts[index].compareTo(other.parts[index]);
if (result != 0) {
return result;
}
}
assert(other.parts.length >= parts.length);
return parts.length - other.parts.length;
}
}
/// The name of a widget used by the RFW package, including its library name.
///
/// This can be used to identify both local widgets and remote widgets.
@immutable
class FullyQualifiedWidgetName implements Comparable<FullyQualifiedWidgetName> {
/// Wrap the given library name and widget name in a [FullyQualifiedWidgetName].
const FullyQualifiedWidgetName(this.library, this.widget);
/// The name of the library in which [widget] can be found.
final LibraryName library;
/// The name of the widget, which should be in the specified [library].
final String widget;
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is FullyQualifiedWidgetName
&& library == other.library && widget == other.widget;
}
@override
int get hashCode => Object.hash(library, widget);
@override
String toString() => '$library:$widget';
@override
int compareTo(FullyQualifiedWidgetName other) {
final int result = library.compareTo(other.library);
if (result != 0) {
return result;
}
return widget.compareTo(other.widget);
}
}
/// The type of the [missing] value.
///
/// This is used internally by the RFW package to avoid needing to use nullable
/// types.
class Missing extends BlobNode {
const Missing._();
@override
String toString() => '<missing>';
}
/// The value used by [DynamicContent] to represent missing data.
///
/// This is return from [DynamicContent.subscribe] when the specified key is not
/// present.
///
/// Content in a [DynamicContent] should not contain [missing] values.
const Missing missing = Missing._();
/// Representation of the `...for` construct in Remote Flutter Widgets library
/// blobs.
class Loop extends BlobNode {
/// Creates a [Loop] with the given [input] and [output].
///
/// The provided objects must not be mutated after being given to the
/// constructor (e.g. the ownership of any lists and maps passes to this
/// object).
const Loop(this.input, this.output);
/// The list on which to iterate.
///
/// This is typically some sort of [Reference], but could be a [DynamicList].
///
/// It is an error for this to be a value that does not resolve to a list.
final Object input;
/// The template to apply for each value on [input].
final Object output;
@override
String toString() => '...for loop in $input: $output';
}
/// Representation of the `switch` construct in Remote Flutter Widgets library
/// blobs.
class Switch extends BlobNode {
/// Creates a [Switch] with the given [input] and [outputs].
///
/// The provided objects must not be mutated after being given to the
/// constructor. In particular, changing the [outputs] map after creating the
/// [Switch] is an error.
const Switch(this.input, this.outputs);
/// The value to switch on (after resolution).
final Object input;
/// The cases for this switch. Keys correspond to values to compare with
/// [input]. The null value is used as the default case.
///
/// At runtime, if none of the keys match [input] and there is no null key,
/// the [Switch] as a whole is treated as if it was [missing]. If the [Switch]
/// is used where a [ConstructorCall] was expected, the result is an
/// [ErrorWidget].
final Map<Object?, Object> outputs;
@override
String toString() => 'switch $input $outputs';
}
/// Representation of references to widgets in Remote Flutter Widgets library
/// blobs.
class ConstructorCall extends BlobNode {
/// Creates a [ConstructorCall] for a widget of the given name in the current
/// library's scope, with the given [arguments].
///
/// The [arguments] must not be mutated after the object is created.
const ConstructorCall(this.name, this.arguments);
/// The name of the widget to create.
///
/// The name is looked up in the current library, or, failing that, in a
/// depth-first search of this library's dependencies.
final String name;
/// The arguments to pass to the constructor.
///
/// Constructors in RFW only have named arguments. This differs from Dart
/// (where arguments can also be positional.)
final DynamicMap arguments;
@override
String toString() => '$name($arguments)';
}
/// Base class for various kinds of references in the RFW data structures.
abstract class Reference extends BlobNode {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
///
/// The [parts] must not be mutated after the object is created.
const Reference(this.parts);
/// The components of the reference. Each entry must be either a String (to
/// index into a [DynamicMap]) an integer (to index into a [DynamicList]).
///
/// It is an error for any of the parts to be of any other type.
final List<Object> parts;
}
/// Unbound reference to arguments.
///
/// This class is used to represent references of the form "args.foo.bar" after
/// parsing, before the arguments are bound.
class ArgsReference extends Reference {
/// Wraps the given [parts] as an [ArgsReference].
///
/// The [parts] must not be mutated after the object is created.
const ArgsReference(List<Object> parts): super(parts);
/// Binds the arguments reference to a specific set of arguments.
///
/// Returns a [BoundArgsReference] with the same [parts] and whose
/// [BoundArgsReference.arguments] is given by `arguments`.
BoundArgsReference bind(Object arguments) {
return BoundArgsReference(arguments, parts);
}
@override
String toString() => 'args.${parts.join(".")}';
}
/// Bound reference to arguments.
///
/// This class is used to represent references of the form "args.foo.bar" after
/// a widget declaration has been bound to specific arguments via a constructor
/// call. The [arguments] property is a reference to the
/// [ConstructorCall.arguments] object (or, more typically, a clone of that
/// object that itself has had references within it bound).
///
/// This class is an internal detail of the RFW [Runtime] and is generally not
/// used directly.
class BoundArgsReference extends Reference {
/// Wraps the given [parts] and [arguments] as an [ArgsReference].
///
/// The parameters must not be mutated after the object is created.
///
/// Generally this class is created using [ArgsReference.bind].
const BoundArgsReference(this.arguments, List<Object> parts): super(parts);
/// The object into which [parts] will be indexed.
///
/// This could contain [Loop]s, which is why it cannot be indexed immediately
/// upon creation.
final Object arguments;
@override
String toString() => 'args($arguments).${parts.join(".")}';
}
/// Reference to the [DynamicContent] data that is passed into the widget (see
/// [Runtime.build]'s `data` argument).
class DataReference extends Reference {
/// Wraps the given [parts] as an [DataReference].
///
/// The [parts] must not be mutated after the object is created.
const DataReference(List<Object> parts): super(parts);
/// Creates a new [DataRefererence] that indexes even deeper than this one.
///
/// For example, suppose a widget's arguments consisted of a map with one key,
/// "a", whose value was a [DataRefererence] referencing "data.foo.bar". Now
/// suppose that the widget itself has an [ArgsReference] that references
/// "args.a.baz". The "args.a" part identifies the aforementioned
/// [DataReference], and so the resulting reference is actually to
/// "data.foo.bar.baz".
///
/// In this example, the [DataReference] to "data.foo.bar" would have its
/// [constructReference] method invoked by the runtime, with `["baz"]` as the
/// `moreParts` argument, so that the resulting [DataReference]'s [parts] is a
/// combination of the original's (`["foo", "bar"]`) and the additional parts
/// provided to the method.
DataReference constructReference(List<Object> moreParts) {
return DataReference(parts + moreParts);
}
@override
String toString() => 'data.${parts.join(".")}';
}
/// Unbound reference to a [Loop].
class LoopReference extends Reference {
/// Wraps the given [loop] and [parts] as a [LoopReference].
///
/// The [parts] must not be mutated after the object is created.
const LoopReference(this.loop, List<Object> parts): super(parts);
/// The index to the referenced loop.
///
/// Loop indices count up, so the nearest loop ancestor of the reference has
/// index zero, with indices counting up when going up the tree towards the
/// root.
final int loop; // this is basically a De Bruijn index
/// Creates a new [LoopRefererence] that indexes even deeper than this one.
///
/// For example, suppose a widget's arguments consisted of a map with one key,
/// "a", whose value was a [LoopRefererence] referencing "loop0.foo.bar". Now
/// suppose that the widget itself has an [ArgsReference] that references
/// "args.a.baz". The "args.a" part identifies the aforementioned
/// [LoopReference], and so the resulting reference is actually to
/// "data.foo.bar.baz".
///
/// In this example, the [LoopReference] to "loop0.foo.bar" would have its
/// [constructReference] method invoked by the runtime, with `["baz"]` as the
/// `moreParts` argument, so that the resulting [LoopReference]'s [parts] is a
/// combination of the original's (`["foo", "bar"]`) and the additional parts
/// provided to the method.
///
/// The [loop] index is maintained in the new object.
LoopReference constructReference(List<Object> moreParts) {
return LoopReference(loop, parts + moreParts);
}
/// Binds the loop reference to a specific value.
///
/// Returns a [BoundLoopReference] with the same [parts] and whose
/// [BoundLoopReference.value] is given by `value`. The [loop] index is
/// dropped in the process.
BoundLoopReference bind(Object value) {
return BoundLoopReference(value, parts);
}
@override
String toString() => 'loop$loop.${parts.join(".")}';
}
/// Bound reference to a [Loop].
///
/// This class is used to represent references of the form "loopvar.foo.bar"
/// after the list containing the relevant loop has been dereferenced so that
/// the loop variable refers to a specific value in the list. The [value] is
/// that resolved value.
///
/// This class is an internal detail of the RFW [Runtime] and is generally not
/// used directly.
class BoundLoopReference extends Reference {
/// Wraps the given [value] and [parts] as a [BoundLoopReference].
///
/// The [parts] must not be mutated after the object is created.
///
/// Generally this class is created using [LoopReference.bind].
const BoundLoopReference(this.value, List<Object> parts): super(parts);
/// The object into which [parts] will index.
///
/// This could contain further [Loop]s or unbound [LoopReference]s, which is
/// why it cannot be indexed immediately upon creation.
final Object value;
/// Creates a new [BoundLoopRefererence] that indexes even deeper than this
/// one.
///
/// For example, suppose a widget's arguments consisted of a map with one key,
/// "a", whose value was a [BoundLoopRefererence] referencing "loop0.foo.bar".
/// Now suppose that the widget itself has an [ArgsReference] that references
/// "args.a.baz". The "args.a" part identifies the aforementioned
/// [BoundLoopReference], and so the resulting reference is actually to
/// "data.foo.bar.baz".
///
/// In this example, the [BoundLoopReference] to "loop0.foo.bar" would have
/// its [constructReference] method invoked by the runtime, with `["baz"]` as
/// the `moreParts` argument, so that the resulting [BoundLoopReference]'s
/// [parts] is a combination of the original's (`["foo", "bar"]`) and the
/// additional parts provided to the method.
///
/// The resolved [value] (which is what the [parts] will eventually index
/// into) is maintained in the new object.
BoundLoopReference constructReference(List<Object> moreParts) {
return BoundLoopReference(value, parts + moreParts);
}
@override
String toString() => 'loop($value).${parts.join(".")}';
}
/// Base class for [StateReference] and [BoundStateReference].
///
/// This is used to ensure [SetStateHandler]'s [SetStateHandler.stateReference]
/// property can only hold a state reference.
abstract class AnyStateReference extends Reference {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
///
/// The [parts] must not be mutated after the object is created.
const AnyStateReference(List<Object> parts): super(parts);
}
/// Unbound reference to remote widget's state.
///
/// This class is used to represent references of the form "state.foo.bar".
class StateReference extends AnyStateReference {
/// Wraps the given [parts] as a [StateReference].
///
/// The [parts] must not be mutated after the object is created.
const StateReference(List<Object> parts): super(parts);
/// Binds the state reference to a specific widget (identified by depth).
///
/// Returns a [BoundStateReference] with the same [parts] and whose
/// [BoundLoopReference.depth] is given by `depth`.
BoundStateReference bind(int depth) {
return BoundStateReference(depth, parts);
}
@override
String toString() => 'state.${parts.join(".")}';
}
/// Bound reference to a remote widget's state.
///
/// This class is used to represent references of the form "state.foo.bar" after
/// the widgets have been constructed, so that the right state can be
/// identified.
///
/// This class is an internal detail of the RFW [Runtime] and is generally not
/// used directly.
class BoundStateReference extends AnyStateReference {
/// Wraps the given [depth] and [parts] as a [BoundStateReference].
///
/// The [parts] must not be mutated after the object is created.
///
/// Generally this class is created using [StateReference.bind].
const BoundStateReference(this.depth, List<Object> parts): super(parts);
/// The widget to whose state the state reference refers.
///
/// This identifies the widget by depth starting at the widget that was
/// created by [Runtime.build] (or a [RemoteWidget], which uses that method).
///
/// Since state references always go up the tree, this is an unambiguous way
/// to reference state, even though in practice in the entire tree multiple
/// widgets may be stateful at the same depth.
final int depth;
/// Creates a new [BoundStateRefererence] that indexes even deeper than this
/// one (deeper into the specified widget's state, not into a deeper widget!).
///
/// For example, suppose a widget's arguments consisted of a map with one key,
/// "a", whose value was a [BoundStateRefererence] referencing "state.foo.bar".
/// Now suppose that the widget itself has an [ArgsReference] that references
/// "args.a.baz". The "args.a" part identifies the aforementioned
/// [BoundStateReference], and so the resulting reference is actually to
/// "state.foo.bar.baz".
///
/// In this example, the [BoundStateReference] to "state.foo.bar" would have
/// its [constructReference] method invoked by the runtime, with `["baz"]` as
/// the `moreParts` argument, so that the resulting [BoundStateReference]'s
/// [parts] is a combination of the original's (`["foo", "bar"]`) and the
/// additional parts provided to the method.
///
/// The [depth] is maintained in the new object.
BoundStateReference constructReference(List<Object> moreParts) {
return BoundStateReference(depth, parts + moreParts);
}
@override
String toString() => 'state^$depth.${parts.join(".")}';
}
/// Base class for [EventHandler] and [SetStateHandler].
///
/// This is used by the [Runtime] to quickly filter out objects that are not
/// event handlers of any kind.
abstract class AnyEventHandler extends BlobNode {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const AnyEventHandler();
}
/// Description of a callback in an RFW widget declaration.
///
/// This represents a signal to send to the application using the RFW package.
/// Typically applications either handle such messages locally, or forward them
/// to a server for further processing.
class EventHandler extends AnyEventHandler {
/// Wraps the given event name and arguments in an [EventHandler] object.
///
/// The [eventArguments] must not be mutated after the object is created.
const EventHandler(this.eventName, this.eventArguments);
/// A string to identify the event. This provides an unambiguous identifier
/// for the event, avoiding the need to establish a convention in the
/// [eventArguments].
final String eventName;
/// The payload to provide with the event.
final DynamicMap eventArguments;
@override
String toString() => 'event $eventName $eventArguments';
}
/// Description of a state setter in an RFW widget declaration.
///
/// This event handler is handled by the RFW [Runtime] itself by setting the
/// state referenced by [stateReference] to the value represented by [value]
/// when the event handler would be invoked.
class SetStateHandler extends AnyEventHandler {
/// Wraps the given [stateReference] and [value] in a [SetStateHandler] object.
///
/// The [value] must not be mutated after the object is created (e.g. in the
/// event that it is a [DynamicMap] or [DynamicList]).
const SetStateHandler(this.stateReference, this.value);
/// Identifies the member in the widget's state to mutate.
final AnyStateReference stateReference;
/// The value to which the specified state will be set.
final Object value;
@override
String toString() => 'set $stateReference = $value';
}
/// A library import.
///
/// Used to describe which libraries a remote widget libraries depends on. The
/// identified libraries can be local or remote. Import loops are invalid.
class Import extends BlobNode {
/// Wraps the given library [name] in an [Import] object.
const Import(this.name);
/// The name of the library to import.
final LibraryName name;
@override
String toString() => 'import $name;';
}
/// A description of a widget in a remote widget library.
///
/// The [root] must be either a [ConstructorCall] or a [Switch] that evaluates
/// to a [ConstructorCall]. (In principle one can imagine that an
/// [ArgsReference] that evaluates to a [ConstructorCall] would also be valid,
/// but such a construct would be redundant and would not provide any additional
/// expressivity, so it is disallowed.)
///
/// The tree rooted at [root] must not contain (directly or indirectly) a
/// [ConstructorCall] that references the widget declared by this
/// [WidgetDeclaration]: widget loops, even indirect loops or loops that would
/// in principle be terminated by use of a [Switch], are not allowed.
class WidgetDeclaration extends BlobNode {
/// Binds the given [name] to the definition given by [root].
///
/// The [initialState] may be null. If it is not, this represents a stateful widget.
const WidgetDeclaration(this.name, this.initialState, this.root) : assert(root is ConstructorCall || root is Switch);
/// The name of the widget that this declaration represents.
///
/// This is the left hand side of a widget declaration.
final String name;
/// If non-null, this is a stateful widget; the value is used to create the
/// initial copy of the state when the widget is created.
final DynamicMap? initialState;
/// The widget to return when this widget is used.
///
/// This is usually a [ConstructorCall], but may be a [Switch] (so long as
/// that [Switch] resolves to a [ConstructorCall]. Other values (or a [Switch]
/// that does not resolve to a constructor call) will result in an
/// [ErrorWidget] being used.
final BlobNode root; // ConstructorCall or Switch
@override
String toString() => 'widget $name = $root;';
}
/// Base class for widget libraries.
abstract class WidgetLibrary {
/// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions.
const WidgetLibrary();
}
/// The in-memory representation of the output of [parseTextLibraryFile] or
/// [decodeLibraryBlob].
class RemoteWidgetLibrary extends WidgetLibrary {
/// Wraps a set of [imports] and [widgets] (widget declarations) in a
/// [RemoteWidgetLibrary] object.
///
/// The provided lists must not be mutated once the library is created.
const RemoteWidgetLibrary(this.imports, this.widgets);
/// The list of libraries that this library depends on.
///
/// This must not be empty, since at least one local widget library must be in
/// scope in order for the remote widget library to be useful.
final List<Import> imports;
/// The list of widgets declared by this library.
///
/// This can be empty.
final List<WidgetDeclaration> widgets;
@override
String toString() => const Iterable<Object>.empty().followedBy(imports).followedBy(widgets).join('\n');
}