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  * YAML events.
9  * Code based on PyYAML: http://www.pyyaml.org
10  */
11 module dyaml.event;
12 
13 import std.array;
14 import std.conv;
15 
16 import dyaml.exception;
17 import dyaml.reader;
18 import dyaml.tagdirective;
19 import dyaml.style;
20 
21 
22 package:
23 ///Event types.
24 enum EventID : ubyte
25 {
26     invalid = 0,     /// Invalid (uninitialized) event.
27     streamStart,     /// Stream start
28     streamEnd,       /// Stream end
29     documentStart,   /// Document start
30     documentEnd,     /// Document end
31     alias_,           /// Alias
32     scalar,          /// Scalar
33     sequenceStart,   /// Sequence start
34     sequenceEnd,     /// Sequence end
35     mappingStart,    /// Mapping start
36     mappingEnd       /// Mapping end
37 }
38 
39 /**
40  * YAML event produced by parser.
41  *
42  * 48 bytes on 64bit.
43  */
44 struct Event
45 {
46     @disable int opCmp(ref Event);
47 
48     ///Value of the event, if any.
49     string value;
50     ///Start position of the event in file/stream.
51     Mark startMark;
52     ///End position of the event in file/stream.
53     Mark endMark;
54     union
55     {
56         struct
57         {
58             ///Anchor of the event, if any.
59             string _anchor;
60             ///Tag of the event, if any.
61             string _tag;
62         }
63         ///Tag directives, if this is a DocumentStart.
64         //TagDirectives tagDirectives;
65         TagDirective[] _tagDirectives;
66     }
67     ///Event type.
68     EventID id = EventID.invalid;
69     ///Style of scalar event, if this is a scalar event.
70     ScalarStyle scalarStyle = ScalarStyle.invalid;
71     ///Should the tag be implicitly resolved?
72     bool implicit;
73     /**
74      * Is this document event explicit?
75      *
76      * Used if this is a DocumentStart or DocumentEnd.
77      */
78     alias explicitDocument = implicit;
79     ///Collection style, if this is a SequenceStart or MappingStart.
80     CollectionStyle collectionStyle = CollectionStyle.invalid;
81 
82     ///Is this a null (uninitialized) event?
83     @property bool isNull() const pure @safe nothrow {return id == EventID.invalid;}
84 
85     ///Get string representation of the token ID.
86     @property string idString() const @safe {return to!string(id);}
87 
88     auto ref anchor() inout @trusted pure {
89         assert(id != EventID.documentStart, "DocumentStart events cannot have anchors.");
90         return _anchor;
91     }
92 
93     auto ref tag() inout @trusted pure {
94         assert(id != EventID.documentStart, "DocumentStart events cannot have tags.");
95         return _tag;
96     }
97 
98     auto ref tagDirectives() inout @trusted pure {
99         assert(id == EventID.documentStart, "Only DocumentStart events have tag directives.");
100         return _tagDirectives;
101     }
102     void toString(W)(ref W writer) const
103     {
104         import std.algorithm.iteration : substitute;
105         import std.format : formattedWrite;
106         import std.range : put;
107         final switch (id)
108         {
109             case EventID.scalar:
110                 put(writer, "=VAL ");
111                 if (anchor != "")
112                 {
113                     writer.formattedWrite!"&%s " (anchor);
114                 }
115                 if (tag != "")
116                 {
117                     writer.formattedWrite!"<%s> " (tag);
118                 }
119                 final switch(scalarStyle)
120                 {
121                     case ScalarStyle.singleQuoted:
122                         put(writer, "'");
123                         break;
124                     case ScalarStyle.doubleQuoted:
125                         put(writer, "\"");
126                         break;
127                     case ScalarStyle.literal:
128                         put(writer, "|");
129                         break;
130                     case ScalarStyle.folded:
131                         put(writer, ">");
132                         break;
133                     case ScalarStyle.invalid: //default to plain
134                     case ScalarStyle.plain:
135                         put(writer, ":");
136                         break;
137                 }
138                 if (value != "")
139                 {
140                     writer.formattedWrite!"%s"(value.substitute("\n", "\\n", `\`, `\\`, "\r", "\\r", "\t", "\\t", "\b", "\\b"));
141                 }
142                 break;
143             case EventID.streamStart:
144                 put(writer, "+STR");
145                 break;
146             case EventID.documentStart:
147                 put(writer, "+DOC");
148                 if (explicitDocument)
149                 {
150                     put(writer, " ---");
151                 }
152                 break;
153             case EventID.mappingStart:
154                 put(writer, "+MAP");
155                 if (collectionStyle == CollectionStyle.flow)
156                 {
157                     put(writer, " {}");
158                 }
159                 if (anchor != "")
160                 {
161                     put(writer, " &");
162                     put(writer, anchor);
163                 }
164                 if (tag != "")
165                 {
166                     put(writer, " <");
167                     put(writer, tag);
168                     put(writer, ">");
169                 }
170                 break;
171             case EventID.sequenceStart:
172                 put(writer, "+SEQ");
173                 if (collectionStyle == CollectionStyle.flow)
174                 {
175                     put(writer, " []");
176                 }
177                 if (anchor != "")
178                 {
179                     put(writer, " &");
180                     put(writer, anchor);
181                 }
182                 if (tag != "")
183                 {
184                     put(writer, " <");
185                     put(writer, tag);
186                     put(writer, ">");
187                 }
188                 break;
189             case EventID.streamEnd:
190                 put(writer, "-STR");
191                 break;
192             case EventID.documentEnd:
193                 put(writer, "-DOC");
194                 if (explicitDocument)
195                 {
196                     put(writer, " ...");
197                 }
198                 break;
199             case EventID.mappingEnd:
200                 put(writer, "-MAP");
201                 break;
202             case EventID.sequenceEnd:
203                 put(writer, "-SEQ");
204                 break;
205             case EventID.alias_:
206                 put(writer, "=ALI *");
207                 put(writer, anchor);
208                 break;
209             case EventID.invalid:
210                 assert(0, "Invalid EventID produced");
211         }
212     }
213 }
214 
215 /**
216  * Construct a simple event.
217  *
218  * Params:  start    = Start position of the event in the file/stream.
219  *          end      = End position of the event in the file/stream.
220  *          anchor   = Anchor, if this is an alias event.
221  */
222 Event event(EventID id)(const Mark start, const Mark end, const string anchor = null)
223     @safe
224     in(!(id == EventID.alias_ && anchor == ""), "Missing anchor for alias event")
225 {
226     Event result;
227     result.startMark = start;
228     result.endMark   = end;
229     result.anchor    = anchor;
230     result.id        = id;
231     return result;
232 }
233 
234 /**
235  * Construct a collection (mapping or sequence) start event.
236  *
237  * Params:  start    = Start position of the event in the file/stream.
238  *          end      = End position of the event in the file/stream.
239  *          anchor   = Anchor of the sequence, if any.
240  *          tag      = Tag of the sequence, if specified.
241  *          implicit = Should the tag be implicitly resolved?
242  *          style = Style to use when outputting document.
243  */
244 Event collectionStartEvent(EventID id)
245     (const Mark start, const Mark end, const string anchor, const string tag,
246      const bool implicit, const CollectionStyle style) pure @safe nothrow
247 {
248     static assert(id == EventID.sequenceStart || id == EventID.sequenceEnd ||
249                   id == EventID.mappingStart || id == EventID.mappingEnd);
250     Event result;
251     result.startMark       = start;
252     result.endMark         = end;
253     result.anchor          = anchor;
254     result.tag             = tag;
255     result.id              = id;
256     result.implicit        = implicit;
257     result.collectionStyle = style;
258     return result;
259 }
260 
261 /**
262  * Construct a stream start event.
263  *
264  * Params:  start    = Start position of the event in the file/stream.
265  *          end      = End position of the event in the file/stream.
266  */
267 Event streamStartEvent(const Mark start, const Mark end)
268     pure @safe nothrow
269 {
270     Event result;
271     result.startMark = start;
272     result.endMark   = end;
273     result.id        = EventID.streamStart;
274     return result;
275 }
276 
277 ///Aliases for simple events.
278 alias streamEndEvent = event!(EventID.streamEnd);
279 alias aliasEvent = event!(EventID.alias_);
280 alias sequenceEndEvent = event!(EventID.sequenceEnd);
281 alias mappingEndEvent = event!(EventID.mappingEnd);
282 
283 ///Aliases for collection start events.
284 alias sequenceStartEvent = collectionStartEvent!(EventID.sequenceStart);
285 alias mappingStartEvent = collectionStartEvent!(EventID.mappingStart);
286 
287 /**
288  * Construct a document start event.
289  *
290  * Params:  start         = Start position of the event in the file/stream.
291  *          end           = End position of the event in the file/stream.
292  *          explicit      = Is this an explicit document start?
293  *          YAMLVersion   = YAML version string of the document.
294  *          tagDirectives = Tag directives of the document.
295  */
296 Event documentStartEvent(const Mark start, const Mark end, const bool explicit, string YAMLVersion,
297                          TagDirective[] tagDirectives) pure @safe nothrow
298 {
299     Event result;
300     result.value            = YAMLVersion;
301     result.startMark        = start;
302     result.endMark          = end;
303     result.id               = EventID.documentStart;
304     result.explicitDocument = explicit;
305     result.tagDirectives    = tagDirectives;
306     return result;
307 }
308 
309 /**
310  * Construct a document end event.
311  *
312  * Params:  start    = Start position of the event in the file/stream.
313  *          end      = End position of the event in the file/stream.
314  *          explicit = Is this an explicit document end?
315  */
316 Event documentEndEvent(const Mark start, const Mark end, const bool explicit) pure @safe nothrow
317 {
318     Event result;
319     result.startMark        = start;
320     result.endMark          = end;
321     result.id               = EventID.documentEnd;
322     result.explicitDocument = explicit;
323     return result;
324 }
325 
326 /// Construct a scalar event.
327 ///
328 /// Params:  start    = Start position of the event in the file/stream.
329 ///          end      = End position of the event in the file/stream.
330 ///          anchor   = Anchor of the scalar, if any.
331 ///          tag      = Tag of the scalar, if specified.
332 ///          implicit = Should the tag be implicitly resolved?
333 ///          value    = String value of the scalar.
334 ///          style    = Scalar style.
335 Event scalarEvent(const Mark start, const Mark end, const string anchor, const string tag,
336                   const bool implicit, const string value,
337                   const ScalarStyle style = ScalarStyle.invalid) @safe pure nothrow @nogc
338 {
339     Event result;
340     result.value       = value;
341     result.startMark   = start;
342     result.endMark     = end;
343 
344     result.anchor  = anchor;
345     result.tag     = tag;
346 
347     result.id          = EventID.scalar;
348     result.scalarStyle = style;
349     result.implicit    = implicit;
350     return result;
351 }