ebnf 1.1.3 → 2.1.2

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +221 -198
  3. data/UNLICENSE +1 -1
  4. data/VERSION +1 -1
  5. data/bin/ebnf +40 -21
  6. data/etc/abnf-core.ebnf +52 -0
  7. data/etc/abnf.abnf +121 -0
  8. data/etc/abnf.ebnf +124 -0
  9. data/etc/abnf.sxp +45 -0
  10. data/etc/doap.ttl +23 -15
  11. data/etc/ebnf.ebnf +21 -33
  12. data/etc/ebnf.html +171 -160
  13. data/etc/{ebnf.rb → ebnf.ll1.rb} +30 -107
  14. data/etc/ebnf.ll1.sxp +182 -183
  15. data/etc/ebnf.peg.rb +90 -0
  16. data/etc/ebnf.peg.sxp +84 -0
  17. data/etc/ebnf.sxp +40 -41
  18. data/etc/iso-ebnf.ebnf +140 -0
  19. data/etc/iso-ebnf.isoebnf +138 -0
  20. data/etc/iso-ebnf.sxp +65 -0
  21. data/etc/sparql.ebnf +4 -4
  22. data/etc/sparql.html +1603 -1751
  23. data/etc/sparql.ll1.sxp +7372 -7372
  24. data/etc/sparql.peg.rb +532 -0
  25. data/etc/sparql.peg.sxp +597 -0
  26. data/etc/sparql.sxp +363 -362
  27. data/etc/turtle.ebnf +3 -3
  28. data/etc/turtle.html +465 -517
  29. data/etc/{turtle.rb → turtle.ll1.rb} +3 -4
  30. data/etc/turtle.ll1.sxp +425 -425
  31. data/etc/turtle.peg.rb +182 -0
  32. data/etc/turtle.peg.sxp +199 -0
  33. data/etc/turtle.sxp +103 -101
  34. data/lib/ebnf.rb +7 -2
  35. data/lib/ebnf/abnf.rb +301 -0
  36. data/lib/ebnf/abnf/core.rb +23 -0
  37. data/lib/ebnf/abnf/meta.rb +111 -0
  38. data/lib/ebnf/base.rb +113 -69
  39. data/lib/ebnf/bnf.rb +1 -26
  40. data/lib/ebnf/ebnf/meta.rb +90 -0
  41. data/lib/ebnf/isoebnf.rb +229 -0
  42. data/lib/ebnf/isoebnf/meta.rb +75 -0
  43. data/lib/ebnf/ll1.rb +138 -6
  44. data/lib/ebnf/ll1/lexer.rb +37 -32
  45. data/lib/ebnf/ll1/parser.rb +113 -73
  46. data/lib/ebnf/ll1/scanner.rb +83 -51
  47. data/lib/ebnf/native.rb +320 -0
  48. data/lib/ebnf/parser.rb +285 -302
  49. data/lib/ebnf/peg.rb +39 -0
  50. data/lib/ebnf/peg/parser.rb +561 -0
  51. data/lib/ebnf/peg/rule.rb +250 -0
  52. data/lib/ebnf/rule.rb +443 -148
  53. data/lib/ebnf/terminals.rb +21 -0
  54. data/lib/ebnf/writer.rb +565 -83
  55. metadata +107 -29
  56. data/etc/sparql.rb +0 -45773
@@ -3,12 +3,52 @@ require 'ebnf/ll1/lexer'
3
3
  module EBNF::LL1
4
4
  ##
5
5
  # A Generic LL1 parser using a lexer and branch tables defined using the SWAP tool chain (modified).
6
+ #
7
+ # # Creating terminal definitions and parser rules to parse generated grammars
8
+ #
9
+ # The parser is initialized to callbacks invoked on entry and exit
10
+ # to each `terminal` and `production`. A trivial parser loop can be described as follows:
11
+ #
12
+ # require 'ebnf/ll1/parser'
13
+ # require 'meta'
14
+ #
15
+ # class Parser
16
+ # include Meta
17
+ # include EBNF::LL1::Parser
18
+ #
19
+ # terminal(:SYMBOL, /([a-z]|[A-Z]|[0-9]|_)+/) do |prod, token, input|
20
+ # # Add data based on scanned token to input
21
+ # input[:symbol] = token.value
22
+ # end
23
+ #
24
+ # start_production(:rule) do |input, current, callback|
25
+ # # Process on start of production
26
+ # # Set state for entry into recursed rules through current
27
+ #
28
+ # # Callback to parser loop with callback
29
+ # end
30
+ #
31
+ # production(:rule) do |input, current, callback|
32
+ # # Process on end of production
33
+ # # return results in input, retrieve results from recursed rules in current
34
+ #
35
+ # # Callback to parser loop with callback
36
+ # end
37
+ #
38
+ # def initialize(input)
39
+ # parse(input, start_symbol,
40
+ # branch: BRANCH,
41
+ # first: FIRST,
42
+ # follow: FOLLOW,
43
+ # cleanup: CLEANUP
44
+ # ) do |context, *data|
45
+ # # Process calls from callback from productions
46
+ #
47
+ # rescue ArgumentError, RDF::LL1::Parser::Error => e
48
+ # progress("Parsing completed with errors:\n\t#{e.message}")
49
+ # raise RDF::ReaderError, e.message if validate?
50
+ # end
6
51
  module Parser
7
- ##
8
- # @private
9
- # level above which debug messages are supressed
10
- DEBUG_LEVEL = 10
11
-
12
52
  ##
13
53
  # @return [Integer] line number of current token
14
54
  attr_reader :lineno
@@ -51,10 +91,10 @@ module EBNF::LL1
51
91
  # @yieldparam [Proc] block
52
92
  # Block passed to initialization for yielding to calling parser.
53
93
  # Should conform to the yield specs for #initialize
54
- def terminal(term, regexp, options = {}, &block)
94
+ def terminal(term, regexp, **options, &block)
55
95
  @patterns ||= []
56
96
  # Passed in order to define evaulation sequence
57
- @patterns << EBNF::LL1::Lexer::Terminal.new(term, regexp, options)
97
+ @patterns << EBNF::LL1::Lexer::Terminal.new(term, regexp, **options)
58
98
  @terminal_handlers ||= {}
59
99
  @terminal_handlers[term] = block if block_given?
60
100
  end
@@ -122,7 +162,14 @@ module EBNF::LL1
122
162
 
123
163
  def method_missing(method, *args, &block)
124
164
  if @delegate ||= nil
125
- @delegate.send method, *args, &block
165
+ # special handling when last arg is **options
166
+ params = @delegate.method(method).parameters
167
+ if params.any? {|t, _| t == :keyrest} && args.last.is_a?(Hash)
168
+ opts = args.pop
169
+ @delegate.send(method, *args, **opts, &block)
170
+ else
171
+ @delegate.send(method, *args, &block)
172
+ end
126
173
  else
127
174
  super
128
175
  end
@@ -179,7 +226,7 @@ module EBNF::LL1
179
226
  # def each_statement(&block)
180
227
  # @callback = block
181
228
  #
182
- # parse(START.to_sym) do |context, *data|
229
+ # parse(input, START.to_sym) do |context, *data|
183
230
  # case context
184
231
  # when :statement
185
232
  # yield *data
@@ -198,16 +245,13 @@ module EBNF::LL1
198
245
  # Lists valid terminals that can precede each production (for error recovery).
199
246
  # @option options [Hash{Symbol,String => Array<Symbol,String>}] :follow ({})
200
247
  # Lists valid terminals that can follow each production (for error recovery).
201
- # @option options [Boolean] :validate (false)
202
- # whether to validate the parsed statements and values. If not validating, the parser will attempt to recover from errors.
203
- # @option options [Boolean] :progress
204
- # Show progress of parser productions
205
- # @option options [Boolean] :debug
206
- # Detailed debug output
207
- # @option options [Boolean] :reset_on_start
208
- # Reset the parser state if the start token set with `prod` is found in a production. This reduces the production stack depth growth, which is appropriate for some grammars.
209
248
  # @option options[Integer] :high_water passed to lexer
249
+ # @option options [Logger] :logger for errors/progress/debug.
210
250
  # @option options[Integer] :low_water passed to lexer
251
+ # @option options [Boolean] :reset_on_start
252
+ # Reset the parser state if the start token set with `prod` is found in a production. This reduces the production stack depth growth, which is appropriate for some grammars.
253
+ # @option options [Boolean] :validate (false)
254
+ # whether to validate the parsed statements and values. If not validating, the parser will attempt to recover from errors.
211
255
  # @yield [context, *data]
212
256
  # Yields for to return data to parser
213
257
  # @yieldparam [:statement, :trace] context
@@ -218,18 +262,14 @@ module EBNF::LL1
218
262
  # @raise [Exception] Raises exceptions for parsing errors
219
263
  # or errors raised during processing callbacks. Internal
220
264
  # errors are raised using {Error}.
221
- # @see http://cs.adelaide.edu.au/~charles/lt/Lectures/07-ErrorRecovery.pdf
222
- def parse(input = nil, start = nil, options = {}, &block)
265
+ # @see https://cs.adelaide.edu.au/~charles/lt/Lectures/07-ErrorRecovery.pdf
266
+ def parse(input = nil, start = nil, **options, &block)
223
267
  @options = options.dup
224
- @options[:debug] ||= case
225
- when @options[:progress] then 2
226
- when @options[:validate] then 1
227
- end
228
268
  @branch = options[:branch]
229
269
  @first = options[:first] ||= {}
230
270
  @follow = options[:follow] ||= {}
231
271
  @cleanup = options[:cleanup] ||= {}
232
- @lexer = input.is_a?(Lexer) ? input : Lexer.new(input, self.class.patterns, @options)
272
+ @lexer = input.is_a?(Lexer) ? input : Lexer.new(input, self.class.patterns, **@options)
233
273
  @productions = []
234
274
  @parse_callback = block
235
275
  @recovering = false
@@ -349,9 +389,9 @@ module EBNF::LL1
349
389
  end
350
390
 
351
391
  # Get the list of follows for this sequence, this production and the stacked productions.
352
- debug("recovery", "stack follows:", level: 4)
392
+ debug("recovery", "stack follows:")
353
393
  todo_stack.reverse.each do |todo|
354
- debug("recovery", level: 4) {" #{todo[:prod]}: #{@follow[todo[:prod]].inspect}"}
394
+ debug("recovery") {" #{todo[:prod]}: #{@follow[todo[:prod]].inspect}"}
355
395
  end
356
396
 
357
397
  # Find all follows to the top of the stack
@@ -459,15 +499,16 @@ module EBNF::LL1
459
499
  protected
460
500
 
461
501
  ##
462
- # Error information, used as level `0` debug messages.
502
+ # Error information, used as level `3` logger messages.
503
+ # Messages may be logged and are saved for reporting at end of parsing.
463
504
  #
464
505
  # @param [String] node Relevant location associated with message
465
506
  # @param [String] message Error string
466
- # @param [Hash] options
507
+ # @param [Hash{Symbol => Object}] options
467
508
  # @option options [URI, #to_s] :production
468
509
  # @option options [Token] :token
469
- # @see {#debug}
470
- def error(node, message, options = {})
510
+ # @see #debug
511
+ def error(node, message, **options)
471
512
  lineno = @lineno || (options[:token].lineno if options[:token].respond_to?(:lineno))
472
513
  m = "ERROR "
473
514
  m += "[line: #{lineno}] " if lineno
@@ -476,83 +517,82 @@ module EBNF::LL1
476
517
  m += ", production = #{options[:production].inspect}" if options[:production]
477
518
  @error_log << m unless @recovering
478
519
  @recovering = true
479
- debug(node, m, options.merge(level: 0))
520
+ debug(node, m, level: options.fetch(:level, 3), **options)
480
521
  if options[:raise] || @options[:validate]
481
522
  raise Error.new(m, lineno: lineno, token: options[:token], production: options[:production])
482
523
  end
483
524
  end
484
525
 
485
526
  ##
486
- # Warning information, used as level `1` debug messages.
527
+ # Warning information, used as level `2` logger messages.
528
+ # Messages may be logged and are saved for reporting at end of parsing.
487
529
  #
488
530
  # @param [String] node Relevant location associated with message
489
531
  # @param [String] message Error string
490
532
  # @param [Hash] options
491
533
  # @option options [URI, #to_s] :production
492
534
  # @option options [Token] :token
493
- # @see {#debug}
494
- def warn(node, message, options = {})
535
+ # @see #debug
536
+ def warn(node, message, **options)
537
+ lineno = @lineno || (options[:token].lineno if options[:token].respond_to?(:lineno))
495
538
  m = "WARNING "
496
- m += "[line: #{@lineno}] " if @lineno
539
+ m += "[line: #{lineno}] " if lineno
497
540
  m += message
498
541
  m += " (found #{options[:token].inspect})" if options[:token]
499
542
  m += ", production = #{options[:production].inspect}" if options[:production]
500
543
  @error_log << m unless @recovering
501
- debug(node, m, options.merge(level: 1))
544
+ debug(node, m, level: 2, lineno: lineno, **options)
502
545
  end
503
546
 
504
547
  ##
505
- # Progress output when parsing. Passed as level `2` debug messages.
548
+ # Progress logged when parsing. Passed as level `1` logger messages.
549
+ #
550
+ # The call is ignored, unless `@options[:logger]` is set.
506
551
  #
507
- # @overload progress(node, message, options)
552
+ # @overload progress(node, message, **options, &block)
508
553
  # @param [String] node Relevant location associated with message
509
554
  # @param [String] message ("")
510
555
  # @param [Hash] options
511
556
  # @option options [Integer] :depth
512
557
  # Recursion depth for indenting output
513
- # @see {#debug}
558
+ # @see #debug
514
559
  def progress(node, *args, &block)
515
- return unless @options[:progress] || @options[:debug]
560
+ return unless @options[:logger]
561
+ lineno = @lineno || (options[:token].lineno if options[:token].respond_to?(:lineno))
516
562
  args << {} unless args.last.is_a?(Hash)
517
- args.last[:level] ||= 2
563
+ args.last[:level] ||= 1
564
+ args.last[:lineno] ||= lineno
518
565
  debug(node, *args, &block)
519
566
  end
520
567
 
521
568
  ##
522
- # Progress output when debugging.
569
+ # Debug logging.
523
570
  #
524
- # The call is ignored, unless `@options[:debug]` is set, in which
525
- # case it yields tracing information as indicated. Additionally,
526
- # if `@options[:debug]` is an Integer, the call is aborted if the
527
- # `:level` option is less than than `:level`.
571
+ # The call is ignored, unless `@options[:logger]` is set.
528
572
  #
529
- # @overload debug(node, message, options)
573
+ # @overload debug(node, message, **options)
530
574
  # @param [Array<String>] args Relevant location associated with message
531
575
  # @param [Hash] options
532
576
  # @option options [Integer] :depth
533
577
  # Recursion depth for indenting output
534
- # @option options [Integer] :level
535
- # Level assigned to message, by convention, level `0` is for
536
- # errors, level `1` is for warnings, level `2` is for parser
537
- # progress information, and anything higher is for various levels
538
- # of debug information.
539
- #
540
- # @yield trace, level, lineno, depth, args
541
- # @yieldparam [:trace] trace
542
- # @yieldparam [Integer] level
543
- # @yieldparam [Integer] lineno
544
- # @yieldparam [Integer] depth Recursive depth of productions
545
- # @yieldparam [Array<String>] args
546
- # @yieldreturn [String] added to message
547
- def debug(*args)
548
- return unless @options[:debug] && @parse_callback
578
+ # @yieldreturn [String] additional string appended to `message`.
579
+ def debug(*args, &block)
580
+ return unless @options[:logger]
549
581
  options = args.last.is_a?(Hash) ? args.pop : {}
550
- debug_level = options.fetch(:level, 3)
551
- return if @options[:debug].is_a?(Integer) && debug_level > @options[:debug]
552
-
582
+ lineno = @lineno || (options[:token].lineno if options[:token].respond_to?(:lineno))
583
+ level = options.fetch(:level, 0)
553
584
  depth = options[:depth] || self.depth
554
- args << yield if block_given?
555
- @parse_callback.call(:trace, debug_level, @lineno, depth, *args)
585
+
586
+ if self.respond_to?(:log_debug)
587
+ level = [:debug, :info, :warn, :error, :fatal][level]
588
+ log_debug(*args, **options.merge(level: level, lineno: lineno, depth: depth), &block)
589
+ elsif @options[:logger].respond_to?(:add)
590
+ args << yield if block_given?
591
+ @options[:logger].add(level, "[#{lineno}]" + (" " * depth) + args.join(" "))
592
+ elsif @options[:logger].respond_to?(:<<)
593
+ args << yield if block_given?
594
+ @options[:logger] << "[#{lineno}]" + (" " * depth) + args.join(" ")
595
+ end
556
596
  end
557
597
 
558
598
  private
@@ -563,7 +603,7 @@ module EBNF::LL1
563
603
  if handler
564
604
  # Create a new production data element, potentially allowing handler
565
605
  # to customize before pushing on the @prod_data stack
566
- progress("#{prod}(:start):#{@prod_data.length}") {@prod_data.last}
606
+ debug("#{prod}(:start):#{@prod_data.length}") {@prod_data.last}
567
607
  data = {}
568
608
  begin
569
609
  self.class.eval_with_binding(self) {
@@ -577,12 +617,12 @@ module EBNF::LL1
577
617
  elsif [:merge, :star].include?(@cleanup[prod])
578
618
  # Save current data to merge later
579
619
  @prod_data << {}
580
- progress("#{prod}(:start}:#{@prod_data.length}:cleanup:#{@cleanup[prod]}") { get_token.inspect + (@recovering ? ' recovering' : '')}
620
+ debug("#{prod}(:start}:#{@prod_data.length}:cleanup:#{@cleanup[prod]}") { get_token.inspect + (@recovering ? ' recovering' : '')}
581
621
  else
582
622
  # Make sure we push as many was we pop, even if there is no
583
623
  # explicit start handler
584
624
  @prod_data << {} if self.class.production_handlers[prod]
585
- progress("#{prod}(:start:#{@prod_data.length})") { get_token.inspect + (@recovering ? ' recovering' : '')}
625
+ debug("#{prod}(:start:#{@prod_data.length})") { get_token.inspect + (@recovering ? ' recovering' : '')}
586
626
  end
587
627
  #puts "prod_data(s): " + @prod_data.inspect
588
628
  end
@@ -616,7 +656,7 @@ module EBNF::LL1
616
656
  else Array(input[k]) + Array(v)
617
657
  end
618
658
  end
619
- progress("#{prod}(:finish):#{@prod_data.length} cleanup:#{@cleanup[prod]}") {@prod_data.last}
659
+ debug("#{prod}(:finish):#{@prod_data.length} cleanup:#{@cleanup[prod]}") {@prod_data.last}
620
660
  else
621
661
  progress("#{prod}(:finish):#{@prod_data.length}") { "recovering" if @recovering }
622
662
  end
@@ -723,7 +763,7 @@ module EBNF::LL1
723
763
  # "invalid token '%' on line 10",
724
764
  # token: '%', lineno: 9, production: :turtleDoc)
725
765
  #
726
- # @see http://ruby-doc.org/core/classes/StandardError.html
766
+ # @see https://ruby-doc.org/core/classes/StandardError.html
727
767
  class Error < StandardError
728
768
  ##
729
769
  # The current production.
@@ -751,7 +791,7 @@ module EBNF::LL1
751
791
  # @option options [Symbol] :production (nil)
752
792
  # @option options [String] :token (nil)
753
793
  # @option options [Integer] :lineno (nil)
754
- def initialize(message, options = {})
794
+ def initialize(message, **options)
755
795
  @production = options[:production]
756
796
  @token = options[:token]
757
797
  @lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
@@ -3,7 +3,7 @@ require 'strscan' unless defined?(StringScanner)
3
3
 
4
4
  module EBNF::LL1
5
5
  ##
6
- # Overload StringScanner with file operations
6
+ # Overload StringScanner with file operations and line counting
7
7
  #
8
8
  # * Reloads scanner as required until EOF.
9
9
  # * Loads to a high-water and reloads when remaining size reaches a low-water.
@@ -14,25 +14,14 @@ module EBNF::LL1
14
14
  LOW_WATER = 4 * 1024
15
15
 
16
16
  ##
17
- # @return [IO, StringIO]
17
+ # @return [String, IO, StringIO]
18
18
  attr_reader :input
19
19
 
20
20
  ##
21
- # If we don't have an IO input, simply use StringScanner directly
22
- # @private
23
- def self.new(input, options = {})
24
- input ||= ""
25
- if input.respond_to?(:read)
26
- scanner = self.allocate
27
- scanner.send(:initialize, input, options)
28
- else
29
- if input.encoding != Encoding::UTF_8
30
- input = input.dup if input.frozen?
31
- input.force_encoding(Encoding::UTF_8)
32
- end
33
- StringScanner.new(input)
34
- end
35
- end
21
+ # The current line number (one-based).
22
+ #
23
+ # @return [Integer]
24
+ attr_accessor :lineno
36
25
 
37
26
  ##
38
27
  # Create a scanner, from an IO
@@ -42,35 +31,26 @@ module EBNF::LL1
42
31
  # @option options[Integer] :high_water (HIGH_WATER)
43
32
  # @option options[Integer] :low_water (LOW_WATER)
44
33
  # @return [Scanner]
45
- def initialize(input, options = {})
34
+ def initialize(input, **options)
46
35
  @options = options.merge(high_water: HIGH_WATER, low_water: LOW_WATER)
47
36
 
48
- @input = input
49
- super("")
37
+ @previous_lineno = @lineno = 1
38
+ @input = input.is_a?(String) ? encode_utf8(input) : input
39
+ super(input.is_a?(String) ? @input : "")
50
40
  feed_me
51
41
  self
52
42
  end
53
43
 
54
44
  ##
55
- # Returns the "rest" of the line, or the next line if at EOL (i.e. everything after the scan pointer).
56
- # If there is no more data (eos? = true), it returns "".
57
- #
58
- # @return [String]
59
- def rest
60
- feed_me
61
- encode_utf8 super
62
- end
63
-
64
- ##
65
- # Attempts to skip over the given `pattern` beginning with the scan pointer.
66
- # If it matches, the scan pointer is advanced to the end of the match,
67
- # and the length of the match is returned. Otherwise, `nil` is returned.
68
- #
69
- # similar to `scan`, but without returning the matched string.
70
- # @param [Regexp] pattern
71
- def skip(pattern)
72
- feed_me
73
- super
45
+ # Ensures that the input buffer is full to the high water mark, or end of file. Useful when matching tokens that may be longer than the low water mark
46
+ def ensure_buffer_full
47
+ # Read up to high-water mark ensuring we're at an end of line
48
+ if @input.respond_to?(:eof?) && !@input.eof?
49
+ diff = @options[:high_water] - rest_size
50
+ string = encode_utf8(@input.read(diff))
51
+ string << encode_utf8(@input.gets) unless @input.eof?
52
+ self << string if string
53
+ end
74
54
  end
75
55
 
76
56
  ##
@@ -83,10 +63,13 @@ module EBNF::LL1
83
63
  end
84
64
 
85
65
  ##
86
- # Set the scan pointer to the end of the string and clear matching data
87
- def terminate
66
+ # Returns the "rest" of the line, or the next line if at EOL (i.e. everything after the scan pointer).
67
+ # If there is no more data (eos? = true), it returns "".
68
+ #
69
+ # @return [String]
70
+ def rest
88
71
  feed_me
89
- super
72
+ encode_utf8 super
90
73
  end
91
74
 
92
75
  ##
@@ -108,19 +91,68 @@ module EBNF::LL1
108
91
  # @return [String]
109
92
  def scan(pattern)
110
93
  feed_me
111
- encode_utf8 super
94
+ @previous_lineno = @lineno
95
+ if matched = encode_utf8(super)
96
+ @lineno += matched.count("\n")
97
+ end
98
+ matched
112
99
  end
113
100
 
114
101
  ##
115
- # Ensures that the input buffer is full to the high water mark, or end of file. Useful when matching tokens that may be longer than the low water mark
116
- def ensure_buffer_full
117
- # Read up to high-water mark ensuring we're at an end of line
118
- if @input && !@input.eof?
119
- diff = @options[:high_water] - rest_size
120
- string = encode_utf8(@input.read(diff))
121
- string << encode_utf8(@input.gets) unless @input.eof?
122
- self << string if string
102
+ # Scans the string until the pattern is matched. Returns the substring up to and including the end of the match, advancing the scan pointer to that location. If there is no match, nil is returned.
103
+ #
104
+ # @example
105
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
106
+ # s.scan_until(/1/) # -> "Fri Dec 1"
107
+ # s.pre_match # -> "Fri Dec "
108
+ # s.scan_until(/XYZ/) # -> nil
109
+ #
110
+ # @param [Regexp] pattern
111
+ # @return [String]
112
+ def scan_until(pattern)
113
+ feed_me
114
+ @previous_lineno = @lineno
115
+ if matched = encode_utf8(super)
116
+ @lineno += matched.count("\n")
123
117
  end
118
+ matched
119
+ end
120
+
121
+ ##
122
+ # Attempts to skip over the given `pattern` beginning with the scan pointer.
123
+ # If it matches, the scan pointer is advanced to the end of the match,
124
+ # and the length of the match is returned. Otherwise, `nil` is returned.
125
+ #
126
+ # similar to `scan`, but without returning the matched string.
127
+ # @param [Regexp] pattern
128
+ def skip(pattern)
129
+ scan(pattern)
130
+ nil
131
+ end
132
+
133
+ ##
134
+ # Advances the scan pointer until pattern is matched and consumed. Returns the number of bytes advanced, or nil if no match was found.
135
+ #
136
+ # Look ahead to match pattern, and advance the scan pointer to the end of the match. Return the number of characters advanced, or nil if the match was unsuccessful.
137
+ #
138
+ # It’s similar to scan_until, but without returning the intervening string.
139
+ # @param [Regexp] pattern
140
+ def skip_until(pattern)
141
+ (matched = scan_until(pattern)) && matched.length
142
+ end
143
+
144
+ ##
145
+ # Sets the scan pointer to the previous position. Only one previous position is remembered, and it changes with each scanning operation.
146
+ def unscan
147
+ @lineno = @previous_lineno
148
+ super
149
+ end
150
+
151
+ ##
152
+ # Set the scan pointer to the end of the string and clear matching data
153
+ def terminate
154
+ feed_me
155
+ super
124
156
  end
125
157
 
126
158
  private