psych-pure 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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