Chromium Code Reviews| Index: lib/yaml/model.dart |
| diff --git a/lib/yaml/model.dart b/lib/yaml/model.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3b92baaeb6e64d6bbb78d1173f7276c10a7d3871 |
| --- /dev/null |
| +++ b/lib/yaml/model.dart |
| @@ -0,0 +1,240 @@ |
| +// Copyright (c) 2012, 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. |
| + |
| +// This file contains the node classes for the internal representations of YAML |
| +// documents. These nodes are used for both the serialization tree and the |
| +// representation graph. |
| + |
| +/** A tag that indicates the type of a YAML node. */ |
| +class _Tag implements Hashable { |
| + // TODO(nweiz): it would better match the semantics of the spec if there were |
| + // a singleton instance of this class for each tag. |
| + |
| + static final SCALAR_KIND = 0; |
| + static final SEQUENCE_KIND = 1; |
| + static final MAPPING_KIND = 2; |
| + |
| + /** The name of the tag, either a URI or a local tag beginning with "!". */ |
| + final String name; |
| + |
| + /** The kind of the tag: SCALAR_KIND, SEQUENCE_KIND, or MAPPING_KIND. */ |
| + final int kind; |
| + |
| + _Tag(String this.name, int this.kind); |
| + |
| + _Tag.scalar(String name) : this(name, SCALAR_KIND); |
| + _Tag.sequence(String name) : this(name, SEQUENCE_KIND); |
| + _Tag.mapping(String name) : this(name, MAPPING_KIND); |
|
Bob Nystrom
2012/04/20 20:28:55
These are nice.
|
| + |
| + /** Returns the standard YAML tag URI for [type]. */ |
| + static String yaml(String type) => "tag:yaml.org,2002:$type"; |
| + |
| + /** Two tags are equal if their URIs are equal. */ |
| + operator ==(var other) { |
|
Bob Nystrom
2012/04/20 20:28:55
No "var" here.
nweiz
2012/04/23 23:06:33
Done.
|
| + if (other is! _Tag) return false; |
| + _Tag otherTag = other; |
|
Bob Nystrom
2012/04/20 20:28:55
Unused var.
nweiz
2012/04/23 23:06:33
Done.
|
| + return name == other.name; |
| + } |
| + |
| + String toString() { |
| + if (name.startsWith('tag:yaml.org,2002:')) { |
|
Bob Nystrom
2012/04/20 20:28:55
Pull this string out into a constant?
nweiz
2012/04/23 23:06:33
Done.
|
| + return '!!${name.substring('tag:yaml.org,2002:'.length)}'; |
| + } else { |
| + return '!<$name>'; |
| + } |
| + } |
| + |
| + int hashCode() => name.hashCode(); |
| +} |
| + |
| +/** The abstract class for YAML nodes. */ |
| +class _Node implements Hashable { |
| + /** Every YAML node has a tag that describes its type. */ |
| + _Tag tag; |
| + |
| + /** Any YAML node can have an anchor associated with it. */ |
| + String anchor; |
| + |
| + _Node(_Tag this.tag, [String this.anchor]); |
|
Bob Nystrom
2012/04/20 20:28:55
Don't need types for "this." params. Here and else
nweiz
2012/04/23 23:06:33
Done.
|
| + |
| + bool operator ==(var other) { |
|
Bob Nystrom
2012/04/20 20:28:55
Remove "var" here and elsewhere.
nweiz
2012/04/23 23:06:33
Done.
|
| + if (other is! _Node) return false; |
| + _Node otherNode = other; |
|
Bob Nystrom
2012/04/20 20:28:55
You can ditch this and be consistent with _Tag.
nweiz
2012/04/23 23:06:33
Done.
|
| + return tag == otherNode.tag; |
| + } |
| + |
| + int hashCode() => _hashCode([tag, anchor]); |
| + |
| + abstract visit(_Visitor v); |
| +} |
| + |
| +/** A sequence node represents an ordered list of nodes. */ |
| +class _SequenceNode extends _Node { |
| + /** The nodes in the sequence. */ |
| + List<_Node> content; |
| + |
| + _SequenceNode(String tagName, List<_Node> this.content) |
| + : super(new _Tag.sequence(tagName)); |
| + |
| + /** Two sequences are equal if their tags and contents are equal. */ |
| + bool operator ==(var other) { |
| + // Should be super != other; bug 2554 |
| + if (!(super == other) || other is! _SequenceNode) return false; |
| + _SequenceNode otherSeq = other; |
| + if (content.length != otherSeq.content.length) return false; |
| + for (var i = 0; i < content.length; i++) { |
| + if (content[i] != otherSeq.content[i]) return false; |
| + } |
| + return true; |
| + } |
| + |
| + String toString() => '$tag [${Strings.join(content.map((e) => '$e'), ', ')}]'; |
|
Bob Nystrom
2012/04/20 20:28:55
This map here is weird. Seems silly that Strings.j
nweiz
2012/04/23 23:06:33
Bug 2690 filed.
|
| + |
| + int hashCode() => super.hashCode() ^ _hashCode(content); |
| + |
| + visit(_Visitor v) => v.visitSequence(this); |
| +} |
| + |
| +/** An alias node is a reference to an anchor. */ |
| +class _AliasNode extends _Node { |
| + _AliasNode(String anchor) : super(new _Tag.scalar(_Tag.yaml("str")), anchor); |
|
Bob Nystrom
2012/04/20 20:28:55
Why "str" here?
nweiz
2012/04/23 23:06:33
I needed something. The spec isn't clear on what t
|
| + |
| + visit(_Visitor v) => v.visitAlias(this); |
| +} |
| + |
| +/** A scalar node represents all YAML nodes that have a single value. */ |
| +class _ScalarNode extends _Node { |
| + /** The string value of the scalar node, if it was created by the parser. */ |
| + final String _content; |
| + |
| + /** The Dart value of the scalar node, if it was created by the composer. */ |
| + final value; |
| + |
| + /** |
| + * Creates a new Scalar node. |
| + * |
| + * Exactly one of [content] and [value] should be specified. Content should be |
| + * specified for a newly-parsed scalar that hasn't yet been composed. Value |
| + * should be specified for a composed scalar, although `null` is a valid |
| + * value. |
| + */ |
| + _ScalarNode(String tagName, [String content, this.value]) |
| + : _content = content, |
| + super(new _Tag.scalar(tagName)); |
| + |
| + /** Two scalars are equal if their string representations are equal. */ |
| + bool operator ==(var other) { |
| + // Should be super != other; bug 2554 |
| + if (!(super == other) || other is! _ScalarNode) return false; |
| + _ScalarNode otherScalar = other; |
| + return content == other.content; |
| + } |
| + |
| + /** |
| + * Returns the string representation of the scalar. After composition, this is |
| + * equal to the canonical serialization of the value of the scalar. |
| + */ |
| + String get content() => _content != null ? _content : canonicalContent; |
| + |
| + /** |
| + * Returns the canonical serialization of the value of the scalar. If the |
| + * value isn't given, the result of this will be "null". |
| + */ |
| + String get canonicalContent() { |
| + if (value == null || value is bool || value is int) return '$value'; |
| + |
| + if (value is num) { |
| + // 20 is the maximum value for this argument, which we use since YAML |
| + // doesn't specify a maximum. |
| + return value.toStringAsExponential(20). |
| + replaceFirst(const RegExp("0+e"), "e"); |
| + } |
| + |
| + if (value is String) { |
| + var escapedValue = value.charCodes().map((c) { |
|
Bob Nystrom
2012/04/20 20:28:55
For the common case where the string doesn't conta
nweiz
2012/04/23 23:06:33
Done.
|
| + switch (c) { |
| + case _Parser.TAB: return "\\t"; |
| + case _Parser.LF: return "\\n"; |
| + case _Parser.CR: return "\\r"; |
| + case _Parser.DOUBLE_QUOTE: return '\\"'; |
| + case _Parser.NULL: return "\\0"; |
| + case _Parser.BELL: return "\\a"; |
| + case _Parser.BACKSPACE: return "\\b"; |
| + case _Parser.VERTICAL_TAB: return "\\v"; |
| + case _Parser.FORM_FEED: return "\\f"; |
| + case _Parser.ESCAPE: return "\\e"; |
| + case _Parser.BACKSLASH: return "\\\\"; |
| + case _Parser.NEL: return "\\N"; |
| + case _Parser.NBSP: return "\\_"; |
| + case _Parser.LINE_SEPARATOR: return "\\L"; |
| + case _Parser.PARAGRAPH_SEPARATOR: return "\\P"; |
| + default: |
| + if (c < 0x20 || (c >= 0x7f && c < 0x100)) { |
| + return "\\x${_pad(c.toRadixString(16).toUpperCase(), 2)}"; |
| + } else if (c >= 0x100 && c < 0x10000) { |
| + return "\\u${_pad(c.toRadixString(16).toUpperCase(), 4)}"; |
| + } else if (c >= 0x10000) { |
| + return "\\u${_pad(c.toRadixString(16).toUpperCase(), 8)}"; |
| + } else { |
| + return new String.fromCharCodes([c]); |
| + } |
| + } |
| + }); |
| + return '"${Strings.join(escapedValue, '')}"'; |
| + } |
| + |
| + throw new Error("unknown scalar value: $value"); |
| + } |
| + |
| + String toString() { |
|
Bob Nystrom
2012/04/20 20:28:55
=>
nweiz
2012/04/23 23:06:33
Done.
|
| + return '$tag "$content"'; |
| + } |
| + |
| + /** |
| + * Left-pads [str] with zeros so that it's at least [length] characters |
| + * long. |
| + */ |
| + String _pad(String str, int length) { |
|
Bob Nystrom
2012/04/20 20:28:55
"_zeroPad"?
nweiz
2012/04/23 23:06:33
Done.
|
| + assert(length >= str.length); |
| + var prefix = new List(); |
|
Bob Nystrom
2012/04/20 20:28:55
new List() -> []
nweiz
2012/04/23 23:06:33
Done.
|
| + prefix.insertRange(0, length - str.length, '0'); |
|
Bob Nystrom
2012/04/20 20:28:55
Clever!
nweiz
2012/04/23 23:06:33
I'd rather write '0' * (length - str.length), but
|
| + return '${Strings.join(prefix, '')}$str'; |
| + } |
| + |
| + int hashCode() => super.hashCode() ^ content.hashCode(); |
| + |
| + visit(_Visitor v) => v.visitScalar(this); |
| +} |
| + |
| +/** A mapping node represents an unordered map of nodes to nodes. */ |
| +class _MappingNode extends _Node { |
| + /** The node map. */ |
| + Map<_Node, _Node> content; |
| + |
| + _MappingNode(String tagName, Map<_Node, _Node> this.content) |
| + : super(new _Tag.mapping(tagName)); |
| + |
| + /** Two mappings are equal if their tags and contents are equal. */ |
| + bool operator ==(var other) { |
| + // Should be super != other; bug 2554 |
| + if (!(super == other) || other is! _MappingNode) return false; |
| + _MappingNode otherMap = other; |
| + if (content.length != otherMap.content.length) return false; |
| + for (var key in content.getKeys()) { |
| + if (!otherMap.content.containsKey(key)) return false; |
| + if (content[key] != otherMap.content[key]) return false; |
| + } |
| + return true; |
| + } |
| + |
| + String toString() { |
| + var strContent = Strings.join(content.getKeys(). |
| + map((k) => '${k}: ${content[k]}'), ', '); |
| + return '$tag {$strContent}'; |
| + } |
| + |
| + int hashCode() => super.hashCode() ^ _hashCode(content); |
| + |
| + visit(_Visitor v) => v.visitMapping(this); |
| +} |