9b23dff467
This reverts commit r17907. TBR. Review URL: https://codereview.chromium.org//12090093 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@17918 260f80e4-7a28-3924-810f-c04153c831b5
235 lines
7.5 KiB
Dart
235 lines
7.5 KiB
Dart
// 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.
|
|
|
|
part of yaml;
|
|
|
|
// 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 {
|
|
// TODO(nweiz): it would better match the semantics of the spec if there were
|
|
// a singleton instance of this class for each tag.
|
|
|
|
static const SCALAR_KIND = 0;
|
|
static const SEQUENCE_KIND = 1;
|
|
static const MAPPING_KIND = 2;
|
|
|
|
static const String YAML_URI_PREFIX = 'tag:yaml.org,2002:';
|
|
|
|
/// 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(this.name, 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);
|
|
|
|
/// 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 ==(other) {
|
|
if (other is! _Tag) return false;
|
|
return name == other.name;
|
|
}
|
|
|
|
String toString() {
|
|
if (name.startsWith(YAML_URI_PREFIX)) {
|
|
return '!!${name.substring(YAML_URI_PREFIX.length)}';
|
|
} else {
|
|
return '!<$name>';
|
|
}
|
|
}
|
|
|
|
int get hashCode => name.hashCode;
|
|
}
|
|
|
|
/// The abstract class for YAML nodes.
|
|
abstract class _Node {
|
|
/// 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(this.tag, [this.anchor]);
|
|
|
|
bool operator ==(other) {
|
|
if (other is! _Node) return false;
|
|
return tag == other.tag;
|
|
}
|
|
|
|
int get hashCode => _hashCode([tag, anchor]);
|
|
|
|
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, this.content)
|
|
: super(new _Tag.sequence(tagName));
|
|
|
|
/// Two sequences are equal if their tags and contents are equal.
|
|
bool operator ==(other) {
|
|
// Should be super != other; bug 2554
|
|
if (!(super == other) || other is! _SequenceNode) return false;
|
|
if (content.length != other.content.length) return false;
|
|
for (var i = 0; i < content.length; i++) {
|
|
if (content[i] != other.content[i]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
String toString() =>
|
|
'$tag [${Strings.join(content.map((e) => '$e'), ', ')}]';
|
|
|
|
int get 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);
|
|
|
|
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 ==(other) {
|
|
// Should be super != other; bug 2554
|
|
if (!(super == other) || other is! _ScalarNode) return false;
|
|
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(new RegExp("0+e"), "e");
|
|
}
|
|
|
|
if (value is String) {
|
|
// TODO(nweiz): This could be faster if we used a RegExp to check for
|
|
// special characters and short-circuited if they didn't exist.
|
|
|
|
var escapedValue = value.charCodes.map((c) {
|
|
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${zeroPad(c.toRadixString(16).toUpperCase(), 2)}";
|
|
} else if (c >= 0x100 && c < 0x10000) {
|
|
return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 4)}";
|
|
} else if (c >= 0x10000) {
|
|
return "\\u${zeroPad(c.toRadixString(16).toUpperCase(), 8)}";
|
|
} else {
|
|
return new String.fromCharCodes([c]);
|
|
}
|
|
}
|
|
});
|
|
return '"${Strings.join(escapedValue, '')}"';
|
|
}
|
|
|
|
throw new YamlException("unknown scalar value: $value");
|
|
}
|
|
|
|
String toString() => '$tag "$content"';
|
|
|
|
/// Left-pads [str] with zeros so that it's at least [length] characters
|
|
/// long.
|
|
String zeroPad(String str, int length) {
|
|
assert(length >= str.length);
|
|
var prefix = [];
|
|
prefix.insertRange(0, length - str.length, '0');
|
|
return '${Strings.join(prefix, '')}$str';
|
|
}
|
|
|
|
int get 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, this.content)
|
|
: super(new _Tag.mapping(tagName));
|
|
|
|
/// Two mappings are equal if their tags and contents are equal.
|
|
bool operator ==(other) {
|
|
// Should be super != other; bug 2554
|
|
if (!(super == other) || other is! _MappingNode) return false;
|
|
if (content.length != other.content.length) return false;
|
|
for (var key in content.keys) {
|
|
if (!other.content.containsKey(key)) return false;
|
|
if (content[key] != other.content[key]) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
String toString() {
|
|
var strContent = content.keys
|
|
.map((k) => '${k}: ${content[k]}')
|
|
.join(', ');
|
|
return '$tag {$strContent}';
|
|
}
|
|
|
|
int get hashCode => super.hashCode ^ _hashCode(content);
|
|
|
|
visit(_Visitor v) => v.visitMapping(this);
|
|
}
|