1 //          Copyright Ferdinand Majerech 2011.
2 // Distributed under the Boost Software License, Version 1.0.
3 //    (See accompanying file LICENSE_1_0.txt or copy at
4 //          http://www.boost.org/LICENSE_1_0.txt)
5 
6 /**
7  * YAML emitter.
8  * Code based on PyYAML: http://www.pyyaml.org
9  */
10 module dyaml.emitter;
11 
12 
13 import std.algorithm;
14 import std.array;
15 import std.ascii;
16 import std.conv;
17 import std.encoding;
18 import std.exception;
19 import std.format;
20 import std.range;
21 import std..string;
22 import std.system;
23 import std.typecons;
24 import std.utf;
25 
26 import dyaml.encoding;
27 import dyaml.escapes;
28 import dyaml.event;
29 import dyaml.exception;
30 import dyaml.linebreak;
31 import dyaml.queue;
32 import dyaml.style;
33 import dyaml.tagdirective;
34 
35 
36 package:
37 
38 /**
39  * Exception thrown at Emitter errors.
40  *
41  * See_Also:
42  *     YAMLException
43  */
44 class EmitterException : YAMLException
45 {
46     mixin ExceptionCtors;
47 }
48 
49 //Stores results of analysis of a scalar, determining e.g. what scalar style to use.
50 struct ScalarAnalysis
51 {
52     //Scalar itself.
53     string scalar;
54 
55     enum AnalysisFlags
56     {
57         empty = 1<<0,
58         multiline = 1<<1,
59         allowFlowPlain = 1<<2,
60         allowBlockPlain = 1<<3,
61         allowSingleQuoted = 1<<4,
62         allowDoubleQuoted = 1<<5,
63         allowBlock = 1<<6,
64         isNull = 1<<7
65     }
66 
67     ///Analysis results.
68     BitFlags!AnalysisFlags flags;
69 }
70 
71 private alias isNewLine = among!('\n', '\u0085', '\u2028', '\u2029');
72 
73 private alias isSpecialChar = among!('#', ',', '[', ']', '{', '}', '&', '*', '!', '|', '>', '\\', '\'', '"', '%', '@', '`');
74 
75 private alias isFlowIndicator = among!(',', '?', '[', ']', '{', '}');
76 
77 private alias isSpace = among!('\0', '\n', '\r', '\u0085', '\u2028', '\u2029', ' ', '\t');
78 
79 //Emits YAML events into a file/stream.
80 struct Emitter(Range, CharType) if (isOutputRange!(Range, CharType))
81 {
82     private:
83         ///Default tag handle shortcuts and replacements.
84         static TagDirective[] defaultTagDirectives_ =
85             [TagDirective("!", "!"), TagDirective("!!", "tag:yaml.org,2002:")];
86 
87         ///Stream to write to.
88         Range stream_;
89 
90         ///Stack of states.
91         Appender!(void function() @safe[]) states_;
92         ///Current state.
93         //WARNING! DO NOT CALL DIRECTLY! Use callNext() instead!
94         void function() @safe state_;
95 
96         ///Event queue.
97         Queue!Event events_;
98         ///Event we're currently emitting.
99         Event event_;
100 
101         ///Stack of previous indentation levels.
102         Appender!(int[]) indents_;
103         ///Current indentation level.
104         int indent_ = -1;
105 
106         ///Level of nesting in flow context. If 0, we're in block context.
107         uint flowLevel_ = 0;
108 
109         /// Describes context (where we are in the document).
110         enum Context
111         {
112             /// Root node of a document.
113             Root,
114             /// Sequence.
115             Sequence,
116             /// Mapping.
117             MappingNoSimpleKey,
118             /// Mapping, in a simple key.
119             MappingSimpleKey
120         }
121         /// Current context.
122         Context context_;
123 
124         ///Characteristics of the last emitted character:
125 
126         ///Line.
127         uint line_ = 0;
128         ///Column.
129         uint column_ = 0;
130         ///Whitespace character?
131         bool whitespace_ = true;
132         ///indentation space, '-', '?', or ':'?
133         bool indentation_ = true;
134 
135         ///Does the document require an explicit document indicator?
136         bool openEnded_;
137 
138         ///Formatting details.
139 
140         ///Canonical scalar format?
141         bool canonical_;
142         ///Best indentation width.
143         uint bestIndent_ = 2;
144         ///Best text width.
145         uint bestWidth_ = 80;
146         ///Best line break character/s.
147         LineBreak bestLineBreak_;
148 
149         ///Tag directive handle - prefix pairs.
150         TagDirective[] tagDirectives_;
151 
152         ///Anchor/alias to process.
153         string preparedAnchor_ = null;
154         ///Tag to process.
155         string preparedTag_ = null;
156 
157         ///Analysis result of the current scalar.
158         ScalarAnalysis analysis_;
159         ///Style of the current scalar.
160         ScalarStyle style_ = ScalarStyle.Invalid;
161 
162     public:
163         @disable int opCmp(ref Emitter);
164         @disable bool opEquals(ref Emitter);
165 
166         /**
167          * Construct an emitter.
168          *
169          * Params:  stream    = Output range to write to.
170          *          canonical = Write scalars in canonical form?
171          *          indent    = Indentation width.
172          *          lineBreak = Line break character/s.
173          */
174         this(Range stream, const bool canonical, const int indent, const int width,
175              const LineBreak lineBreak) @trusted
176         {
177             states_.reserve(32);
178             indents_.reserve(32);
179             stream_ = stream;
180             canonical_ = canonical;
181             nextExpected(&expectStreamStart);
182 
183             if(indent > 1 && indent < 10){bestIndent_ = indent;}
184             if(width > bestIndent_ * 2)  {bestWidth_ = width;}
185             bestLineBreak_ = lineBreak;
186 
187             analysis_.flags.isNull = true;
188         }
189 
190         ///Emit an event. Throws EmitterException on error.
191         void emit(Event event) @safe
192         {
193             events_.push(event);
194             while(!needMoreEvents())
195             {
196                 event_ = events_.pop();
197                 callNext();
198                 event_.destroy();
199             }
200         }
201 
202     private:
203         ///Pop and return the newest state in states_.
204         void function() @safe popState() @safe
205         {
206             enforce(states_.data.length > 0,
207                     new YAMLException("Emitter: Need to pop a state but there are no states left"));
208             const result = states_.data[$-1];
209             states_.shrinkTo(states_.data.length - 1);
210             return result;
211         }
212 
213         void pushState(void delegate() @safe func) @trusted
214         {
215             states_ ~= func.funcptr;
216         }
217 
218         ///Pop and return the newest indent in indents_.
219         int popIndent() @safe
220         {
221             enforce(indents_.data.length > 0,
222                     new YAMLException("Emitter: Need to pop an indent level but there" ~
223                                       " are no indent levels left"));
224             const result = indents_.data[$-1];
225             indents_.shrinkTo(indents_.data.length - 1);
226             return result;
227         }
228 
229         ///Write a string to the file/stream.
230         void writeString(const char[] str) @safe
231         {
232             try
233             {
234                 static if(is(CharType == char))
235                 {
236                     copy(str, stream_);
237                 }
238                 static if(is(CharType == wchar))
239                 {
240                     const buffer = to!wstring(str);
241                     copy(buffer, stream_);
242                 }
243                 static if(is(CharType == dchar))
244                 {
245                     const buffer = to!dstring(str);
246                     copy(buffer, stream_);
247                 }
248             }
249             catch(Exception e)
250             {
251                 throw new EmitterException("Unable to write to stream: " ~ e.msg);
252             }
253         }
254 
255         ///In some cases, we wait for a few next events before emitting.
256         bool needMoreEvents() @safe nothrow
257         {
258             if(events_.length == 0){return true;}
259 
260             const event = events_.peek();
261             if(event.id == EventID.DocumentStart){return needEvents(1);}
262             if(event.id == EventID.SequenceStart){return needEvents(2);}
263             if(event.id == EventID.MappingStart) {return needEvents(3);}
264 
265             return false;
266         }
267 
268         ///Determines if we need specified number of more events.
269         bool needEvents(in uint count) @safe nothrow
270         {
271             int level;
272 
273             //Rather ugly, but good enough for now.
274             //Couldn't be bothered writing a range as events_ should eventually
275             //become a Phobos queue/linked list.
276             events_.startIteration();
277             events_.next();
278             while(!events_.iterationOver())
279             {
280                 const event = events_.next();
281                 static starts = [EventID.DocumentStart, EventID.SequenceStart, EventID.MappingStart];
282                 static ends   = [EventID.DocumentEnd, EventID.SequenceEnd, EventID.MappingEnd];
283                 if(starts.canFind(event.id))   {++level;}
284                 else if(ends.canFind(event.id)){--level;}
285                 else if(event.id == EventID.StreamStart){level = -1;}
286 
287                 if(level < 0)
288                 {
289                     return false;
290                 }
291             }
292 
293             return events_.length < (count + 1);
294         }
295 
296         ///Increase indentation level.
297         void increaseIndent(const Flag!"flow" flow = No.flow, const bool indentless = false) @safe
298         {
299             indents_ ~= indent_;
300             if(indent_ == -1)
301             {
302                 indent_ = flow ? bestIndent_ : 0;
303             }
304             else if(!indentless)
305             {
306                 indent_ += bestIndent_;
307             }
308         }
309 
310         ///Determines if the type of current event is as specified. Throws if no event.
311         bool eventTypeIs(in EventID id) const pure @safe
312         {
313             enforce(!event_.isNull,
314                     new EmitterException("Expected an event, but no event is available."));
315             return event_.id == id;
316         }
317 
318 
319         //States.
320 
321 
322         //Stream handlers.
323 
324         ///Handle start of a file/stream.
325         void expectStreamStart() @safe
326         {
327             enforce(eventTypeIs(EventID.StreamStart),
328                     new EmitterException("Expected StreamStart, but got " ~ event_.idString));
329 
330             writeStreamStart();
331             nextExpected(&expectDocumentStart!(Yes.first));
332         }
333 
334         ///Expect nothing, throwing if we still have something.
335         void expectNothing() @safe
336         {
337             throw new EmitterException("Expected nothing, but got " ~ event_.idString);
338         }
339 
340         //Document handlers.
341 
342         ///Handle start of a document.
343         void expectDocumentStart(Flag!"first" first)() @safe
344         {
345             enforce(eventTypeIs(EventID.DocumentStart) || eventTypeIs(EventID.StreamEnd),
346                     new EmitterException("Expected DocumentStart or StreamEnd, but got "
347                               ~ event_.idString));
348 
349             if(event_.id == EventID.DocumentStart)
350             {
351                 const YAMLVersion = event_.value;
352                 auto tagDirectives = event_.tagDirectives;
353                 if(openEnded_ && (YAMLVersion !is null || tagDirectives !is null))
354                 {
355                     writeIndicator("...", Yes.needWhitespace);
356                     writeIndent();
357                 }
358 
359                 if(YAMLVersion !is null)
360                 {
361                     writeVersionDirective(prepareVersion(YAMLVersion));
362                 }
363 
364                 if(tagDirectives !is null)
365                 {
366                     tagDirectives_ = tagDirectives;
367                     sort!"icmp(a.handle, b.handle) < 0"(tagDirectives_);
368 
369                     foreach(ref pair; tagDirectives_)
370                     {
371                         writeTagDirective(prepareTagHandle(pair.handle),
372                                           prepareTagPrefix(pair.prefix));
373                     }
374                 }
375 
376                 bool eq(ref TagDirective a, ref TagDirective b){return a.handle == b.handle;}
377                 //Add any default tag directives that have not been overriden.
378                 foreach(ref def; defaultTagDirectives_)
379                 {
380                     if(!std.algorithm.canFind!eq(tagDirectives_, def))
381                     {
382                         tagDirectives_ ~= def;
383                     }
384                 }
385 
386                 const implicit = first && !event_.explicitDocument && !canonical_ &&
387                                  YAMLVersion is null && tagDirectives is null &&
388                                  !checkEmptyDocument();
389                 if(!implicit)
390                 {
391                     writeIndent();
392                     writeIndicator("---", Yes.needWhitespace);
393                     if(canonical_){writeIndent();}
394                 }
395                 nextExpected(&expectRootNode);
396             }
397             else if(event_.id == EventID.StreamEnd)
398             {
399                 if(openEnded_)
400                 {
401                     writeIndicator("...", Yes.needWhitespace);
402                     writeIndent();
403                 }
404                 writeStreamEnd();
405                 nextExpected(&expectNothing);
406             }
407         }
408 
409         ///Handle end of a document.
410         void expectDocumentEnd() @safe
411         {
412             enforce(eventTypeIs(EventID.DocumentEnd),
413                     new EmitterException("Expected DocumentEnd, but got " ~ event_.idString));
414 
415             writeIndent();
416             if(event_.explicitDocument)
417             {
418                 writeIndicator("...", Yes.needWhitespace);
419                 writeIndent();
420             }
421             nextExpected(&expectDocumentStart!(No.first));
422         }
423 
424         ///Handle the root node of a document.
425         void expectRootNode() @safe
426         {
427             pushState(&expectDocumentEnd);
428             expectNode(Context.Root);
429         }
430 
431         ///Handle a mapping node.
432         //
433         //Params: simpleKey = Are we in a simple key?
434         void expectMappingNode(const bool simpleKey = false) @safe
435         {
436             expectNode(simpleKey ? Context.MappingSimpleKey : Context.MappingNoSimpleKey);
437         }
438 
439         ///Handle a sequence node.
440         void expectSequenceNode() @safe
441         {
442             expectNode(Context.Sequence);
443         }
444 
445         ///Handle a new node. Context specifies where in the document we are.
446         void expectNode(const Context context) @safe
447         {
448             context_ = context;
449 
450             const flowCollection = event_.collectionStyle == CollectionStyle.Flow;
451 
452             switch(event_.id)
453             {
454                 case EventID.Alias: expectAlias(); break;
455                 case EventID.Scalar:
456                      processAnchor("&");
457                      processTag();
458                      expectScalar();
459                      break;
460                 case EventID.SequenceStart:
461                      processAnchor("&");
462                      processTag();
463                      if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptySequence())
464                      {
465                          expectFlowSequence();
466                      }
467                      else
468                      {
469                          expectBlockSequence();
470                      }
471                      break;
472                 case EventID.MappingStart:
473                      processAnchor("&");
474                      processTag();
475                      if(flowLevel_ > 0 || canonical_ || flowCollection || checkEmptyMapping())
476                      {
477                          expectFlowMapping();
478                      }
479                      else
480                      {
481                          expectBlockMapping();
482                      }
483                      break;
484                 default:
485                      throw new EmitterException("Expected Alias, Scalar, SequenceStart or " ~
486                                      "MappingStart, but got: " ~ event_.idString);
487             }
488         }
489         ///Handle an alias.
490         void expectAlias() @safe
491         {
492             enforce(event_.anchor !is null, new EmitterException("Anchor is not specified for alias"));
493             processAnchor("*");
494             nextExpected(popState());
495         }
496 
497         ///Handle a scalar.
498         void expectScalar() @safe
499         {
500             increaseIndent(Yes.flow);
501             processScalar();
502             indent_ = popIndent();
503             nextExpected(popState());
504         }
505 
506         //Flow sequence handlers.
507 
508         ///Handle a flow sequence.
509         void expectFlowSequence() @safe
510         {
511             writeIndicator("[", Yes.needWhitespace, Yes.whitespace);
512             ++flowLevel_;
513             increaseIndent(Yes.flow);
514             nextExpected(&expectFlowSequenceItem!(Yes.first));
515         }
516 
517         ///Handle a flow sequence item.
518         void expectFlowSequenceItem(Flag!"first" first)() @safe
519         {
520             if(event_.id == EventID.SequenceEnd)
521             {
522                 indent_ = popIndent();
523                 --flowLevel_;
524                 static if(!first) if(canonical_)
525                 {
526                     writeIndicator(",", No.needWhitespace);
527                     writeIndent();
528                 }
529                 writeIndicator("]", No.needWhitespace);
530                 nextExpected(popState());
531                 return;
532             }
533             static if(!first){writeIndicator(",", No.needWhitespace);}
534             if(canonical_ || column_ > bestWidth_){writeIndent();}
535             pushState(&expectFlowSequenceItem!(No.first));
536             expectSequenceNode();
537         }
538 
539         //Flow mapping handlers.
540 
541         ///Handle a flow mapping.
542         void expectFlowMapping() @safe
543         {
544             writeIndicator("{", Yes.needWhitespace, Yes.whitespace);
545             ++flowLevel_;
546             increaseIndent(Yes.flow);
547             nextExpected(&expectFlowMappingKey!(Yes.first));
548         }
549 
550         ///Handle a key in a flow mapping.
551         void expectFlowMappingKey(Flag!"first" first)() @safe
552         {
553             if(event_.id == EventID.MappingEnd)
554             {
555                 indent_ = popIndent();
556                 --flowLevel_;
557                 static if (!first) if(canonical_)
558                 {
559                     writeIndicator(",", No.needWhitespace);
560                     writeIndent();
561                 }
562                 writeIndicator("}", No.needWhitespace);
563                 nextExpected(popState());
564                 return;
565             }
566 
567             static if(!first){writeIndicator(",", No.needWhitespace);}
568             if(canonical_ || column_ > bestWidth_){writeIndent();}
569             if(!canonical_ && checkSimpleKey())
570             {
571                 pushState(&expectFlowMappingSimpleValue);
572                 expectMappingNode(true);
573                 return;
574             }
575 
576             writeIndicator("?", Yes.needWhitespace);
577             pushState(&expectFlowMappingValue);
578             expectMappingNode();
579         }
580 
581         ///Handle a simple value in a flow mapping.
582         void expectFlowMappingSimpleValue() @safe
583         {
584             writeIndicator(":", No.needWhitespace);
585             pushState(&expectFlowMappingKey!(No.first));
586             expectMappingNode();
587         }
588 
589         ///Handle a complex value in a flow mapping.
590         void expectFlowMappingValue() @safe
591         {
592             if(canonical_ || column_ > bestWidth_){writeIndent();}
593             writeIndicator(":", Yes.needWhitespace);
594             pushState(&expectFlowMappingKey!(No.first));
595             expectMappingNode();
596         }
597 
598         //Block sequence handlers.
599 
600         ///Handle a block sequence.
601         void expectBlockSequence() @safe
602         {
603             const indentless = (context_ == Context.MappingNoSimpleKey ||
604                                 context_ == Context.MappingSimpleKey) && !indentation_;
605             increaseIndent(No.flow, indentless);
606             nextExpected(&expectBlockSequenceItem!(Yes.first));
607         }
608 
609         ///Handle a block sequence item.
610         void expectBlockSequenceItem(Flag!"first" first)() @safe
611         {
612             static if(!first) if(event_.id == EventID.SequenceEnd)
613             {
614                 indent_ = popIndent();
615                 nextExpected(popState());
616                 return;
617             }
618 
619             writeIndent();
620             writeIndicator("-", Yes.needWhitespace, No.whitespace, Yes.indentation);
621             pushState(&expectBlockSequenceItem!(No.first));
622             expectSequenceNode();
623         }
624 
625         //Block mapping handlers.
626 
627         ///Handle a block mapping.
628         void expectBlockMapping() @safe
629         {
630             increaseIndent(No.flow);
631             nextExpected(&expectBlockMappingKey!(Yes.first));
632         }
633 
634         ///Handle a key in a block mapping.
635         void expectBlockMappingKey(Flag!"first" first)() @safe
636         {
637             static if(!first) if(event_.id == EventID.MappingEnd)
638             {
639                 indent_ = popIndent();
640                 nextExpected(popState());
641                 return;
642             }
643 
644             writeIndent();
645             if(checkSimpleKey())
646             {
647                 pushState(&expectBlockMappingSimpleValue);
648                 expectMappingNode(true);
649                 return;
650             }
651 
652             writeIndicator("?", Yes.needWhitespace, No.whitespace, Yes.indentation);
653             pushState(&expectBlockMappingValue);
654             expectMappingNode();
655         }
656 
657         ///Handle a simple value in a block mapping.
658         void expectBlockMappingSimpleValue() @safe
659         {
660             writeIndicator(":", No.needWhitespace);
661             pushState(&expectBlockMappingKey!(No.first));
662             expectMappingNode();
663         }
664 
665         ///Handle a complex value in a block mapping.
666         void expectBlockMappingValue() @safe
667         {
668             writeIndent();
669             writeIndicator(":", Yes.needWhitespace, No.whitespace, Yes.indentation);
670             pushState(&expectBlockMappingKey!(No.first));
671             expectMappingNode();
672         }
673 
674         //Checkers.
675 
676         ///Check if an empty sequence is next.
677         bool checkEmptySequence() const @safe pure nothrow
678         {
679             return event_.id == EventID.SequenceStart && events_.length > 0
680                    && events_.peek().id == EventID.SequenceEnd;
681         }
682 
683         ///Check if an empty mapping is next.
684         bool checkEmptyMapping() const @safe pure nothrow
685         {
686             return event_.id == EventID.MappingStart && events_.length > 0
687                    && events_.peek().id == EventID.MappingEnd;
688         }
689 
690         ///Check if an empty document is next.
691         bool checkEmptyDocument() const @safe pure nothrow
692         {
693             if(event_.id != EventID.DocumentStart || events_.length == 0)
694             {
695                 return false;
696             }
697 
698             const event = events_.peek();
699             const emptyScalar = event.id == EventID.Scalar && (event.anchor is null) &&
700                                 (event.tag is null) && event.implicit && event.value == "";
701             return emptyScalar;
702         }
703 
704         ///Check if a simple key is next.
705         bool checkSimpleKey() @safe
706         {
707             uint length;
708             const id = event_.id;
709             const scalar = id == EventID.Scalar;
710             const collectionStart = id == EventID.MappingStart ||
711                                     id == EventID.SequenceStart;
712 
713             if((id == EventID.Alias || scalar || collectionStart)
714                && (event_.anchor !is null))
715             {
716                 if(preparedAnchor_ is null)
717                 {
718                     preparedAnchor_ = prepareAnchor(event_.anchor);
719                 }
720                 length += preparedAnchor_.length;
721             }
722 
723             if((scalar || collectionStart) && (event_.tag !is null))
724             {
725                 if(preparedTag_ is null){preparedTag_ = prepareTag(event_.tag);}
726                 length += preparedTag_.length;
727             }
728 
729             if(scalar)
730             {
731                 if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
732                 length += analysis_.scalar.length;
733             }
734 
735             if(length >= 128){return false;}
736 
737             return id == EventID.Alias ||
738                    (scalar && !analysis_.flags.empty && !analysis_.flags.multiline) ||
739                    checkEmptySequence() ||
740                    checkEmptyMapping();
741         }
742 
743         ///Process and write a scalar.
744         void processScalar() @safe
745         {
746             if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
747             if(style_ == ScalarStyle.Invalid)
748             {
749                 style_ = chooseScalarStyle();
750             }
751 
752             //if(analysis_.flags.multiline && (context_ != Context.MappingSimpleKey) &&
753             //   ([ScalarStyle.Invalid, ScalarStyle.Plain, ScalarStyle.SingleQuoted, ScalarStyle.DoubleQuoted)
754             //    .canFind(style_))
755             //{
756             //    writeIndent();
757             //}
758             auto writer = ScalarWriter!(Range, CharType)(this, analysis_.scalar,
759                                        context_ != Context.MappingSimpleKey);
760             with(writer) final switch(style_)
761             {
762                 case ScalarStyle.Invalid:      assert(false);
763                 case ScalarStyle.DoubleQuoted: writeDoubleQuoted(); break;
764                 case ScalarStyle.SingleQuoted: writeSingleQuoted(); break;
765                 case ScalarStyle.Folded:       writeFolded();       break;
766                 case ScalarStyle.Literal:      writeLiteral();      break;
767                 case ScalarStyle.Plain:        writePlain();        break;
768             }
769             analysis_.flags.isNull = true;
770             style_ = ScalarStyle.Invalid;
771         }
772 
773         ///Process and write an anchor/alias.
774         void processAnchor(const string indicator) @safe
775         {
776             if(event_.anchor is null)
777             {
778                 preparedAnchor_ = null;
779                 return;
780             }
781             if(preparedAnchor_ is null)
782             {
783                 preparedAnchor_ = prepareAnchor(event_.anchor);
784             }
785             if(preparedAnchor_ !is null && preparedAnchor_ != "")
786             {
787                 writeIndicator(indicator, Yes.needWhitespace);
788                 writeString(preparedAnchor_);
789             }
790             preparedAnchor_ = null;
791         }
792 
793         ///Process and write a tag.
794         void processTag() @safe
795         {
796             string tag = event_.tag;
797 
798             if(event_.id == EventID.Scalar)
799             {
800                 if(style_ == ScalarStyle.Invalid){style_ = chooseScalarStyle();}
801                 if((!canonical_ || (tag is null)) &&
802                    (style_ == ScalarStyle.Plain ? event_.implicit : !event_.implicit && (tag is null)))
803                 {
804                     preparedTag_ = null;
805                     return;
806                 }
807                 if(event_.implicit && (tag is null))
808                 {
809                     tag = "!";
810                     preparedTag_ = null;
811                 }
812             }
813             else if((!canonical_ || (tag is null)) && event_.implicit)
814             {
815                 preparedTag_ = null;
816                 return;
817             }
818 
819             enforce(tag !is null, new EmitterException("Tag is not specified"));
820             if(preparedTag_ is null){preparedTag_ = prepareTag(tag);}
821             if(preparedTag_ !is null && preparedTag_ != "")
822             {
823                 writeIndicator(preparedTag_, Yes.needWhitespace);
824             }
825             preparedTag_ = null;
826         }
827 
828         ///Determine style to write the current scalar in.
829         ScalarStyle chooseScalarStyle() @safe
830         {
831             if(analysis_.flags.isNull){analysis_ = analyzeScalar(event_.value);}
832 
833             const style          = event_.scalarStyle;
834             const invalidOrPlain = style == ScalarStyle.Invalid || style == ScalarStyle.Plain;
835             const block          = style == ScalarStyle.Literal || style == ScalarStyle.Folded;
836             const singleQuoted   = style == ScalarStyle.SingleQuoted;
837             const doubleQuoted   = style == ScalarStyle.DoubleQuoted;
838 
839             const allowPlain     = flowLevel_ > 0 ? analysis_.flags.allowFlowPlain
840                                                   : analysis_.flags.allowBlockPlain;
841             //simple empty or multiline scalars can't be written in plain style
842             const simpleNonPlain = (context_ == Context.MappingSimpleKey) &&
843                                    (analysis_.flags.empty || analysis_.flags.multiline);
844 
845             if(doubleQuoted || canonical_)
846             {
847                 return ScalarStyle.DoubleQuoted;
848             }
849 
850             if(invalidOrPlain && event_.implicit && !simpleNonPlain && allowPlain)
851             {
852                 return ScalarStyle.Plain;
853             }
854 
855             if(block && flowLevel_ == 0 && context_ != Context.MappingSimpleKey &&
856                analysis_.flags.allowBlock)
857             {
858                 return style;
859             }
860 
861             if((invalidOrPlain || singleQuoted) &&
862                analysis_.flags.allowSingleQuoted &&
863                !(context_ == Context.MappingSimpleKey && analysis_.flags.multiline))
864             {
865                 return ScalarStyle.SingleQuoted;
866             }
867 
868             return ScalarStyle.DoubleQuoted;
869         }
870 
871         ///Prepare YAML version string for output.
872         static string prepareVersion(const string YAMLVersion) @safe
873         {
874             enforce(YAMLVersion.split(".")[0] == "1",
875                     new EmitterException("Unsupported YAML version: " ~ YAMLVersion));
876             return YAMLVersion;
877         }
878 
879         ///Encode an Unicode character for tag directive and write it to writer.
880         static void encodeChar(Writer)(ref Writer writer, in dchar c) @safe
881         {
882             char[4] data;
883             const bytes = encode(data, c);
884             //For each byte add string in format %AB , where AB are hex digits of the byte.
885             foreach(const char b; data[0 .. bytes])
886             {
887                 formattedWrite(writer, "%%%02X", cast(ubyte)b);
888             }
889         }
890 
891         ///Prepare tag directive handle for output.
892         static string prepareTagHandle(const string handle) @safe
893         {
894             enforce(handle !is null && handle != "",
895                     new EmitterException("Tag handle must not be empty"));
896 
897             if(handle.length > 1) foreach(const dchar c; handle[1 .. $ - 1])
898             {
899                 enforce(isAlphaNum(c) || "-_"d.canFind(c),
900                         new EmitterException("Invalid character: " ~ to!string(c)  ~
901                                   " in tag handle " ~ handle));
902             }
903             return handle;
904         }
905 
906         ///Prepare tag directive prefix for output.
907         static string prepareTagPrefix(const string prefix) @safe
908         {
909             enforce(prefix !is null && prefix != "",
910                     new EmitterException("Tag prefix must not be empty"));
911 
912             auto appender = appender!string();
913             const int offset = prefix[0] == '!';
914             size_t start, end;
915 
916             foreach(const size_t i, const dchar c; prefix)
917             {
918                 const size_t idx = i + offset;
919                 if(isAlphaNum(c) || "-;/?:@&=+$,_.!~*\'()[]%"d.canFind(c))
920                 {
921                     end = idx + 1;
922                     continue;
923                 }
924 
925                 if(start < idx){appender.put(prefix[start .. idx]);}
926                 start = end = idx + 1;
927 
928                 encodeChar(appender, c);
929             }
930 
931             end = min(end, prefix.length);
932             if(start < end){appender.put(prefix[start .. end]);}
933             return appender.data;
934         }
935 
936         ///Prepare tag for output.
937         string prepareTag(in string tag) @safe
938         {
939             enforce(tag !is null, new EmitterException("Tag must not be empty"));
940 
941             string tagString = tag;
942             if(tagString == "!"){return tagString;}
943             string handle;
944             string suffix = tagString;
945 
946             //Sort lexicographically by prefix.
947             sort!"icmp(a.prefix, b.prefix) < 0"(tagDirectives_);
948             foreach(ref pair; tagDirectives_)
949             {
950                 auto prefix = pair.prefix;
951                 if(tagString.startsWith(prefix) &&
952                    (prefix != "!" || prefix.length < tagString.length))
953                 {
954                     handle = pair.handle;
955                     suffix = tagString[prefix.length .. $];
956                 }
957             }
958 
959             auto appender = appender!string();
960             appender.put(handle !is null && handle != "" ? handle : "!<");
961             size_t start, end;
962             foreach(const dchar c; suffix)
963             {
964                 if(isAlphaNum(c) || "-;/?:@&=+$,_.~*\'()[]"d.canFind(c) ||
965                    (c == '!' && handle != "!"))
966                 {
967                     ++end;
968                     continue;
969                 }
970                 if(start < end){appender.put(suffix[start .. end]);}
971                 start = end = end + 1;
972 
973                 encodeChar(appender, c);
974             }
975 
976             if(start < end){appender.put(suffix[start .. end]);}
977             if(handle is null || handle == ""){appender.put(">");}
978 
979             return appender.data;
980         }
981 
982         ///Prepare anchor for output.
983         static string prepareAnchor(const string anchor) @safe
984         {
985             enforce(anchor != "",
986                     new EmitterException("Anchor must not be empty"));
987             const str = anchor;
988             foreach(const dchar c; str)
989             {
990                 enforce(isAlphaNum(c) || "-_"d.canFind(c),
991                         new EmitterException("Invalid character: " ~ to!string(c) ~ " in anchor: " ~ str));
992             }
993             return str;
994         }
995 
996         ///Analyze specifed scalar and return the analysis result.
997         static ScalarAnalysis analyzeScalar(string scalar) @safe
998         {
999             ScalarAnalysis analysis;
1000             analysis.flags.isNull = false;
1001             analysis.scalar = scalar;
1002 
1003             //Empty scalar is a special case.
1004             if(scalar is null || scalar == "")
1005             {
1006                 with(ScalarAnalysis.AnalysisFlags)
1007                     analysis.flags =
1008                         empty |
1009                         allowBlockPlain |
1010                         allowSingleQuoted |
1011                         allowDoubleQuoted;
1012                 return analysis;
1013             }
1014 
1015             //Indicators and special characters (All false by default).
1016             bool blockIndicators, flowIndicators, lineBreaks, specialCharacters;
1017 
1018             //Important whitespace combinations (All false by default).
1019             bool leadingSpace, leadingBreak, trailingSpace, trailingBreak,
1020                  breakSpace, spaceBreak;
1021 
1022             //Check document indicators.
1023             if(scalar.startsWith("---", "..."))
1024             {
1025                 blockIndicators = flowIndicators = true;
1026             }
1027 
1028             //First character or preceded by a whitespace.
1029             bool preceededByWhitespace = true;
1030 
1031             //Last character or followed by a whitespace.
1032             bool followedByWhitespace = scalar.length == 1 ||
1033                                         " \t\0\n\r\u0085\u2028\u2029"d.canFind(scalar[1]);
1034 
1035             //The previous character is a space/break (false by default).
1036             bool previousSpace, previousBreak;
1037 
1038             foreach(const size_t index, const dchar c; scalar)
1039             {
1040                 //Check for indicators.
1041                 if(index == 0)
1042                 {
1043                     //Leading indicators are special characters.
1044                     if(c.isSpecialChar)
1045                     {
1046                         flowIndicators = blockIndicators = true;
1047                     }
1048                     if(':' == c || '?' == c)
1049                     {
1050                         flowIndicators = true;
1051                         if(followedByWhitespace){blockIndicators = true;}
1052                     }
1053                     if(c == '-' && followedByWhitespace)
1054                     {
1055                         flowIndicators = blockIndicators = true;
1056                     }
1057                 }
1058                 else
1059                 {
1060                     //Some indicators cannot appear within a scalar as well.
1061                     if(c.isFlowIndicator){flowIndicators = true;}
1062                     if(c == ':')
1063                     {
1064                         flowIndicators = true;
1065                         if(followedByWhitespace){blockIndicators = true;}
1066                     }
1067                     if(c == '#' && preceededByWhitespace)
1068                     {
1069                         flowIndicators = blockIndicators = true;
1070                     }
1071                 }
1072 
1073                 //Check for line breaks, special, and unicode characters.
1074                 if(c.isNewLine){lineBreaks = true;}
1075                 if(!(c == '\n' || (c >= '\x20' && c <= '\x7E')) &&
1076                    !((c == '\u0085' || (c >= '\xA0' && c <= '\uD7FF') ||
1077                      (c >= '\uE000' && c <= '\uFFFD')) && c != '\uFEFF'))
1078                 {
1079                     specialCharacters = true;
1080                 }
1081 
1082                 //Detect important whitespace combinations.
1083                 if(c == ' ')
1084                 {
1085                     if(index == 0){leadingSpace = true;}
1086                     if(index == scalar.length - 1){trailingSpace = true;}
1087                     if(previousBreak){breakSpace = true;}
1088                     previousSpace = true;
1089                     previousBreak = false;
1090                 }
1091                 else if(c.isNewLine)
1092                 {
1093                     if(index == 0){leadingBreak = true;}
1094                     if(index == scalar.length - 1){trailingBreak = true;}
1095                     if(previousSpace){spaceBreak = true;}
1096                     previousSpace = false;
1097                     previousBreak = true;
1098                 }
1099                 else
1100                 {
1101                     previousSpace = previousBreak = false;
1102                 }
1103 
1104                 //Prepare for the next character.
1105                 preceededByWhitespace = c.isSpace != 0;
1106                 followedByWhitespace = index + 2 >= scalar.length ||
1107                                        scalar[index + 2].isSpace;
1108             }
1109 
1110             with(ScalarAnalysis.AnalysisFlags)
1111             {
1112                 //Let's decide what styles are allowed.
1113                 analysis.flags |= allowFlowPlain | allowBlockPlain | allowSingleQuoted |
1114                                allowDoubleQuoted | allowBlock;
1115 
1116                 //Leading and trailing whitespaces are bad for plain scalars.
1117                 if(leadingSpace || leadingBreak || trailingSpace || trailingBreak)
1118                 {
1119                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain);
1120                 }
1121 
1122                 //We do not permit trailing spaces for block scalars.
1123                 if(trailingSpace)
1124                 {
1125                     analysis.flags &= ~allowBlock;
1126                 }
1127 
1128                 //Spaces at the beginning of a new line are only acceptable for block
1129                 //scalars.
1130                 if(breakSpace)
1131                 {
1132                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain | allowSingleQuoted);
1133                 }
1134 
1135                 //Spaces followed by breaks, as well as special character are only
1136                 //allowed for double quoted scalars.
1137                 if(spaceBreak || specialCharacters)
1138                 {
1139                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain | allowSingleQuoted | allowBlock);
1140                 }
1141 
1142                 //Although the plain scalar writer supports breaks, we never emit
1143                 //multiline plain scalars.
1144                 if(lineBreaks)
1145                 {
1146                     analysis.flags &= ~(allowFlowPlain | allowBlockPlain);
1147                     analysis.flags |= multiline;
1148                 }
1149 
1150                 //Flow indicators are forbidden for flow plain scalars.
1151                 if(flowIndicators)
1152                 {
1153                     analysis.flags &= ~allowFlowPlain;
1154                 }
1155 
1156                 //Block indicators are forbidden for block plain scalars.
1157                 if(blockIndicators)
1158                 {
1159                     analysis.flags &= ~allowBlockPlain;
1160                 }
1161             }
1162             return analysis;
1163         }
1164 
1165         @safe unittest
1166         {
1167             with(analyzeScalar("").flags)
1168             {
1169                 // workaround for empty being std.range.primitives.empty here
1170                 alias empty = ScalarAnalysis.AnalysisFlags.empty;
1171                 assert(empty && allowBlockPlain && allowSingleQuoted && allowDoubleQuoted);
1172             }
1173             with(analyzeScalar("a").flags)
1174             {
1175                 assert(allowFlowPlain && allowBlockPlain && allowSingleQuoted && allowDoubleQuoted && allowBlock);
1176             }
1177             with(analyzeScalar(" ").flags)
1178             {
1179                 assert(allowSingleQuoted && allowDoubleQuoted);
1180             }
1181             with(analyzeScalar(" a").flags)
1182             {
1183                 assert(allowSingleQuoted && allowDoubleQuoted);
1184             }
1185             with(analyzeScalar("a ").flags)
1186             {
1187                 assert(allowSingleQuoted && allowDoubleQuoted);
1188             }
1189             with(analyzeScalar("\na").flags)
1190             {
1191                 assert(allowSingleQuoted && allowDoubleQuoted);
1192             }
1193             with(analyzeScalar("a\n").flags)
1194             {
1195                 assert(allowSingleQuoted && allowDoubleQuoted);
1196             }
1197             with(analyzeScalar("\n").flags)
1198             {
1199                 assert(multiline && allowSingleQuoted && allowDoubleQuoted && allowBlock);
1200             }
1201             with(analyzeScalar(" \n").flags)
1202             {
1203                 assert(multiline && allowDoubleQuoted);
1204             }
1205             with(analyzeScalar("\n a").flags)
1206             {
1207                 assert(multiline && allowDoubleQuoted && allowBlock);
1208             }
1209         }
1210 
1211         //Writers.
1212 
1213         ///Start the YAML stream (write the unicode byte order mark).
1214         void writeStreamStart() @safe
1215         {
1216             //Write BOM (except for UTF-8)
1217             static if(is(CharType == wchar) || is(CharType == dchar))
1218             {
1219                 stream_.put(cast(CharType)'\uFEFF');
1220             }
1221         }
1222 
1223         ///End the YAML stream.
1224         void writeStreamEnd() @safe {}
1225 
1226         ///Write an indicator (e.g. ":", "[", ">", etc.).
1227         void writeIndicator(const char[] indicator,
1228                             const Flag!"needWhitespace" needWhitespace,
1229                             const Flag!"whitespace" whitespace = No.whitespace,
1230                             const Flag!"indentation" indentation = No.indentation) @safe
1231         {
1232             const bool prefixSpace = !whitespace_ && needWhitespace;
1233             whitespace_  = whitespace;
1234             indentation_ = indentation_ && indentation;
1235             openEnded_   = false;
1236             column_ += indicator.length;
1237             if(prefixSpace)
1238             {
1239                 ++column_;
1240                 writeString(" ");
1241             }
1242             writeString(indicator);
1243         }
1244 
1245         ///Write indentation.
1246         void writeIndent() @safe
1247         {
1248             const indent = indent_ == -1 ? 0 : indent_;
1249 
1250             if(!indentation_ || column_ > indent || (column_ == indent && !whitespace_))
1251             {
1252                 writeLineBreak();
1253             }
1254             if(column_ < indent)
1255             {
1256                 whitespace_ = true;
1257 
1258                 //Used to avoid allocation of arbitrary length strings.
1259                 static immutable spaces = "    ";
1260                 size_t numSpaces = indent - column_;
1261                 column_ = indent;
1262                 while(numSpaces >= spaces.length)
1263                 {
1264                     writeString(spaces);
1265                     numSpaces -= spaces.length;
1266                 }
1267                 writeString(spaces[0 .. numSpaces]);
1268             }
1269         }
1270 
1271         ///Start new line.
1272         void writeLineBreak(const char[] data = null) @safe
1273         {
1274             whitespace_ = indentation_ = true;
1275             ++line_;
1276             column_ = 0;
1277             writeString(data is null ? lineBreak(bestLineBreak_) : data);
1278         }
1279 
1280         ///Write a YAML version directive.
1281         void writeVersionDirective(const string versionText) @safe
1282         {
1283             writeString("%YAML ");
1284             writeString(versionText);
1285             writeLineBreak();
1286         }
1287 
1288         ///Write a tag directive.
1289         void writeTagDirective(const string handle, const string prefix) @safe
1290         {
1291             writeString("%TAG ");
1292             writeString(handle);
1293             writeString(" ");
1294             writeString(prefix);
1295             writeLineBreak();
1296         }
1297 
1298         void nextExpected(void delegate() @safe func) @trusted
1299         {
1300             state_ = func.funcptr;
1301         }
1302         void nextExpected(void function() @safe func) @safe
1303         {
1304             state_ = func;
1305         }
1306         void callNext() @trusted
1307         {
1308             void delegate() @safe func;
1309             func.funcptr = state_;
1310             func.ptr = &this;
1311             func();
1312         }
1313 }
1314 
1315 
1316 private:
1317 
1318 ///RAII struct used to write out scalar values.
1319 struct ScalarWriter(Range, CharType)
1320 {
1321     invariant()
1322     {
1323         assert(emitter_.bestIndent_ > 0 && emitter_.bestIndent_ < 10,
1324                "Emitter bestIndent must be 1 to 9 for one-character indent hint");
1325     }
1326 
1327     private:
1328         @disable int opCmp(ref Emitter!(Range, CharType));
1329         @disable bool opEquals(ref Emitter!(Range, CharType));
1330 
1331         ///Used as "null" UTF-32 character.
1332         static immutable dcharNone = dchar.max;
1333 
1334         ///Emitter used to emit the scalar.
1335         Emitter!(Range, CharType)* emitter_;
1336 
1337         ///UTF-8 encoded text of the scalar to write.
1338         string text_;
1339 
1340         ///Can we split the scalar into multiple lines?
1341         bool split_;
1342         ///Are we currently going over spaces in the text?
1343         bool spaces_;
1344         ///Are we currently going over line breaks in the text?
1345         bool breaks_;
1346 
1347         ///Start and end byte of the text range we're currently working with.
1348         size_t startByte_, endByte_;
1349         ///End byte of the text range including the currently processed character.
1350         size_t nextEndByte_;
1351         ///Start and end character of the text range we're currently working with.
1352         long startChar_, endChar_;
1353 
1354     public:
1355         ///Construct a ScalarWriter using emitter to output text.
1356         this(ref Emitter!(Range, CharType) emitter, string text, const bool split = true) @trusted nothrow
1357         {
1358             emitter_ = &emitter;
1359             text_ = text;
1360             split_ = split;
1361         }
1362 
1363         ///Write text as single quoted scalar.
1364         void writeSingleQuoted() @safe
1365         {
1366             emitter_.writeIndicator("\'", Yes.needWhitespace);
1367             spaces_ = breaks_ = false;
1368             resetTextPosition();
1369 
1370             do
1371             {
1372                 const dchar c = nextChar();
1373                 if(spaces_)
1374                 {
1375                     if(c != ' ' && tooWide() && split_ &&
1376                        startByte_ != 0 && endByte_ != text_.length)
1377                     {
1378                         writeIndent(Flag!"ResetSpace".no);
1379                         updateRangeStart();
1380                     }
1381                     else if(c != ' ')
1382                     {
1383                         writeCurrentRange(Flag!"UpdateColumn".yes);
1384                     }
1385                 }
1386                 else if(breaks_)
1387                 {
1388                     if(!c.isNewLine)
1389                     {
1390                         writeStartLineBreak();
1391                         writeLineBreaks();
1392                         emitter_.writeIndent();
1393                     }
1394                 }
1395                 else if((c == dcharNone || c == '\'' || c == ' ' || c.isNewLine)
1396                         && startChar_ < endChar_)
1397                 {
1398                     writeCurrentRange(Flag!"UpdateColumn".yes);
1399                 }
1400                 if(c == '\'')
1401                 {
1402                     emitter_.column_ += 2;
1403                     emitter_.writeString("\'\'");
1404                     startByte_ = endByte_ + 1;
1405                     startChar_ = endChar_ + 1;
1406                 }
1407                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1408             }while(endByte_ < text_.length);
1409 
1410             emitter_.writeIndicator("\'", No.needWhitespace);
1411         }
1412 
1413         ///Write text as double quoted scalar.
1414         void writeDoubleQuoted() @safe
1415         {
1416             resetTextPosition();
1417             emitter_.writeIndicator("\"", Yes.needWhitespace);
1418             do
1419             {
1420                 const dchar c = nextChar();
1421                 //handle special characters
1422                 if(c == dcharNone || "\"\\\u0085\u2028\u2029\uFEFF"d.canFind(c) ||
1423                    !((c >= '\x20' && c <= '\x7E') ||
1424                      ((c >= '\xA0' && c <= '\uD7FF') || (c >= '\uE000' && c <= '\uFFFD'))))
1425                 {
1426                     if(startChar_ < endChar_)
1427                     {
1428                         writeCurrentRange(Flag!"UpdateColumn".yes);
1429                     }
1430                     if(c != dcharNone)
1431                     {
1432                         auto appender = appender!string();
1433                         if(const dchar es = toEscape(c))
1434                         {
1435                             appender.put('\\');
1436                             appender.put(es);
1437                         }
1438                         else
1439                         {
1440                             //Write an escaped Unicode character.
1441                             const format = c <= 255   ? "\\x%02X":
1442                                            c <= 65535 ? "\\u%04X": "\\U%08X";
1443                             formattedWrite(appender, format, cast(uint)c);
1444                         }
1445 
1446                         emitter_.column_ += appender.data.length;
1447                         emitter_.writeString(appender.data);
1448                         startChar_ = endChar_ + 1;
1449                         startByte_ = nextEndByte_;
1450                     }
1451                 }
1452                 if((endByte_ > 0 && endByte_ < text_.length - strideBack(text_, text_.length))
1453                    && (c == ' ' || startChar_ >= endChar_)
1454                    && (emitter_.column_ + endChar_ - startChar_ > emitter_.bestWidth_)
1455                    && split_)
1456                 {
1457                     //text_[2:1] is ok in Python but not in D, so we have to use min()
1458                     emitter_.writeString(text_[min(startByte_, endByte_) .. endByte_]);
1459                     emitter_.writeString("\\");
1460                     emitter_.column_ += startChar_ - endChar_ + 1;
1461                     startChar_ = max(startChar_, endChar_);
1462                     startByte_ = max(startByte_, endByte_);
1463 
1464                     writeIndent(Flag!"ResetSpace".yes);
1465                     if(charAtStart() == ' ')
1466                     {
1467                         emitter_.writeString("\\");
1468                         ++emitter_.column_;
1469                     }
1470                 }
1471             }while(endByte_ < text_.length);
1472             emitter_.writeIndicator("\"", No.needWhitespace);
1473         }
1474 
1475         ///Write text as folded block scalar.
1476         void writeFolded() @safe
1477         {
1478             initBlock('>');
1479             bool leadingSpace = true;
1480             spaces_ = false;
1481             breaks_ = true;
1482             resetTextPosition();
1483 
1484             do
1485             {
1486                 const dchar c = nextChar();
1487                 if(breaks_)
1488                 {
1489                     if(!c.isNewLine)
1490                     {
1491                         if(!leadingSpace && c != dcharNone && c != ' ')
1492                         {
1493                             writeStartLineBreak();
1494                         }
1495                         leadingSpace = (c == ' ');
1496                         writeLineBreaks();
1497                         if(c != dcharNone){emitter_.writeIndent();}
1498                     }
1499                 }
1500                 else if(spaces_)
1501                 {
1502                     if(c != ' ' && tooWide())
1503                     {
1504                         writeIndent(Flag!"ResetSpace".no);
1505                         updateRangeStart();
1506                     }
1507                     else if(c != ' ')
1508                     {
1509                         writeCurrentRange(Flag!"UpdateColumn".yes);
1510                     }
1511                 }
1512                 else if(c == dcharNone || c.isNewLine || c == ' ')
1513                 {
1514                     writeCurrentRange(Flag!"UpdateColumn".yes);
1515                     if(c == dcharNone){emitter_.writeLineBreak();}
1516                 }
1517                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1518             }while(endByte_ < text_.length);
1519         }
1520 
1521         ///Write text as literal block scalar.
1522         void writeLiteral() @safe
1523         {
1524             initBlock('|');
1525             breaks_ = true;
1526             resetTextPosition();
1527 
1528             do
1529             {
1530                 const dchar c = nextChar();
1531                 if(breaks_)
1532                 {
1533                     if(!c.isNewLine)
1534                     {
1535                         writeLineBreaks();
1536                         if(c != dcharNone){emitter_.writeIndent();}
1537                     }
1538                 }
1539                 else if(c == dcharNone || c.isNewLine)
1540                 {
1541                     writeCurrentRange(Flag!"UpdateColumn".no);
1542                     if(c == dcharNone){emitter_.writeLineBreak();}
1543                 }
1544                 updateBreaks(c, Flag!"UpdateSpaces".no);
1545             }while(endByte_ < text_.length);
1546         }
1547 
1548         ///Write text as plain scalar.
1549         void writePlain() @safe
1550         {
1551             if(emitter_.context_ == Emitter!(Range, CharType).Context.Root){emitter_.openEnded_ = true;}
1552             if(text_ == ""){return;}
1553             if(!emitter_.whitespace_)
1554             {
1555                 ++emitter_.column_;
1556                 emitter_.writeString(" ");
1557             }
1558             emitter_.whitespace_ = emitter_.indentation_ = false;
1559             spaces_ = breaks_ = false;
1560             resetTextPosition();
1561 
1562             do
1563             {
1564                 const dchar c = nextChar();
1565                 if(spaces_)
1566                 {
1567                     if(c != ' ' && tooWide() && split_)
1568                     {
1569                         writeIndent(Flag!"ResetSpace".yes);
1570                         updateRangeStart();
1571                     }
1572                     else if(c != ' ')
1573                     {
1574                         writeCurrentRange(Flag!"UpdateColumn".yes);
1575                     }
1576                 }
1577                 else if(breaks_)
1578                 {
1579                     if(!c.isNewLine)
1580                     {
1581                         writeStartLineBreak();
1582                         writeLineBreaks();
1583                         writeIndent(Flag!"ResetSpace".yes);
1584                     }
1585                 }
1586                 else if(c == dcharNone || c.isNewLine || c == ' ')
1587                 {
1588                     writeCurrentRange(Flag!"UpdateColumn".yes);
1589                 }
1590                 updateBreaks(c, Flag!"UpdateSpaces".yes);
1591             }while(endByte_ < text_.length);
1592         }
1593 
1594     private:
1595         ///Get next character and move end of the text range to it.
1596         @property dchar nextChar() pure @safe
1597         {
1598             ++endChar_;
1599             endByte_ = nextEndByte_;
1600             if(endByte_ >= text_.length){return dcharNone;}
1601             const c = text_[nextEndByte_];
1602             //c is ascii, no need to decode.
1603             if(c < 0x80)
1604             {
1605                 ++nextEndByte_;
1606                 return c;
1607             }
1608             return decode(text_, nextEndByte_);
1609         }
1610 
1611         ///Get character at start of the text range.
1612         @property dchar charAtStart() const pure @safe
1613         {
1614             size_t idx = startByte_;
1615             return decode(text_, idx);
1616         }
1617 
1618         ///Is the current line too wide?
1619         @property bool tooWide() const pure @safe nothrow
1620         {
1621             return startChar_ + 1 == endChar_ &&
1622                    emitter_.column_ > emitter_.bestWidth_;
1623         }
1624 
1625         ///Determine hints (indicators) for block scalar.
1626         size_t determineBlockHints(char[] hints, uint bestIndent) const pure @safe
1627         {
1628             size_t hintsIdx;
1629             if(text_.length == 0)
1630                 return hintsIdx;
1631 
1632             dchar lastChar(const string str, ref size_t end)
1633             {
1634                 size_t idx = end = end - strideBack(str, end);
1635                 return decode(text_, idx);
1636             }
1637 
1638             size_t end = text_.length;
1639             const last = lastChar(text_, end);
1640             const secondLast = end > 0 ? lastChar(text_, end) : 0;
1641 
1642             if(text_[0].isNewLine || text_[0] == ' ')
1643             {
1644                 hints[hintsIdx++] = cast(char)('0' + bestIndent);
1645             }
1646             if(!last.isNewLine)
1647             {
1648                 hints[hintsIdx++] = '-';
1649             }
1650             else if(std.utf.count(text_) == 1 || secondLast.isNewLine)
1651             {
1652                 hints[hintsIdx++] = '+';
1653             }
1654             return hintsIdx;
1655         }
1656 
1657         ///Initialize for block scalar writing with specified indicator.
1658         void initBlock(const char indicator) @safe
1659         {
1660             char[4] hints;
1661             hints[0] = indicator;
1662             const hintsLength = 1 + determineBlockHints(hints[1 .. $], emitter_.bestIndent_);
1663             emitter_.writeIndicator(hints[0 .. hintsLength], Yes.needWhitespace);
1664             if(hints.length > 0 && hints[$ - 1] == '+')
1665             {
1666                 emitter_.openEnded_ = true;
1667             }
1668             emitter_.writeLineBreak();
1669         }
1670 
1671         ///Write out the current text range.
1672         void writeCurrentRange(const Flag!"UpdateColumn" updateColumn) @safe
1673         {
1674             emitter_.writeString(text_[startByte_ .. endByte_]);
1675             if(updateColumn){emitter_.column_ += endChar_ - startChar_;}
1676             updateRangeStart();
1677         }
1678 
1679         ///Write line breaks in the text range.
1680         void writeLineBreaks() @safe
1681         {
1682             foreach(const dchar br; text_[startByte_ .. endByte_])
1683             {
1684                 if(br == '\n'){emitter_.writeLineBreak();}
1685                 else
1686                 {
1687                     char[4] brString;
1688                     const bytes = encode(brString, br);
1689                     emitter_.writeLineBreak(brString[0 .. bytes]);
1690                 }
1691             }
1692             updateRangeStart();
1693         }
1694 
1695         ///Write line break if start of the text range is a newline.
1696         void writeStartLineBreak() @safe
1697         {
1698             if(charAtStart == '\n'){emitter_.writeLineBreak();}
1699         }
1700 
1701         ///Write indentation, optionally resetting whitespace/indentation flags.
1702         void writeIndent(const Flag!"ResetSpace" resetSpace) @safe
1703         {
1704             emitter_.writeIndent();
1705             if(resetSpace)
1706             {
1707                 emitter_.whitespace_ = emitter_.indentation_ = false;
1708             }
1709         }
1710 
1711         ///Move start of text range to its end.
1712         void updateRangeStart() pure @safe nothrow
1713         {
1714             startByte_ = endByte_;
1715             startChar_ = endChar_;
1716         }
1717 
1718         ///Update the line breaks_ flag, optionally updating the spaces_ flag.
1719         void updateBreaks(in dchar c, const Flag!"UpdateSpaces" updateSpaces) pure @safe
1720         {
1721             if(c == dcharNone){return;}
1722             breaks_ = (c.isNewLine != 0);
1723             if(updateSpaces){spaces_ = c == ' ';}
1724         }
1725 
1726         ///Move to the beginning of text.
1727         void resetTextPosition() pure @safe nothrow
1728         {
1729             startByte_ = endByte_ = nextEndByte_ = 0;
1730             startChar_ = endChar_ = -1;
1731         }
1732 }