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:source_span/source_span.dart'; | |
8 | |
9 import 'equality.dart'; | |
10 import 'event.dart'; | |
11 import 'parser.dart'; | |
12 import 'yaml_document.dart'; | |
13 import 'yaml_exception.dart'; | |
14 import 'yaml_node.dart'; | |
15 | |
16 /// A loader that reads [Event]s emitted by a [Parser] and emits | |
17 /// [YamlDocument]s. | |
18 /// | |
19 /// This is based on the libyaml loader, available at | |
20 /// https://github.com/yaml/libyaml/blob/master/src/loader.c. The license for | |
21 /// that is available in ../../libyaml-license.txt. | |
22 class Loader { | |
Bob Nystrom
2014/10/31 20:03:27
The separation between Loader and Parser is intere
nweiz
2014/11/04 22:19:36
It might make sense to expose the parser on its ow
| |
23 /// The underlying [Parser] that generates [Event]s. | |
24 final Parser _parser; | |
25 | |
26 /// Aliases by the alias name. | |
27 final _aliases = new Map<String, YamlNode>(); | |
28 | |
29 /// The span of the entire stream emitted so far. | |
30 FileSpan get span => _span; | |
31 FileSpan _span; | |
32 | |
33 /// Creates a parser that loads [source]. | |
Bob Nystrom
2014/10/31 20:03:27
"loader".
nweiz
2014/11/04 22:19:36
Done.
| |
34 /// | |
35 /// [sourceUrl] can be a String or a [Uri]. | |
36 Loader(String source, {sourceUrl}) | |
37 : _parser = new Parser(source, sourceUrl: sourceUrl) { | |
38 var event = _parser.parse(); | |
39 _span = event.span; | |
40 assert(event.type == EventType.STREAM_START); | |
41 } | |
42 | |
43 /// Loads the next document from the stream. | |
44 YamlDocument load() { | |
45 if (_parser.isDone) return null; | |
Bob Nystrom
2014/10/31 20:03:27
Document this.
nweiz
2014/11/04 22:19:36
Done.
| |
46 | |
47 var event = _parser.parse(); | |
48 if (event.type == EventType.STREAM_END) { | |
49 _span = _span.expand(event.span); | |
50 return null; | |
51 } | |
52 | |
53 var document = _loadDocument(event); | |
54 _span = _span.expand(document.span); | |
55 _aliases.clear(); | |
56 return document; | |
57 } | |
58 | |
59 /// Composes a document object. | |
60 YamlDocument _loadDocument(DocumentStartEvent firstEvent) { | |
61 var contents = _loadNode(_parser.parse()); | |
62 | |
63 var lastEvent = _parser.parse(); | |
64 assert(lastEvent.type == EventType.DOCUMENT_END); | |
65 | |
66 return new YamlDocument.internal( | |
67 contents, | |
68 firstEvent.span.expand(lastEvent.span), | |
69 firstEvent.versionDirective, | |
70 firstEvent.tagDirectives, | |
71 startImplicit: firstEvent.implicit, | |
72 endImplicit: lastEvent.implicit); | |
73 } | |
74 | |
75 /// Composes a node. | |
76 YamlNode _loadNode(Event firstEvent) { | |
77 switch (firstEvent.type) { | |
78 case EventType.ALIAS: return _loadAlias(firstEvent); | |
79 case EventType.SCALAR: return _loadScalar(firstEvent); | |
80 case EventType.SEQUENCE_START: return _loadSequence(firstEvent); | |
81 case EventType.MAPPING_START: return _loadMapping(firstEvent); | |
82 default: throw "Unreachable"; | |
83 } | |
84 } | |
85 | |
86 /// Registers an anchor. | |
87 void _registerAnchor(String anchor, YamlNode node) { | |
88 if (anchor == null) return; | |
89 | |
90 // libyaml throws an error for duplicate anchors, but example 7.1 makes it | |
91 // clear that they should be overridden: | |
92 // http://yaml.org/spec/1.2/spec.html#id2786448. | |
93 | |
94 _aliases[anchor] = node; | |
95 } | |
96 | |
97 /// Composes a node corresponding to an alias. | |
98 YamlNode _loadAlias(AliasEvent event) { | |
99 var alias = _aliases[event.name]; | |
100 if (alias != null) return alias; | |
101 | |
102 throw new YamlException("Undefined alias.", event.span); | |
103 } | |
104 | |
105 /// Composes a scalar node. | |
106 YamlNode _loadScalar(ScalarEvent scalar) { | |
107 var node; | |
108 if (scalar.tag == "!") { | |
109 node = _parseString(scalar); | |
110 } else if (scalar.tag == null) { | |
111 node = _parseNull(scalar); | |
112 if (node == null) node = _parseBool(scalar); | |
113 if (node == null) node = _parseInt(scalar); | |
114 if (node == null) node = _parseFloat(scalar); | |
115 if (node == null) node = _parseString(scalar); | |
116 } else { | |
117 node = _parseByTag(scalar); | |
Bob Nystrom
2014/10/31 20:03:27
I think it would read a little clearer to do this
nweiz
2014/11/04 22:19:36
Done.
| |
118 } | |
119 | |
120 _registerAnchor(scalar.anchor, node); | |
121 return node; | |
122 } | |
123 | |
124 /// Composes a sequence node. | |
125 YamlNode _loadSequence(SequenceStartEvent firstEvent) { | |
126 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
127 firstEvent.tag != "tag:yaml.org,2002:seq") { | |
128 throw new YamlException("Invalid tag for sequence.", firstEvent.span); | |
129 } | |
130 | |
131 var children = []; | |
132 var node = new YamlList.internal( | |
133 children, firstEvent.span, firstEvent.style); | |
134 _registerAnchor(firstEvent.anchor, node); | |
135 | |
136 var event = _parser.parse(); | |
137 while (event.type != EventType.SEQUENCE_END) { | |
138 children.add(_loadNode(event)); | |
139 event = _parser.parse(); | |
140 } | |
141 | |
142 setSpan(node, firstEvent.span.expand(event.span)); | |
143 return node; | |
144 } | |
145 | |
146 /// Composes a mapping node. | |
147 YamlNode _loadMapping(MappingStartEvent firstEvent) { | |
148 if (firstEvent.tag != "!" && firstEvent.tag != null && | |
149 firstEvent.tag != "tag:yaml.org,2002:map") { | |
150 throw new YamlException("Invalid tag for mapping.", firstEvent.span); | |
151 } | |
152 | |
153 var children = deepEqualsMap(); | |
154 var node = new YamlMap.internal( | |
155 children, firstEvent.span, firstEvent.style); | |
156 _registerAnchor(firstEvent.anchor, node); | |
157 | |
158 var event = _parser.parse(); | |
159 while (event.type != EventType.MAPPING_END) { | |
160 var key = _loadNode(event); | |
161 var value = _loadNode(_parser.parse()); | |
162 children[key] = value; | |
163 event = _parser.parse(); | |
164 } | |
165 | |
166 setSpan(node, firstEvent.span.expand(event.span)); | |
167 return node; | |
168 } | |
169 | |
170 /// Parses a scalar according to its tag name. | |
171 YamlScalar _parseByTag(ScalarEvent scalar) { | |
172 switch (scalar.tag) { | |
173 case "tag:yaml.org,2002:null": return _parseNull(scalar); | |
174 case "tag:yaml.org,2002:bool": return _parseBool(scalar); | |
175 case "tag:yaml.org,2002:int": return _parseInt(scalar); | |
176 case "tag:yaml.org,2002:float": return _parseFloat(scalar); | |
177 case "tag:yaml.org,2002:str": return _parseString(scalar); | |
178 } | |
179 throw new YamlException('Undefined tag: ${scalar.tag}.', scalar.span); | |
180 } | |
181 | |
182 /// Parses a null scalar. | |
183 YamlScalar _parseNull(ScalarEvent scalar) { | |
184 // TODO(nweiz): stop using regexps. | |
185 // TODO(nweiz): add ScalarStyle and implicit metadata to the scalars. | |
186 if (new RegExp(r"^(null|Null|NULL|~|)$").hasMatch(scalar.value)) { | |
187 return new YamlScalar.internal(null, scalar.span, scalar.style); | |
188 } else { | |
189 return null; | |
190 } | |
191 } | |
192 | |
193 /// Parses a boolean scalar. | |
194 YamlScalar _parseBool(ScalarEvent scalar) { | |
195 var match = new RegExp(r"^(?:(true|True|TRUE)|(false|False|FALSE))$"). | |
196 firstMatch(scalar.value); | |
197 if (match == null) return null; | |
198 return new YamlScalar.internal( | |
199 match.group(1) != null, scalar.span, scalar.style); | |
200 } | |
201 | |
202 /// Parses an integer scalar. | |
203 YamlScalar _parseInt(ScalarEvent scalar) { | |
204 var match = new RegExp(r"^[-+]?[0-9]+$").firstMatch(scalar.value); | |
Bob Nystrom
2014/10/31 20:03:27
Is "0000123" allowed?
I wonder if it's better to
nweiz
2014/11/04 22:19:36
Yes.
| |
205 if (match != null) { | |
206 return new YamlScalar.internal( | |
207 int.parse(match.group(0)), scalar.span, scalar.style); | |
208 } | |
209 | |
210 match = new RegExp(r"^0o([0-7]+)$").firstMatch(scalar.value); | |
211 if (match != null) { | |
212 var n = int.parse(match.group(1), radix: 8); | |
213 return new YamlScalar.internal(n, scalar.span, scalar.style); | |
214 } | |
215 | |
216 match = new RegExp(r"^0x[0-9a-fA-F]+$").firstMatch(scalar.value); | |
217 if (match != null) { | |
218 return new YamlScalar.internal( | |
219 int.parse(match.group(0)), scalar.span, scalar.style); | |
220 } | |
221 | |
222 return null; | |
223 } | |
224 | |
225 /// Parses a floating-point scalar. | |
226 YamlScalar _parseFloat(ScalarEvent scalar) { | |
227 var match = new RegExp( | |
228 r"^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$"). | |
229 firstMatch(scalar.value); | |
230 if (match != null) { | |
231 // YAML allows floats of the form "0.", but Dart does not. Fix up those | |
232 // floats by removing the trailing dot. | |
233 var matchStr = match.group(0).replaceAll(new RegExp(r"\.$"), ""); | |
234 return new YamlScalar.internal( | |
235 double.parse(matchStr), scalar.span, scalar.style); | |
236 } | |
237 | |
238 match = new RegExp(r"^([+-]?)\.(inf|Inf|INF)$").firstMatch(scalar.value); | |
239 if (match != null) { | |
240 var value = match.group(1) == "-" ? -double.INFINITY : double.INFINITY; | |
241 return new YamlScalar.internal(value, scalar.span, scalar.style); | |
242 } | |
243 | |
244 match = new RegExp(r"^\.(nan|NaN|NAN)$").firstMatch(scalar.value); | |
245 if (match != null) { | |
246 return new YamlScalar.internal(double.NAN, scalar.span, scalar.style); | |
247 } | |
248 | |
249 return null; | |
250 } | |
251 | |
252 /// Parses a string scalar. | |
253 YamlScalar _parseString(ScalarEvent scalar) => | |
254 new YamlScalar.internal(scalar.value, scalar.span, scalar.style); | |
255 } | |
OLD | NEW |