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 }