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