OLD | NEW |
| (Empty) |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
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. | |
4 | |
5 library yaml.loader; | |
6 | |
7 import 'package:charcode/ascii.dart'; | |
8 import 'package:source_span/source_span.dart'; | |
9 | |
10 import 'equality.dart'; | |
11 import 'event.dart'; | |
12 import 'parser.dart'; | |
13 import 'yaml_document.dart'; | |
14 import 'yaml_exception.dart'; | |
15 import 'yaml_node.dart'; | |
16 | |
17 /// A loader that reads [Event]s emitted by a [Parser] and emits | |
18 /// [YamlDocument]s. | |
19 /// | |
20 /// This is based on the libyaml loader, available at | |
21 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for | |
22 /// that is available in ../../libyaml-license.txt. | |
23 class Loader { | |
24 /// The underlying [Parser] that generates [Event]s. | |
25 final Parser _parser; | |
26 | |
27 /// Aliases by the alias name. | |
28 final _aliases = new Map<String, YamlNode>(); | |
29 | |
30 /// The span of the entire stream emitted so far. | |
31 FileSpan get span => _span; | |
32 FileSpan _span; | |
33 | |
34 /// Creates a loader that loads [source]. | |
35 /// | |
36 /// [sourceUrl] can be a String or a [Uri]. | |
37 Loader(String source, {sourceUrl}) | |
38 : _parser = new Parser(source, sourceUrl: sourceUrl) { | |
39 var event = _parser.parse(); | |
40 _span = event.span; | |
41 assert(event.type == EventType.STREAM_START); | |
42 } | |
43 | |
44 /// Loads the next document from the stream. | |
45 /// | |
46 /// If there are no more documents, returns `null`. | |
47 YamlDocument load() { | |
48 if (_parser.isDone) return null; | |
49 | |
50 var event = _parser.parse(); | |
51 if (event.type == EventType.STREAM_END) { | |
52 _span = _span.expand(event.span); | |
53 return null; | |
54 } | |
55 | |
56 var document = _loadDocument(event); | |
57 _span = _span.expand(document.span); | |
58 _aliases.clear(); | |
59 return document; | |
60 } | |
61 | |
62 /// Composes a document object. | |
63 YamlDocument _loadDocument(DocumentStartEvent firstEvent) { | |
64 var contents = _loadNode(_parser.parse()); | |
65 | |
66 var lastEvent = _parser.parse(); | |
67 assert(lastEvent.type == EventType.DOCUMENT_END); | |
68 | |
69 return new YamlDocument.internal( | |
70 contents, | |
71 firstEvent.span.expand(lastEvent.span), | |
72 firstEvent.versionDirective, | |
73 firstEvent.tagDirectives, | |
74 startImplicit: firstEvent.isImplicit, | |
75 endImplicit: lastEvent.isImplicit); | |
76 } | |
77 | |
78 /// Composes a node. | |
79 YamlNode _loadNode(Event firstEvent) { | |
80 switch (firstEvent.type) { | |
81 case EventType.ALIAS: return _loadAlias(firstEvent); | |
82 case EventType.SCALAR: return _loadScalar(firstEvent); | |
83 case EventType.SEQUENCE_START: return _loadSequence(firstEvent); | |
84 case EventType.MAPPING_START: return _loadMapping(firstEvent); | |
85 default: throw "Unreachable"; | |
86 } | |
87 } | |
88 | |
89 /// Registers an anchor. | |
90 void _registerAnchor(String anchor, YamlNode node) { | |
91 if (anchor == null) return; | |
92 | |
93 // libyaml throws an error for duplicate anchors, but example 7.1 makes it | |
94 // clear that they should be overridden: | |
95 // http://yaml.org/spec/1.2/spec.html#id2786448. | |
96 | |
97 _aliases[anchor] = node; | |
98 } | |
99 | |
100 /// Composes a node corresponding to an alias. | |
101 YamlNode _loadAlias(AliasEvent event) { | |
102 var alias = _aliases[event.name]; | |
103 if (alias != null) return alias; | |
104 | |
105 throw new YamlException("Undefined alias.", event.span); | |
106 } | |
107 | |
108 /// Composes a scalar node. | |
109 YamlNode _loadScalar(ScalarEvent scalar) { | |
110 var node; | |
111 if (scalar.tag == "!") { | |
112 node = new YamlScalar.internal(scalar.value, scalar); | |
113 } else if (scalar.tag != null) { | |
114 node = _parseByTag(scalar); | |
115 } else { | |
116 node = _parseScalar(scalar); | |
117 } | |
118 | |
119 _registerAnchor(scalar.anchor, node); | |
120 return node; | |
121 } | |
122 | |
123 /// Composes a sequence node. | |
124 YamlNode _loadSequence(SequenceStartEvent firstEvent) { | |
125 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
126 firstEvent.tag != "tag:yaml.org,2002:seq") { | |
127 throw new YamlException("Invalid tag for sequence.", firstEvent.span); | |
128 } | |
129 | |
130 var children = []; | |
131 var node = new YamlList.internal( | |
132 children, firstEvent.span, firstEvent.style); | |
133 _registerAnchor(firstEvent.anchor, node); | |
134 | |
135 var event = _parser.parse(); | |
136 while (event.type != EventType.SEQUENCE_END) { | |
137 children.add(_loadNode(event)); | |
138 event = _parser.parse(); | |
139 } | |
140 | |
141 setSpan(node, firstEvent.span.expand(event.span)); | |
142 return node; | |
143 } | |
144 | |
145 /// Composes a mapping node. | |
146 YamlNode _loadMapping(MappingStartEvent firstEvent) { | |
147 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
148 firstEvent.tag != "tag:yaml.org,2002:map") { | |
149 throw new YamlException("Invalid tag for mapping.", firstEvent.span); | |
150 } | |
151 | |
152 var children = deepEqualsMap(); | |
153 var node = new YamlMap.internal( | |
154 children, firstEvent.span, firstEvent.style); | |
155 _registerAnchor(firstEvent.anchor, node); | |
156 | |
157 var event = _parser.parse(); | |
158 while (event.type != EventType.MAPPING_END) { | |
159 var key = _loadNode(event); | |
160 var value = _loadNode(_parser.parse()); | |
161 children[key] = value; | |
162 event = _parser.parse(); | |
163 } | |
164 | |
165 setSpan(node, firstEvent.span.expand(event.span)); | |
166 return node; | |
167 } | |
168 | |
169 /// Parses a scalar according to its tag name. | |
170 YamlScalar _parseByTag(ScalarEvent scalar) { | |
171 switch (scalar.tag) { | |
172 case "tag:yaml.org,2002:null": | |
173 var result = _parseNull(scalar); | |
174 if (result != null) return result; | |
175 throw new YamlException("Invalid null scalar.", scalar.span); | |
176 case "tag:yaml.org,2002:bool": | |
177 var result = _parseBool(scalar); | |
178 if (result != null) return result; | |
179 throw new YamlException("Invalid bool scalar.", scalar.span); | |
180 case "tag:yaml.org,2002:int": | |
181 var result = _parseNumber(scalar, allowFloat: false); | |
182 if (result != null) return result; | |
183 throw new YamlException("Invalid int scalar.", scalar.span); | |
184 case "tag:yaml.org,2002:float": | |
185 var result = _parseNumber(scalar, allowInt: false); | |
186 if (result != null) return result; | |
187 throw new YamlException("Invalid float scalar.", scalar.span); | |
188 case "tag:yaml.org,2002:str": | |
189 return new YamlScalar.internal(scalar.value, scalar); | |
190 default: | |
191 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); | |
192 } | |
193 } | |
194 | |
195 /// Parses [scalar], which may be one of several types. | |
196 YamlScalar _parseScalar(ScalarEvent scalar) => | |
197 _tryParseScalar(scalar) ?? new YamlScalar.internal(scalar.value, scalar); | |
198 | |
199 /// Tries to parse [scalar]. | |
200 /// | |
201 /// If parsing fails, this returns `null`, indicating that the scalar should | |
202 /// be parsed as a string. | |
203 YamlScalar _tryParseScalar(ScalarEvent scalar) { | |
204 // Quickly check for the empty string, which means null. | |
205 var length = scalar.value.length; | |
206 if (length == 0) return new YamlScalar.internal(null, scalar); | |
207 | |
208 // Dispatch on the first character. | |
209 var firstChar = scalar.value.codeUnitAt(0); | |
210 switch (firstChar) { | |
211 case $dot: | |
212 case $plus: | |
213 case $minus: | |
214 return _parseNumber(scalar); | |
215 case $n: | |
216 case $N: | |
217 return length == 4 ? _parseNull(scalar) : null; | |
218 case $t: | |
219 case $T: | |
220 return length == 4 ? _parseBool(scalar) : null; | |
221 case $f: | |
222 case $F: | |
223 return length == 5 ? _parseBool(scalar) : null; | |
224 case $tilde: | |
225 return length == 1 ? new YamlScalar.internal(null, scalar) : null; | |
226 default: | |
227 if (firstChar >= $0 && firstChar <= $9) return _parseNumber(scalar); | |
228 return null; | |
229 } | |
230 } | |
231 | |
232 /// Parse a null scalar. | |
233 /// | |
234 /// Returns a Dart `null` if parsing fails. | |
235 YamlScalar _parseNull(ScalarEvent scalar) { | |
236 switch (scalar.value) { | |
237 case "": | |
238 case "null": | |
239 case "Null": | |
240 case "NULL": | |
241 case "~": | |
242 return new YamlScalar.internal(null, scalar); | |
243 default: | |
244 return null; | |
245 } | |
246 } | |
247 | |
248 /// Parse a boolean scalar. | |
249 /// | |
250 /// Returns `null` if parsing fails. | |
251 YamlScalar _parseBool(ScalarEvent scalar) { | |
252 switch (scalar.value) { | |
253 case "true": | |
254 case "True": | |
255 case "TRUE": | |
256 return new YamlScalar.internal(true, scalar); | |
257 case "false": | |
258 case "False": | |
259 case "FALSE": | |
260 return new YamlScalar.internal(false, scalar); | |
261 default: | |
262 return null; | |
263 } | |
264 } | |
265 | |
266 /// Parses a numeric scalar. | |
267 /// | |
268 /// Returns `null` if parsing fails. | |
269 YamlNode _parseNumber(ScalarEvent scalar, {bool allowInt: true, | |
270 bool allowFloat: true}) { | |
271 var value = _parseNumberValue(scalar.value, | |
272 allowInt: allowInt, allowFloat: allowFloat); | |
273 return value == null ? null : new YamlScalar.internal(value, scalar); | |
274 } | |
275 | |
276 /// Parses the value of a number. | |
277 /// | |
278 /// Returns the number if it's parsed successfully, or `null` if it's not. | |
279 num _parseNumberValue(String contents, {bool allowInt: true, | |
280 bool allowFloat: true}) { | |
281 assert(allowInt || allowFloat); | |
282 | |
283 var firstChar = contents.codeUnitAt(0); | |
284 var length = contents.length; | |
285 | |
286 // Quick check for single digit integers. | |
287 if (allowInt && length == 1) { | |
288 var value = firstChar - $0; | |
289 return value >= 0 && value <= 9 ? value : null; | |
290 } | |
291 | |
292 var secondChar = contents.codeUnitAt(1); | |
293 | |
294 // Hexadecimal or octal integers. | |
295 if (allowInt && firstChar == $0) { | |
296 // int.parse supports 0x natively. | |
297 if (secondChar == $x) return int.parse(contents, onError: (_) => null); | |
298 | |
299 if (secondChar == $o) { | |
300 var afterRadix = contents.substring(2); | |
301 return int.parse(afterRadix, radix: 8, onError: (_) => null); | |
302 } | |
303 } | |
304 | |
305 // Int or float starting with a digit or a +/- sign. | |
306 if ((firstChar >= $0 && firstChar <= $9) || | |
307 ((firstChar == $plus || firstChar == $minus) && | |
308 secondChar >= $0 && secondChar <= $9)) { | |
309 // Try to parse an int or, failing that, a double. | |
310 var result = null; | |
311 if (allowInt) { | |
312 // Pass "radix: 10" explicitly to ensure that "-0x10", which is valid | |
313 // Dart but invalid YAML, doesn't get parsed. | |
314 result = int.parse(contents, radix: 10, onError: (_) => null); | |
315 } | |
316 | |
317 if (allowFloat) result ??= double.parse(contents, (_) => null); | |
318 return result; | |
319 } | |
320 | |
321 if (!allowFloat) return null; | |
322 | |
323 // Now the only possibility is to parse a float starting with a dot or a | |
324 // sign and a dot, or the signed/unsigned infinity values and not-a-numbers. | |
325 if ((firstChar == $dot && secondChar >= $0 && secondChar <= $9) || | |
326 (firstChar == $minus || firstChar == $plus) && secondChar == $dot) { | |
327 // Starting with a . and a number or a sign followed by a dot. | |
328 if (length == 5) { | |
329 switch (contents) { | |
330 case "+.inf": | |
331 case "+.Inf": | |
332 case "+.INF": | |
333 return double.INFINITY; | |
334 case "-.inf": | |
335 case "-.Inf": | |
336 case "-.INF": | |
337 return -double.INFINITY; | |
338 } | |
339 } | |
340 | |
341 return double.parse(contents, (_) => null); | |
342 } | |
343 | |
344 if (length == 4 && firstChar == $dot) { | |
345 switch (contents) { | |
346 case ".inf": | |
347 case ".Inf": | |
348 case ".INF": | |
349 return double.INFINITY; | |
350 case ".nan": | |
351 case ".NaN": | |
352 case ".NAN": | |
353 return double.NAN; | |
354 } | |
355 } | |
356 | |
357 return null; | |
358 } | |
359 } | |
OLD | NEW |