OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 part of yaml; | 5 part of yaml; |
6 | 6 |
7 /** | 7 /// Takes a parsed YAML document (what the spec calls the "serialization tree") |
8 * Takes a parsed YAML document (what the spec calls the "serialization tree") | 8 /// and resolves aliases, resolves tags, and parses scalars to produce the |
9 * and resolves aliases, resolves tags, and parses scalars to produce the | 9 /// "representation graph". |
10 * "representation graph". | |
11 */ | |
12 class _Composer extends _Visitor { | 10 class _Composer extends _Visitor { |
13 /** The root node of the serialization tree. */ | 11 /// The root node of the serialization tree. |
14 _Node root; | 12 _Node root; |
15 | 13 |
16 /** | 14 /// Map from anchor names to the most recent representation graph node with |
17 * Map from anchor names to the most recent representation graph node with | 15 /// that anchor. |
18 * that anchor. | |
19 */ | |
20 Map<String, _Node> anchors; | 16 Map<String, _Node> anchors; |
21 | 17 |
22 /** | 18 /// The next id to use for the represenation graph's anchors. The spec doesn't |
23 * The next id to use for the represenation graph's anchors. The spec doesn't | 19 /// use anchors in the representation graph, but we do so that the constructor |
24 * use anchors in the representation graph, but we do so that the constructor | 20 /// can ensure that the same node in the representation graph produces the |
25 * can ensure that the same node in the representation graph produces the same | 21 /// same native object. |
26 * native object. | |
27 */ | |
28 int idCounter; | 22 int idCounter; |
29 | 23 |
30 _Composer(this.root) : this.anchors = <String, _Node>{}, this.idCounter = 0; | 24 _Composer(this.root) : this.anchors = <String, _Node>{}, this.idCounter = 0; |
31 | 25 |
32 /** Runs the Composer to produce a representation graph. */ | 26 /// Runs the Composer to produce a representation graph. |
33 _Node compose() => root.visit(this); | 27 _Node compose() => root.visit(this); |
34 | 28 |
35 /** Returns the anchor to which an alias node refers. */ | 29 /// Returns the anchor to which an alias node refers. |
36 _Node visitAlias(_AliasNode alias) { | 30 _Node visitAlias(_AliasNode alias) { |
37 if (!anchors.containsKey(alias.anchor)) { | 31 if (!anchors.containsKey(alias.anchor)) { |
38 throw new YamlException("no anchor for alias ${alias.anchor}"); | 32 throw new YamlException("no anchor for alias ${alias.anchor}"); |
39 } | 33 } |
40 return anchors[alias.anchor]; | 34 return anchors[alias.anchor]; |
41 } | 35 } |
42 | 36 |
43 /** | 37 /// Parses a scalar node according to its tag, or auto-detects the type if no |
44 * Parses a scalar node according to its tag, or auto-detects the type if no | 38 /// tag exists. Currently this only supports the YAML core type schema. |
45 * tag exists. Currently this only supports the YAML core type schema. | |
46 */ | |
47 _Node visitScalar(_ScalarNode scalar) { | 39 _Node visitScalar(_ScalarNode scalar) { |
48 if (scalar.tag.name == "!") { | 40 if (scalar.tag.name == "!") { |
49 return setAnchor(scalar, parseString(scalar.content)); | 41 return setAnchor(scalar, parseString(scalar.content)); |
50 } else if (scalar.tag.name == "?") { | 42 } else if (scalar.tag.name == "?") { |
51 for (var fn in [parseNull, parseBool, parseInt, parseFloat]) { | 43 for (var fn in [parseNull, parseBool, parseInt, parseFloat]) { |
52 var result = fn(scalar.content); | 44 var result = fn(scalar.content); |
53 if (result != null) return result; | 45 if (result != null) return result; |
54 } | 46 } |
55 return setAnchor(scalar, parseString(scalar.content)); | 47 return setAnchor(scalar, parseString(scalar.content)); |
56 } | 48 } |
57 | 49 |
58 // TODO(nweiz): support the full YAML type repository | 50 // TODO(nweiz): support the full YAML type repository |
59 var tagParsers = { | 51 var tagParsers = { |
60 'null': parseNull, 'bool': parseBool, 'int': parseInt, | 52 'null': parseNull, 'bool': parseBool, 'int': parseInt, |
61 'float': parseFloat, 'str': parseString | 53 'float': parseFloat, 'str': parseString |
62 }; | 54 }; |
63 | 55 |
64 for (var key in tagParsers.keys) { | 56 for (var key in tagParsers.keys) { |
65 if (scalar.tag.name != _Tag.yaml(key)) continue; | 57 if (scalar.tag.name != _Tag.yaml(key)) continue; |
66 var result = tagParsers[key](scalar.content); | 58 var result = tagParsers[key](scalar.content); |
67 if (result != null) return setAnchor(scalar, result); | 59 if (result != null) return setAnchor(scalar, result); |
68 throw new YamlException('invalid literal for $key: "${scalar.content}"'); | 60 throw new YamlException('invalid literal for $key: "${scalar.content}"'); |
69 } | 61 } |
70 | 62 |
71 throw new YamlException('undefined tag: "${scalar.tag.name}"'); | 63 throw new YamlException('undefined tag: "${scalar.tag.name}"'); |
72 } | 64 } |
73 | 65 |
74 /** Assigns a tag to the sequence and recursively composes its contents. */ | 66 /// Assigns a tag to the sequence and recursively composes its contents. |
75 _Node visitSequence(_SequenceNode seq) { | 67 _Node visitSequence(_SequenceNode seq) { |
76 var tagName = seq.tag.name; | 68 var tagName = seq.tag.name; |
77 if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("seq")) { | 69 if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("seq")) { |
78 throw new YamlException("invalid tag for sequence: ${tagName}"); | 70 throw new YamlException("invalid tag for sequence: ${tagName}"); |
79 } | 71 } |
80 | 72 |
81 var result = setAnchor(seq, new _SequenceNode(_Tag.yaml("seq"), null)); | 73 var result = setAnchor(seq, new _SequenceNode(_Tag.yaml("seq"), null)); |
82 result.content = super.visitSequence(seq); | 74 result.content = super.visitSequence(seq); |
83 return result; | 75 return result; |
84 } | 76 } |
85 | 77 |
86 /** Assigns a tag to the mapping and recursively composes its contents. */ | 78 /// Assigns a tag to the mapping and recursively composes its contents. |
87 _Node visitMapping(_MappingNode map) { | 79 _Node visitMapping(_MappingNode map) { |
88 var tagName = map.tag.name; | 80 var tagName = map.tag.name; |
89 if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("map")) { | 81 if (tagName != "!" && tagName != "?" && tagName != _Tag.yaml("map")) { |
90 throw new YamlException("invalid tag for mapping: ${tagName}"); | 82 throw new YamlException("invalid tag for mapping: ${tagName}"); |
91 } | 83 } |
92 | 84 |
93 var result = setAnchor(map, new _MappingNode(_Tag.yaml("map"), null)); | 85 var result = setAnchor(map, new _MappingNode(_Tag.yaml("map"), null)); |
94 result.content = super.visitMapping(map); | 86 result.content = super.visitMapping(map); |
95 return result; | 87 return result; |
96 } | 88 } |
97 | 89 |
98 /** | 90 /// If the serialization tree node [anchored] has an anchor, records that |
99 * If the serialization tree node [anchored] has an anchor, records that | 91 /// that anchor is pointing to the representation graph node [result]. |
100 * that anchor is pointing to the representation graph node [result]. | |
101 */ | |
102 _Node setAnchor(_Node anchored, _Node result) { | 92 _Node setAnchor(_Node anchored, _Node result) { |
103 if (anchored.anchor == null) return result; | 93 if (anchored.anchor == null) return result; |
104 result.anchor = '${idCounter++}'; | 94 result.anchor = '${idCounter++}'; |
105 anchors[anchored.anchor] = result; | 95 anchors[anchored.anchor] = result; |
106 return result; | 96 return result; |
107 } | 97 } |
108 | 98 |
109 /** Parses a null scalar. */ | 99 /// Parses a null scalar. |
110 _ScalarNode parseNull(String content) { | 100 _ScalarNode parseNull(String content) { |
111 if (!new RegExp("^(null|Null|NULL|~|)\$").hasMatch(content)) return null; | 101 if (!new RegExp("^(null|Null|NULL|~|)\$").hasMatch(content)) return null; |
112 return new _ScalarNode(_Tag.yaml("null"), value: null); | 102 return new _ScalarNode(_Tag.yaml("null"), value: null); |
113 } | 103 } |
114 | 104 |
115 /** Parses a boolean scalar. */ | 105 /// Parses a boolean scalar. |
116 _ScalarNode parseBool(String content) { | 106 _ScalarNode parseBool(String content) { |
117 var match = new RegExp("^(?:(true|True|TRUE)|(false|False|FALSE))\$"). | 107 var match = new RegExp("^(?:(true|True|TRUE)|(false|False|FALSE))\$"). |
118 firstMatch(content); | 108 firstMatch(content); |
119 if (match == null) return null; | 109 if (match == null) return null; |
120 return new _ScalarNode(_Tag.yaml("bool"), value: match.group(1) != null); | 110 return new _ScalarNode(_Tag.yaml("bool"), value: match.group(1) != null); |
121 } | 111 } |
122 | 112 |
123 /** Parses an integer scalar. */ | 113 /// Parses an integer scalar. |
124 _ScalarNode parseInt(String content) { | 114 _ScalarNode parseInt(String content) { |
125 var match = new RegExp("^[-+]?[0-9]+\$").firstMatch(content); | 115 var match = new RegExp("^[-+]?[0-9]+\$").firstMatch(content); |
126 if (match != null) { | 116 if (match != null) { |
127 return new _ScalarNode(_Tag.yaml("int"), | 117 return new _ScalarNode(_Tag.yaml("int"), |
128 value: Math.parseInt(match.group(0))); | 118 value: Math.parseInt(match.group(0))); |
129 } | 119 } |
130 | 120 |
131 match = new RegExp("^0o([0-7]+)\$").firstMatch(content); | 121 match = new RegExp("^0o([0-7]+)\$").firstMatch(content); |
132 if (match != null) { | 122 if (match != null) { |
133 // TODO(nweiz): clean this up when Dart can parse an octal string | 123 // TODO(nweiz): clean this up when Dart can parse an octal string |
134 var n = 0; | 124 var n = 0; |
135 for (var c in match.group(1).charCodes) { | 125 for (var c in match.group(1).charCodes) { |
136 n *= 8; | 126 n *= 8; |
137 n += c - 48; | 127 n += c - 48; |
138 } | 128 } |
139 return new _ScalarNode(_Tag.yaml("int"), value: n); | 129 return new _ScalarNode(_Tag.yaml("int"), value: n); |
140 } | 130 } |
141 | 131 |
142 match = new RegExp("^0x[0-9a-fA-F]+\$").firstMatch(content); | 132 match = new RegExp("^0x[0-9a-fA-F]+\$").firstMatch(content); |
143 if (match != null) { | 133 if (match != null) { |
144 return new _ScalarNode(_Tag.yaml("int"), | 134 return new _ScalarNode(_Tag.yaml("int"), |
145 value: Math.parseInt(match.group(0))); | 135 value: Math.parseInt(match.group(0))); |
146 } | 136 } |
147 | 137 |
148 return null; | 138 return null; |
149 } | 139 } |
150 | 140 |
151 /** Parses a floating-point scalar. */ | 141 /// Parses a floating-point scalar. |
152 _ScalarNode parseFloat(String content) { | 142 _ScalarNode parseFloat(String content) { |
153 var match = new RegExp( | 143 var match = new RegExp( |
154 "^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?\$"). | 144 "^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?\$"). |
155 firstMatch(content); | 145 firstMatch(content); |
156 if (match != null) { | 146 if (match != null) { |
157 // YAML allows floats of the form "0.", but Dart does not. Fix up those | 147 // YAML allows floats of the form "0.", but Dart does not. Fix up those |
158 // floats by removing the trailing dot. | 148 // floats by removing the trailing dot. |
159 var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); | 149 var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); |
160 return new _ScalarNode(_Tag.yaml("float"), | 150 return new _ScalarNode(_Tag.yaml("float"), |
161 value: Math.parseDouble(matchStr)); | 151 value: Math.parseDouble(matchStr)); |
162 } | 152 } |
163 | 153 |
164 match = new RegExp("^([+-]?)\.(inf|Inf|INF)\$").firstMatch(content); | 154 match = new RegExp("^([+-]?)\.(inf|Inf|INF)\$").firstMatch(content); |
165 if (match != null) { | 155 if (match != null) { |
166 var infinityStr = match.group(1) == "-" ? "-Infinity" : "Infinity"; | 156 var infinityStr = match.group(1) == "-" ? "-Infinity" : "Infinity"; |
167 return new _ScalarNode(_Tag.yaml("float"), | 157 return new _ScalarNode(_Tag.yaml("float"), |
168 value: Math.parseDouble(infinityStr)); | 158 value: Math.parseDouble(infinityStr)); |
169 } | 159 } |
170 | 160 |
171 match = new RegExp("^\.(nan|NaN|NAN)\$").firstMatch(content); | 161 match = new RegExp("^\.(nan|NaN|NAN)\$").firstMatch(content); |
172 if (match != null) { | 162 if (match != null) { |
173 return new _ScalarNode(_Tag.yaml("float"), | 163 return new _ScalarNode(_Tag.yaml("float"), |
174 value: Math.parseDouble("NaN")); | 164 value: Math.parseDouble("NaN")); |
175 } | 165 } |
176 | 166 |
177 return null; | 167 return null; |
178 } | 168 } |
179 | 169 |
180 /** Parses a string scalar. */ | 170 /// Parses a string scalar. |
181 _ScalarNode parseString(String content) => | 171 _ScalarNode parseString(String content) => |
182 new _ScalarNode(_Tag.yaml("str"), value: content); | 172 new _ScalarNode(_Tag.yaml("str"), value: content); |
183 } | 173 } |
OLD | NEW |