blob: 9f1474e9634476284de382c01f72eab3c8785700 [file] [log] [blame]
// 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 [${content.map((e) => '$e').join(', ')}]';
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 '"${escapedValue.join()}"';
}
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 '${prefix.join()}$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);
}