ebnf 1.1.2 → 2.1.1
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.
- checksums.yaml +5 -5
- data/README.md +218 -196
- data/UNLICENSE +1 -1
- data/VERSION +1 -1
- data/bin/ebnf +40 -21
- data/etc/abnf-core.ebnf +52 -0
- data/etc/abnf.abnf +121 -0
- data/etc/abnf.ebnf +124 -0
- data/etc/abnf.sxp +45 -0
- data/etc/doap.ttl +23 -15
- data/etc/ebnf.ebnf +21 -33
- data/etc/ebnf.html +171 -160
- data/etc/{ebnf.rb → ebnf.ll1.rb} +30 -107
- data/etc/ebnf.ll1.sxp +182 -183
- data/etc/ebnf.peg.rb +90 -0
- data/etc/ebnf.peg.sxp +84 -0
- data/etc/ebnf.sxp +40 -41
- data/etc/iso-ebnf.ebnf +140 -0
- data/etc/iso-ebnf.isoebnf +138 -0
- data/etc/iso-ebnf.sxp +65 -0
- data/etc/sparql.ebnf +4 -4
- data/etc/sparql.html +1603 -1751
- data/etc/sparql.ll1.sxp +7372 -7372
- data/etc/sparql.peg.rb +532 -0
- data/etc/sparql.peg.sxp +597 -0
- data/etc/sparql.sxp +363 -362
- data/etc/turtle.ebnf +3 -3
- data/etc/turtle.html +465 -517
- data/etc/{turtle.rb → turtle.ll1.rb} +3 -4
- data/etc/turtle.ll1.sxp +425 -425
- data/etc/turtle.peg.rb +182 -0
- data/etc/turtle.peg.sxp +199 -0
- data/etc/turtle.sxp +103 -101
- data/lib/ebnf.rb +7 -2
- data/lib/ebnf/abnf.rb +301 -0
- data/lib/ebnf/abnf/core.rb +23 -0
- data/lib/ebnf/abnf/meta.rb +111 -0
- data/lib/ebnf/base.rb +128 -87
- data/lib/ebnf/bnf.rb +1 -26
- data/lib/ebnf/ebnf/meta.rb +90 -0
- data/lib/ebnf/isoebnf.rb +229 -0
- data/lib/ebnf/isoebnf/meta.rb +75 -0
- data/lib/ebnf/ll1.rb +140 -8
- data/lib/ebnf/ll1/lexer.rb +37 -32
- data/lib/ebnf/ll1/parser.rb +113 -73
- data/lib/ebnf/ll1/scanner.rb +83 -51
- data/lib/ebnf/native.rb +320 -0
- data/lib/ebnf/parser.rb +285 -302
- data/lib/ebnf/peg.rb +39 -0
- data/lib/ebnf/peg/parser.rb +561 -0
- data/lib/ebnf/peg/rule.rb +241 -0
- data/lib/ebnf/rule.rb +453 -163
- data/lib/ebnf/terminals.rb +21 -0
- data/lib/ebnf/writer.rb +561 -88
- metadata +114 -28
- data/etc/sparql.rb +0 -45773
data/lib/ebnf/ll1/lexer.rb
CHANGED
@@ -29,7 +29,7 @@ module EBNF::LL1
|
|
29
29
|
# warn error.inspect
|
30
30
|
# end
|
31
31
|
#
|
32
|
-
# @see
|
32
|
+
# @see https://en.wikipedia.org/wiki/Lexical_analysis
|
33
33
|
class Lexer
|
34
34
|
include Enumerable
|
35
35
|
|
@@ -43,10 +43,10 @@ module EBNF::LL1
|
|
43
43
|
"\\'" => '\'', # \u0027 (apostrophe-quote, single quote mark)
|
44
44
|
'\\\\' => '\\' # \u005C (backslash)
|
45
45
|
}.freeze
|
46
|
-
ESCAPE_CHAR4 = /\\u(?:[0-9A-Fa-f]{4,4})
|
47
|
-
ESCAPE_CHAR8 = /\\U(?:[0-9A-Fa-f]{8,8})
|
48
|
-
ECHAR = /\\./
|
49
|
-
UCHAR = /#{ESCAPE_CHAR4}|#{ESCAPE_CHAR8}
|
46
|
+
ESCAPE_CHAR4 = /\\u(?:[0-9A-Fa-f]{4,4})/u.freeze # \uXXXX
|
47
|
+
ESCAPE_CHAR8 = /\\U(?:[0-9A-Fa-f]{8,8})/u.freeze # \UXXXXXXXX
|
48
|
+
ECHAR = /\\./u.freeze # More liberal unescaping
|
49
|
+
UCHAR = /#{ESCAPE_CHAR4}|#{ESCAPE_CHAR8}/n.freeze
|
50
50
|
|
51
51
|
##
|
52
52
|
# @return [Regexp] defines whitespace, including comments, otherwise whitespace must be explicit in terminals
|
@@ -59,7 +59,7 @@ module EBNF::LL1
|
|
59
59
|
#
|
60
60
|
# @param [String] string
|
61
61
|
# @return [String]
|
62
|
-
# @see
|
62
|
+
# @see https://www.w3.org/TR/rdf-sparql-query/#codepointEscape
|
63
63
|
def self.unescape_codepoints(string)
|
64
64
|
string = string.dup
|
65
65
|
string.force_encoding(Encoding::ASCII_8BIT) if string.respond_to?(:force_encoding)
|
@@ -81,7 +81,7 @@ module EBNF::LL1
|
|
81
81
|
#
|
82
82
|
# @param [String] input
|
83
83
|
# @return [String]
|
84
|
-
# @see
|
84
|
+
# @see https://www.w3.org/TR/rdf-sparql-query/#grammarEscapes
|
85
85
|
def self.unescape_string(input)
|
86
86
|
input.gsub(ECHAR) { |escaped| ESCAPE_CHARS[escaped] || escaped[1..-1]}
|
87
87
|
end
|
@@ -98,8 +98,8 @@ module EBNF::LL1
|
|
98
98
|
# @yieldparam [Lexer] lexer
|
99
99
|
# @return [Lexer]
|
100
100
|
# @raise [Lexer::Error] on invalid input
|
101
|
-
def self.tokenize(input, terminals, options
|
102
|
-
lexer = self.new(input, terminals, options)
|
101
|
+
def self.tokenize(input, terminals, **options, &block)
|
102
|
+
lexer = self.new(input, terminals, **options)
|
103
103
|
block_given? ? block.call(lexer) : lexer
|
104
104
|
end
|
105
105
|
|
@@ -115,17 +115,23 @@ module EBNF::LL1
|
|
115
115
|
# Whitespace between tokens, including comments
|
116
116
|
# @option options[Integer] :high_water passed to scanner
|
117
117
|
# @option options[Integer] :low_water passed to scanner
|
118
|
-
def initialize(input = nil, terminals = nil, options
|
118
|
+
def initialize(input = nil, terminals = nil, **options)
|
119
119
|
@options = options.dup
|
120
120
|
@whitespace = @options[:whitespace]
|
121
121
|
@terminals = terminals.map do |term|
|
122
|
-
term.is_a?(Array)
|
122
|
+
if term.is_a?(Array) && term.length ==3
|
123
|
+
# Last element is options
|
124
|
+
Terminal.new(term[0], term[1], **term[2])
|
125
|
+
elsif term.is_a?(Array)
|
126
|
+
Terminal.new(*term)
|
127
|
+
else
|
128
|
+
term
|
129
|
+
end
|
123
130
|
end
|
124
131
|
|
125
132
|
raise Error, "Terminal patterns not defined" unless @terminals && @terminals.length > 0
|
126
133
|
|
127
|
-
@
|
128
|
-
@scanner = Scanner.new(input, options)
|
134
|
+
@scanner = Scanner.new(input, **options)
|
129
135
|
end
|
130
136
|
|
131
137
|
##
|
@@ -140,12 +146,6 @@ module EBNF::LL1
|
|
140
146
|
# @return [String]
|
141
147
|
attr_accessor :input
|
142
148
|
|
143
|
-
##
|
144
|
-
# The current line number (zero-based).
|
145
|
-
#
|
146
|
-
# @return [Integer]
|
147
|
-
attr_reader :lineno
|
148
|
-
|
149
149
|
##
|
150
150
|
# Returns `true` if the input string is lexically valid.
|
151
151
|
#
|
@@ -187,7 +187,7 @@ module EBNF::LL1
|
|
187
187
|
|
188
188
|
@first ||= begin
|
189
189
|
{} while !scanner.eos? && skip_whitespace
|
190
|
-
return
|
190
|
+
return nil if scanner.eos?
|
191
191
|
|
192
192
|
token = match_token(*types)
|
193
193
|
|
@@ -226,7 +226,7 @@ module EBNF::LL1
|
|
226
226
|
# @return [Token]
|
227
227
|
def recover(*types)
|
228
228
|
until scanner.eos? || tok = match_token(*types)
|
229
|
-
if scanner.skip_until(@whitespace || /\s
|
229
|
+
if scanner.skip_until(@whitespace || /\s+/m).nil? # Skip past current "token"
|
230
230
|
# No whitespace at the end, must be and end of string
|
231
231
|
scanner.terminate
|
232
232
|
else
|
@@ -236,6 +236,14 @@ module EBNF::LL1
|
|
236
236
|
scanner.unscan if tok
|
237
237
|
first
|
238
238
|
end
|
239
|
+
|
240
|
+
##
|
241
|
+
# The current line number (one-based).
|
242
|
+
#
|
243
|
+
# @return [Integer]
|
244
|
+
def lineno
|
245
|
+
scanner.lineno
|
246
|
+
end
|
239
247
|
protected
|
240
248
|
|
241
249
|
# @return [StringScanner]
|
@@ -246,9 +254,7 @@ module EBNF::LL1
|
|
246
254
|
def skip_whitespace
|
247
255
|
# skip all white space, but keep track of the current line number
|
248
256
|
while @whitespace && !scanner.eos?
|
249
|
-
|
250
|
-
@lineno += matched.count("\n")
|
251
|
-
else
|
257
|
+
unless scanner.scan(@whitespace)
|
252
258
|
return
|
253
259
|
end
|
254
260
|
end
|
@@ -274,7 +280,6 @@ module EBNF::LL1
|
|
274
280
|
if matched = scanner.scan(term.regexp)
|
275
281
|
#STDERR.puts " matched #{term.type.inspect}: #{matched.inspect}"
|
276
282
|
tok = token(term.type, term.canonicalize(matched))
|
277
|
-
@lineno += matched.count("\n")
|
278
283
|
return tok
|
279
284
|
end
|
280
285
|
end
|
@@ -300,7 +305,7 @@ module EBNF::LL1
|
|
300
305
|
# Cause strings and codepoints to be unescaped.
|
301
306
|
# @option options [Regexp] :partial_regexp
|
302
307
|
# A regular expression matching the beginning of this terminal; useful for terminals that match things longer than the scanner low water mark.
|
303
|
-
def initialize(type, regexp, options
|
308
|
+
def initialize(type, regexp, **options)
|
304
309
|
@type, @regexp, @options = type, regexp, options
|
305
310
|
@partial_regexp = options[:partial_regexp]
|
306
311
|
@map = options.fetch(:map, {})
|
@@ -353,8 +358,8 @@ module EBNF::LL1
|
|
353
358
|
# Scanner instance with access to matched groups
|
354
359
|
# @param [Hash{Symbol => Object}] options
|
355
360
|
# @return [Token]
|
356
|
-
def token(type, value, options
|
357
|
-
Token.new(type, value,
|
361
|
+
def token(type, value, **options)
|
362
|
+
Token.new(type, value, lineno: lineno, **options)
|
358
363
|
end
|
359
364
|
|
360
365
|
##
|
@@ -365,7 +370,7 @@ module EBNF::LL1
|
|
365
370
|
# token.type #=> :LANGTAG
|
366
371
|
# token.value #=> "en"
|
367
372
|
#
|
368
|
-
# @see
|
373
|
+
# @see https://en.wikipedia.org/wiki/Lexical_analysis#Token
|
369
374
|
class Token
|
370
375
|
##
|
371
376
|
# The token's symbol type.
|
@@ -398,7 +403,7 @@ module EBNF::LL1
|
|
398
403
|
# @param [String] value
|
399
404
|
# @param [Hash{Symbol => Object}] options
|
400
405
|
# @option options [Integer] :lineno (nil)
|
401
|
-
def initialize(type, value, options
|
406
|
+
def initialize(type, value, **options)
|
402
407
|
@type = type.to_s.to_sym if type
|
403
408
|
@value = value.to_s
|
404
409
|
@options = options.dup
|
@@ -486,7 +491,7 @@ module EBNF::LL1
|
|
486
491
|
# "invalid token '%' on line 10",
|
487
492
|
# input: query, token: '%', lineno: 9)
|
488
493
|
#
|
489
|
-
# @see
|
494
|
+
# @see https://ruby-doc.org/core/classes/StandardError.html
|
490
495
|
class Error < StandardError
|
491
496
|
##
|
492
497
|
# The input string associated with the error.
|
@@ -514,7 +519,7 @@ module EBNF::LL1
|
|
514
519
|
# @option options [String] :input (nil)
|
515
520
|
# @option options [String] :token (nil)
|
516
521
|
# @option options [Integer] :lineno (nil)
|
517
|
-
def initialize(message, options
|
522
|
+
def initialize(message, **options)
|
518
523
|
@input = options[:input]
|
519
524
|
@token = options[:token]
|
520
525
|
@lineno = options[:lineno]
|
data/lib/ebnf/ll1/parser.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
222
|
-
def parse(input = nil, start = nil, options
|
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,
|
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:"
|
392
|
+
debug("recovery", "stack follows:")
|
353
393
|
todo_stack.reverse.each do |todo|
|
354
|
-
debug("recovery"
|
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 `
|
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
|
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.
|
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 `
|
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
|
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: #{
|
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,
|
544
|
+
debug(node, m, level: 2, lineno: lineno, **options)
|
502
545
|
end
|
503
546
|
|
504
547
|
##
|
505
|
-
# Progress
|
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
|
558
|
+
# @see #debug
|
514
559
|
def progress(node, *args, &block)
|
515
|
-
return unless @options[:
|
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] ||=
|
563
|
+
args.last[:level] ||= 1
|
564
|
+
args.last[:lineno] ||= lineno
|
518
565
|
debug(node, *args, &block)
|
519
566
|
end
|
520
567
|
|
521
568
|
##
|
522
|
-
#
|
569
|
+
# Debug logging.
|
523
570
|
#
|
524
|
-
# The call is ignored, unless `@options[:
|
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
|
-
# @
|
535
|
-
|
536
|
-
|
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
|
-
|
551
|
-
|
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
|
-
|
555
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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))
|