1 
2 //          Copyright Ferdinand Majerech 2011.
3 // Distributed under the Boost Software License, Version 1.0.
4 //    (See accompanying file LICENSE_1_0.txt or copy at
5 //          http://www.boost.org/LICENSE_1_0.txt)
6 
7 /**
8  * Composes nodes from YAML events provided by parser.
9  * Code based on PyYAML: http://www.pyyaml.org
10  */
11 module dyaml.composer;
12 
13 import core.memory;
14 
15 import std.array;
16 import std.conv;
17 import std.exception;
18 import std.typecons;
19 
20 import dyaml.anchor;
21 import dyaml.constructor;
22 import dyaml.event;
23 import dyaml.exception;
24 import dyaml.node;
25 import dyaml.parser;
26 import dyaml.resolver;
27 
28 
29 package:
30 /**
31  * Exception thrown at composer errors.
32  *
33  * See_Also: MarkedYAMLException
34  */
35 class ComposerException : MarkedYAMLException
36 {
37     mixin MarkedExceptionCtors;
38 }
39 
40 ///Composes YAML documents from events provided by a Parser.
41 final class Composer
42 {
43     private:
44         ///Parser providing YAML events.
45         Parser parser_;
46         ///Resolver resolving tags (data types).
47         Resolver resolver_;
48         ///Constructor constructing YAML values.
49         Constructor constructor_;
50         ///Nodes associated with anchors. Used by YAML aliases.
51         Node[Anchor] anchors_;
52 
53         ///Used to reduce allocations when creating pair arrays.
54         ///
55         ///We need one appender for each nesting level that involves
56         ///a pair array, as the inner levels are processed as a
57         ///part of the outer levels. Used as a stack.
58         Appender!(Node.Pair[])[] pairAppenders_;
59         ///Used to reduce allocations when creating node arrays.
60         ///
61         ///We need one appender for each nesting level that involves
62         ///a node array, as the inner levels are processed as a
63         ///part of the outer levels. Used as a stack.
64         Appender!(Node[])[] nodeAppenders_;
65 
66     public:
67         /**
68          * Construct a composer.
69          *
70          * Params:  parser      = Parser to provide YAML events.
71          *          resolver    = Resolver to resolve tags (data types).
72          *          constructor = Constructor to construct nodes.
73          */
74         this(Parser parser, Resolver resolver, Constructor constructor) @safe
75         {
76             parser_ = parser;
77             resolver_ = resolver;
78             constructor_ = constructor;
79         }
80 
81         ///Destroy the composer.
82         pure @safe nothrow ~this()
83         {
84             parser_ = null;
85             resolver_ = null;
86             constructor_ = null;
87             anchors_.destroy();
88             anchors_ = null;
89         }
90 
91         /**
92          * Determine if there are any nodes left.
93          *
94          * Must be called before loading as it handles the stream start event.
95          */
96         bool checkNode() @safe
97         {
98             //Drop the STREAM-START event.
99             if(parser_.checkEvent(EventID.StreamStart))
100             {
101                 parser_.getEvent();
102             }
103 
104             //True if there are more documents available.
105             return !parser_.checkEvent(EventID.StreamEnd);
106         }
107 
108         ///Get a YAML document as a node (the root of the document).
109         Node getNode() @safe
110         {
111             //Get the root node of the next document.
112             assert(!parser_.checkEvent(EventID.StreamEnd), 
113                    "Trying to get a node from Composer when there is no node to " ~
114                    "get. use checkNode() to determine if there is a node.");
115 
116             return composeDocument();
117         }
118 
119         ///Get single YAML document, throwing if there is more than one document.
120         Node getSingleNode() @trusted
121         {
122             assert(!parser_.checkEvent(EventID.StreamEnd), 
123                    "Trying to get a node from Composer when there is no node to " ~
124                    "get. use checkNode() to determine if there is a node.");
125 
126             Node document = composeDocument();
127 
128             //Ensure that the stream contains no more documents.
129             enforce(parser_.checkEvent(EventID.StreamEnd),
130                     new ComposerException("Expected single document in the stream, " ~
131                                           "but found another document.",
132                                           parser_.getEvent().startMark));
133 
134             //Drop the STREAM-END event.
135             parser_.getEvent();
136 
137             return document;
138         }
139 
140     private:
141         ///Ensure that appenders for specified nesting levels exist.
142         ///
143         ///Params:  pairAppenderLevel = Current level in the pair appender stack.
144         ///         nodeAppenderLevel = Current level the node appender stack.
145         void ensureAppendersExist(const uint pairAppenderLevel, const uint nodeAppenderLevel) 
146             @trusted
147         {
148             while(pairAppenders_.length <= pairAppenderLevel)
149             {
150                 pairAppenders_ ~= appender!(Node.Pair[])();
151             }
152             while(nodeAppenders_.length <= nodeAppenderLevel)
153             {
154                 nodeAppenders_ ~= appender!(Node[])();
155             }
156         }
157 
158         ///Compose a YAML document and return its root node.
159         Node composeDocument() @trusted
160         {
161             //Drop the DOCUMENT-START event.
162             parser_.getEvent();
163 
164             //Compose the root node.
165             Node node = composeNode(0, 0);
166 
167             //Drop the DOCUMENT-END event.
168             parser_.getEvent();
169 
170             anchors_.destroy();
171             return node;
172         }
173 
174         /// Compose a node.
175         ///
176         /// Params: pairAppenderLevel = Current level of the pair appender stack.
177         ///         nodeAppenderLevel = Current level of the node appender stack.
178         Node composeNode(const uint pairAppenderLevel, const uint nodeAppenderLevel) @system
179         {
180             if(parser_.checkEvent(EventID.Alias))
181             {
182                 immutable event = parser_.getEvent();
183                 const anchor = event.anchor;
184                 enforce((anchor in anchors_) !is null,
185                         new ComposerException("Found undefined alias: " ~ anchor.get,
186                                               event.startMark));
187 
188                 //If the node referenced by the anchor is uninitialized,
189                 //it's not finished, i.e. we're currently composing it
190                 //and trying to use it recursively here.
191                 enforce(anchors_[anchor] != Node(),
192                         new ComposerException("Found recursive alias: " ~ anchor.get,
193                                               event.startMark));
194 
195                 return anchors_[anchor];
196             }
197 
198             immutable event = parser_.peekEvent();
199             const anchor = event.anchor;
200             if(!anchor.isNull() && (anchor in anchors_) !is null)
201             {
202                 throw new ComposerException("Found duplicate anchor: " ~ anchor.get, 
203                                             event.startMark);
204             }
205 
206             Node result;
207             //Associate the anchor, if any, with an uninitialized node.
208             //used to detect duplicate and recursive anchors.
209             if(!anchor.isNull())
210             {
211                 anchors_[anchor] = Node();
212             }
213 
214             if(parser_.checkEvent(EventID.Scalar))
215             {
216                 result = composeScalarNode();
217             }
218             else if(parser_.checkEvent(EventID.SequenceStart))
219             {
220                 result = composeSequenceNode(pairAppenderLevel, nodeAppenderLevel);
221             }
222             else if(parser_.checkEvent(EventID.MappingStart))
223             {
224                 result = composeMappingNode(pairAppenderLevel, nodeAppenderLevel);
225             }
226             else{assert(false, "This code should never be reached");}
227 
228             if(!anchor.isNull())
229             {
230                 anchors_[anchor] = result;
231             }
232             return result;
233         }
234 
235         ///Compose a scalar node.
236         Node composeScalarNode() @system
237         {
238             immutable event = parser_.getEvent();
239             const tag = resolver_.resolve(NodeID.Scalar, event.tag, event.value, 
240                                           event.implicit);
241 
242             Node node = constructor_.node(event.startMark, event.endMark, tag, 
243                                           event.value, event.scalarStyle);
244 
245             return node;
246         }
247 
248         /// Compose a sequence node.
249         ///
250         /// Params: pairAppenderLevel = Current level of the pair appender stack.
251         ///         nodeAppenderLevel = Current level of the node appender stack.
252         Node composeSequenceNode(const uint pairAppenderLevel, const uint nodeAppenderLevel) 
253             @system
254         {
255             ensureAppendersExist(pairAppenderLevel, nodeAppenderLevel);
256             auto nodeAppender = &(nodeAppenders_[nodeAppenderLevel]);
257 
258             immutable startEvent = parser_.getEvent();
259             const tag = resolver_.resolve(NodeID.Sequence, startEvent.tag, null, 
260                                           startEvent.implicit);
261 
262             while(!parser_.checkEvent(EventID.SequenceEnd))
263             {
264                 nodeAppender.put(composeNode(pairAppenderLevel, nodeAppenderLevel + 1));
265             }
266 
267             core.memory.GC.disable();
268             scope(exit){core.memory.GC.enable();}
269             Node node = constructor_.node(startEvent.startMark, parser_.getEvent().endMark, 
270                                           tag, nodeAppender.data.dup, startEvent.collectionStyle);
271             nodeAppender.clear();
272 
273             return node;
274         }
275 
276         /**
277          * Flatten a node, merging it with nodes referenced through YAMLMerge data type.
278          *
279          * Node must be a mapping or a sequence of mappings.
280          *
281          * Params:  root              = Node to flatten.
282          *          startMark         = Start position of the node.
283          *          endMark           = End position of the node.
284          *          pairAppenderLevel = Current level of the pair appender stack.
285          *          nodeAppenderLevel = Current level of the node appender stack.
286          *
287          * Returns: Flattened mapping as pairs.
288          */
289         Node.Pair[] flatten(ref Node root, const Mark startMark, const Mark endMark,
290                             const uint pairAppenderLevel, const uint nodeAppenderLevel) @system
291         {
292             void error(Node node)
293             {
294                 //this is Composer, but the code is related to Constructor.
295                 throw new ConstructorException("While constructing a mapping, " ~
296                                                "expected a mapping or a list of " ~
297                                                "mappings for merging, but found: " ~
298                                                node.type.toString() ~
299                                                " NOTE: line/column shows topmost parent " ~
300                                                "to which the content is being merged",
301                                                startMark, endMark);
302             }
303 
304             ensureAppendersExist(pairAppenderLevel, nodeAppenderLevel);
305             auto pairAppender = &(pairAppenders_[pairAppenderLevel]);
306 
307             if(root.isMapping)
308             {
309                 Node[] toMerge;
310                 foreach(ref Node key, ref Node value; root)
311                 {
312                     if(key.isType!YAMLMerge)
313                     {
314                         toMerge.assumeSafeAppend();
315                         toMerge ~= value;
316                     }
317                     else
318                     {
319                         auto temp = Node.Pair(key, value);
320                         merge(*pairAppender, temp);
321                     }
322                 }
323                 foreach(node; toMerge)
324                 {
325                     merge(*pairAppender, flatten(node, startMark, endMark, 
326                                                  pairAppenderLevel + 1, nodeAppenderLevel));
327                 }
328             }
329             //Must be a sequence of mappings.
330             else if(root.isSequence) foreach(ref Node node; root)
331             {
332                 if(!node.isType!(Node.Pair[])){error(node);}
333                 merge(*pairAppender, flatten(node, startMark, endMark, 
334                                              pairAppenderLevel + 1, nodeAppenderLevel));
335             }
336             else
337             {
338                 error(root);
339             }
340 
341             core.memory.GC.disable();
342             scope(exit){core.memory.GC.enable();}
343             auto flattened = pairAppender.data.dup;
344             pairAppender.clear();
345 
346             return flattened;
347         }
348 
349         /// Compose a mapping node.
350         ///
351         /// Params: pairAppenderLevel = Current level of the pair appender stack.
352         ///         nodeAppenderLevel = Current level of the node appender stack.
353         Node composeMappingNode(const uint pairAppenderLevel, const uint nodeAppenderLevel)
354             @system
355         {
356             ensureAppendersExist(pairAppenderLevel, nodeAppenderLevel);
357             immutable startEvent = parser_.getEvent();
358             const tag = resolver_.resolve(NodeID.Mapping, startEvent.tag, null, 
359                                           startEvent.implicit);
360             auto pairAppender = &(pairAppenders_[pairAppenderLevel]);
361 
362             Tuple!(Node, Mark)[] toMerge;
363             while(!parser_.checkEvent(EventID.MappingEnd))
364             {
365                 auto pair = Node.Pair(composeNode(pairAppenderLevel + 1, nodeAppenderLevel), 
366                                       composeNode(pairAppenderLevel + 1, nodeAppenderLevel));
367 
368                 //Need to flatten and merge the node referred by YAMLMerge.
369                 if(pair.key.isType!YAMLMerge)
370                 {
371                     toMerge ~= tuple(pair.value, cast(Mark)parser_.peekEvent().endMark);
372                 }
373                 //Not YAMLMerge, just add the pair.
374                 else
375                 {
376                     merge(*pairAppender, pair);
377                 }
378             }
379             foreach(node; toMerge)
380             {
381                 merge(*pairAppender, flatten(node[0], startEvent.startMark, node[1], 
382                                              pairAppenderLevel + 1, nodeAppenderLevel));
383             }
384 
385             core.memory.GC.disable();
386             scope(exit){core.memory.GC.enable();}
387             Node node = constructor_.node(startEvent.startMark, parser_.getEvent().endMark, 
388                                           tag, pairAppender.data.dup, startEvent.collectionStyle);
389 
390             pairAppender.clear();
391             return node;
392         }
393 }