• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dryruby / ebnf / 11355616381

15 Oct 2024 10:53PM UTC coverage: 94.066% (-0.2%) from 94.25%
11355616381

push

github

gkellogg
Check for terminals that also match a string production but match longer than that string; they should not match.

7 of 7 new or added lines in 1 file covered. (100.0%)

23 existing lines in 2 files now uncovered.

2140 of 2275 relevant lines covered (94.07%)

26130.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.71
/lib/ebnf/peg/parser.rb
1
module EBNF::PEG
2✔
2
  ##
3
  # A Generic PEG parser using the parsed rules modified for PEG parseing.
4
  module Parser
2✔
5
    ##
6
    # @return [Regexp, Rule] how to remove inter-rule whitespace
7
    attr_reader :whitespace
2✔
8

9
    ##
10
    # @return [Scanner] used for scanning input.
11
    attr_reader :scanner
2✔
12

13
    ##
14
    # A Hash structure used for memoizing rule results for a given input location.
15
    #
16
    #  @example Partial structure for memoizing results for a particular rule
17
    #
18
    #      {
19
    #        rule: {
20
    #          86: {
21
    #                pos: 
22
    #                result: [<EBNF::Rule:80 {
23
    #                  sym: :ebnf,
24
    #                    id: "1",
25
    #                    kind: :rule,
26
    #                    expr: [:star, [:alt, :declaration, :rule]]}>],
27
    #               }
28
    #          131: [<EBNF::Rule:80 {sym: :ebnf,
29
    #              id: "1",
30
    #              kind: :rule,
31
    #              expr: [:star, [:alt, :declaration, :rule]]}>,
32
    #            <EBNF::Rule:100 {
33
    #              sym: :declaration,
34
    #              id: "2",
35
    #              kind: :rule,
36
    #              expr: [:alt, "@terminals", :pass]}>]
37
    #        },
38
    #        POSTFIX: {
39
    #          80: "*",
40
    #          368: "*",
41
    #          399: "+"
42
    #        }
43
    #      }
44
    # @return [Hash{Integer => Hash{Symbol => Object}}]
45
    attr_reader :packrat
2✔
46

47
    def self.included(base)
2✔
48
      base.extend(ClassMethods)
10✔
49
    end
50

51
    # DSL for creating terminals and productions
52
    module ClassMethods
2✔
53
      def start_handlers; (@start_handlers ||= {}); end
585,850✔
54
      def start_options; (@start_hoptions ||= {}); end
445,980✔
55
      def production_handlers; (@production_handlers ||= {}); end
891,974✔
56
      def terminal_handlers; (@terminal_handlers ||= {}); end
295,308✔
57
      def terminal_regexps; (@terminal_regexps ||= {}); end
319,740✔
58
      def terminal_options; (@terminal_options ||= {}); end
295,332✔
59

60
      ##
61
      # Defines the pattern for a terminal node and a block to be invoked
62
      # when ther terminal is encountered. If the block is missing, the
63
      # value of the terminal will be placed on the input hash to be returned
64
      # to a previous production. Block is called in an evaluation block from
65
      # the enclosing parser.
66
      #
67
      # If no block is provided, then the value which would have been passed to the block is used as the result directly.
68
      #
69
      # @param [Symbol] term
70
      #   The terminal name.
71
      # @param [Regexp] regexp (nil)
72
      #   Pattern used to scan for this terminal,
73
      #   defaults to the expression defined in the associated rule.
74
      #   If unset, the terminal rule is used for matching.
75
      # @param [Hash] options
76
      # @option options [Boolean] :unescape
77
      #   Cause strings and codepoints to be unescaped.
78
      # @yield [value, prod]
79
      # @yieldparam [String] value
80
      #   The scanned terminal value.
81
      # @yieldparam [Symbol] prod
82
      #   A symbol indicating the production which referenced this terminal
83
      # @yieldparam [Proc] block
84
      #   Block passed to initialization for yielding to calling parser.
85
      #   Should conform to the yield specs for #initialize
86
      def terminal(term, regexp = nil, **options, &block)
2✔
87
        terminal_regexps[term] = regexp if regexp
78✔
88
        terminal_handlers[term] = block if block_given?
78✔
89
        terminal_options[term] = options.freeze
78✔
90
      end
91

92
      ##
93
      # Defines a production called at the beggining of a particular production
94
      # with data from previous production along with data defined for the
95
      # current production. Block is called in an evaluation block from
96
      # the enclosing parser.
97
      #
98
      # @param [Symbol] term
99
      #   The rule name
100
      # @param [Hash{Symbol => Object}] options
101
      #   Options which are returned from {Parser#onStart}.
102
      # @option options [Boolean] :as_hash (false)
103
      #   If the production is a `seq`, causes the value to be represented as a single hash, rather than an array of individual hashes for each sub-production. Note that this is not always advisable due to the possibility of repeated productions within the sequence.
104
      # @option options[:upper, :lower] :insensitive_strings
105
      #   Perform case-insensitive match of strings not defined as terminals, and map to either upper or lower case.
106
      # @yield [data, block]
107
      # @yieldparam [Hash] data
108
      #   A Hash defined for the current production, during :start
109
      #   may be initialized with data to pass to further productions,
110
      #   during :finish, it contains data placed by earlier productions
111
      # @yieldparam [Proc] block
112
      #   Block passed to initialization for yielding to calling parser.
113
      #   Should conform to the yield specs for #initialize
114
      # Yield to generate a triple
115
      def start_production(term, **options, &block)
2✔
116
        start_handlers[term] = block
44✔
117
        start_options[term] = options.freeze
44✔
118
      end
119

120
      ##
121
      # Defines a production called when production of associated
122
      # non-terminals has completed
123
      # with data from previous production along with data defined for the
124
      # current production. Block is called in an evaluation block from
125
      # the enclosing parser.
126
      #
127
      # @param [Symbol] term
128
      #   Term which is a key in the branch table
129
      # @param [Boolean] clear_packrat (false)
130
      #   Clears the packrat state on completion to reduce memory requirements of parser. Use only on a top-level rule when it is determined that no further backtracking is necessary.
131
      # @yield [result, data, block]
132
      # @yieldparam [Object] result
133
      #   The result from sucessfully parsing the production.
134
      # @yieldparam [Hash] data
135
      #   A Hash defined for the current production, during :start
136
      #   may be initialized with data to pass to further productions,
137
      #   during :finish, it contains data placed by earlier productions
138
      # @yieldparam [Proc] block
139
      #   Block passed to initialization for yielding to calling parser.
140
      #   Should conform to the yield specs for #initialize
141
      # @yieldparam [Hash] **options
142
      #   Other data that may be passed to the production
143
      # @yieldreturn [Object] the result of this production.
144
      # Yield to generate a triple
145
      def production(term, clear_packrat: false, &block)
2✔
146
        production_handlers[term] = [block, clear_packrat]
112✔
147
      end
148

149
      # Evaluate a handler, delegating to the specified object.
150
      # This is necessary so that handlers can operate within the
151
      # binding context of the parser in which they're invoked.
152
      # @param [Object] object
153
      # @return [Object]
154
      def eval_with_binding(object)
2✔
155
        @delegate = object
235,116✔
156
        object.instance_eval {yield}
470,232✔
157
      end
158

159
      private
2✔
160

161
      def method_missing(method, *args, &block)
2✔
162
        if @delegate ||= nil
2,952✔
163
          # special handling when last arg is **options
164
          params = @delegate.method(method).parameters
2,952✔
165
          if params.any? {|t, _| t == :keyrest} && args.last.is_a?(Hash)
5,912✔
UNCOV
166
            opts = args.pop
×
167
            @delegate.send(method, *args, **opts, &block)
×
168
          else
169
            @delegate.send(method, *args, &block)
2,952✔
170
          end
171
        else
UNCOV
172
          super
×
173
        end
174
      end
175
    end
176

177
    ##
178
    # Initializes a new parser instance.
179
    #
180
    # @param  [String, #to_s] input
181
    # @param [Symbol, #to_s] start
182
    #   The starting production for the parser. It may be a URI from the grammar, or a symbol representing the local_name portion of the grammar URI.
183
    # @param [Array<EBNF::PEG::Rule>] rules
184
    #   The parsed rules, which control parsing sequence.
185
    #   Identify the symbol of the starting rule with `start`.
186
    # @param  [Hash{Symbol => Object}] options
187
    # @option options[Integer] :high_water passed to lexer
188
    # @option options[:upper, :lower] :insensitive_strings
189
    #   Perform case-insensitive match of strings not defined as terminals, and map to either upper or lower case.
190
    # @option options [Logger] :logger for errors/progress/debug.
191
    # @option options[Integer] :low_water passed to lexer
192
    # @option options[Boolean] :seq_hash (false)
193
    #   If `true`, sets the default for the value sent to a production handler that is for a `seq` to a hash composed of the flattened consitutent hashes that are otherwise provided.
194
    # @option options [Symbol, Regexp] :whitespace 
195
    #   Symbol of whitespace rule (defaults to `@pass`), or a regular expression
196
    #   for eating whitespace between non-terminal rules (strongly encouraged).
197
    # @yield [context, *data]
198
    #   Yields to return data to parser
199
    # @yieldparam [:statement, :trace] context
200
    #   Context for block
201
    # @yieldparam [Symbol] *data
202
    #   Data specific to the call
203
    # @return [Object] AST resulting from parse
204
    # @raise [Exception] Raises exceptions for parsing errors
205
    #   or errors raised during processing callbacks. Internal
206
    #   errors are raised using {Error}.
207
    # @todo FIXME implement seq_hash
208
    def parse(input = nil, start = nil, rules = nil, insensitive_strings: nil, **options, &block)
2✔
209
      start ||= options[:start]
584✔
210
      rules ||= options[:rules] || []
584✔
211
      @rules = rules.inject({}) {|memo, rule| memo.merge(rule.sym => rule)}
48,488✔
212
      @packrat = {}
584✔
213

214
      # Add parser reference to each rule
215
      @rules.each_value {|rule| rule.parser = self}
48,488✔
216

217
      # Take whitespace from options, a named rule, a `pass` rule, a rule named :WS, or a default
218
      @whitespace = case options[:whitespace]
584✔
219
      when Regexp then options[:whitespace]
414✔
UNCOV
220
      when Symbol then @rules[options[:whitespace]]
×
221
      else options[:whitespace]
170✔
222
      end ||
223
        @rules.values.detect(&:pass?) ||
224
        /(?:\s|(?:#[^x][^\n\r]*))+/m.freeze
225

226
      @options = options.dup
584✔
227
      @productions = []
584✔
228
      @parse_callback = block
584✔
229
      @error_log = []
584✔
230
      @prod_data = []
584✔
231

232
      @scanner = EBNF::LL1::Scanner.new(input)
584✔
233
      start = start.split('#').last.to_sym unless start.is_a?(Symbol)
584✔
234
      start_rule = @rules[start]
584✔
235
      raise Error, "Starting production #{start.inspect} not defined" unless start_rule
584✔
236

237
      result = start_rule.parse(scanner, insensitive_strings: insensitive_strings)
582✔
238
      if result == :unmatched
582✔
239
        # Start rule wasn't matched, which is about the only error condition
240
        error("--top--", @furthest_failure.to_s,
20✔
241
          pos: @furthest_failure.pos,
242
          lineno: @furthest_failure.lineno,
243
          rest: scanner.string[@furthest_failure.pos, 20])
244
      end
245

246
      # Eat any remaining whitespace
247
      start_rule.eat_whitespace(scanner)
582✔
248
      if !scanner.eos?
582✔
249
        error("--top--", @furthest_failure.to_s,
42✔
250
          pos: @furthest_failure.pos,
251
          lineno: @furthest_failure.lineno,
252
          rest: scanner.string[@furthest_failure.pos, 20])
253
      end
254

255
      # When all is said and done, raise the error log
256
      unless @error_log.empty?
574✔
257
        raise Error, @error_log.join("\n")
36✔
258
      end
259

260
      result
538✔
261
    end
262

263
    # Depth of parsing, for log output.
264
    def depth; (@productions || []).length; end
303,168✔
265

266
    # Current ProdData element
267
    def prod_data; @prod_data.last || {}; end
2✔
268

269
    # Clear out packrat memoizer. This is appropriate when completing a top-level rule when there is no possibility of backtracking.
270
    def clear_packrat; @packrat.clear; end
11,854✔
271

272
    ##
273
    # Error information, used as level `3` logger messages.
274
    # Messages may be logged and are saved for reporting at end of parsing.
275
    #
276
    # @param [String] node Relevant location associated with message
277
    # @param [String] message Error string
278
    # @param [Hash{Symbol => Object}] options
279
    # @option options [URI, #to_s] :production
280
    # @option options [Boolean] :raise abort furhter processing
281
    # @option options [Array] :backtrace state where error occured
282
    # @see #debug
283
    def error(node, message, **options)
2✔
284
      lineno = options[:lineno] || (scanner.lineno if scanner)
62✔
285
      m = "ERROR "
62✔
286
      m += "[line: #{lineno}] " if lineno
62✔
287
      m += message
62✔
288
      m += " (found #{options[:rest].inspect})" if options[:rest]
62✔
289
      m += ", production = #{options[:production].inspect}" if options[:production]
62✔
290
      @error_log << m unless @recovering
62✔
291
      @recovering = true
62✔
292
      debug(node, m, level: 3, **options)
62✔
293
      if options[:raise] || @options[:validate]
62✔
294
        raise Error.new(m,
8✔
295
                lineno: lineno,
296
                rest: options[:rest],
297
                production: options[:production],
298
                backtrace: options[:backtrace])
299
      end
300
    end
301

302
    ##
303
    # Warning information, used as level `2` logger messages.
304
    # Messages may be logged and are saved for reporting at end of parsing.
305
    #
306
    # @param [String] node Relevant location associated with message
307
    # @param [String] message Error string
308
    # @param [Hash] options
309
    # @option options [URI, #to_s] :production
310
    # @option options [Token] :token
311
    # @see #debug
312
    def warn(node, message, **options)
2✔
UNCOV
313
      lineno = options[:lineno] || (scanner.lineno if scanner)
×
314
      m = "WARNING "
×
315
      m += "[line: #{lineno}] " if lineno
×
316
      m += message
×
317
      m += " (found #{options[:rest].inspect})" if options[:rest]
×
318
      m += ", production = #{options[:production].inspect}" if options[:production]
×
319
      debug(node, m, level: 2, **options)
×
320
    end
321

322
    ##
323
    # Progress logged when parsing. Passed as level `1` logger messages.
324
    #
325
    # The call is ignored, unless `@options[:logger]` is set.
326
    #
327
    # @overload progress(node, message, **options, &block)
328
    #   @param [String] node Relevant location associated with message
329
    #   @param [String] message ("")
330
    #   @param [Hash] options
331
    #   @option options [Integer] :depth
332
    #       Recursion depth for indenting output
333
    # @see #debug
334
    def progress(node, *args, &block)
2✔
335
      return unless @options[:logger]
1,188,094✔
336
      args << {} unless args.last.is_a?(Hash)
10,052✔
337
      args.last[:level] ||= 1
10,052✔
338
      debug(node, *args, &block)
10,052✔
339
    end
340

341
    ##
342
    # Debug logging.
343
    #
344
    # The call is ignored, unless `@options[:logger]` is set.
345
    #
346
    # @overload debug(node, message, **options)
347
    #   @param [Array<String>] args Relevant location associated with message
348
    #   @param [Hash] options
349
    #   @option options [Integer] :depth
350
    #     Recursion depth for indenting output
351
    #   @yieldreturn [String] additional string appended to `message`.
352
    def debug(*args, &block)
2✔
353
      return unless @options[:logger]
35,184✔
354
      options = args.last.is_a?(Hash) ? args.pop : {}
10,114✔
355
      lineno = options[:lineno] || (scanner.lineno if scanner)
10,114✔
356
      level = options.fetch(:level, 0)
10,114✔
357
      depth = options[:depth] || self.depth
10,114✔
358

359
      if self.respond_to?(:log_debug)
10,114✔
UNCOV
360
        level = [:debug, :info, :warn, :error, :fatal][level]
×
361
        log_debug(*args, **options.merge(level: level, lineno: lineno, depth: depth), &block)
×
362
      elsif @options[:logger].respond_to?(:add)
10,114✔
363
        args << yield if block_given?
10,114✔
364
        @options[:logger].add(level, "[#{lineno}]" + (" " * depth) + args.join(" "))
10,114✔
UNCOV
365
      elsif @options[:logger].respond_to?(:<<)
×
366
        args << yield if block_given?
×
367
        @options[:logger] << "[#{lineno}]" + (" " * depth) + args.join(" ")
×
368
      end
369
    end
370

371
    # Start for production
372
    # Adds data avoiable during the processing of the production
373
    #
374
    # @param [Symbol] prod
375
    # @param [Hash] **options other options available for handlers
376
    # @return [Hash] composed of production options. Currently only `as_hash` is supported.
377
    # @see ClassMethods#start_production
378
    def onStart(prod, **options)
2✔
379
      handler = self.class.start_handlers[prod]
445,934✔
380
      @productions << prod
445,934✔
381
      if handler
445,934✔
382
        # Create a new production data element, potentially allowing handler
383
        # to customize before pushing on the @prod_data stack
384
        data = {_production: prod}.merge(options)
12✔
385
        begin
386
          self.class.eval_with_binding(self) {
12✔
387
            handler.call(data, @parse_callback)
12✔
388
          }
389
        rescue ArgumentError, Error => e
UNCOV
390
          error("start", "#{e.class}: #{e.message}", production: prod, backtrace: e.backtrace)
×
391
          @recovering = false
×
392
        end
393
        @prod_data << data
12✔
394
      elsif self.class.production_handlers[prod]
445,922✔
395
        # Make sure we push as many was we pop, even if there is no
396
        # explicit start handler
397
        @prod_data << {_production: prod}
306,068✔
398
      end
399
      progress("#{prod}(:start)", "",
445,934✔
400
        lineno: (scanner.lineno if scanner),
445,934✔
401
        pos: (scanner.pos if scanner)
445,934✔
402
      ) do
403
        "#{data.inspect}@(#{scanner ? scanner.pos : '?'}), rest: #{scanner ? scanner.rest[0..20].inspect : '?'}"
3,926✔
404
      end
405
      return self.class.start_options.fetch(prod, {}) # any options on this production
445,934✔
406
    end
407

408
    # Finish of production
409
    #
410
    # @param [Object] result parse result
411
    # @param [Hash] **options other options available for handlers
412
    # @return [Object] parse result, or the value returned from the handler
413
    def onFinish(result, **options)
2✔
414
      #puts "prod_data(f): " + @prod_data.inspect
415
      prod = @productions.last
445,934✔
416
      handler, clear_packrat = self.class.production_handlers[prod]
445,934✔
417
      data = @prod_data.pop if handler || self.class.start_handlers[prod]
445,934✔
418
      error("finish",
419
        "prod_data production mismatch: expected #{prod.inspect}, got #{data[:_production].inspect}",
420
        production: prod, prod_data: @prod_data) if data && prod != data[:_production]
445,934✔
421
      if handler && !@recovering && result != :unmatched
445,934✔
422
        # Pop production data element from stack, potentially allowing handler to use it
423
        result = begin
424
          self.class.eval_with_binding(self) {
198,706✔
425
            handler.call(result, data, @parse_callback, **options)
198,706✔
426
          }
427
        rescue ArgumentError, Error => e
UNCOV
428
          error("finish", "#{e.class}: #{e.message}", production: prod, backtrace: e.backtrace)
×
429
          @recovering = false
×
430
        end
431
      end
432
      progress("#{prod}(:finish)", "",
445,934✔
433
             lineno: (scanner.lineno if scanner),
445,934✔
434
             level: result == :unmatched ? 0 : 1) do
445,934✔
435
        "#{result.inspect}@(#{scanner ? scanner.pos : '?'}), rest: #{scanner ? scanner.rest[0..20].inspect : '?'}"
3,926✔
436
      end
437
      self.clear_packrat if clear_packrat
445,934✔
438
      @productions.pop
445,934✔
439
      result
445,934✔
440
    end
441

442
    # A terminal with a defined handler
443
    #
444
    # @param [Symbol] prod from the symbol of the associated rule
445
    # @param [String] value the scanned string
446
    # @return [String, Object] either the result from the handler, or the token
447
    def onTerminal(prod, value)
2✔
448
      parentProd = @productions.last
295,252✔
449
      handler = self.class.terminal_handlers[prod]
295,252✔
450
      if handler && value != :unmatched
295,252✔
451
        value = begin
452
          self.class.eval_with_binding(self) {
36,398✔
453
            handler.call(value, parentProd, @parse_callback)
36,398✔
454
          }
455
        rescue ArgumentError, Error => e
UNCOV
456
          error("terminal", "#{e.class}: #{e.message}", value: value, production: prod, backtrace: e.backtrace)
×
457
          @recovering = false
×
458
        end
459
      end
460
      progress("#{prod}(:terminal)", "",
295,252✔
461
               depth: (depth + 1),
295,252✔
462
               lineno: (scanner.lineno if scanner),
295,252✔
463
               level: value == :unmatched ? 0 : 1) do
295,252✔
464
        "#{value.inspect}@(#{scanner ? scanner.pos : '?'})"
2,200✔
465
      end
466
      value
295,252✔
467
    end
468

469
    ##
470
    # Find a rule for a symbol
471
    #
472
    # @param [Symbol] sym
473
    # @return [Rule]
474
    def find_rule(sym)
2✔
475
      @rules[sym]
690,734✔
476
    end
477

478
    ##
479
    # Find a regular expression defined for a terminal
480
    #
481
    # @param [Symbol] sym
482
    # @return [Regexp]
483
    def terminal_regexp(sym)
2✔
484
      self.class.terminal_regexps[sym]
299,948✔
485
    end
486

487
    ##
488
    # Find a regular expression defined for a terminal
489
    #
490
    # @param [Symbol] sym
491
    # @return [Regexp]
492
    def terminal_options(sym)
2✔
493
      self.class.terminal_options[sym]
295,252✔
494
    end
495

496
    ##
497
    # Record furthest failure.
498
    #
499
    # @param [Integer] pos
500
    #   The position in the input stream where the failure occured.
501
    # @param [Integer] lineno
502
    #   Line where the failure occured.
503
    # @param [Symbol, String] token
504
    #   The terminal token or string which attempted to match.
505
    # @see https://arxiv.org/pdf/1405.6646.pdf
506
    def update_furthest_failure(pos, lineno, token)
2✔
507
      # Skip generated productions
508
      return if token.is_a?(Symbol) && token.to_s.start_with?('_')
568,720✔
509
      if @furthest_failure.nil? || pos > @furthest_failure.pos
567,260✔
510
        @furthest_failure = Unmatched.new(pos, lineno, [token])
57,584✔
511
      elsif pos == @furthest_failure.pos && !@furthest_failure[:expecting].include?(token)
509,676✔
512
        @furthest_failure[:expecting] << token
305,932✔
513
      end
514
    end
515

516
  public
2✔
517

518
    ##
519
    # @!parse
520
    #   # Record details about an inmatched rule, including the following:
521
    #   # 
522
    #   # * Input location and line number at time of failure.
523
    #   # * The rule at which this was found (non-terminal, and nat starting with '_').
524
    #   class Unmatched
525
    #     # @return [Integer] The position within the scanner which did not match.
526
    #     attr_reader :pos
527
    #     # @return [Integer] The line number which did not match.
528
    #     attr_reader :lineno
529
    #     # @return [Array<Symbol,String>]
530
    #     #   Strings or production rules that attempted to match at this position.
531
    #     attr_reader :expecting
532
    #   end
533
    class Unmatched < Struct.new(:pos, :lineno, :expecting)
2✔
534
      def to_s
2✔
535
        "syntax error, expecting #{expecting.map(&:inspect).join(', ')}"
62✔
536
      end
537
    end
538

539
    ##
540
    # Raised for errors during parsing.
541
    #
542
    # @example Raising a parser error
543
    #   raise Error.new(
544
    #     "invalid token '%' on line 10",
545
    #     rest: '%', lineno: 9, production: :turtleDoc)
546
    #
547
    # @see https://ruby-doc.org/core/classes/StandardError.html
548
    class Error < StandardError
2✔
549
      ##
550
      # The current production.
551
      #
552
      # @return [Symbol]
553
      attr_reader :production
2✔
554

555
      ##
556
      # The read head when scanning failed
557
      #
558
      # @return [String]
559
      attr_reader :rest
2✔
560

561
      ##
562
      # The line number where the error occurred.
563
      #
564
      # @return [Integer]
565
      attr_reader :lineno
2✔
566

567
      ##
568
      # Initializes a new lexer error instance.
569
      #
570
      # @param  [String, #to_s]          message
571
      # @param  [Hash{Symbol => Object}] options
572
      # @option options [Symbol]         :production  (nil)
573
      # @option options [String]         :rest  (nil)
574
      # @option options [Integer]        :lineno (nil)
575
      def initialize(message, **options)
2✔
576
        @production = options[:production]
46✔
577
        @rest       = options[:rest]
46✔
578
        @lineno     = options[:lineno]
46✔
579
        super(message.to_s)
46✔
580
      end
581
    end # class Error
582
  end # module Parser
583
end # module EBNF::PEG
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc