blob: 9f10c181c8816f2729edd80fa28d47b28206c27b [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// A [Node] is an abstract base class for all [Node]s parsed from json
/// constraints.
abstract class Node {
Map<String, dynamic> toJson();
}
/// A [NamedNode] is an abstract base class for all [Node]s that have a name.
abstract class NamedNode extends Node {
final String name;
NamedNode(this.name);
}
/// A [UriAndPrefix] is a simple POD data type wrapping a uri and prefix.
class UriAndPrefix {
final Uri uri;
final String prefix;
UriAndPrefix(this.uri, this.prefix);
@override
String toString() {
return '$uri#$prefix';
}
static UriAndPrefix fromJson(String json) {
var uriAndPrefix = json.split('#');
if (uriAndPrefix.length != 2) {
throw 'Invalid "import" "uri#prefix" value in $json';
}
var uri = Uri.parse(uriAndPrefix[0]);
var prefix = uriAndPrefix[1];
return UriAndPrefix(uri, prefix);
}
}
/// A [ReferenceNode] is a [NamedNode] with a uri and prefix.
class ReferenceNode extends NamedNode {
final UriAndPrefix _uriAndPrefix;
Uri get uri => _uriAndPrefix.uri;
String get prefix => _uriAndPrefix.prefix;
ReferenceNode(this._uriAndPrefix, {name})
: super(name ?? _uriAndPrefix.prefix);
@override
Map<String, dynamic> toJson() {
return {
'type': 'reference',
'name': name,
'import': _uriAndPrefix.toString()
};
}
static ReferenceNode fromJson(Map<String, dynamic> nodeJson) {
if (nodeJson['type'] != 'reference') {
throw 'Unrecognized type for reference node: ${nodeJson['type']}.';
}
return ReferenceNode(UriAndPrefix.fromJson(nodeJson['import']),
name: nodeJson['name']);
}
@override
String toString() {
return 'ReferenceNode(name=$name, uri=$uri, prefix=$prefix)';
}
}
/// A [CombinerType] defines how to combine multiple [ReferenceNode]s in a
/// single step.
enum CombinerType { fuse, and, or }
CombinerType parseCombinerType(Map<String, dynamic> nodeJson) {
String type = nodeJson['type'];
switch (type) {
case 'fuse':
return CombinerType.fuse;
case 'and':
return CombinerType.and;
case 'or':
return CombinerType.or;
default:
throw 'Unrecognized Combiner $nodeJson';
}
}
String combinerTypeToString(CombinerType type) {
switch (type) {
case CombinerType.fuse:
return 'fuse';
case CombinerType.and:
return 'and';
case CombinerType.or:
return 'or';
}
throw 'Unreachable';
}
T _jsonLookup<T>(Map<String, dynamic> nodeJson, String key) {
var value = nodeJson[key];
if (value == null) {
throw 'Missing "$key" key in $nodeJson';
}
return value;
}
NamedNode _jsonLookupNode(
Map<String, dynamic> nodeJson, String key, Map<String, NamedNode> nameMap) {
var node = nameMap[_jsonLookup(nodeJson, key)];
if (node == null) {
throw 'Invalid "$key" name in $nodeJson';
}
return node;
}
/// A [CombinerNode] is a [NamedNode] with a list of [ReferenceNode] children
/// and a [CombinerType] for combining them.
class CombinerNode extends NamedNode {
final CombinerType type;
final Set<ReferenceNode> nodes;
CombinerNode(String name, this.type, this.nodes) : super(name);
@override
Map<String, dynamic> toJson() {
return {
'type': combinerTypeToString(type),
'name': name,
'nodes': nodes.map((node) => node.name).toList()
};
}
static CombinerNode fromJson(
Map<String, dynamic> nodeJson, Map<String, NamedNode> nameMap) {
String name = _jsonLookup(nodeJson, 'name');
List<dynamic> referencesJson = _jsonLookup(nodeJson, 'nodes');
Set<ReferenceNode> references = {};
for (String reference in referencesJson) {
references.add(nameMap[reference]);
}
return CombinerNode(name, parseCombinerType(nodeJson), references);
}
@override
String toString() {
var nodeNames = nodes.map((node) => node.name).join(', ');
return 'CombinerNode(name=$name, type=$type, nodes=$nodeNames)';
}
}
/// A [RelativeOrderNode] is an unnamed [Node] which defines a relative
/// load order between two [NamedNode]s.
class RelativeOrderNode extends Node {
final NamedNode predecessor;
final NamedNode successor;
RelativeOrderNode({this.predecessor, this.successor}) {
// TODO(joshualitt) make these both required parameters.
assert(this.predecessor != null && this.successor != null);
}
@override
Map<String, dynamic> toJson() {
return {
'type': 'order',
'predecessor': predecessor.name,
'successor': successor.name
};
}
static RelativeOrderNode fromJson(
Map<String, dynamic> nodeJson, Map<String, NamedNode> nameMap) {
var predecessor = _jsonLookupNode(nodeJson, 'predecessor', nameMap);
var successor = _jsonLookupNode(nodeJson, 'successor', nameMap);
return RelativeOrderNode(predecessor: predecessor, successor: successor);
}
@override
String toString() {
return 'RelativeOrderNode(predecessor=${predecessor.name}, '
'successor=${successor.name})';
}
}
/// A builder class for constructing constraint nodes.
typedef ReferenceNodeNamer = String Function(UriAndPrefix);
class ProgramSplitBuilder {
final Map<String, NamedNode> namedNodes = {};
ReferenceNodeNamer _referenceNodeNamer;
/// The prefix in the 'uri#prefix' string will become a key to reference this
/// node in other builder calls.
String _prefixNamer(UriAndPrefix uriAndPrefix) => uriAndPrefix.prefix;
/// Override the default reference node namer.
set referenceNodeNamer(ReferenceNodeNamer namer) =>
_referenceNodeNamer = namer;
/// Returns the [ReferenceNodeNamer] to use for naming.
ReferenceNodeNamer get referenceNodeNamer =>
_referenceNodeNamer ?? _prefixNamer;
/// Returns a [ReferenceNode] referencing [importUriAndPrefix].
/// [ReferenceNode]s are typically created in bulk, by mapping over a list of
/// strings of imports in the form 'uri#prefix'. In further builder calls,
/// created nodes can be referenced by their namers, derived from calling
/// [referenceNodeNamer] per [ReferenceNode].
ReferenceNode referenceNode(String importUriAndPrefix) {
var uriAndPrefix = UriAndPrefix.fromJson(importUriAndPrefix);
var referenceNode = ReferenceNode(uriAndPrefix);
var name = referenceNodeNamer(uriAndPrefix);
namedNodes[name] = referenceNode;
return referenceNode;
}
/// Creates an unnamed [RelativeOrderNode] referencing two [NamedNode]s.
RelativeOrderNode orderNode(String predecessor, String successor) {
return RelativeOrderNode(
predecessor: namedNodes[predecessor], successor: namedNodes[successor]);
}
/// Creates a [CombinerNode] which can be referenced by [name] in further
/// calls to the builder.
CombinerNode combinerNode(
String name, List<String> nodes, CombinerType type) {
var combinerNode = CombinerNode(name, type,
nodes.map((name) => namedNodes[name] as ReferenceNode).toSet());
namedNodes[name] = combinerNode;
return combinerNode;
}
/// Creates an 'and' [CombinerNode] which can be referenced by [name] in
/// further calls to the builder.
CombinerNode andNode(String name, List<String> nodes) {
return combinerNode(name, nodes, CombinerType.and);
}
/// Creates a 'fuse' [CombinerNode] which can be referenced by [name] in
/// further calls to the builder.
CombinerNode fuseNode(String name, List<String> nodes) {
return combinerNode(name, nodes, CombinerType.fuse);
}
/// Creates an 'or' [CombinerNode] which can be referenced by [name] in
/// further calls to the builder.
CombinerNode orNode(String name, List<String> nodes) {
return combinerNode(name, nodes, CombinerType.or);
}
}
/// [ConstraintData] is a data object which contains the results of parsing json
/// program split constraints.
class ConstraintData {
final List<NamedNode> named;
final List<RelativeOrderNode> ordered;
ConstraintData(this.named, this.ordered);
}