psych-pure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/psych/pure.rb ADDED
@@ -0,0 +1,3577 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require "pp"
5
+ require "psych"
6
+ require "strscan"
7
+ require "stringio"
8
+
9
+ module Psych
10
+ # A YAML parser written in Ruby.
11
+ module Pure
12
+ # Represents a comment in the input.
13
+ class Comment
14
+ attr_reader :value, :start_line, :start_column, :end_line, :end_column
15
+
16
+ def initialize(value, start_line, start_column, end_line, end_column, inline)
17
+ @value = value
18
+ @start_line = start_line
19
+ @start_column = start_column
20
+ @end_line = end_line
21
+ @end_column = end_column
22
+ @inline = inline
23
+ end
24
+
25
+ def inline?
26
+ @inline
27
+ end
28
+ end
29
+
30
+ # Wraps a Ruby object with its leading and trailing YAML comments from the
31
+ # source input.
32
+ class YAMLCommentsDelegator < SimpleDelegator
33
+ attr_reader :yaml_leading_comments, :yaml_trailing_comments
34
+
35
+ def initialize(object, yaml_leading_comments, yaml_trailing_comments)
36
+ super(object)
37
+ @yaml_leading_comments = yaml_leading_comments
38
+ @yaml_trailing_comments = yaml_trailing_comments
39
+ end
40
+ end
41
+
42
+ # This module contains all of the extensions to Psych that we need in order
43
+ # to support parsing comments.
44
+ module CommentExtensions
45
+ # Extend the Handler to be able to handle comments coming out of the
46
+ # parser.
47
+ module Handler
48
+ def comment(value, start_line, start_column, end_line, end_column, inline)
49
+ end
50
+ end
51
+
52
+ # Extend the TreeBuilder to be able to attach comments to nodes.
53
+ module TreeBuilder
54
+ def comments
55
+ @comments ||= []
56
+ end
57
+
58
+ def comment(value, start_line, start_column, end_line, end_column, inline)
59
+ comments << Comment.new(value, start_line, start_column, end_line, end_column, inline)
60
+ end
61
+
62
+ def end_stream
63
+ attach_comments(super)
64
+ end
65
+
66
+ private
67
+
68
+ def attach_comments(node)
69
+ comments.each do |comment|
70
+ preceding, enclosing, following = nearest_nodes(node, comment)
71
+
72
+ if comment.inline?
73
+ if preceding
74
+ preceding.trailing_comment(comment)
75
+ else
76
+ (following || enclosing || node).leading_comment(comment)
77
+ end
78
+ else
79
+ # If a comment exists on its own line, prefer a leading comment.
80
+ if following
81
+ following.leading_comment(comment)
82
+ elsif preceding
83
+ preceding.trailing_comment(comment)
84
+ else
85
+ (enclosing || node).leading_comment(comment)
86
+ end
87
+ end
88
+ end
89
+
90
+ comments.clear
91
+ node
92
+ end
93
+
94
+ def nearest_nodes(node, comment)
95
+ candidates = (node.children || []).sort_by { |child| [child.start_line, child.start_column] }
96
+ preceding = nil
97
+ following = nil
98
+
99
+ comment_start_line = comment.start_line
100
+ comment_start_column = comment.start_column
101
+ comment_end_line = comment.end_line
102
+ comment_end_column = comment.end_column
103
+
104
+ left = 0
105
+ right = candidates.length
106
+
107
+ # This is a custom binary search that finds the nearest nodes to the
108
+ # given comment. When it finds a node that completely encapsulates the
109
+ # comment, it recurses downward into the tree.
110
+ while left < right
111
+ middle = (left + right) / 2
112
+ candidate = candidates[middle]
113
+
114
+ if ((comment_start_line > candidate.start_line) || (comment_start_line == candidate.start_line && comment_start_column >= candidate.start_column)) &&
115
+ ((comment_end_line < candidate.end_line) || (comment_end_line == candidate.end_line && comment_end_column <= candidate.end_column))
116
+ # The comment is completely contained by this candidate node.
117
+ # Abandon the binary search at this level.
118
+ return nearest_nodes(candidate, comment)
119
+ end
120
+
121
+ if (candidate.end_line < comment_start_line) ||
122
+ (candidate.end_line == comment_start_line && candidate.end_column <= comment_start_column)
123
+ # This candidate falls completely before the comment. Because we
124
+ # will never consider this candidate or any candidates before it
125
+ # again, this candidate must be the closest preceding candidate we
126
+ # have encountered so far.
127
+ preceding = candidate
128
+ left = middle + 1
129
+ next
130
+ end
131
+
132
+ if (candidate.start_line > comment_end_line) ||
133
+ (candidate.start_line == comment_end_line && candidate.start_column >= comment_end_column)
134
+ # This candidate falls completely after the comment. Because we
135
+ # will never consider this candidate or any candidates after it
136
+ # again, this candidate must be the closest following candidate we
137
+ # have encountered so far.
138
+ following = candidate
139
+ right = middle
140
+ next
141
+ end
142
+
143
+ # This should only happen if there is a bug in this parser.
144
+ raise InternalException, "Comment location overlaps with a target location"
145
+ end
146
+
147
+ [preceding, node, following]
148
+ end
149
+ end
150
+
151
+ # Extend the document stream to be able to attach comments to the
152
+ # document.
153
+ module DocumentStream
154
+ def end_document(implicit_end = !streaming?)
155
+ @last.implicit_end = implicit_end
156
+ @block.call(attach_comments(pop))
157
+ end
158
+ end
159
+
160
+ # Extend the nodes to be able to store comments.
161
+ module Node
162
+ def leading_comments
163
+ @leading_comments ||= []
164
+ end
165
+
166
+ def leading_comment(comment)
167
+ leading_comments << comment
168
+ end
169
+
170
+ def trailing_comments
171
+ @trailing_comments ||= []
172
+ end
173
+
174
+ def trailing_comment(comment)
175
+ trailing_comments << comment
176
+ end
177
+
178
+ def comments?
179
+ (defined?(@leading_comments) && @leading_comments.any?) ||
180
+ (defined?(@trailing_comments) && @trailing_comments.any?)
181
+ end
182
+ end
183
+
184
+ # Extend the ToRuby visitor to be able to attach comments to the resulting
185
+ # Ruby objects.
186
+ module ToRuby
187
+ def accept(node)
188
+ result = super
189
+
190
+ if node.comments?
191
+ result =
192
+ YAMLCommentsDelegator.new(
193
+ result,
194
+ node.leading_comments,
195
+ node.trailing_comments
196
+ )
197
+ end
198
+
199
+ result
200
+ end
201
+ end
202
+ end
203
+
204
+ ::Psych::Handler.prepend(CommentExtensions::Handler)
205
+ ::Psych::TreeBuilder.prepend(CommentExtensions::TreeBuilder)
206
+ ::Psych::Handlers::DocumentStream.prepend(CommentExtensions::DocumentStream)
207
+ ::Psych::Nodes::Node.prepend(CommentExtensions::Node)
208
+ ::Psych::Visitors::ToRuby.prepend(CommentExtensions::ToRuby)
209
+
210
+ # An internal exception is an exception that should not have occurred. It is
211
+ # effectively an assertion.
212
+ class InternalException < Exception
213
+ def initialize(message = "An internal exception occurred")
214
+ super(message)
215
+ end
216
+ end
217
+
218
+ # A source is wraps the input string and provides methods to access line and
219
+ # column information from a byte offset.
220
+ class Source
221
+ def initialize(string)
222
+ @line_offsets = []
223
+
224
+ offset = 0
225
+ string.each_line do |line|
226
+ @line_offsets << offset
227
+ offset += line.bytesize
228
+ end
229
+
230
+ @line_offsets << offset
231
+ end
232
+
233
+ def line(offset)
234
+ index = @line_offsets.bsearch_index { |line_offset| line_offset > offset }
235
+ return @line_offsets.size - 1 if index.nil?
236
+ index - 1
237
+ end
238
+
239
+ def column(offset)
240
+ offset - @line_offsets[line(offset)]
241
+ end
242
+ end
243
+
244
+ # A location represents a range of bytes in the input string.
245
+ class Location
246
+ protected attr_reader :pos_end
247
+
248
+ def initialize(source, pos_start, pos_end)
249
+ @source = source
250
+ @pos_start = pos_start
251
+ @pos_end = pos_end
252
+ end
253
+
254
+ def join(other)
255
+ @pos_end = other.pos_end
256
+ end
257
+
258
+ def to_a
259
+ [
260
+ @source.line(@pos_start),
261
+ @source.column(@pos_start),
262
+ @source.line(@pos_end),
263
+ @source.column(@pos_end)
264
+ ]
265
+ end
266
+
267
+ def self.point(source, pos)
268
+ new(source, pos, pos)
269
+ end
270
+ end
271
+
272
+ # An alias event represents a reference to a previously defined anchor.
273
+ class Alias
274
+ attr_reader :location, :name
275
+
276
+ def initialize(location, name)
277
+ @location = location
278
+ @name = name
279
+ end
280
+
281
+ def accept(handler)
282
+ handler.event_location(*@location)
283
+ handler.alias(@name)
284
+ end
285
+ end
286
+
287
+ # A document start event represents the beginning of a new document, of
288
+ # which there can be multiple in a single YAML stream.
289
+ class DocumentStart
290
+ attr_reader :location, :tag_directives
291
+ attr_accessor :version
292
+ attr_writer :implicit
293
+
294
+ def initialize(location)
295
+ @location = location
296
+ @version = nil
297
+ @tag_directives = {}
298
+ @implicit = true
299
+ end
300
+
301
+ def accept(handler)
302
+ handler.event_location(*@location)
303
+ handler.start_document(@version, @tag_directives.to_a, @implicit)
304
+ end
305
+ end
306
+
307
+ # A document end event represents the end of a document, which may be
308
+ # implicit at the end of the stream or explicit with the ... delimiter.
309
+ class DocumentEnd
310
+ attr_reader :location
311
+ attr_writer :implicit
312
+
313
+ def initialize(location)
314
+ @location = location
315
+ @implicit = true
316
+ end
317
+
318
+ def accept(handler)
319
+ handler.event_location(*@location)
320
+ handler.end_document(@implicit)
321
+ end
322
+ end
323
+
324
+ # A mapping start event represents the beginning of a new mapping, which is
325
+ # a set of key-value pairs.
326
+ class MappingStart
327
+ attr_reader :location, :style
328
+ attr_accessor :anchor, :tag
329
+
330
+ def initialize(location, style)
331
+ @location = location
332
+ @anchor = nil
333
+ @tag = nil
334
+ @style = style
335
+ end
336
+
337
+ def accept(handler)
338
+ handler.event_location(*@location)
339
+ handler.start_mapping(@anchor, @tag, @style == Nodes::Mapping::BLOCK, @style)
340
+ end
341
+ end
342
+
343
+ # A mapping end event represents the end of a mapping.
344
+ class MappingEnd
345
+ attr_reader :location
346
+
347
+ def initialize(location)
348
+ @location = location
349
+ end
350
+
351
+ def accept(handler)
352
+ handler.event_location(*@location)
353
+ handler.end_mapping
354
+ end
355
+ end
356
+
357
+ # A scalar event represents a single value in the YAML document. It can be
358
+ # many different types.
359
+ class Scalar
360
+ attr_reader :location, :value, :style
361
+ attr_accessor :anchor, :tag
362
+
363
+ def initialize(location, value, style)
364
+ @location = location
365
+ @value = value
366
+ @anchor = nil
367
+ @tag = nil
368
+ @style = style
369
+ end
370
+
371
+ def accept(handler)
372
+ handler.event_location(*@location)
373
+ handler.scalar(
374
+ @value,
375
+ @anchor,
376
+ @tag,
377
+ (!@tag || @tag == "!") && (@style == Nodes::Scalar::PLAIN),
378
+ (!@tag || @tag == "!") && (@style != Nodes::Scalar::PLAIN),
379
+ @style
380
+ )
381
+ end
382
+ end
383
+
384
+ # A sequence start event represents the beginning of a new sequence, which
385
+ # is a list of values.
386
+ class SequenceStart
387
+ attr_reader :location, :style
388
+ attr_accessor :anchor, :tag
389
+
390
+ def initialize(location, style)
391
+ @location = location
392
+ @anchor = nil
393
+ @tag = nil
394
+ @style = style
395
+ end
396
+
397
+ def accept(handler)
398
+ handler.event_location(*@location)
399
+ handler.start_sequence(@anchor, @tag, @style == Nodes::Sequence::BLOCK, @style)
400
+ end
401
+ end
402
+
403
+ # A sequence end event represents the end of a sequence.
404
+ class SequenceEnd
405
+ attr_reader :location
406
+
407
+ def initialize(location)
408
+ @location = location
409
+ end
410
+
411
+ def accept(handler)
412
+ handler.event_location(*@location)
413
+ handler.end_sequence
414
+ end
415
+ end
416
+
417
+ # A stream start event represents the beginning of a new stream. There
418
+ # should only be one of these in a YAML stream.
419
+ class StreamStart
420
+ attr_reader :location
421
+
422
+ def initialize(location)
423
+ @location = location
424
+ end
425
+
426
+ def accept(handler)
427
+ handler.event_location(*@location)
428
+ handler.start_stream(Psych::Parser::UTF8)
429
+ end
430
+ end
431
+
432
+ # A stream end event represents the end of a stream. There should only be
433
+ # one of these in a YAML stream.
434
+ class StreamEnd
435
+ attr_reader :location
436
+
437
+ def initialize(location)
438
+ @location = location
439
+ end
440
+
441
+ def accept(handler)
442
+ handler.event_location(*@location)
443
+ handler.end_stream
444
+ end
445
+ end
446
+
447
+ # The parser is responsible for taking a YAML string and converting it into
448
+ # a series of events that can be used by the consumer.
449
+ class Parser
450
+ # Initialize a new parser with the given source string.
451
+ def initialize(handler)
452
+ # These are used to track the current state of the parser.
453
+ @scanner = nil
454
+ @filename = nil
455
+ @source = nil
456
+
457
+ # The handler is the consumer of the events generated by the parser.
458
+ @handler = handler
459
+
460
+ # This functions as a list of temporary lists of events that may be
461
+ # flushed into the handler if current context is matched.
462
+ @events_cache = []
463
+
464
+ # These events are used to track the start and end of a document. They
465
+ # are flushed into the main events list when a new document is started.
466
+ @document_start_event = nil
467
+ @document_end_event = nil
468
+
469
+ # Each document gets its own set of tags. This is a mapping of tag
470
+ # handles to tag prefixes.
471
+ @tag_directives = nil
472
+
473
+ # When a tag property is parsed, it is stored here until it can be
474
+ # flushed into the next event.
475
+ @tag = nil
476
+
477
+ # When an anchor is parsed, it is stored here until it can be flushed
478
+ # into the next event.
479
+ @anchor = nil
480
+
481
+ # In a bare document, explicit document starts (---) and ends (...) are
482
+ # disallowed. In that case we need to check for those delimiters.
483
+ @in_bare_document = false
484
+
485
+ # In a literal or folded scalar, we need to track that state in order to
486
+ # insert the correct plain text prefix.
487
+ @in_scalar = false
488
+ @text_prefix = +""
489
+
490
+ # This parser can optionally parse comments and attach them to the
491
+ # resulting tree, if the option is passed.
492
+ @comments = false
493
+ end
494
+
495
+ # Top-level parse function that starts the parsing process.
496
+ def parse(yaml, filename = yaml.respond_to?(:path) ? yaml.path : "<unknown>", comments: false)
497
+ if yaml.respond_to?(:read)
498
+ yaml = yaml.read
499
+ elsif !yaml.is_a?(String)
500
+ raise TypeError, "Expected an IO or a String, got #{yaml.class}"
501
+ end
502
+
503
+ # This parser only supports UTF-8 encoding at the moment. This is
504
+ # largely due to the performance impact of having to convert all of the
505
+ # strings and regular expressions into compatible encodings. We do not
506
+ # attempt to transcode, as the locations would all be off at that point.
507
+ if yaml.encoding != Encoding::UTF_8
508
+ raise ArgumentError, "Expected UTF-8 encoding, got #{yaml.encoding}"
509
+ end
510
+
511
+ yaml += "\n" if !yaml.empty? && !yaml.end_with?("\n")
512
+ @scanner = StringScanner.new(yaml)
513
+ @filename = filename
514
+ @source = Source.new(yaml)
515
+ @comments = comments
516
+
517
+ raise_syntax_error("Parser failed to complete") unless parse_l_yaml_stream
518
+ raise_syntax_error("Parser finished before end of input") unless @scanner.eos?
519
+ true
520
+ end
521
+
522
+ private
523
+
524
+ # Raise a syntax error with the given message.
525
+ def raise_syntax_error(message)
526
+ line = @source.line(@scanner.pos)
527
+ column = @source.column(@scanner.pos)
528
+ raise SyntaxError.new(@filename, line, column, @scanner.pos, message, nil)
529
+ end
530
+
531
+ # ------------------------------------------------------------------------
532
+ # :section: Parsing helpers
533
+ # ------------------------------------------------------------------------
534
+
535
+ # In certain cirumstances, we need to determine the indent based on the
536
+ # content that follows the current position. This method implements that
537
+ # logic.
538
+ def detect_indent(n)
539
+ pos = @scanner.pos
540
+ in_seq = pos > 0 && @scanner.string.byteslice(pos - 1).match?(/[\-\?\:]/)
541
+
542
+ match = @scanner.check(%r{((?:\ *(?:\#.*)?\n)*)(\ *)}) or raise InternalException
543
+ pre = @scanner[1]
544
+ m = @scanner[2].length
545
+
546
+ if in_seq && pre.empty?
547
+ m += 1 if n == -1
548
+ else
549
+ m -= n
550
+ end
551
+
552
+ m = 0 if m < 0
553
+ m
554
+ end
555
+
556
+ # This is a convenience method used to retrieve a segment of the string
557
+ # that was just matched by the scanner. It takes a position and returns
558
+ # the input string from that position to the current scanner position.
559
+ def from(pos)
560
+ @scanner.string.byteslice(pos...@scanner.pos)
561
+ end
562
+
563
+ # This is the only way that the scanner is advanced. It checks if the
564
+ # given value matches the current position (either with a string or
565
+ # regular expression). If it does, it advances the scanner and returns
566
+ # true. If it does not, it returns false.
567
+ def match(value)
568
+ if @in_bare_document
569
+ return false if @scanner.eos?
570
+ return false if ((pos = @scanner.pos) == 0 || (@scanner.string.byteslice(pos - 1) == "\n")) && @scanner.check(/(?:---|\.\.\.)(?=\s|$)/)
571
+ end
572
+
573
+ @scanner.skip(value)
574
+ end
575
+
576
+ # This is effectively the same as match, except that it does not advance
577
+ # the scanner if the given match is found.
578
+ def peek
579
+ pos_start = @scanner.pos
580
+ result = try { yield }
581
+ @scanner.pos = pos_start
582
+ result
583
+ end
584
+
585
+ # In the grammar when a rule has rule+, it means it should match one or
586
+ # more times. This is a convenience method that implements that logic by
587
+ # attempting to match the given block one or more times.
588
+ def plus
589
+ return false unless yield
590
+ pos_current = @scanner.pos
591
+ pos_current = @scanner.pos while yield && (@scanner.pos != pos_current)
592
+ true
593
+ end
594
+
595
+ # In the grammar when a rule has rule*, it means it should match zero or
596
+ # more times. This is a convenience method that implements that logic by
597
+ # attempting to match the given block zero or more times.
598
+ def star
599
+ pos_current = @scanner.pos
600
+ pos_current = @scanner.pos while yield && (@scanner.pos != pos_current)
601
+ true
602
+ end
603
+
604
+ # True if the scanner it at the beginning of the string, the end of the
605
+ # string, or the previous character was a newline.
606
+ def start_of_line?
607
+ (pos = @scanner.pos) == 0 ||
608
+ @scanner.eos? ||
609
+ (@scanner.string.byteslice(pos - 1) == "\n")
610
+ end
611
+
612
+ # This is our main backtracking mechanism. It attempts to parse forward
613
+ # using the given block and return true. If it fails, it backtracks to the
614
+ # original position and returns false.
615
+ def try
616
+ pos_start = @scanner.pos
617
+ yield || (@scanner.pos = pos_start; false)
618
+ end
619
+
620
+ # ------------------------------------------------------------------------
621
+ # :section: Event handling
622
+ # ------------------------------------------------------------------------
623
+
624
+ # If there is a document end event, then flush it to the list of events
625
+ # and reset back to the starting state to parse the next document.
626
+ def document_end_event_flush
627
+ if @document_end_event
628
+ @document_end_event.accept(@handler)
629
+ @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
630
+ @tag_directives = @document_start_event.tag_directives
631
+ @document_end_event = nil
632
+ end
633
+ end
634
+
635
+ # Push a new temporary list onto the events cache.
636
+ def events_cache_push
637
+ @events_cache << []
638
+ end
639
+
640
+ # Pop a temporary list from the events cache.
641
+ def events_cache_pop
642
+ @events_cache.pop or raise InternalException
643
+ end
644
+
645
+ # Pop a temporary list from the events cache and flush it to the next
646
+ # level down in the cache or directly to the handler.
647
+ def events_cache_flush
648
+ events_cache_pop.each { |event| events_push(event) }
649
+ end
650
+
651
+ # Push an event into the events list. This could be pushing into the most
652
+ # recent temporary list if there is one, or flushed directly to the
653
+ # handler.
654
+ def events_push(event)
655
+ if @events_cache.empty?
656
+ if @document_start_event
657
+ case event
658
+ when MappingStart, SequenceStart, Scalar
659
+ @document_start_event.accept(@handler)
660
+ @document_start_event = nil
661
+ @document_end_event = DocumentEnd.new(Location.point(@source, @scanner.pos))
662
+ end
663
+ end
664
+
665
+ event.accept(@handler)
666
+ else
667
+ @events_cache.last << event
668
+ end
669
+ end
670
+
671
+ # Push an event into the events list and flush the anchor and tag
672
+ # properties if they are set.
673
+ def events_push_flush_properties(event)
674
+ if @anchor
675
+ event.anchor = @anchor
676
+ @anchor = nil
677
+ end
678
+
679
+ if @tag
680
+ event.tag = @tag
681
+ @tag = nil
682
+ end
683
+
684
+ events_push(event)
685
+ end
686
+
687
+ # ------------------------------------------------------------------------
688
+ # :section: Grammar rules
689
+ # ------------------------------------------------------------------------
690
+
691
+ # [002]
692
+ # nb-json ::=
693
+ # x:9 | [x:20-x:10FFFF]
694
+ def parse_nb_json
695
+ match(/[\u{09}\u{20}-\u{10FFFF}]/)
696
+ end
697
+
698
+ # [023]
699
+ # c-flow-indicator ::=
700
+ # ',' | '[' | ']' | '{' | '}'
701
+ def parse_c_flow_indicator
702
+ match(/[,\[\]{}]/)
703
+ end
704
+
705
+ # [027]
706
+ # nb-char ::=
707
+ # c-printable - b-char - c-byte-order-mark
708
+ def parse_nb_char
709
+ pos_start = @scanner.pos
710
+
711
+ if match(/[\u{09}\u{0A}\u{0D}\u{20}-\u{7E}\u{85}\u{A0}-\u{D7FF}\u{E000}-\u{FFFD}\u{10000}-\u{10FFFF}]/)
712
+ pos_end = @scanner.pos
713
+ @scanner.pos = pos_start
714
+
715
+ if match(/[\u{0A}\u{0D}\u{FEFF}]/)
716
+ @scanner.pos = pos_start
717
+ false
718
+ else
719
+ @scanner.pos = pos_end
720
+ true
721
+ end
722
+ else
723
+ @scanner.pos = pos_start
724
+ false
725
+ end
726
+ end
727
+
728
+ # [028]
729
+ # b-break ::=
730
+ # ( b-carriage-return b-line-feed )
731
+ # | b-carriage-return
732
+ # | b-line-feed
733
+ def parse_b_break
734
+ match(/\u{0A}|\u{0D}\u{0A}?/)
735
+ end
736
+
737
+ # [029]
738
+ # b-as-line-feed ::=
739
+ # b-break
740
+ alias parse_b_as_line_feed parse_b_break
741
+
742
+ # [030]
743
+ # b-non-content ::=
744
+ # b-break
745
+ alias parse_b_non_content parse_b_break
746
+
747
+ # [033]
748
+ # s-white ::=
749
+ # s-space | s-tab
750
+ def parse_s_white
751
+ pos_start = @scanner.pos
752
+
753
+ if match(/[\u{20}\u{09}]/)
754
+ @text_prefix = from(pos_start) if @in_scalar
755
+ true
756
+ end
757
+ end
758
+
759
+ # Effectively star { parse_s_white }
760
+ def parse_s_white_star
761
+ match(/[\u{20}\u{09}]*/)
762
+ true
763
+ end
764
+
765
+ # [034]
766
+ # ns-char ::=
767
+ # nb-char - s-white
768
+ def parse_ns_char
769
+ pos_start = @scanner.pos
770
+
771
+ if begin
772
+ if parse_nb_char
773
+ pos_end = @scanner.pos
774
+ @scanner.pos = pos_start
775
+
776
+ if parse_s_white
777
+ @scanner.pos = pos_start
778
+ false
779
+ else
780
+ @scanner.pos = pos_end
781
+ true
782
+ end
783
+ end
784
+ end then
785
+ @text_prefix = from(pos_start) if @in_scalar
786
+ true
787
+ end
788
+ end
789
+
790
+ # [036]
791
+ # ns-hex-digit ::=
792
+ # ns-dec-digit
793
+ # | [x:41-x:46] | [x:61-x:66]
794
+ def parse_ns_hex_digit
795
+ match(/[\u{30}-\u{39}\u{41}-\u{46}\u{61}-\u{66}]/)
796
+ end
797
+
798
+ # [039]
799
+ # ns-uri-char ::=
800
+ # '%' ns-hex-digit ns-hex-digit | ns-word-char | '#'
801
+ # | ';' | '/' | '?' | ':' | '@' | '&' | '=' | '+' | '$' | ','
802
+ # | '_' | '.' | '!' | '~' | '*' | ''' | '(' | ')' | '[' | ']'
803
+ def parse_ns_uri_char
804
+ try { match("%") && parse_ns_hex_digit && parse_ns_hex_digit } ||
805
+ match(/[\u{30}-\u{39}\u{41}-\u{5A}\u{61}-\u{7A}\-#;\/?:@&=+$,_.!~*'\(\)\[\]]/)
806
+ end
807
+
808
+ # [040]
809
+ # ns-tag-char ::=
810
+ # ns-uri-char - '!' - c-flow-indicator
811
+ def parse_ns_tag_char
812
+ pos_start = @scanner.pos
813
+
814
+ if parse_ns_uri_char
815
+ pos_end = @scanner.pos
816
+ @scanner.pos = pos_start
817
+
818
+ if match("!") || parse_c_flow_indicator
819
+ @scanner.pos = pos_start
820
+ false
821
+ else
822
+ @scanner.pos = pos_end
823
+ true
824
+ end
825
+ end
826
+ end
827
+
828
+ # [062]
829
+ # c-ns-esc-char ::=
830
+ # c-escape
831
+ # ( ns-esc-null | ns-esc-bell | ns-esc-backspace
832
+ # | ns-esc-horizontal-tab | ns-esc-line-feed
833
+ # | ns-esc-vertical-tab | ns-esc-form-feed
834
+ # | ns-esc-carriage-return | ns-esc-escape | ns-esc-space
835
+ # | ns-esc-double-quote | ns-esc-slash | ns-esc-backslash
836
+ # | ns-esc-next-line | ns-esc-non-breaking-space
837
+ # | ns-esc-line-separator | ns-esc-paragraph-separator
838
+ # | ns-esc-8-bit | ns-esc-16-bit | ns-esc-32-bit )
839
+ def parse_c_ns_esc_char
840
+ match(/\\[0abt\u{09}nvfre\u{20}"\/\\N_LP]/) ||
841
+ try { match("\\x") && parse_ns_hex_digit && parse_ns_hex_digit } ||
842
+ try { match("\\u") && 4.times.all? { parse_ns_hex_digit } } ||
843
+ try { match("\\U") && 8.times.all? { parse_ns_hex_digit } }
844
+ end
845
+
846
+ # [063]
847
+ # s-indent(n) ::=
848
+ # s-space{n}
849
+ def parse_s_indent(n)
850
+ match(/\u{20}{#{n}}/)
851
+ end
852
+
853
+ # [031]
854
+ # s-space ::=
855
+ # x:20
856
+ #
857
+ # [064]
858
+ # s-indent(<n) ::=
859
+ # s-space{m} <where_m_<_n>
860
+ def parse_s_indent_lt(n)
861
+ pos_start = @scanner.pos
862
+ match(/\u{20}*/)
863
+
864
+ if (@scanner.pos - pos_start) < n
865
+ true
866
+ else
867
+ @scanner.pos = pos_start
868
+ false
869
+ end
870
+ end
871
+
872
+ # [031]
873
+ # s-space ::=
874
+ # x:20
875
+ #
876
+ # [065]
877
+ # s-indent(<=n) ::=
878
+ # s-space{m} <where_m_<=_n>
879
+ def parse_s_indent_le(n)
880
+ pos_start = @scanner.pos
881
+ match(/\u{20}*/)
882
+
883
+ if (@scanner.pos - pos_start) <= n
884
+ true
885
+ else
886
+ @scanner.pos = pos_start
887
+ false
888
+ end
889
+ end
890
+
891
+ # [066]
892
+ # s-separate-in-line ::=
893
+ # s-white+ | <start_of_line>
894
+ def parse_s_separate_in_line
895
+ plus { parse_s_white } || start_of_line?
896
+ end
897
+
898
+ # [067]
899
+ # s-line-prefix(n,c) ::=
900
+ # ( c = block-out => s-block-line-prefix(n) )
901
+ # ( c = block-in => s-block-line-prefix(n) )
902
+ # ( c = flow-out => s-flow-line-prefix(n) )
903
+ # ( c = flow-in => s-flow-line-prefix(n) )
904
+ def parse_s_line_prefix(n, c)
905
+ case c
906
+ when :block_in then parse_s_block_line_prefix(n)
907
+ when :block_out then parse_s_block_line_prefix(n)
908
+ when :flow_in then parse_s_flow_line_prefix(n)
909
+ when :flow_out then parse_s_flow_line_prefix(n)
910
+ else raise InternalException, c.inspect
911
+ end
912
+ end
913
+
914
+ # [068]
915
+ # s-block-line-prefix(n) ::=
916
+ # s-indent(n)
917
+ def parse_s_block_line_prefix(n)
918
+ parse_s_indent(n)
919
+ end
920
+
921
+ # [069]
922
+ # s-flow-line-prefix(n) ::=
923
+ # s-indent(n)
924
+ # s-separate-in-line?
925
+ def parse_s_flow_line_prefix(n)
926
+ try do
927
+ if parse_s_indent(n)
928
+ parse_s_separate_in_line
929
+ true
930
+ end
931
+ end
932
+ end
933
+
934
+ # [070]
935
+ # l-empty(n,c) ::=
936
+ # ( s-line-prefix(n,c) | s-indent(<n) )
937
+ # b-as-line-feed
938
+ def parse_l_empty(n, c)
939
+ if try {
940
+ (parse_s_line_prefix(n, c) || parse_s_indent_lt(n)) &&
941
+ parse_b_as_line_feed
942
+ } then
943
+ events_push("") if @in_scalar
944
+ true
945
+ end
946
+ end
947
+
948
+ # [071]
949
+ # b-l-trimmed(n,c) ::=
950
+ # b-non-content l-empty(n,c)+
951
+ def parse_b_l_trimmed(n, c)
952
+ try { parse_b_non_content && plus { parse_l_empty(n, c) } }
953
+ end
954
+
955
+ # [072]
956
+ # b-as-space ::=
957
+ # b-break
958
+ alias parse_b_as_space parse_b_break
959
+
960
+ # [073]
961
+ # b-l-folded(n,c) ::=
962
+ # b-l-trimmed(n,c) | b-as-space
963
+ def parse_b_l_folded(n, c)
964
+ parse_b_l_trimmed(n, c) || parse_b_as_space
965
+ end
966
+
967
+ # [074]
968
+ # s-flow-folded(n) ::=
969
+ # s-separate-in-line?
970
+ # b-l-folded(n,flow-in)
971
+ # s-flow-line-prefix(n)
972
+ def parse_s_flow_folded(n)
973
+ try do
974
+ parse_s_separate_in_line
975
+ parse_b_l_folded(n, :flow_in) && parse_s_flow_line_prefix(n)
976
+ end
977
+ end
978
+
979
+ # [075]
980
+ # c-nb-comment-text ::=
981
+ # '#' nb-char*
982
+ def parse_c_nb_comment_text(inline)
983
+ return false unless match("#")
984
+
985
+ pos = @scanner.pos - 1
986
+ star { parse_nb_char }
987
+
988
+ @handler.comment(from(pos), *Location.new(@source, pos, @scanner.pos), inline) if @comments
989
+ true
990
+ end
991
+
992
+ # [076]
993
+ # b-comment ::=
994
+ # b-non-content | <end_of_file>
995
+ def parse_b_comment
996
+ parse_b_non_content || @scanner.eos?
997
+ end
998
+
999
+ # [077]
1000
+ # s-b-comment ::=
1001
+ # ( s-separate-in-line
1002
+ # c-nb-comment-text? )?
1003
+ # b-comment
1004
+ def parse_s_b_comment
1005
+ try do
1006
+ try do
1007
+ if parse_s_separate_in_line
1008
+ parse_c_nb_comment_text(true)
1009
+ true
1010
+ end
1011
+ end
1012
+
1013
+ parse_b_comment
1014
+ end
1015
+ end
1016
+
1017
+ # [078]
1018
+ # l-comment ::=
1019
+ # s-separate-in-line c-nb-comment-text?
1020
+ # b-comment
1021
+ def parse_l_comment
1022
+ try do
1023
+ if parse_s_separate_in_line
1024
+ parse_c_nb_comment_text(false)
1025
+ parse_b_comment
1026
+ end
1027
+ end
1028
+ end
1029
+
1030
+ # [079]
1031
+ # s-l-comments ::=
1032
+ # ( s-b-comment | <start_of_line> )
1033
+ # l-comment*
1034
+ def parse_s_l_comments
1035
+ try { (parse_s_b_comment || start_of_line?) && star { parse_l_comment } }
1036
+ end
1037
+
1038
+ # [080]
1039
+ # s-separate(n,c) ::=
1040
+ # ( c = block-out => s-separate-lines(n) )
1041
+ # ( c = block-in => s-separate-lines(n) )
1042
+ # ( c = flow-out => s-separate-lines(n) )
1043
+ # ( c = flow-in => s-separate-lines(n) )
1044
+ # ( c = block-key => s-separate-in-line )
1045
+ # ( c = flow-key => s-separate-in-line )
1046
+ def parse_s_separate(n, c)
1047
+ case c
1048
+ when :block_in then parse_s_separate_lines(n)
1049
+ when :block_key then parse_s_separate_in_line
1050
+ when :block_out then parse_s_separate_lines(n)
1051
+ when :flow_in then parse_s_separate_lines(n)
1052
+ when :flow_key then parse_s_separate_in_line
1053
+ when :flow_out then parse_s_separate_lines(n)
1054
+ else raise InternalException, c.inspect
1055
+ end
1056
+ end
1057
+
1058
+ # [081]
1059
+ # s-separate-lines(n) ::=
1060
+ # ( s-l-comments
1061
+ # s-flow-line-prefix(n) )
1062
+ # | s-separate-in-line
1063
+ def parse_s_separate_lines(n)
1064
+ try { parse_s_l_comments && parse_s_flow_line_prefix(n) } ||
1065
+ parse_s_separate_in_line
1066
+ end
1067
+
1068
+ # [082]
1069
+ # l-directive ::=
1070
+ # '%'
1071
+ # ( ns-yaml-directive
1072
+ # | ns-tag-directive
1073
+ # | ns-reserved-directive )
1074
+ # s-l-comments
1075
+ def parse_l_directive
1076
+ try do
1077
+ match("%") &&
1078
+ (parse_ns_yaml_directive || parse_ns_tag_directive || parse_ns_reserved_directive) &&
1079
+ parse_s_l_comments
1080
+ end
1081
+ end
1082
+
1083
+ # [083]
1084
+ # ns-reserved-directive ::=
1085
+ # ns-directive-name
1086
+ # ( s-separate-in-line ns-directive-parameter )*
1087
+ def parse_ns_reserved_directive
1088
+ try do
1089
+ parse_ns_directive_name &&
1090
+ star { try { parse_s_separate_in_line && parse_ns_directive_parameter } }
1091
+ end
1092
+ end
1093
+
1094
+ # [084]
1095
+ # ns-directive-name ::=
1096
+ # ns-char+
1097
+ def parse_ns_directive_name
1098
+ plus { parse_ns_char }
1099
+ end
1100
+
1101
+ # [085]
1102
+ # ns-directive-parameter ::=
1103
+ # ns-char+
1104
+ def parse_ns_directive_parameter
1105
+ plus { parse_ns_char }
1106
+ end
1107
+
1108
+ # [086]
1109
+ # ns-yaml-directive ::=
1110
+ # 'Y' 'A' 'M' 'L'
1111
+ # s-separate-in-line ns-yaml-version
1112
+ def parse_ns_yaml_directive
1113
+ try { match("YAML") && parse_s_separate_in_line && parse_ns_yaml_version }
1114
+ end
1115
+
1116
+ # [035]
1117
+ # ns-dec-digit ::=
1118
+ # [x:30-x:39]
1119
+ #
1120
+ # [087]
1121
+ # ns-yaml-version ::=
1122
+ # ns-dec-digit+ '.' ns-dec-digit+
1123
+ def parse_ns_yaml_version
1124
+ pos_start = @scanner.pos
1125
+
1126
+ if try {
1127
+ plus { match(/[\u{30}-\u{39}]/) } &&
1128
+ match(".") &&
1129
+ plus { match(/[\u{30}-\u{39}]/) }
1130
+ } then
1131
+ raise_syntax_error("Multiple %YAML directives not allowed") if @document_start_event.version
1132
+ @document_start_event.version = from(pos_start).split(".").map { |digits| digits.to_i(10) }
1133
+ true
1134
+ end
1135
+ end
1136
+
1137
+ # [088]
1138
+ # ns-tag-directive ::=
1139
+ # 'T' 'A' 'G'
1140
+ # s-separate-in-line c-tag-handle
1141
+ # s-separate-in-line ns-tag-prefix
1142
+ def parse_ns_tag_directive
1143
+ try do
1144
+ match("TAG") &&
1145
+ parse_s_separate_in_line &&
1146
+ parse_c_tag_handle &&
1147
+ parse_s_separate_in_line &&
1148
+ parse_ns_tag_prefix
1149
+ end
1150
+ end
1151
+
1152
+ # [092]
1153
+ # c-named-tag-handle ::=
1154
+ # '!' ns-word-char+ '!'
1155
+ def parse_c_tag_handle
1156
+ pos_start = @scanner.pos
1157
+
1158
+ if begin
1159
+ try do
1160
+ match("!") &&
1161
+ plus { match(/[\u{30}-\u{39}\u{41}-\u{5A}\u{61}-\u{7A}-]/) } &&
1162
+ match("!")
1163
+ end || match(/!!?/)
1164
+ end then
1165
+ @tag_handle = from(pos_start)
1166
+ true
1167
+ end
1168
+ end
1169
+
1170
+ # [093]
1171
+ # ns-tag-prefix ::=
1172
+ # c-ns-local-tag-prefix | ns-global-tag-prefix
1173
+ def parse_ns_tag_prefix
1174
+ pos_start = @scanner.pos
1175
+
1176
+ if parse_c_ns_local_tag_prefix || parse_ns_global_tag_prefix
1177
+ @tag_directives[@tag_handle] = from(pos_start)
1178
+ true
1179
+ end
1180
+ end
1181
+
1182
+ # [094]
1183
+ # c-ns-local-tag-prefix ::=
1184
+ # '!' ns-uri-char*
1185
+ def parse_c_ns_local_tag_prefix
1186
+ try { match("!") && star { parse_ns_uri_char } }
1187
+ end
1188
+
1189
+ # [095]
1190
+ # ns-global-tag-prefix ::=
1191
+ # ns-tag-char ns-uri-char*
1192
+ def parse_ns_global_tag_prefix
1193
+ try { parse_ns_tag_char && star { parse_ns_uri_char } }
1194
+ end
1195
+
1196
+ # [096]
1197
+ # c-ns-properties(n,c) ::=
1198
+ # ( c-ns-tag-property
1199
+ # ( s-separate(n,c) c-ns-anchor-property )? )
1200
+ # | ( c-ns-anchor-property
1201
+ # ( s-separate(n,c) c-ns-tag-property )? )
1202
+ def parse_c_ns_properties(n, c)
1203
+ try do
1204
+ if parse_c_ns_tag_property
1205
+ try { parse_s_separate(n, c) && parse_c_ns_anchor_property }
1206
+ true
1207
+ end
1208
+ end ||
1209
+ try do
1210
+ if parse_c_ns_anchor_property
1211
+ try { parse_s_separate(n, c) && parse_c_ns_tag_property }
1212
+ true
1213
+ end
1214
+ end
1215
+ end
1216
+
1217
+ # [097]
1218
+ # c-ns-tag-property ::=
1219
+ # c-verbatim-tag
1220
+ # | c-ns-shorthand-tag
1221
+ # | c-non-specific-tag
1222
+ #
1223
+ # [098]
1224
+ # c-verbatim-tag ::=
1225
+ # '!' '<' ns-uri-char+ '>'
1226
+ #
1227
+ # [099]
1228
+ # c-ns-shorthand-tag ::=
1229
+ # c-tag-handle ns-tag-char+
1230
+ #
1231
+ # [100]
1232
+ # c-non-specific-tag ::=
1233
+ # '!'
1234
+ def parse_c_ns_tag_property
1235
+ pos_start = @scanner.pos
1236
+
1237
+ if try { match("!<") && plus { parse_ns_uri_char } && match(">") }
1238
+ @tag = from(pos_start)[/\A!<(.*)>\z/, 1].gsub(/%([0-9a-fA-F]{2})/) { $1.to_i(16).chr(Encoding::UTF_8) }
1239
+ true
1240
+ elsif try { parse_c_tag_handle && plus { parse_ns_tag_char } }
1241
+ tag = from(pos_start)
1242
+ @tag =
1243
+ if (m = tag.match(/\A!!(.*)/))
1244
+ (prefix = @tag_directives["!!"]) ? (prefix + tag[2..]) : "tag:yaml.org,2002:#{m[1]}"
1245
+ elsif (m = tag.match(/\A(!.*?!)/))
1246
+ raise_syntax_error("No %TAG entry for '#{prefix}'") if !(prefix = @tag_directives[m[1]])
1247
+ prefix + tag[m[1].length..]
1248
+ elsif (prefix = @tag_directives["!"])
1249
+ prefix + tag[1..]
1250
+ else
1251
+ tag
1252
+ end
1253
+
1254
+ @tag.gsub!(/%([0-9a-fA-F]{2})/) { $1.to_i(16).chr(Encoding::UTF_8) }
1255
+ true
1256
+ elsif match("!")
1257
+ @tag = @tag_directives.fetch("!", "!")
1258
+ true
1259
+ end
1260
+ end
1261
+
1262
+ # [101]
1263
+ # c-ns-anchor-property ::=
1264
+ # '&' ns-anchor-name
1265
+ def parse_c_ns_anchor_property
1266
+ pos_start = @scanner.pos
1267
+
1268
+ if try { match("&") && plus { parse_ns_anchor_char } }
1269
+ @anchor = from(pos_start).byteslice(1..)
1270
+ true
1271
+ end
1272
+ end
1273
+
1274
+ # [102]
1275
+ # ns-anchor-char ::=
1276
+ # ns-char - c-flow-indicator
1277
+ def parse_ns_anchor_char
1278
+ pos_start = @scanner.pos
1279
+
1280
+ if parse_ns_char
1281
+ pos_end = @scanner.pos
1282
+ @scanner.pos = pos_start
1283
+
1284
+ if parse_c_flow_indicator
1285
+ @scanner.pos = pos_start
1286
+ false
1287
+ else
1288
+ @scanner.pos = pos_end
1289
+ true
1290
+ end
1291
+ end
1292
+ end
1293
+
1294
+ # [104]
1295
+ # c-ns-alias-node ::=
1296
+ # '*' ns-anchor-name
1297
+ def parse_c_ns_alias_node
1298
+ pos_start = @scanner.pos
1299
+
1300
+ if try { match("*") && plus { parse_ns_anchor_char } }
1301
+ events_push_flush_properties(Alias.new(Location.new(@source, pos_start, @scanner.pos), from(pos_start).byteslice(1..)))
1302
+ true
1303
+ end
1304
+ end
1305
+
1306
+ # [105]
1307
+ # e-scalar ::=
1308
+ # <empty>
1309
+ def parse_e_scalar
1310
+ events_push_flush_properties(Scalar.new(Location.point(@source, @scanner.pos), "", Nodes::Scalar::PLAIN))
1311
+ true
1312
+ end
1313
+
1314
+ # [106]
1315
+ # e-node ::=
1316
+ # e-scalar
1317
+ alias parse_e_node parse_e_scalar
1318
+
1319
+ # [107]
1320
+ # nb-double-char ::=
1321
+ # c-ns-esc-char | ( nb-json - '\' - '"' )
1322
+ def parse_nb_double_char
1323
+ return true if parse_c_ns_esc_char
1324
+ pos_start = @scanner.pos
1325
+
1326
+ if parse_nb_json
1327
+ pos_end = @scanner.pos
1328
+ @scanner.pos = pos_start
1329
+
1330
+ if match(/[\\"]/)
1331
+ @scanner.pos = pos_start
1332
+ false
1333
+ else
1334
+ @scanner.pos = pos_end
1335
+ true
1336
+ end
1337
+ end
1338
+ end
1339
+
1340
+ # [108]
1341
+ # ns-double-char ::=
1342
+ # nb-double-char - s-white
1343
+ def parse_ns_double_char
1344
+ pos_start = @scanner.pos
1345
+
1346
+ if parse_nb_double_char
1347
+ pos_end = @scanner.pos
1348
+ @scanner.pos = pos_start
1349
+
1350
+ if parse_s_white
1351
+ @scanner.pos = pos_start
1352
+ false
1353
+ else
1354
+ @scanner.pos = pos_end
1355
+ true
1356
+ end
1357
+ end
1358
+ end
1359
+
1360
+ # The following unescape sequences are supported in double quoted scalars.
1361
+ C_DOUBLE_QUOTED_UNESCAPES = {
1362
+ "\\\\" => "\\",
1363
+ "\\r\\n" => "\n",
1364
+ "\\ " => " ",
1365
+ '\\"' => '"',
1366
+ "\\/" => "/",
1367
+ "\\_" => "\u{a0}",
1368
+ "\\0" => "\x00",
1369
+ "\\a" => "\x07",
1370
+ "\\b" => "\x08",
1371
+ "\\e" => "\x1b",
1372
+ "\\f" => "\x0c",
1373
+ "\\n" => "\x0a",
1374
+ "\\r" => "\x0d",
1375
+ "\\t" => "\x09",
1376
+ "\\\t" => "\x09",
1377
+ "\\v" => "\x0b",
1378
+ "\\L" => "\u{2028}",
1379
+ "\\N" => "\u{85}",
1380
+ "\\P" => "\u{2029}"
1381
+ }.freeze
1382
+
1383
+ private_constant :C_DOUBLE_QUOTED_UNESCAPES
1384
+
1385
+ # [109]
1386
+ # c-double-quoted(n,c) ::=
1387
+ # '"' nb-double-text(n,c)
1388
+ # '"'
1389
+ def parse_c_double_quoted(n, c)
1390
+ pos_start = @scanner.pos
1391
+
1392
+ if try { match("\"") && parse_nb_double_text(n, c) && match("\"") }
1393
+ end1 = "(?:\\\\\\r?\\n[ \\t]*)"
1394
+ end2 = "(?:[ \\t]*\\r?\\n[ \\t]*)"
1395
+ hex = "[0-9a-fA-F]"
1396
+ hex2 = "(?:\\\\x(#{hex}{2}))"
1397
+ hex4 = "(?:\\\\u(#{hex}{4}))"
1398
+ hex8 = "(?:\\\\U(#{hex}{8}))"
1399
+
1400
+ value = from(pos_start).byteslice(1...-1)
1401
+ value.gsub!(%r{(?:\r\n|#{end1}|#{end2}+|#{hex2}|#{hex4}|#{hex8}|\\[\\ "/_0abefnrt\tvLNP])}) do |m|
1402
+ case m
1403
+ when /\A(?:#{hex2}|#{hex4}|#{hex8})\z/o
1404
+ m[2..].to_i(16).chr(Encoding::UTF_8)
1405
+ when /\A#{end1}\z/o
1406
+ ""
1407
+ when /\A#{end2}+\z/o
1408
+ m.sub(/#{end2}/, "").gsub(/#{end2}/, "\n").then { |r| r.empty? ? " " : r }
1409
+ else
1410
+ C_DOUBLE_QUOTED_UNESCAPES.fetch(m, m)
1411
+ end
1412
+ end
1413
+
1414
+ events_push_flush_properties(Scalar.new(Location.new(@source, pos_start, @scanner.pos), value, Nodes::Scalar::DOUBLE_QUOTED))
1415
+ true
1416
+ end
1417
+ end
1418
+
1419
+ # [110]
1420
+ # nb-double-text(n,c) ::=
1421
+ # ( c = flow-out => nb-double-multi-line(n) )
1422
+ # ( c = flow-in => nb-double-multi-line(n) )
1423
+ # ( c = block-key => nb-double-one-line )
1424
+ # ( c = flow-key => nb-double-one-line )
1425
+ def parse_nb_double_text(n, c)
1426
+ case c
1427
+ when :block_key then parse_nb_double_one_line
1428
+ when :flow_in then parse_nb_double_multi_line(n)
1429
+ when :flow_key then parse_nb_double_one_line
1430
+ when :flow_out then parse_nb_double_multi_line(n)
1431
+ else raise InternalException, c.inspect
1432
+ end
1433
+ end
1434
+
1435
+ # [111]
1436
+ # nb-double-one-line ::=
1437
+ # nb-double-char*
1438
+ def parse_nb_double_one_line
1439
+ star { parse_nb_double_char }
1440
+ end
1441
+
1442
+ # [112]
1443
+ # s-double-escaped(n) ::=
1444
+ # s-white* '\'
1445
+ # b-non-content
1446
+ # l-empty(n,flow-in)* s-flow-line-prefix(n)
1447
+ def parse_s_double_escaped(n)
1448
+ try do
1449
+ parse_s_white_star &&
1450
+ match("\\") &&
1451
+ parse_b_non_content &&
1452
+ star { parse_l_empty(n, :flow_in) } &&
1453
+ parse_s_flow_line_prefix(n)
1454
+ end
1455
+ end
1456
+
1457
+ # [113]
1458
+ # s-double-break(n) ::=
1459
+ # s-double-escaped(n) | s-flow-folded(n)
1460
+ def parse_s_double_break(n)
1461
+ parse_s_double_escaped(n) || parse_s_flow_folded(n)
1462
+ end
1463
+
1464
+ # [114]
1465
+ # nb-ns-double-in-line ::=
1466
+ # ( s-white* ns-double-char )*
1467
+ def parse_nb_ns_double_in_line
1468
+ star { try { parse_s_white_star && parse_ns_double_char } }
1469
+ end
1470
+
1471
+ # [115]
1472
+ # s-double-next-line(n) ::=
1473
+ # s-double-break(n)
1474
+ # ( ns-double-char nb-ns-double-in-line
1475
+ # ( s-double-next-line(n) | s-white* ) )?
1476
+ def parse_s_double_next_line(n)
1477
+ try do
1478
+ if parse_s_double_break(n)
1479
+ try do
1480
+ parse_ns_double_char &&
1481
+ parse_nb_ns_double_in_line &&
1482
+ (parse_s_double_next_line(n) || parse_s_white_star)
1483
+ end
1484
+
1485
+ true
1486
+ end
1487
+ end
1488
+ end
1489
+
1490
+ # [116]
1491
+ # nb-double-multi-line(n) ::=
1492
+ # nb-ns-double-in-line
1493
+ # ( s-double-next-line(n) | s-white* )
1494
+ def parse_nb_double_multi_line(n)
1495
+ try do
1496
+ parse_nb_ns_double_in_line &&
1497
+ (parse_s_double_next_line(n) || parse_s_white_star)
1498
+ end
1499
+ end
1500
+
1501
+ # [117]
1502
+ # c-quoted-quote ::=
1503
+ # ''' '''
1504
+ #
1505
+ # [118]
1506
+ # nb-single-char ::=
1507
+ # c-quoted-quote | ( nb-json - ''' )
1508
+ def parse_nb_single_char
1509
+ return true if match("''")
1510
+ pos_start = @scanner.pos
1511
+
1512
+ if parse_nb_json
1513
+ pos_end = @scanner.pos
1514
+ @scanner.pos = pos_start
1515
+
1516
+ if match("'")
1517
+ @scanner.pos = pos_start
1518
+ false
1519
+ else
1520
+ @scanner.pos = pos_end
1521
+ true
1522
+ end
1523
+ end
1524
+ end
1525
+
1526
+ # [119]
1527
+ # ns-single-char ::=
1528
+ # nb-single-char - s-white
1529
+ def parse_ns_single_char
1530
+ pos_start = @scanner.pos
1531
+
1532
+ if parse_nb_single_char
1533
+ pos_end = @scanner.pos
1534
+ @scanner.pos = pos_start
1535
+
1536
+ if parse_s_white
1537
+ @scanner.pos = pos_start
1538
+ false
1539
+ else
1540
+ @scanner.pos = pos_end
1541
+ true
1542
+ end
1543
+ end
1544
+ end
1545
+
1546
+ # [120]
1547
+ # c-single-quoted(n,c) ::=
1548
+ # ''' nb-single-text(n,c)
1549
+ # '''
1550
+ def parse_c_single_quoted(n, c)
1551
+ pos_start = @scanner.pos
1552
+
1553
+ if try { match("'") && parse_nb_single_text(n, c) && match("'") }
1554
+ value = from(pos_start).byteslice(1...-1)
1555
+ value.gsub!(/(?:[\ \t]*\r?\n[\ \t]*)/, "\n")
1556
+ value.gsub!(/\n(\n*)/) { $1.empty? ? " " : $1 }
1557
+ value.gsub!("''", "'")
1558
+ events_push_flush_properties(Scalar.new(Location.new(@source, pos_start, @scanner.pos), value, Nodes::Scalar::SINGLE_QUOTED))
1559
+ true
1560
+ end
1561
+ end
1562
+
1563
+ # [121]
1564
+ # nb-single-text(n,c) ::=
1565
+ # ( c = flow-out => nb-single-multi-line(n) )
1566
+ # ( c = flow-in => nb-single-multi-line(n) )
1567
+ # ( c = block-key => nb-single-one-line )
1568
+ # ( c = flow-key => nb-single-one-line )
1569
+ def parse_nb_single_text(n, c)
1570
+ case c
1571
+ when :block_key then parse_nb_single_one_line
1572
+ when :flow_in then parse_nb_single_multi_line(n)
1573
+ when :flow_key then parse_nb_single_one_line
1574
+ when :flow_out then parse_nb_single_multi_line(n)
1575
+ else raise InternalException, c.inspect
1576
+ end
1577
+ end
1578
+
1579
+ # [122]
1580
+ # nb-single-one-line ::=
1581
+ # nb-single-char*
1582
+ def parse_nb_single_one_line
1583
+ star { parse_nb_single_char }
1584
+ end
1585
+
1586
+ # [123]
1587
+ # nb-ns-single-in-line ::=
1588
+ # ( s-white* ns-single-char )*
1589
+ def parse_nb_ns_single_in_line
1590
+ star { try { parse_s_white_star && parse_ns_single_char } }
1591
+ end
1592
+
1593
+ # [124]
1594
+ # s-single-next-line(n) ::=
1595
+ # s-flow-folded(n)
1596
+ # ( ns-single-char nb-ns-single-in-line
1597
+ # ( s-single-next-line(n) | s-white* ) )?
1598
+ def parse_s_single_next_line(n)
1599
+ try do
1600
+ if parse_s_flow_folded(n)
1601
+ try do
1602
+ parse_ns_single_char &&
1603
+ parse_nb_ns_single_in_line &&
1604
+ (parse_s_single_next_line(n) || parse_s_white_star)
1605
+ end
1606
+
1607
+ true
1608
+ end
1609
+ end
1610
+ end
1611
+
1612
+ # [125]
1613
+ # nb-single-multi-line(n) ::=
1614
+ # nb-ns-single-in-line
1615
+ # ( s-single-next-line(n) | s-white* )
1616
+ def parse_nb_single_multi_line(n)
1617
+ try do
1618
+ parse_nb_ns_single_in_line &&
1619
+ (parse_s_single_next_line(n) || parse_s_white_star)
1620
+ end
1621
+ end
1622
+
1623
+ # [126]
1624
+ # ns-plain-first(c) ::=
1625
+ # ( ns-char - c-indicator )
1626
+ # | ( ( '?' | ':' | '-' )
1627
+ # <followed_by_an_ns-plain-safe(c)> )
1628
+ def parse_ns_plain_first(c)
1629
+ begin
1630
+ pos_start = @scanner.pos
1631
+
1632
+ if parse_ns_char
1633
+ pos_end = @scanner.pos
1634
+ @scanner.pos = pos_start
1635
+
1636
+ if match(/[-?:,\[\]{}#&*!|>'"%@`]/)
1637
+ @scanner.pos = pos_start
1638
+ false
1639
+ else
1640
+ @scanner.pos = pos_end
1641
+ true
1642
+ end
1643
+ end
1644
+ end || try { match(/[?:-]/) && peek { parse_ns_plain_safe(c) } }
1645
+ end
1646
+
1647
+ # [127]
1648
+ # ns-plain-safe(c) ::=
1649
+ # ( c = flow-out => ns-plain-safe-out )
1650
+ # ( c = flow-in => ns-plain-safe-in )
1651
+ # ( c = block-key => ns-plain-safe-out )
1652
+ # ( c = flow-key => ns-plain-safe-in )
1653
+ def parse_ns_plain_safe(c)
1654
+ case c
1655
+ when :block_key then parse_ns_plain_safe_out
1656
+ when :flow_in then parse_ns_plain_safe_in
1657
+ when :flow_key then parse_ns_plain_safe_in
1658
+ when :flow_out then parse_ns_plain_safe_out
1659
+ else raise InternalException, c.inspect
1660
+ end
1661
+ end
1662
+
1663
+ # [128]
1664
+ # ns-plain-safe-out ::=
1665
+ # ns-char
1666
+ alias parse_ns_plain_safe_out parse_ns_char
1667
+
1668
+ # [129]
1669
+ # ns-plain-safe-in ::=
1670
+ # ns-char - c-flow-indicator
1671
+ def parse_ns_plain_safe_in
1672
+ pos_start = @scanner.pos
1673
+
1674
+ if parse_ns_char
1675
+ pos_end = @scanner.pos
1676
+ @scanner.pos = pos_start
1677
+
1678
+ if parse_c_flow_indicator
1679
+ @scanner.pos = pos_start
1680
+ false
1681
+ else
1682
+ @scanner.pos = pos_end
1683
+ true
1684
+ end
1685
+ end
1686
+ end
1687
+
1688
+ # [130]
1689
+ # ns-plain-char(c) ::=
1690
+ # ( ns-plain-safe(c) - ':' - '#' )
1691
+ # | ( <an_ns-char_preceding> '#' )
1692
+ # | ( ':' <followed_by_an_ns-plain-safe(c)> )
1693
+ def parse_ns_plain_char(c)
1694
+ try do
1695
+ pos_start = @scanner.pos
1696
+
1697
+ if parse_ns_plain_safe(c)
1698
+ pos_end = @scanner.pos
1699
+ @scanner.pos = pos_start
1700
+
1701
+ if match(/[:#]/)
1702
+ false
1703
+ else
1704
+ @scanner.pos = pos_end
1705
+ true
1706
+ end
1707
+ end
1708
+ end ||
1709
+ try do
1710
+ pos_start = @scanner.pos
1711
+ @scanner.pos -= 1
1712
+
1713
+ was_ns_char = parse_ns_char
1714
+ @scanner.pos = pos_start
1715
+
1716
+ was_ns_char && match("#")
1717
+ end ||
1718
+ try do
1719
+ match(":") && peek { parse_ns_plain_safe(c) }
1720
+ end
1721
+ end
1722
+
1723
+ # [132]
1724
+ # nb-ns-plain-in-line(c) ::=
1725
+ # ( s-white*
1726
+ # ns-plain-char(c) )*
1727
+ def parse_nb_ns_plain_in_line(c)
1728
+ star { try { parse_s_white_star && parse_ns_plain_char(c) } }
1729
+ end
1730
+
1731
+ # [133]
1732
+ # ns-plain-one-line(c) ::=
1733
+ # ns-plain-first(c)
1734
+ # nb-ns-plain-in-line(c)
1735
+ def parse_ns_plain_one_line(c)
1736
+ try { parse_ns_plain_first(c) && parse_nb_ns_plain_in_line(c) }
1737
+ end
1738
+
1739
+ # [134]
1740
+ # s-ns-plain-next-line(n,c) ::=
1741
+ # s-flow-folded(n)
1742
+ # ns-plain-char(c) nb-ns-plain-in-line(c)
1743
+ def parse_s_ns_plain_next_line(n, c)
1744
+ try do
1745
+ parse_s_flow_folded(n) &&
1746
+ parse_ns_plain_char(c) &&
1747
+ parse_nb_ns_plain_in_line(c)
1748
+ end
1749
+ end
1750
+
1751
+ # [135]
1752
+ # ns-plain-multi-line(n,c) ::=
1753
+ # ns-plain-one-line(c)
1754
+ # s-ns-plain-next-line(n,c)*
1755
+ def parse_ns_plain_multi_line(n, c)
1756
+ try do
1757
+ parse_ns_plain_one_line(c) &&
1758
+ star { parse_s_ns_plain_next_line(n, c) }
1759
+ end
1760
+ end
1761
+
1762
+ # [136]
1763
+ # in-flow(c) ::=
1764
+ # ( c = flow-out => flow-in )
1765
+ # ( c = flow-in => flow-in )
1766
+ # ( c = block-key => flow-key )
1767
+ # ( c = flow-key => flow-key )
1768
+ def parse_in_flow(c)
1769
+ case c
1770
+ when :block_key then :flow_key
1771
+ when :flow_in then :flow_in
1772
+ when :flow_key then :flow_key
1773
+ when :flow_out then :flow_in
1774
+ else raise InternalException, c.inspect
1775
+ end
1776
+ end
1777
+
1778
+ # [137]
1779
+ # c-flow-sequence(n,c) ::=
1780
+ # '[' s-separate(n,c)?
1781
+ # ns-s-flow-seq-entries(n,in-flow(c))? ']'
1782
+ def parse_c_flow_sequence(n, c)
1783
+ try do
1784
+ if match("[")
1785
+ events_push_flush_properties(SequenceStart.new(Location.new(@source, @scanner.pos - 1, @scanner.pos), Nodes::Sequence::FLOW))
1786
+
1787
+ parse_s_separate(n, c)
1788
+ parse_ns_s_flow_seq_entries(n, parse_in_flow(c))
1789
+
1790
+ if match("]")
1791
+ events_push_flush_properties(SequenceEnd.new(Location.new(@source, @scanner.pos - 1, @scanner.pos)))
1792
+ true
1793
+ end
1794
+ end
1795
+ end
1796
+ end
1797
+
1798
+ # [138]
1799
+ # ns-s-flow-seq-entries(n,c) ::=
1800
+ # ns-flow-seq-entry(n,c)
1801
+ # s-separate(n,c)?
1802
+ # ( ',' s-separate(n,c)?
1803
+ # ns-s-flow-seq-entries(n,c)? )?
1804
+ def parse_ns_s_flow_seq_entries(n, c)
1805
+ try do
1806
+ if parse_ns_flow_seq_entry(n, c)
1807
+ parse_s_separate(n, c)
1808
+
1809
+ try do
1810
+ if match(",")
1811
+ parse_s_separate(n, c)
1812
+ parse_ns_s_flow_seq_entries(n, c)
1813
+ true
1814
+ end
1815
+ end
1816
+
1817
+ true
1818
+ end
1819
+ end
1820
+ end
1821
+
1822
+ # [139]
1823
+ # ns-flow-seq-entry(n,c) ::=
1824
+ # ns-flow-pair(n,c) | ns-flow-node(n,c)
1825
+ def parse_ns_flow_seq_entry(n, c)
1826
+ parse_ns_flow_pair(n, c) || parse_ns_flow_node(n, c)
1827
+ end
1828
+
1829
+ # [140]
1830
+ # c-flow-mapping(n,c) ::=
1831
+ # '{' s-separate(n,c)?
1832
+ # ns-s-flow-map-entries(n,in-flow(c))? '}'
1833
+ def parse_c_flow_mapping(n, c)
1834
+ try do
1835
+ if match("{")
1836
+ events_push_flush_properties(MappingStart.new(Location.new(@source, @scanner.pos - 1, @scanner.pos), Nodes::Mapping::FLOW))
1837
+
1838
+ parse_s_separate(n, c)
1839
+ parse_ns_s_flow_map_entries(n, parse_in_flow(c))
1840
+
1841
+ if match("}")
1842
+ events_push_flush_properties(MappingEnd.new(Location.new(@source, @scanner.pos - 1, @scanner.pos)))
1843
+ true
1844
+ end
1845
+ end
1846
+ end
1847
+ end
1848
+
1849
+ # [141]
1850
+ # ns-s-flow-map-entries(n,c) ::=
1851
+ # ns-flow-map-entry(n,c)
1852
+ # s-separate(n,c)?
1853
+ # ( ',' s-separate(n,c)?
1854
+ # ns-s-flow-map-entries(n,c)? )?
1855
+ def parse_ns_s_flow_map_entries(n, c)
1856
+ try do
1857
+ if parse_ns_flow_map_entry(n, c)
1858
+ parse_s_separate(n, c)
1859
+
1860
+ try do
1861
+ if match(",")
1862
+ parse_s_separate(n, c)
1863
+ parse_ns_s_flow_map_entries(n, c)
1864
+ true
1865
+ end
1866
+ end
1867
+
1868
+ true
1869
+ end
1870
+ end
1871
+ end
1872
+
1873
+ # [142]
1874
+ # ns-flow-map-entry(n,c) ::=
1875
+ # ( '?' s-separate(n,c)
1876
+ # ns-flow-map-explicit-entry(n,c) )
1877
+ # | ns-flow-map-implicit-entry(n,c)
1878
+ def parse_ns_flow_map_entry(n, c)
1879
+ try do
1880
+ match("?") &&
1881
+ peek { @scanner.eos? || parse_s_white || parse_b_break } &&
1882
+ parse_s_separate(n, c) && parse_ns_flow_map_explicit_entry(n, c)
1883
+ end || parse_ns_flow_map_implicit_entry(n, c)
1884
+ end
1885
+
1886
+ # [143]
1887
+ # ns-flow-map-explicit-entry(n,c) ::=
1888
+ # ns-flow-map-implicit-entry(n,c)
1889
+ # | ( e-node
1890
+ # e-node )
1891
+ def parse_ns_flow_map_explicit_entry(n, c)
1892
+ parse_ns_flow_map_implicit_entry(n, c) ||
1893
+ try { parse_e_node && parse_e_node }
1894
+ end
1895
+
1896
+ # [144]
1897
+ # ns-flow-map-implicit-entry(n,c) ::=
1898
+ # ns-flow-map-yaml-key-entry(n,c)
1899
+ # | c-ns-flow-map-empty-key-entry(n,c)
1900
+ # | c-ns-flow-map-json-key-entry(n,c)
1901
+ def parse_ns_flow_map_implicit_entry(n, c)
1902
+ parse_ns_flow_map_yaml_key_entry(n, c) ||
1903
+ parse_c_ns_flow_map_empty_key_entry(n, c) ||
1904
+ parse_c_ns_flow_map_json_key_entry(n, c)
1905
+ end
1906
+
1907
+ # [145]
1908
+ # ns-flow-map-yaml-key-entry(n,c) ::=
1909
+ # ns-flow-yaml-node(n,c)
1910
+ # ( ( s-separate(n,c)?
1911
+ # c-ns-flow-map-separate-value(n,c) )
1912
+ # | e-node )
1913
+ def parse_ns_flow_map_yaml_key_entry(n, c)
1914
+ try do
1915
+ parse_ns_flow_yaml_node(n, c) && (
1916
+ try do
1917
+ parse_s_separate(n, c)
1918
+ parse_c_ns_flow_map_separate_value(n, c)
1919
+ end || parse_e_node
1920
+ )
1921
+ end
1922
+ end
1923
+
1924
+ # [146]
1925
+ # c-ns-flow-map-empty-key-entry(n,c) ::=
1926
+ # e-node
1927
+ # c-ns-flow-map-separate-value(n,c)
1928
+ def parse_c_ns_flow_map_empty_key_entry(n, c)
1929
+ events_cache_push
1930
+
1931
+ if try { parse_e_node && parse_c_ns_flow_map_separate_value(n, c) }
1932
+ events_cache_flush
1933
+ true
1934
+ else
1935
+ events_cache_pop
1936
+ false
1937
+ end
1938
+ end
1939
+
1940
+ # [147]
1941
+ # c-ns-flow-map-separate-value(n,c) ::=
1942
+ # ':' <not_followed_by_an_ns-plain-safe(c)>
1943
+ # ( ( s-separate(n,c) ns-flow-node(n,c) )
1944
+ # | e-node )
1945
+ def parse_c_ns_flow_map_separate_value(n, c)
1946
+ try do
1947
+ match(":") &&
1948
+ !peek { parse_ns_plain_safe(c) } &&
1949
+ (try { parse_s_separate(n, c) && parse_ns_flow_node(n, c) } || parse_e_node)
1950
+ end
1951
+ end
1952
+
1953
+ # [148]
1954
+ # c-ns-flow-map-json-key-entry(n,c) ::=
1955
+ # c-flow-json-node(n,c)
1956
+ # ( ( s-separate(n,c)?
1957
+ # c-ns-flow-map-adjacent-value(n,c) )
1958
+ # | e-node )
1959
+ def parse_c_ns_flow_map_json_key_entry(n, c)
1960
+ try do
1961
+ parse_c_flow_json_node(n, c) && (
1962
+ try do
1963
+ parse_s_separate(n, c)
1964
+ parse_c_ns_flow_map_adjacent_value(n, c)
1965
+ end || parse_e_node
1966
+ )
1967
+ end
1968
+ end
1969
+
1970
+ # [149]
1971
+ # c-ns-flow-map-adjacent-value(n,c) ::=
1972
+ # ':' ( (
1973
+ # s-separate(n,c)?
1974
+ # ns-flow-node(n,c) )
1975
+ # | e-node )
1976
+ def parse_c_ns_flow_map_adjacent_value(n, c)
1977
+ try do
1978
+ match(":") && (
1979
+ try do
1980
+ parse_s_separate(n, c)
1981
+ parse_ns_flow_node(n, c)
1982
+ end || parse_e_node
1983
+ )
1984
+ end
1985
+ end
1986
+
1987
+ # [150]
1988
+ # ns-flow-pair(n,c) ::=
1989
+ # ( '?' s-separate(n,c)
1990
+ # ns-flow-map-explicit-entry(n,c) )
1991
+ # | ns-flow-pair-entry(n,c)
1992
+ def parse_ns_flow_pair(n, c)
1993
+ events_cache_push
1994
+ events_push_flush_properties(MappingStart.new(Location.point(@source, @scanner.pos), Nodes::Mapping::FLOW))
1995
+
1996
+ if begin
1997
+ try do
1998
+ match("?") &&
1999
+ peek { @scanner.eos? || parse_s_white || parse_b_break } &&
2000
+ parse_s_separate(n, c) &&
2001
+ parse_ns_flow_map_explicit_entry(n, c)
2002
+ end || parse_ns_flow_pair_entry(n, c)
2003
+ end then
2004
+ events_cache_flush
2005
+ events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos)))
2006
+ true
2007
+ else
2008
+ events_cache_pop
2009
+ false
2010
+ end
2011
+ end
2012
+
2013
+ # [151]
2014
+ # ns-flow-pair-entry(n,c) ::=
2015
+ # ns-flow-pair-yaml-key-entry(n,c)
2016
+ # | c-ns-flow-map-empty-key-entry(n,c)
2017
+ # | c-ns-flow-pair-json-key-entry(n,c)
2018
+ def parse_ns_flow_pair_entry(n, c)
2019
+ parse_ns_flow_pair_yaml_key_entry(n, c) ||
2020
+ parse_c_ns_flow_map_empty_key_entry(n, c) ||
2021
+ parse_c_ns_flow_pair_json_key_entry(n, c)
2022
+ end
2023
+
2024
+ # [152]
2025
+ # ns-flow-pair-yaml-key-entry(n,c) ::=
2026
+ # ns-s-implicit-yaml-key(flow-key)
2027
+ # c-ns-flow-map-separate-value(n,c)
2028
+ def parse_ns_flow_pair_yaml_key_entry(n, c)
2029
+ try do
2030
+ parse_ns_s_implicit_yaml_key(:flow_key) &&
2031
+ parse_c_ns_flow_map_separate_value(n, c)
2032
+ end
2033
+ end
2034
+
2035
+ # [153]
2036
+ # c-ns-flow-pair-json-key-entry(n,c) ::=
2037
+ # c-s-implicit-json-key(flow-key)
2038
+ # c-ns-flow-map-adjacent-value(n,c)
2039
+ def parse_c_ns_flow_pair_json_key_entry(n, c)
2040
+ try do
2041
+ parse_c_s_implicit_json_key(:flow_key) &&
2042
+ parse_c_ns_flow_map_adjacent_value(n, c)
2043
+ end
2044
+ end
2045
+
2046
+ # [154]
2047
+ # ns-s-implicit-yaml-key(c) ::=
2048
+ # ns-flow-yaml-node(n/a,c)
2049
+ # s-separate-in-line?
2050
+ # <at_most_1024_characters_altogether>
2051
+ def parse_ns_s_implicit_yaml_key(c)
2052
+ pos_start = @scanner.pos
2053
+ try do
2054
+ if parse_ns_flow_yaml_node(nil, c)
2055
+ parse_s_separate_in_line
2056
+ (@scanner.pos - pos_start) <= 1024
2057
+ end
2058
+ end
2059
+ end
2060
+
2061
+ # [155]
2062
+ # c-s-implicit-json-key(c) ::=
2063
+ # c-flow-json-node(n/a,c)
2064
+ # s-separate-in-line?
2065
+ # <at_most_1024_characters_altogether>
2066
+ def parse_c_s_implicit_json_key(c)
2067
+ pos_start = @scanner.pos
2068
+ try do
2069
+ if parse_c_flow_json_node(nil, c)
2070
+ parse_s_separate_in_line
2071
+ (@scanner.pos - pos_start) <= 1024
2072
+ end
2073
+ end
2074
+ end
2075
+
2076
+ # [131]
2077
+ # ns-plain(n,c) ::=
2078
+ # ( c = flow-out => ns-plain-multi-line(n,c) )
2079
+ # ( c = flow-in => ns-plain-multi-line(n,c) )
2080
+ # ( c = block-key => ns-plain-one-line(c) )
2081
+ # ( c = flow-key => ns-plain-one-line(c) )
2082
+ #
2083
+ # [156]
2084
+ # ns-flow-yaml-content(n,c) ::=
2085
+ # ns-plain(n,c)
2086
+ def parse_ns_flow_yaml_content(n, c)
2087
+ pos_start = @scanner.pos
2088
+ result =
2089
+ case c
2090
+ when :block_key then parse_ns_plain_one_line(c)
2091
+ when :flow_in then parse_ns_plain_multi_line(n, c)
2092
+ when :flow_key then parse_ns_plain_one_line(c)
2093
+ when :flow_out then parse_ns_plain_multi_line(n, c)
2094
+ else raise InternalException, c.inspect
2095
+ end
2096
+
2097
+ if result
2098
+ value = from(pos_start)
2099
+ value.gsub!(/(?:[\ \t]*\r?\n[\ \t]*)/, "\n")
2100
+ value.gsub!(/\n(\n*)/) { $1.empty? ? " " : $1 }
2101
+ events_push_flush_properties(Scalar.new(Location.new(@source, pos_start, @scanner.pos), value, Nodes::Scalar::PLAIN))
2102
+ end
2103
+
2104
+ result
2105
+ end
2106
+
2107
+ # [157]
2108
+ # c-flow-json-content(n,c) ::=
2109
+ # c-flow-sequence(n,c) | c-flow-mapping(n,c)
2110
+ # | c-single-quoted(n,c) | c-double-quoted(n,c)
2111
+ def parse_c_flow_json_content(n, c)
2112
+ parse_c_flow_sequence(n, c) ||
2113
+ parse_c_flow_mapping(n, c) ||
2114
+ parse_c_single_quoted(n, c) ||
2115
+ parse_c_double_quoted(n, c)
2116
+ end
2117
+
2118
+ # [158]
2119
+ # ns-flow-content(n,c) ::=
2120
+ # ns-flow-yaml-content(n,c) | c-flow-json-content(n,c)
2121
+ def parse_ns_flow_content(n, c)
2122
+ parse_ns_flow_yaml_content(n, c) ||
2123
+ parse_c_flow_json_content(n, c)
2124
+ end
2125
+
2126
+ # [159]
2127
+ # ns-flow-yaml-node(n,c) ::=
2128
+ # c-ns-alias-node
2129
+ # | ns-flow-yaml-content(n,c)
2130
+ # | ( c-ns-properties(n,c)
2131
+ # ( ( s-separate(n,c)
2132
+ # ns-flow-yaml-content(n,c) )
2133
+ # | e-scalar ) )
2134
+ def parse_ns_flow_yaml_node(n, c)
2135
+ parse_c_ns_alias_node ||
2136
+ parse_ns_flow_yaml_content(n, c) ||
2137
+ try do
2138
+ parse_c_ns_properties(n, c) &&
2139
+ (try { parse_s_separate(n, c) && parse_ns_flow_content(n, c) } || parse_e_scalar)
2140
+ end
2141
+ end
2142
+
2143
+ # [160]
2144
+ # c-flow-json-node(n,c) ::=
2145
+ # ( c-ns-properties(n,c)
2146
+ # s-separate(n,c) )?
2147
+ # c-flow-json-content(n,c)
2148
+ def parse_c_flow_json_node(n, c)
2149
+ try do
2150
+ try { parse_c_ns_properties(n, c) && parse_s_separate(n, c) }
2151
+ parse_c_flow_json_content(n, c)
2152
+ end
2153
+ end
2154
+
2155
+ # [161]
2156
+ # ns-flow-node(n,c) ::=
2157
+ # c-ns-alias-node
2158
+ # | ns-flow-content(n,c)
2159
+ # | ( c-ns-properties(n,c)
2160
+ # ( ( s-separate(n,c)
2161
+ # ns-flow-content(n,c) )
2162
+ # | e-scalar ) )
2163
+ def parse_ns_flow_node(n, c)
2164
+ parse_c_ns_alias_node ||
2165
+ parse_ns_flow_content(n, c) ||
2166
+ try do
2167
+ parse_c_ns_properties(n, c) &&
2168
+ (try { parse_s_separate(n, c) && parse_ns_flow_content(n, c) } || parse_e_scalar)
2169
+ end
2170
+ end
2171
+
2172
+ # [162]
2173
+ # c-b-block-header(m,t) ::=
2174
+ # ( ( c-indentation-indicator(m)
2175
+ # c-chomping-indicator(t) )
2176
+ # | ( c-chomping-indicator(t)
2177
+ # c-indentation-indicator(m) ) )
2178
+ # s-b-comment
2179
+ def parse_c_b_block_header(n)
2180
+ m = nil
2181
+ t = nil
2182
+
2183
+ result =
2184
+ try do
2185
+ (
2186
+ try do
2187
+ (m = parse_c_indentation_indicator(n)) &&
2188
+ (t = parse_c_chomping_indicator) &&
2189
+ peek { @scanner.eos? || parse_s_white || parse_b_break }
2190
+ end ||
2191
+ try do
2192
+ (t = parse_c_chomping_indicator) &&
2193
+ (m = parse_c_indentation_indicator(n)) &&
2194
+ peek { @scanner.eos? || parse_s_white || parse_b_break }
2195
+ end
2196
+ ) && parse_s_b_comment
2197
+ end
2198
+
2199
+ result ? [m, t] : false
2200
+ end
2201
+
2202
+ # [163]
2203
+ # c-indentation-indicator(m) ::=
2204
+ # ( ns-dec-digit => m = ns-dec-digit - x:30 )
2205
+ # ( <empty> => m = auto-detect() )
2206
+ def parse_c_indentation_indicator(n)
2207
+ pos_start = @scanner.pos
2208
+
2209
+ if match(/[\u{31}-\u{39}]/)
2210
+ Integer(from(pos_start))
2211
+ else
2212
+ @scanner.check(/.*\n((?:\ *\n)*)(\ *)(.?)/)
2213
+
2214
+ pre = @scanner[1]
2215
+ if !@scanner[3].empty?
2216
+ m = @scanner[2].length - n
2217
+ else
2218
+ m = 0
2219
+ while pre.match?(/\ {#{m}}/)
2220
+ m += 1
2221
+ end
2222
+ m = m - n - 1
2223
+ end
2224
+
2225
+ if m > 0 && pre.match?(/^.{#{m + n}}\ /)
2226
+ raise_syntax_error("Invalid indentation indicator")
2227
+ end
2228
+
2229
+ m == 0 ? 1 : m
2230
+ end
2231
+ end
2232
+
2233
+ # [164]
2234
+ # c-chomping-indicator(t) ::=
2235
+ # ( '-' => t = strip )
2236
+ # ( '+' => t = keep )
2237
+ # ( <empty> => t = clip )
2238
+ def parse_c_chomping_indicator
2239
+ if match("-") then :strip
2240
+ elsif match("+") then :keep
2241
+ else :clip
2242
+ end
2243
+ end
2244
+
2245
+ # [165]
2246
+ # b-chomped-last(t) ::=
2247
+ # ( t = strip => b-non-content | <end_of_file> )
2248
+ # ( t = clip => b-as-line-feed | <end_of_file> )
2249
+ # ( t = keep => b-as-line-feed | <end_of_file> )
2250
+ def parse_b_chomped_last(t)
2251
+ case t
2252
+ when :clip then parse_b_as_line_feed || @scanner.eos?
2253
+ when :keep then parse_b_as_line_feed || @scanner.eos?
2254
+ when :strip then parse_b_non_content || @scanner.eos?
2255
+ else raise InternalException, t.inspect
2256
+ end
2257
+ end
2258
+
2259
+ # [166]
2260
+ # l-chomped-empty(n,t) ::=
2261
+ # ( t = strip => l-strip-empty(n) )
2262
+ # ( t = clip => l-strip-empty(n) )
2263
+ # ( t = keep => l-keep-empty(n) )
2264
+ #
2265
+ # [167]
2266
+ # l-strip-empty(n) ::=
2267
+ # ( s-indent(<=n) b-non-content )*
2268
+ # l-trail-comments(n)?
2269
+ #
2270
+ # [168]
2271
+ # l-keep-empty(n) ::=
2272
+ # l-empty(n,block-in)*
2273
+ # l-trail-comments(n)?
2274
+ def parse_l_chomped_empty(n, t)
2275
+ case t
2276
+ when :clip, :strip
2277
+ try do
2278
+ if star { try { parse_s_indent_le(n) && parse_b_non_content } }
2279
+ parse_l_trail_comments(n)
2280
+ true
2281
+ end
2282
+ end
2283
+ when :keep
2284
+ try do
2285
+ if star { parse_l_empty(n, :block_in) }
2286
+ parse_l_trail_comments(n)
2287
+ true
2288
+ end
2289
+ end
2290
+ else
2291
+ raise InternalException, t.inspect
2292
+ end
2293
+ end
2294
+
2295
+ # [169]
2296
+ # l-trail-comments(n) ::=
2297
+ # s-indent(<n)
2298
+ # c-nb-comment-text b-comment
2299
+ # l-comment*
2300
+ def parse_l_trail_comments(n)
2301
+ try do
2302
+ parse_s_indent_lt(n) &&
2303
+ parse_c_nb_comment_text(false) &&
2304
+ parse_b_comment &&
2305
+ star { parse_l_comment }
2306
+ end
2307
+ end
2308
+
2309
+ # [170]
2310
+ # c-l+literal(n) ::=
2311
+ # '|' c-b-block-header(m,t)
2312
+ # l-literal-content(n+m,t)
2313
+ def parse_c_l_literal(n)
2314
+ @in_scalar = true
2315
+ events_cache_push
2316
+
2317
+ m = nil
2318
+ t = nil
2319
+ pos_start = @scanner.pos
2320
+
2321
+ if try {
2322
+ match("|") &&
2323
+ (m, t = parse_c_b_block_header(n)) &&
2324
+ parse_l_literal_content(n + m, t)
2325
+ } then
2326
+ @in_scalar = false
2327
+ lines = events_cache_pop
2328
+ lines.pop if lines.length > 0 && lines.last.empty?
2329
+ value = lines.map { |line| "#{line}\n" }.join
2330
+
2331
+ case t
2332
+ when :clip
2333
+ value.sub!(/\n+\z/, "\n")
2334
+ when :strip
2335
+ value.sub!(/\n+\z/, "")
2336
+ when :keep
2337
+ value.sub!(/\n(\n+)\z/) { $1 } if !value.match?(/\S/)
2338
+ else
2339
+ raise InternalException, t.inspect
2340
+ end
2341
+
2342
+ events_push_flush_properties(Scalar.new(Location.new(@source, pos_start, @scanner.pos), value, Nodes::Scalar::LITERAL))
2343
+ true
2344
+ else
2345
+ @in_scalar = false
2346
+ events_cache_pop
2347
+ false
2348
+ end
2349
+ end
2350
+
2351
+ # [171]
2352
+ # l-nb-literal-text(n) ::=
2353
+ # l-empty(n,block-in)*
2354
+ # s-indent(n) nb-char+
2355
+ def parse_l_nb_literal_text(n)
2356
+ try do
2357
+ if star { parse_l_empty(n, :block_in) } && parse_s_indent(n)
2358
+ pos_start = @scanner.pos
2359
+
2360
+ if plus { parse_nb_char }
2361
+ events_push(from(pos_start))
2362
+ true
2363
+ end
2364
+ end
2365
+ end
2366
+ end
2367
+
2368
+ # [172]
2369
+ # b-nb-literal-next(n) ::=
2370
+ # b-as-line-feed
2371
+ # l-nb-literal-text(n)
2372
+ def parse_b_nb_literal_next(n)
2373
+ try { parse_b_as_line_feed && parse_l_nb_literal_text(n) }
2374
+ end
2375
+
2376
+ # [173]
2377
+ # l-literal-content(n,t) ::=
2378
+ # ( l-nb-literal-text(n)
2379
+ # b-nb-literal-next(n)*
2380
+ # b-chomped-last(t) )?
2381
+ # l-chomped-empty(n,t)
2382
+ def parse_l_literal_content(n, t)
2383
+ try do
2384
+ try do
2385
+ parse_l_nb_literal_text(n) &&
2386
+ star { parse_b_nb_literal_next(n) } &&
2387
+ parse_b_chomped_last(t)
2388
+ end
2389
+
2390
+ parse_l_chomped_empty(n, t)
2391
+ end
2392
+ end
2393
+
2394
+ # [174]
2395
+ # c-l+folded(n) ::=
2396
+ # '>' c-b-block-header(m,t)
2397
+ # l-folded-content(n+m,t)
2398
+ def parse_c_l_folded(n)
2399
+ @in_scalar = true
2400
+ @text_prefix.clear
2401
+ events_cache_push
2402
+
2403
+ m = nil
2404
+ t = nil
2405
+ pos_start = @scanner.pos
2406
+
2407
+ if try {
2408
+ match(">") &&
2409
+ (m, t = parse_c_b_block_header(n)) &&
2410
+ parse_l_folded_content(n + m, t)
2411
+ } then
2412
+ @in_scalar = false
2413
+
2414
+ value = events_cache_pop.join("\n")
2415
+ value.gsub!(/^(\S.*)\n(?=\S)/) { "#{$1} " }
2416
+ value.gsub!(/^(\S.*)\n(\n+)/) { "#{$1}#{$2}" }
2417
+ value.gsub!(/^([\ \t]+\S.*)\n(\n+)(?=\S)/) { "#{$1}#{$2}" }
2418
+ value << "\n"
2419
+
2420
+ case t
2421
+ when :clip
2422
+ value.sub!(/\n+\z/, "\n")
2423
+ value.clear if value == "\n"
2424
+ when :strip
2425
+ value.sub!(/\n+\z/, "")
2426
+ when :keep
2427
+ # nothing
2428
+ else
2429
+ raise InternalException, t.inspect
2430
+ end
2431
+
2432
+ events_push_flush_properties(Scalar.new(Location.new(@source, pos_start, @scanner.pos), value, Nodes::Scalar::FOLDED))
2433
+ true
2434
+ else
2435
+ @in_scalar = false
2436
+ events_cache_pop
2437
+ false
2438
+ end
2439
+ end
2440
+
2441
+ # [175]
2442
+ # s-nb-folded-text(n) ::=
2443
+ # s-indent(n) ns-char
2444
+ # nb-char*
2445
+ def parse_s_nb_folded_text(n)
2446
+ try do
2447
+ if parse_s_indent(n) && parse_ns_char
2448
+ pos_start = @scanner.pos
2449
+
2450
+ if star { parse_nb_char }
2451
+ events_push("#{@text_prefix}#{from(pos_start)}")
2452
+ true
2453
+ end
2454
+ end
2455
+ end
2456
+ end
2457
+
2458
+ # [176]
2459
+ # l-nb-folded-lines(n) ::=
2460
+ # s-nb-folded-text(n)
2461
+ # ( b-l-folded(n,block-in) s-nb-folded-text(n) )*
2462
+ def parse_l_nb_folded_lines(n)
2463
+ try do
2464
+ parse_s_nb_folded_text(n) &&
2465
+ star { try { parse_b_l_folded(n, :block_in) && parse_s_nb_folded_text(n) } }
2466
+ end
2467
+ end
2468
+
2469
+ # [177]
2470
+ # s-nb-spaced-text(n) ::=
2471
+ # s-indent(n) s-white
2472
+ # nb-char*
2473
+ def parse_s_nb_spaced_text(n)
2474
+ try do
2475
+ if parse_s_indent(n) && parse_s_white
2476
+ pos_start = @scanner.pos
2477
+ star { parse_nb_char }
2478
+ events_push("#{@text_prefix}#{from(pos_start)}")
2479
+ true
2480
+ end
2481
+ end
2482
+ end
2483
+
2484
+ # [178]
2485
+ # b-l-spaced(n) ::=
2486
+ # b-as-line-feed
2487
+ # l-empty(n,block-in)*
2488
+ def parse_b_l_spaced(n)
2489
+ try { parse_b_as_line_feed && star { parse_l_empty(n, :block_in) } }
2490
+ end
2491
+
2492
+ # [179]
2493
+ # l-nb-spaced-lines(n) ::=
2494
+ # s-nb-spaced-text(n)
2495
+ # ( b-l-spaced(n) s-nb-spaced-text(n) )*
2496
+ def parse_l_nb_spaced_lines(n)
2497
+ try do
2498
+ parse_s_nb_spaced_text(n) &&
2499
+ star { try { parse_b_l_spaced(n) && parse_s_nb_spaced_text(n) } }
2500
+ end
2501
+ end
2502
+
2503
+ # [180]
2504
+ # l-nb-same-lines(n) ::=
2505
+ # l-empty(n,block-in)*
2506
+ # ( l-nb-folded-lines(n) | l-nb-spaced-lines(n) )
2507
+ def parse_l_nb_same_lines(n)
2508
+ try do
2509
+ star { parse_l_empty(n, :block_in) }
2510
+ parse_l_nb_folded_lines(n) || parse_l_nb_spaced_lines(n)
2511
+ end
2512
+ end
2513
+
2514
+ # [181]
2515
+ # l-nb-diff-lines(n) ::=
2516
+ # l-nb-same-lines(n)
2517
+ # ( b-as-line-feed l-nb-same-lines(n) )*
2518
+ def parse_l_nb_diff_lines(n)
2519
+ try do
2520
+ parse_l_nb_same_lines(n) &&
2521
+ star { try { parse_b_as_line_feed && parse_l_nb_same_lines(n) } }
2522
+ end
2523
+ end
2524
+
2525
+ # [182]
2526
+ # l-folded-content(n,t) ::=
2527
+ # ( l-nb-diff-lines(n)
2528
+ # b-chomped-last(t) )?
2529
+ # l-chomped-empty(n,t)
2530
+ def parse_l_folded_content(n, t)
2531
+ try do
2532
+ try { parse_l_nb_diff_lines(n) && parse_b_chomped_last(t) }
2533
+ parse_l_chomped_empty(n, t)
2534
+ end
2535
+ end
2536
+
2537
+ # [183]
2538
+ # l+block-sequence(n) ::=
2539
+ # ( s-indent(n+m)
2540
+ # c-l-block-seq-entry(n+m) )+
2541
+ # <for_some_fixed_auto-detected_m_>_0>
2542
+ def parse_l_block_sequence(n)
2543
+ return false if (m = detect_indent(n)) == 0
2544
+
2545
+ events_cache_push
2546
+ events_push_flush_properties(SequenceStart.new(Location.point(@source, @scanner.pos), Nodes::Sequence::BLOCK))
2547
+
2548
+ if try { plus { try { parse_s_indent(n + m) && parse_c_l_block_seq_entry(n + m) } } }
2549
+ events_cache_flush
2550
+ events_push_flush_properties(SequenceEnd.new(Location.point(@source, @scanner.pos)))
2551
+ true
2552
+ else
2553
+ event = events_cache_pop[0]
2554
+ @anchor = event.anchor
2555
+ @tag = event.tag
2556
+ false
2557
+ end
2558
+ end
2559
+
2560
+ # [004]
2561
+ # c-sequence-entry ::=
2562
+ # '-'
2563
+ #
2564
+ # [184]
2565
+ # c-l-block-seq-entry(n) ::=
2566
+ # '-' <not_followed_by_an_ns-char>
2567
+ # s-l+block-indented(n,block-in)
2568
+ def parse_c_l_block_seq_entry(n)
2569
+ try do
2570
+ match("-") &&
2571
+ !peek { parse_ns_char } &&
2572
+ parse_s_l_block_indented(n, :block_in)
2573
+ end
2574
+ end
2575
+
2576
+ # [185]
2577
+ # s-l+block-indented(n,c) ::=
2578
+ # ( s-indent(m)
2579
+ # ( ns-l-compact-sequence(n+1+m)
2580
+ # | ns-l-compact-mapping(n+1+m) ) )
2581
+ # | s-l+block-node(n,c)
2582
+ # | ( e-node s-l-comments )
2583
+ def parse_s_l_block_indented(n, c)
2584
+ m = detect_indent(n)
2585
+
2586
+ try do
2587
+ parse_s_indent(m) &&
2588
+ (parse_ns_l_compact_sequence(n + 1 + m) || parse_ns_l_compact_mapping(n + 1 + m))
2589
+ end || parse_s_l_block_node(n, c) || try { parse_e_node && parse_s_l_comments }
2590
+ end
2591
+
2592
+ # [186]
2593
+ # ns-l-compact-sequence(n) ::=
2594
+ # c-l-block-seq-entry(n)
2595
+ # ( s-indent(n) c-l-block-seq-entry(n) )*
2596
+ def parse_ns_l_compact_sequence(n)
2597
+ events_cache_push
2598
+ events_push_flush_properties(SequenceStart.new(Location.point(@source, @scanner.pos), Nodes::Sequence::BLOCK))
2599
+
2600
+ if try {
2601
+ parse_c_l_block_seq_entry(n) &&
2602
+ star { try { parse_s_indent(n) && parse_c_l_block_seq_entry(n) } }
2603
+ } then
2604
+ events_cache_flush
2605
+ events_push_flush_properties(SequenceEnd.new(Location.point(@source, @scanner.pos)))
2606
+ true
2607
+ else
2608
+ events_cache_pop
2609
+ false
2610
+ end
2611
+ end
2612
+
2613
+ # [187]
2614
+ # l+block-mapping(n) ::=
2615
+ # ( s-indent(n+m)
2616
+ # ns-l-block-map-entry(n+m) )+
2617
+ # <for_some_fixed_auto-detected_m_>_0>
2618
+ def parse_l_block_mapping(n)
2619
+ return false if (m = detect_indent(n)) == 0
2620
+
2621
+ events_cache_push
2622
+ events_push_flush_properties(MappingStart.new(Location.point(@source, @scanner.pos), Nodes::Mapping::BLOCK))
2623
+
2624
+ if try { plus { try { parse_s_indent(n + m) && parse_ns_l_block_map_entry(n + m) } } }
2625
+ events_cache_flush
2626
+ events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos)))
2627
+ true
2628
+ else
2629
+ events_cache_pop
2630
+ false
2631
+ end
2632
+ end
2633
+
2634
+ # [188]
2635
+ # ns-l-block-map-entry(n) ::=
2636
+ # c-l-block-map-explicit-entry(n)
2637
+ # | ns-l-block-map-implicit-entry(n)
2638
+ def parse_ns_l_block_map_entry(n)
2639
+ parse_c_l_block_map_explicit_entry(n) ||
2640
+ parse_ns_l_block_map_implicit_entry(n)
2641
+ end
2642
+
2643
+ # [189]
2644
+ # c-l-block-map-explicit-entry(n) ::=
2645
+ # c-l-block-map-explicit-key(n)
2646
+ # ( l-block-map-explicit-value(n)
2647
+ # | e-node )
2648
+ def parse_c_l_block_map_explicit_entry(n)
2649
+ events_cache_push
2650
+
2651
+ if try {
2652
+ parse_c_l_block_map_explicit_key(n) &&
2653
+ (parse_l_block_map_explicit_value(n) || parse_e_node)
2654
+ } then
2655
+ events_cache_flush
2656
+ true
2657
+ else
2658
+ events_cache_pop
2659
+ false
2660
+ end
2661
+ end
2662
+
2663
+ # [190]
2664
+ # c-l-block-map-explicit-key(n) ::=
2665
+ # '?'
2666
+ # s-l+block-indented(n,block-out)
2667
+ def parse_c_l_block_map_explicit_key(n)
2668
+ try do
2669
+ match("?") &&
2670
+ peek { @scanner.eos? || parse_s_white || parse_b_break } &&
2671
+ parse_s_l_block_indented(n, :block_out)
2672
+ end
2673
+ end
2674
+
2675
+ # [191]
2676
+ # l-block-map-explicit-value(n) ::=
2677
+ # s-indent(n)
2678
+ # ':' s-l+block-indented(n,block-out)
2679
+ def parse_l_block_map_explicit_value(n)
2680
+ try do
2681
+ parse_s_indent(n) &&
2682
+ match(":") &&
2683
+ parse_s_l_block_indented(n, :block_out)
2684
+ end
2685
+ end
2686
+
2687
+ # [192]
2688
+ # ns-l-block-map-implicit-entry(n) ::=
2689
+ # (
2690
+ # ns-s-block-map-implicit-key
2691
+ # | e-node )
2692
+ # c-l-block-map-implicit-value(n)
2693
+ def parse_ns_l_block_map_implicit_entry(n)
2694
+ events_cache_push
2695
+
2696
+ if try {
2697
+ (parse_ns_s_block_map_implicit_key || parse_e_node) &&
2698
+ parse_c_l_block_map_implicit_value(n)
2699
+ } then
2700
+ events_cache_flush
2701
+ true
2702
+ else
2703
+ events_cache_pop
2704
+ false
2705
+ end
2706
+ end
2707
+
2708
+ # [193]
2709
+ # ns-s-block-map-implicit-key ::=
2710
+ # c-s-implicit-json-key(block-key)
2711
+ # | ns-s-implicit-yaml-key(block-key)
2712
+ def parse_ns_s_block_map_implicit_key
2713
+ parse_c_s_implicit_json_key(:block_key) ||
2714
+ parse_ns_s_implicit_yaml_key(:block_key)
2715
+ end
2716
+
2717
+ # [194]
2718
+ # c-l-block-map-implicit-value(n) ::=
2719
+ # ':' (
2720
+ # s-l+block-node(n,block-out)
2721
+ # | ( e-node s-l-comments ) )
2722
+ def parse_c_l_block_map_implicit_value(n)
2723
+ try do
2724
+ match(":") &&
2725
+ (parse_s_l_block_node(n, :block_out) || try { parse_e_node && parse_s_l_comments })
2726
+ end
2727
+ end
2728
+
2729
+ # [195]
2730
+ # ns-l-compact-mapping(n) ::=
2731
+ # ns-l-block-map-entry(n)
2732
+ # ( s-indent(n) ns-l-block-map-entry(n) )*
2733
+ def parse_ns_l_compact_mapping(n)
2734
+ events_cache_push
2735
+ events_push_flush_properties(MappingStart.new(Location.point(@source, @scanner.pos), Nodes::Mapping::BLOCK))
2736
+
2737
+ if try {
2738
+ parse_ns_l_block_map_entry(n) &&
2739
+ star { try { parse_s_indent(n) && parse_ns_l_block_map_entry(n) } }
2740
+ } then
2741
+ events_cache_flush
2742
+ events_push_flush_properties(MappingEnd.new(Location.point(@source, @scanner.pos)))
2743
+ true
2744
+ else
2745
+ events_cache_pop
2746
+ false
2747
+ end
2748
+ end
2749
+
2750
+ # [196]
2751
+ # s-l+block-node(n,c) ::=
2752
+ # s-l+block-in-block(n,c) | s-l+flow-in-block(n)
2753
+ def parse_s_l_block_node(n, c)
2754
+ parse_s_l_block_in_block(n, c) || parse_s_l_flow_in_block(n)
2755
+ end
2756
+
2757
+ # [197]
2758
+ # s-l+flow-in-block(n) ::=
2759
+ # s-separate(n+1,flow-out)
2760
+ # ns-flow-node(n+1,flow-out) s-l-comments
2761
+ def parse_s_l_flow_in_block(n)
2762
+ try do
2763
+ parse_s_separate(n + 1, :flow_out) &&
2764
+ parse_ns_flow_node(n + 1, :flow_out) &&
2765
+ parse_s_l_comments
2766
+ end
2767
+ end
2768
+
2769
+ # [198]
2770
+ # s-l+block-in-block(n,c) ::=
2771
+ # s-l+block-scalar(n,c) | s-l+block-collection(n,c)
2772
+ def parse_s_l_block_in_block(n, c)
2773
+ parse_s_l_block_scalar(n, c) || parse_s_l_block_collection(n, c)
2774
+ end
2775
+
2776
+ # [199]
2777
+ # s-l+block-scalar(n,c) ::=
2778
+ # s-separate(n+1,c)
2779
+ # ( c-ns-properties(n+1,c) s-separate(n+1,c) )?
2780
+ # ( c-l+literal(n) | c-l+folded(n) )
2781
+ def parse_s_l_block_scalar(n, c)
2782
+ try do
2783
+ if parse_s_separate(n + 1, c)
2784
+ try { parse_c_ns_properties(n + 1, c) && parse_s_separate(n + 1, c) }
2785
+ parse_c_l_literal(n) || parse_c_l_folded(n)
2786
+ end
2787
+ end
2788
+ end
2789
+
2790
+ # [200]
2791
+ # s-l+block-collection(n,c) ::=
2792
+ # ( s-separate(n+1,c)
2793
+ # c-ns-properties(n+1,c) )?
2794
+ # s-l-comments
2795
+ # ( l+block-sequence(seq-spaces(n,c))
2796
+ # | l+block-mapping(n) )
2797
+ def parse_s_l_block_collection(n, c)
2798
+ try do
2799
+ try do
2800
+ next false if !parse_s_separate(n + 1, c)
2801
+
2802
+ next true if try { parse_c_ns_properties(n + 1, c) && parse_s_l_comments }
2803
+ @tag = nil
2804
+ @anchor = nil
2805
+
2806
+ next true if try { parse_c_ns_tag_property && parse_s_l_comments }
2807
+ @tag = nil
2808
+
2809
+ next true if try { parse_c_ns_anchor_property && parse_s_l_comments }
2810
+ @anchor = nil
2811
+
2812
+ false
2813
+ end
2814
+
2815
+ parse_s_l_comments && (parse_l_block_sequence(parse_seq_spaces(n, c)) || parse_l_block_mapping(n))
2816
+ end
2817
+ end
2818
+
2819
+ # [201]
2820
+ # seq-spaces(n,c) ::=
2821
+ # ( c = block-out => n-1 )
2822
+ # ( c = block-in => n )
2823
+ def parse_seq_spaces(n, c)
2824
+ case c
2825
+ when :block_in then n
2826
+ when :block_out then n - 1
2827
+ else raise InternalException, c.inspect
2828
+ end
2829
+ end
2830
+
2831
+ # [003]
2832
+ # c-byte-order-mark ::=
2833
+ # x:FEFF
2834
+ #
2835
+ # [202]
2836
+ # l-document-prefix ::=
2837
+ # c-byte-order-mark? l-comment*
2838
+ def parse_l_document_prefix
2839
+ try do
2840
+ @scanner.skip("\u{FEFF}")
2841
+ star { parse_l_comment }
2842
+ end
2843
+ end
2844
+
2845
+ # [203]
2846
+ # c-directives-end ::=
2847
+ # '-' '-' '-'
2848
+ def parse_c_directives_end
2849
+ if try { match("---") && peek { @scanner.eos? || parse_s_white || parse_b_break } }
2850
+ document_end_event_flush
2851
+ @document_start_event.implicit = false
2852
+ true
2853
+ end
2854
+ end
2855
+
2856
+ # [204]
2857
+ # c-document-end ::=
2858
+ # '.' '.' '.'
2859
+ def parse_c_document_end
2860
+ if match("...")
2861
+ @document_end_event.implicit = false if @document_end_event
2862
+ document_end_event_flush
2863
+ true
2864
+ end
2865
+ end
2866
+
2867
+ # [205]
2868
+ # l-document-suffix ::=
2869
+ # c-document-end s-l-comments
2870
+ def parse_l_document_suffix
2871
+ try { parse_c_document_end && parse_s_l_comments }
2872
+ end
2873
+
2874
+ # [207]
2875
+ # l-bare-document ::=
2876
+ # s-l+block-node(-1,block-in)
2877
+ # <excluding_c-forbidden_content>
2878
+ def parse_l_bare_document
2879
+ previous = @in_bare_document
2880
+ @in_bare_document = true
2881
+
2882
+ result =
2883
+ try do
2884
+ !try { start_of_line? && (parse_c_directives_end || parse_c_document_end) && (match(/[\u{0A}\u{0D}]/) || parse_s_white || @scanner.eos?) } &&
2885
+ parse_s_l_block_node(-1, :block_in)
2886
+ end
2887
+
2888
+ @in_bare_document = previous
2889
+ result
2890
+ end
2891
+
2892
+ # [208]
2893
+ # l-explicit-document ::=
2894
+ # c-directives-end
2895
+ # ( l-bare-document
2896
+ # | ( e-node s-l-comments ) )
2897
+ def parse_l_explicit_document
2898
+ try do
2899
+ parse_c_directives_end &&
2900
+ (parse_l_bare_document || try { parse_e_node && parse_s_l_comments })
2901
+ end
2902
+ end
2903
+
2904
+ # [209]
2905
+ # l-directive-document ::=
2906
+ # l-directive+
2907
+ # l-explicit-document
2908
+ #
2909
+ # [210]
2910
+ # l-any-document ::=
2911
+ # l-directive-document
2912
+ # | l-explicit-document
2913
+ # | l-bare-document
2914
+ def parse_l_any_document
2915
+ try { plus { parse_l_directive } && parse_l_explicit_document } ||
2916
+ parse_l_explicit_document ||
2917
+ parse_l_bare_document
2918
+ end
2919
+
2920
+ # [211]
2921
+ # l-yaml-stream ::=
2922
+ # l-document-prefix* l-any-document?
2923
+ # ( ( l-document-suffix+ l-document-prefix*
2924
+ # l-any-document? )
2925
+ # | ( l-document-prefix* l-explicit-document? ) )*
2926
+ def parse_l_yaml_stream
2927
+ events_push_flush_properties(StreamStart.new(Location.point(@source, @scanner.pos)))
2928
+
2929
+ @document_start_event = DocumentStart.new(Location.point(@source, @scanner.pos))
2930
+ @tag_directives = @document_start_event.tag_directives
2931
+ @document_end_event = nil
2932
+
2933
+ if try {
2934
+ if parse_l_document_prefix
2935
+ parse_l_any_document
2936
+ star do
2937
+ try do
2938
+ if parse_l_document_suffix
2939
+ star { parse_l_document_prefix }
2940
+ parse_l_any_document
2941
+ true
2942
+ end
2943
+ end ||
2944
+ try do
2945
+ if parse_l_document_prefix
2946
+ parse_l_explicit_document
2947
+ true
2948
+ end
2949
+ end
2950
+ end
2951
+ end
2952
+ } then
2953
+ document_end_event_flush
2954
+ events_push_flush_properties(StreamEnd.new(Location.point(@source, @scanner.pos)))
2955
+ true
2956
+ end
2957
+ end
2958
+
2959
+ # ------------------------------------------------------------------------
2960
+ # :section: Debugging
2961
+ # ------------------------------------------------------------------------
2962
+
2963
+ # If the DEBUG environment variable is set, we'll decorate all of the
2964
+ # parse methods and print them out as they are encountered.
2965
+ if !ENV.fetch("DEBUG", "").empty?
2966
+ class Debug < Module
2967
+ def initialize(methods)
2968
+ methods.each do |method|
2969
+ prefix = method.name.delete_prefix("parse_")
2970
+
2971
+ define_method(method) do |*args|
2972
+ norm = args.map { |arg| arg.nil? ? "nil" : arg }.join(",")
2973
+ $stderr.puts(">>> #{prefix}(#{norm})")
2974
+ super(*args)
2975
+ end
2976
+ end
2977
+ end
2978
+ end
2979
+
2980
+ prepend Debug.new(private_instance_methods.grep(/\Aparse_/))
2981
+ end
2982
+ end
2983
+
2984
+ # The emitter is responsible for taking Ruby objects and converting them
2985
+ # into YAML documents.
2986
+ class Emitter
2987
+ # The base class for all emitter nodes. We need to build a tree of nodes
2988
+ # here in order to support dumping repeated objects as anchors and
2989
+ # aliases, since we may find that we need to add an anchor after the
2990
+ # object has already been flushed.
2991
+ class Node
2992
+ attr_reader :value, :leading_comments, :trailing_comments
2993
+
2994
+ def initialize(value, leading_comments, trailing_comments)
2995
+ @value = value
2996
+ @leading_comments = leading_comments
2997
+ @trailing_comments = trailing_comments
2998
+ @anchor = nil
2999
+ end
3000
+
3001
+ def accept(visitor)
3002
+ raise
3003
+ end
3004
+ end
3005
+
3006
+ # Represents an alias to another node in the tree.
3007
+ class AliasNode < Node
3008
+ def accept(visitor)
3009
+ visitor.visit_alias(self)
3010
+ end
3011
+ end
3012
+
3013
+ # Represents an array of nodes.
3014
+ class ArrayNode < Node
3015
+ attr_accessor :anchor
3016
+
3017
+ def accept(visitor)
3018
+ visitor.visit_array(self)
3019
+ end
3020
+ end
3021
+
3022
+ # Represents a hash of nodes.
3023
+ class HashNode < Node
3024
+ attr_accessor :anchor
3025
+
3026
+ def accept(visitor)
3027
+ visitor.visit_hash(self)
3028
+ end
3029
+ end
3030
+
3031
+ # Represents the nil value.
3032
+ class NilNode < Node
3033
+ end
3034
+
3035
+ # Represents a generic object that is not matched by any of the other node
3036
+ # types.
3037
+ class ObjectNode < Node
3038
+ def accept(visitor)
3039
+ visitor.visit_object(self)
3040
+ end
3041
+ end
3042
+
3043
+ # Represents a Psych::Omap object.
3044
+ class OmapNode < Node
3045
+ attr_accessor :anchor
3046
+
3047
+ def accept(visitor)
3048
+ visitor.visit_omap(self)
3049
+ end
3050
+ end
3051
+
3052
+ # Represents a Psych::Set object.
3053
+ class SetNode < Node
3054
+ attr_accessor :anchor
3055
+
3056
+ def accept(visitor)
3057
+ visitor.visit_set(self)
3058
+ end
3059
+ end
3060
+
3061
+ # Represents a string object.
3062
+ class StringNode < Node
3063
+ def accept(visitor)
3064
+ visitor.visit_string(self)
3065
+ end
3066
+ end
3067
+
3068
+ # The visitor is responsible for walking the tree and generating the YAML
3069
+ # output.
3070
+ class Visitor
3071
+ def initialize(q)
3072
+ @q = q
3073
+ end
3074
+
3075
+ # Visit an AliasNode.
3076
+ def visit_alias(node)
3077
+ with_comments(node) { |value| @q.text("*#{value}") }
3078
+ end
3079
+
3080
+ # Visit an ArrayNode.
3081
+ def visit_array(node)
3082
+ with_comments(node) do |value|
3083
+ if (anchor = node.anchor)
3084
+ @q.text("&#{anchor} ")
3085
+ end
3086
+
3087
+ if value.empty?
3088
+ @q.text("[]")
3089
+ else
3090
+ visit_array_contents(value)
3091
+ end
3092
+ end
3093
+ end
3094
+
3095
+ # Visit a HashNode.
3096
+ def visit_hash(node)
3097
+ with_comments(node) do |value|
3098
+ if (anchor = node.anchor)
3099
+ @q.text("&#{anchor}")
3100
+ end
3101
+
3102
+ if value.empty?
3103
+ @q.text(" ") if anchor
3104
+ @q.text("{}")
3105
+ else
3106
+ @q.breakable if anchor
3107
+ visit_hash_contents(value)
3108
+ end
3109
+ end
3110
+ end
3111
+
3112
+ # Visit an ObjectNode.
3113
+ def visit_object(node)
3114
+ with_comments(node) do |value|
3115
+ @q.text(Psych.dump(value, indentation: @q.indent)[/\A--- (.+)\n\z/m, 1]) # TODO
3116
+ end
3117
+ end
3118
+
3119
+ # Visit an OmapNode.
3120
+ def visit_omap(node)
3121
+ with_comments(node) do |value|
3122
+ if (anchor = node.anchor)
3123
+ @q.text("&#{anchor} ")
3124
+ end
3125
+
3126
+ @q.text("!!omap")
3127
+ @q.breakable
3128
+
3129
+ visit_array_contents(value)
3130
+ end
3131
+ end
3132
+
3133
+ # Visit a SetNode.
3134
+ def visit_set(node)
3135
+ with_comments(node) do |value|
3136
+ if (anchor = node.anchor)
3137
+ @q.text("&#{anchor} ")
3138
+ end
3139
+
3140
+ @q.text("!set")
3141
+ @q.breakable
3142
+
3143
+ visit_hash_contents(node.value)
3144
+ end
3145
+ end
3146
+
3147
+ # Visit a StringNode.
3148
+ alias visit_string visit_object
3149
+
3150
+ private
3151
+
3152
+ # Shortcut to visit a node by passing this visitor to the accept method.
3153
+ def visit(node)
3154
+ node.accept(self)
3155
+ end
3156
+
3157
+ # Visit the elements within an array.
3158
+ def visit_array_contents(contents)
3159
+ @q.seplist(contents, -> { @q.breakable }) do |element|
3160
+ @q.text("-")
3161
+ next if element.is_a?(NilNode)
3162
+
3163
+ @q.text(" ")
3164
+ @q.nest(2) { visit(element) }
3165
+ end
3166
+ end
3167
+
3168
+ # Visit the key/value pairs within a hash.
3169
+ def visit_hash_contents(contents)
3170
+ @q.seplist(contents, -> { @q.breakable }) do |key, value|
3171
+ inlined = false
3172
+
3173
+ case key
3174
+ when NilNode
3175
+ @q.text("! ''")
3176
+ when ArrayNode, HashNode, OmapNode, SetNode
3177
+ if key.anchor.nil?
3178
+ @q.text("? ")
3179
+ @q.nest(2) { visit(key) }
3180
+ @q.breakable
3181
+ inlined = true
3182
+ else
3183
+ visit(key)
3184
+ end
3185
+ when AliasNode, ObjectNode
3186
+ visit(key)
3187
+ when StringNode
3188
+ if key.value.include?("\n")
3189
+ @q.text("? ")
3190
+ visit(key)
3191
+ @q.breakable
3192
+ inlined = true
3193
+ else
3194
+ visit(key)
3195
+ end
3196
+ end
3197
+
3198
+ @q.text(":")
3199
+
3200
+ case value
3201
+ when NilNode
3202
+ # skip
3203
+ when OmapNode, SetNode
3204
+ @q.text(" ")
3205
+ @q.nest(2) { visit(value) }
3206
+ when ArrayNode
3207
+ if value.value.empty?
3208
+ @q.text(" []")
3209
+ elsif inlined || value.anchor
3210
+ @q.text(" ")
3211
+ @q.nest(2) { visit(value) }
3212
+ else
3213
+ @q.breakable
3214
+ visit(value)
3215
+ end
3216
+ when HashNode
3217
+ if value.value.empty?
3218
+ @q.text(" {}")
3219
+ elsif inlined || value.anchor
3220
+ @q.text(" ")
3221
+ @q.nest(2) { visit(value) }
3222
+ else
3223
+ @q.nest(2) do
3224
+ @q.breakable
3225
+ visit(value)
3226
+ end
3227
+ end
3228
+ when AliasNode, ObjectNode, StringNode
3229
+ @q.text(" ")
3230
+ @q.nest(2) { visit(value) }
3231
+ end
3232
+ end
3233
+ end
3234
+
3235
+ # Print out the leading and trailing comments of a node, as well as
3236
+ # yielding the value of the node to the block.
3237
+ def with_comments(node)
3238
+ node.leading_comments.each do |comment|
3239
+ @q.text(comment.value)
3240
+ @q.breakable
3241
+ end
3242
+
3243
+ yield node.value
3244
+
3245
+ if (trailing_comments = node.trailing_comments).any?
3246
+ if trailing_comments[0].inline?
3247
+ inline_comment = trailing_comments.shift
3248
+ @q.trailer { @q.text(" "); @q.text(inline_comment.value) }
3249
+ end
3250
+
3251
+ trailing_comments.each do |comment|
3252
+ @q.breakable
3253
+ @q.text(comment.value)
3254
+ end
3255
+ end
3256
+ end
3257
+ end
3258
+
3259
+ # This is a specialized pretty printer that knows how to format trailing
3260
+ # comment.
3261
+ class Formatter < PP
3262
+ def breakable(sep = " ", width = sep.length)
3263
+ (current_trailers = trailers).each(&:call)
3264
+ current_trailers.clear
3265
+ super(sep, width)
3266
+ end
3267
+
3268
+ # These are blocks in the doc tree that should be flushed whenever we
3269
+ # are about to flush a breakable.
3270
+ def trailers
3271
+ @trailers ||= []
3272
+ end
3273
+
3274
+ # Register a block to be called when the next breakable is flushed.
3275
+ def trailer(&block)
3276
+ trailers << block
3277
+ end
3278
+ end
3279
+
3280
+ # Initialize a new emitter with the given io and options.
3281
+ def initialize(io, options)
3282
+ @io = io || $stdout
3283
+ @options = options
3284
+ @started = false
3285
+
3286
+ # These three instance variables are used to support dumping repeated
3287
+ # objects. When the same object is found more than once, we switch to
3288
+ # using an anchor and an alias.
3289
+ @object_nodes = {}.compare_by_identity
3290
+ @object_anchors = {}.compare_by_identity
3291
+ @object_anchor = 0
3292
+ end
3293
+
3294
+ # This is the main entrypoint into this object. It is responsible for
3295
+ # pushing a new object onto the emitter, which is then represented as a
3296
+ # YAML document.
3297
+ def <<(object)
3298
+ if @started
3299
+ @io << "...\n---"
3300
+ else
3301
+ @io << "---"
3302
+ @started = true
3303
+ end
3304
+
3305
+ if (node = dump(object)).is_a?(NilNode)
3306
+ @io << "\n"
3307
+ else
3308
+ q = Formatter.new(+"", 79, "\n") { |n| " " * n }
3309
+
3310
+ if (node.is_a?(ArrayNode) || node.is_a?(HashNode)) && !node.value.empty?
3311
+ q.breakable
3312
+ else
3313
+ q.text(" ")
3314
+ end
3315
+
3316
+ node.accept(Visitor.new(q))
3317
+ q.breakable
3318
+ q.current_group.break
3319
+ q.flush
3320
+
3321
+ @io << q.output
3322
+ end
3323
+ end
3324
+
3325
+ private
3326
+
3327
+ # Walk through the given object and convert it into a tree of nodes.
3328
+ def dump(base_object)
3329
+ object = base_object
3330
+ leading_comments = []
3331
+ trailing_comments = []
3332
+
3333
+ if base_object.is_a?(YAMLCommentsDelegator)
3334
+ object = base_object.__getobj__
3335
+ leading_comments.concat(base_object.yaml_leading_comments)
3336
+ trailing_comments.concat(base_object.yaml_trailing_comments)
3337
+ end
3338
+
3339
+ if object.nil?
3340
+ NilNode.new(object, leading_comments, trailing_comments)
3341
+ elsif @object_nodes.key?(object)
3342
+ AliasNode.new(
3343
+ @object_nodes[object].anchor = (@object_anchors[object] ||= (@object_anchor += 1)),
3344
+ leading_comments,
3345
+ trailing_comments
3346
+ )
3347
+ else
3348
+ case object
3349
+ when Psych::Omap
3350
+ @object_nodes[object] =
3351
+ OmapNode.new(
3352
+ object.map { |(key, value)| HashNode.new({ dump(key) => dump(value) }, [], []) },
3353
+ leading_comments,
3354
+ trailing_comments
3355
+ )
3356
+ when Psych::Set
3357
+ @object_nodes[object] =
3358
+ SetNode.new(
3359
+ object.to_h { |key, value| [dump(key), dump(value)] },
3360
+ leading_comments,
3361
+ trailing_comments
3362
+ )
3363
+ when Array
3364
+ @object_nodes[object] =
3365
+ ArrayNode.new(
3366
+ object.map { |element| dump(element) },
3367
+ leading_comments,
3368
+ trailing_comments
3369
+ )
3370
+ when Hash
3371
+ @object_nodes[object] =
3372
+ HashNode.new(
3373
+ object.to_h { |key, value| [dump(key), dump(value)] },
3374
+ leading_comments,
3375
+ trailing_comments
3376
+ )
3377
+ when String
3378
+ StringNode.new(object, leading_comments, trailing_comments)
3379
+ else
3380
+ ObjectNode.new(object, leading_comments, trailing_comments)
3381
+ end
3382
+ end
3383
+ end
3384
+ end
3385
+
3386
+ # A safe emitter is a subclass of the emitter that restricts the types of
3387
+ # objects that can be serialized.
3388
+ class SafeEmitter < Emitter
3389
+ DEFAULT_PERMITTED_CLASSES = {
3390
+ TrueClass => true,
3391
+ FalseClass => true,
3392
+ NilClass => true,
3393
+ Integer => true,
3394
+ Float => true,
3395
+ String => true,
3396
+ Array => true,
3397
+ Hash => true,
3398
+ }.compare_by_identity.freeze
3399
+
3400
+ # Initialize a new safe emitter with the given io and options.
3401
+ def initialize(io, options)
3402
+ super(io, options)
3403
+
3404
+ @permitted_classes = DEFAULT_PERMITTED_CLASSES.dup
3405
+ Array(options[:permitted_classes]).each do |klass|
3406
+ @permitted_classes[klass] = true
3407
+ end
3408
+
3409
+ @permitted_symbols = {}.compare_by_identity
3410
+ Array(options[:permitted_symbols]).each do |symbol|
3411
+ @permitted_symbols[symbol] = true
3412
+ end
3413
+
3414
+ @aliases = options.fetch(:aliases, false)
3415
+ end
3416
+
3417
+ private
3418
+
3419
+ # Dump the given object, ensuring that it is a permitted object.
3420
+ def dump(object)
3421
+ if !@aliases && @object_nodes.key?(object)
3422
+ raise BadAlias, "Tried to dump an aliased object"
3423
+ end
3424
+
3425
+ if Symbol === object
3426
+ if !@permitted_classes[Symbol] || !@permitted_symbols[object]
3427
+ raise DisallowedClass.new("dump", "Symbol(#{object.inspect})")
3428
+ end
3429
+ elsif !@permitted_classes[object.class]
3430
+ raise DisallowedClass.new("dump", object.class.name || object.class.inspect)
3431
+ end
3432
+
3433
+ super
3434
+ end
3435
+ end
3436
+
3437
+ # --------------------------------------------------------------------------
3438
+ # :section: Public API specific to Psych::Pure
3439
+ # --------------------------------------------------------------------------
3440
+
3441
+ # Create a new default parser.
3442
+ def self.parser
3443
+ Pure::Parser.new(TreeBuilder.new)
3444
+ end
3445
+
3446
+ # Parse a YAML stream and return the root node.
3447
+ def self.parse_stream(yaml, filename: nil, comments: false, &block)
3448
+ if block_given?
3449
+ parser = Pure::Parser.new(Handlers::DocumentStream.new(&block))
3450
+ parser.parse(yaml, filename, comments: comments)
3451
+ else
3452
+ parser = self.parser
3453
+ parser.parse(yaml, filename, comments: comments)
3454
+ parser.handler.root
3455
+ end
3456
+ end
3457
+
3458
+ # Dump an object to a YAML string.
3459
+ def self.dump(o, io = nil, options = {})
3460
+ if Hash === io
3461
+ options = io
3462
+ io = nil
3463
+ end
3464
+
3465
+ real_io = io || StringIO.new
3466
+ emitter = Emitter.new(real_io, options)
3467
+ emitter << o
3468
+ io || real_io.string
3469
+ end
3470
+
3471
+ # Dump an object to a YAML string, with restricted classes, symbols, and
3472
+ # aliases.
3473
+ def self.safe_dump(o, io = nil, options = {})
3474
+ if Hash === io
3475
+ options = io
3476
+ io = nil
3477
+ end
3478
+
3479
+ real_io = io || StringIO.new
3480
+ emitter = SafeEmitter.new(real_io, options)
3481
+ emitter << o
3482
+ io || real_io.string
3483
+ end
3484
+
3485
+ # Dump a stream of objects to a YAML string.
3486
+ def self.dump_stream(*objects)
3487
+ real_io = io || StringIO.new
3488
+ emitter = Emitter.new(real_io, {})
3489
+ objects.each { |object| emitter << object }
3490
+ io || real_io.string
3491
+ end
3492
+
3493
+ # --------------------------------------------------------------------------
3494
+ # :section: Public API copied directly from Psych
3495
+ # --------------------------------------------------------------------------
3496
+
3497
+ def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3498
+ result = parse(yaml, filename: filename, comments: comments)
3499
+ return fallback unless result
3500
+ result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer)
3501
+ end
3502
+
3503
+ def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3504
+ result = parse(yaml, filename: filename, comments: comments)
3505
+ return fallback unless result
3506
+
3507
+ class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s),
3508
+ permitted_symbols.map(&:to_s))
3509
+ scanner = ScalarScanner.new class_loader, strict_integer: strict_integer
3510
+ visitor = if aliases
3511
+ Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
3512
+ else
3513
+ Visitors::NoAliasRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
3514
+ end
3515
+ result = visitor.accept result
3516
+ result
3517
+ end
3518
+
3519
+ def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false, comments: false
3520
+ safe_load yaml, permitted_classes: permitted_classes,
3521
+ permitted_symbols: permitted_symbols,
3522
+ aliases: aliases,
3523
+ filename: filename,
3524
+ fallback: fallback,
3525
+ symbolize_names: symbolize_names,
3526
+ freeze: freeze,
3527
+ strict_integer: strict_integer,
3528
+ comments: comments
3529
+ end
3530
+
3531
+ def self.parse yaml, filename: nil, comments: false
3532
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3533
+ return node
3534
+ end
3535
+
3536
+ false
3537
+ end
3538
+
3539
+ def self.parse_file filename, fallback: false, comments: false
3540
+ result = File.open filename, 'r:bom|utf-8' do |f|
3541
+ parse f, filename: filename, comments: comments
3542
+ end
3543
+ result || fallback
3544
+ end
3545
+
3546
+ def self.load_stream yaml, filename: nil, fallback: [], comments: false, **kwargs
3547
+ result = if block_given?
3548
+ parse_stream(yaml, filename: filename, comments: comments) do |node|
3549
+ yield node.to_ruby(**kwargs)
3550
+ end
3551
+ else
3552
+ parse_stream(yaml, filename: filename, comments: comments).children.map { |node| node.to_ruby(**kwargs) }
3553
+ end
3554
+
3555
+ return fallback if result.is_a?(Array) && result.empty?
3556
+ result
3557
+ end
3558
+
3559
+ def self.unsafe_load_file filename, **kwargs
3560
+ File.open(filename, 'r:bom|utf-8') { |f|
3561
+ self.unsafe_load f, filename: filename, **kwargs
3562
+ }
3563
+ end
3564
+
3565
+ def self.safe_load_file filename, **kwargs
3566
+ File.open(filename, 'r:bom|utf-8') { |f|
3567
+ self.safe_load f, filename: filename, **kwargs
3568
+ }
3569
+ end
3570
+
3571
+ def self.load_file filename, **kwargs
3572
+ File.open(filename, 'r:bom|utf-8') { |f|
3573
+ self.load f, filename: filename, **kwargs
3574
+ }
3575
+ end
3576
+ end
3577
+ end