ebnf 1.0.2 → 1.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.
- checksums.yaml +4 -4
- data/README.md +123 -11
- data/VERSION +1 -1
- data/bin/ebnf +3 -3
- data/etc/ebnf.ebnf +15 -9
- data/etc/ebnf.html +35 -9
- data/etc/ebnf.ll1.sxp +70 -50
- data/etc/ebnf.rb +87 -0
- data/etc/ebnf.sxp +18 -10
- data/etc/sparql.ll1.sxp +277 -102
- data/etc/sparql.rb +140 -0
- data/etc/turtle.ll1.sxp +27 -16
- data/etc/turtle.rb +13 -0
- data/lib/ebnf/base.rb +3 -2
- data/lib/ebnf/bnf.rb +1 -1
- data/lib/ebnf/ll1.rb +19 -9
- data/lib/ebnf/ll1/lexer.rb +15 -11
- data/lib/ebnf/ll1/parser.rb +34 -16
- data/lib/ebnf/ll1/scanner.rb +22 -8
- data/lib/ebnf/parser.rb +1 -1
- data/lib/ebnf/rule.rb +22 -10
- data/lib/ebnf/writer.rb +1 -1
- metadata +3 -3
data/lib/ebnf/ll1/parser.rb
CHANGED
@@ -226,8 +226,9 @@ module EBNF::LL1
|
|
226
226
|
when @options[:validate] then 1
|
227
227
|
end
|
228
228
|
@branch = options[:branch]
|
229
|
-
@first
|
229
|
+
@first = options[:first] ||= {}
|
230
230
|
@follow = options[:follow] ||= {}
|
231
|
+
@cleanup = options[:cleanup] ||= {}
|
231
232
|
@lexer = input.is_a?(Lexer) ? input : Lexer.new(input, self.class.patterns, @options)
|
232
233
|
@productions = []
|
233
234
|
@parse_callback = block
|
@@ -241,7 +242,7 @@ module EBNF::LL1
|
|
241
242
|
|
242
243
|
@prod_data = [{}]
|
243
244
|
start = start.split('#').last.to_sym unless start.is_a?(Symbol)
|
244
|
-
todo_stack = [{:
|
245
|
+
todo_stack = [{prod: start, terms: nil}]
|
245
246
|
|
246
247
|
while !todo_stack.empty?
|
247
248
|
begin
|
@@ -255,7 +256,7 @@ module EBNF::LL1
|
|
255
256
|
# to the beginning to avoid excessive growth in the production
|
256
257
|
# stack
|
257
258
|
if options[:reset_on_start] && cur_prod == start
|
258
|
-
todo_stack = [{:
|
259
|
+
todo_stack = [{prod: start, terms: []}]
|
259
260
|
@productions = []
|
260
261
|
@prod_data = [{}]
|
261
262
|
end
|
@@ -320,7 +321,7 @@ module EBNF::LL1
|
|
320
321
|
break
|
321
322
|
else
|
322
323
|
# Push term onto stack
|
323
|
-
todo_stack << {:
|
324
|
+
todo_stack << {prod: term, terms: nil}
|
324
325
|
debug("parse(push)") {"term #{term.inspect}, depth #{depth}"}
|
325
326
|
pushed = true
|
326
327
|
break
|
@@ -348,9 +349,9 @@ module EBNF::LL1
|
|
348
349
|
end
|
349
350
|
|
350
351
|
# Get the list of follows for this sequence, this production and the stacked productions.
|
351
|
-
debug("recovery", "stack follows:", :
|
352
|
+
debug("recovery", "stack follows:", level: 4)
|
352
353
|
todo_stack.reverse.each do |todo|
|
353
|
-
debug("recovery", :
|
354
|
+
debug("recovery", level: 4) {" #{todo[:prod]}: #{@follow[todo[:prod]].inspect}"}
|
354
355
|
end
|
355
356
|
|
356
357
|
# Find all follows to the top of the stack
|
@@ -403,7 +404,7 @@ module EBNF::LL1
|
|
403
404
|
end
|
404
405
|
end
|
405
406
|
|
406
|
-
error("parse(eof)", "Finished processing before end of file", :
|
407
|
+
error("parse(eof)", "Finished processing before end of file", token: @lexer.first) if @lexer.first
|
407
408
|
|
408
409
|
# Continue popping contexts off of the stack
|
409
410
|
while !todo_stack.empty?
|
@@ -475,7 +476,7 @@ module EBNF::LL1
|
|
475
476
|
m += ", production = #{options[:production].inspect}" if options[:production]
|
476
477
|
@error_log << m unless @recovering
|
477
478
|
@recovering = true
|
478
|
-
debug(node, m, options.merge(:
|
479
|
+
debug(node, m, options.merge(level: 0))
|
479
480
|
if options[:raise] || @options[:validate]
|
480
481
|
raise Error.new(m, lineno: lineno, token: options[:token], production: options[:production])
|
481
482
|
end
|
@@ -497,7 +498,7 @@ module EBNF::LL1
|
|
497
498
|
m += " (found #{options[:token].inspect})" if options[:token]
|
498
499
|
m += ", production = #{options[:production].inspect}" if options[:production]
|
499
500
|
@error_log << m unless @recovering
|
500
|
-
debug(node, m, options.merge(:
|
501
|
+
debug(node, m, options.merge(level: 1))
|
501
502
|
end
|
502
503
|
|
503
504
|
##
|
@@ -569,10 +570,14 @@ module EBNF::LL1
|
|
569
570
|
handler.call(@prod_data.last, data, @parse_callback)
|
570
571
|
}
|
571
572
|
rescue ArgumentError, Error => e
|
572
|
-
error("start", "#{e.class}: #{e.message}", :
|
573
|
+
error("start", "#{e.class}: #{e.message}", production: prod)
|
573
574
|
@recovering = false
|
574
575
|
end
|
575
576
|
@prod_data << data
|
577
|
+
elsif [:merge, :star].include?(@cleanup[prod])
|
578
|
+
# Save current data to merge later
|
579
|
+
@prod_data << {}
|
580
|
+
progress("#{prod}(:start}:#{@prod_data.length}:cleanup:#{@cleanup[prod]}") { get_token.inspect + (@recovering ? ' recovering' : '')}
|
576
581
|
else
|
577
582
|
# Make sure we push as many was we pop, even if there is no
|
578
583
|
# explicit start handler
|
@@ -595,10 +600,23 @@ module EBNF::LL1
|
|
595
600
|
handler.call(@prod_data.last, data, @parse_callback)
|
596
601
|
}
|
597
602
|
rescue ArgumentError, Error => e
|
598
|
-
error("finish", "#{e.class}: #{e.message}", :
|
603
|
+
error("finish", "#{e.class}: #{e.message}", production: prod)
|
599
604
|
@recovering = false
|
600
605
|
end
|
601
606
|
progress("#{prod}(:finish):#{@prod_data.length}") {@prod_data.last}
|
607
|
+
elsif [:merge, :star].include?(@cleanup[prod])
|
608
|
+
data = @prod_data.pop
|
609
|
+
input = @prod_data.last
|
610
|
+
|
611
|
+
# Append every element in data to last prod_data
|
612
|
+
data.each do |k, v|
|
613
|
+
input[k] = case input[k]
|
614
|
+
when nil then v.is_a?(Hash) ? v : Array(v)
|
615
|
+
when Hash then input[k].merge!(v)
|
616
|
+
else Array(input[k]) + Array(v)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
progress("#{prod}(:finish):#{@prod_data.length} cleanup:#{@cleanup[prod]}") {@prod_data.last}
|
602
620
|
else
|
603
621
|
progress("#{prod}(:finish):#{@prod_data.length}") { "recovering" if @recovering }
|
604
622
|
end
|
@@ -618,15 +636,15 @@ module EBNF::LL1
|
|
618
636
|
handler.call(parentProd, token, @prod_data.last, @parse_callback)
|
619
637
|
}
|
620
638
|
rescue ArgumentError, Error => e
|
621
|
-
error("terminal", "#{e.class}: #{e.message}", :
|
639
|
+
error("terminal", "#{e.class}: #{e.message}", token: token, production: prod)
|
622
640
|
@recovering = false
|
623
641
|
end
|
624
|
-
progress("#{prod}(:terminal)", "", :
|
642
|
+
progress("#{prod}(:terminal)", "", depth: (depth + 1)) {"#{token}: #{@prod_data.last}"}
|
625
643
|
else
|
626
|
-
progress("#{prod}(:terminal)", "", :
|
644
|
+
progress("#{prod}(:terminal)", "", depth: (depth + 1)) {token.to_s}
|
627
645
|
end
|
628
646
|
else
|
629
|
-
error("#{parentProd}(:terminal)", "Terminal has no parent production", :
|
647
|
+
error("#{parentProd}(:terminal)", "Terminal has no parent production", token: token, production: prod)
|
630
648
|
end
|
631
649
|
end
|
632
650
|
|
@@ -703,7 +721,7 @@ module EBNF::LL1
|
|
703
721
|
# @example Raising a parser error
|
704
722
|
# raise Error.new(
|
705
723
|
# "invalid token '%' on line 10",
|
706
|
-
# :
|
724
|
+
# token: '%', lineno: 9, production: :turtleDoc)
|
707
725
|
#
|
708
726
|
# @see http://ruby-doc.org/core/classes/StandardError.html
|
709
727
|
class Error < StandardError
|
data/lib/ebnf/ll1/scanner.rb
CHANGED
@@ -18,7 +18,24 @@ module EBNF::LL1
|
|
18
18
|
attr_reader :input
|
19
19
|
|
20
20
|
##
|
21
|
-
#
|
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
|
36
|
+
|
37
|
+
##
|
38
|
+
# Create a scanner, from an IO
|
22
39
|
#
|
23
40
|
# @param [String, IO, #read] input
|
24
41
|
# @param [Hash{Symbol => Object}] options
|
@@ -28,13 +45,10 @@ module EBNF::LL1
|
|
28
45
|
def initialize(input, options = {})
|
29
46
|
@options = options.merge(high_water: HIGH_WATER, low_water: LOW_WATER)
|
30
47
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
else
|
36
|
-
super(encode_utf8 input.to_s)
|
37
|
-
end
|
48
|
+
@input = input
|
49
|
+
super("")
|
50
|
+
feed_me
|
51
|
+
self
|
38
52
|
end
|
39
53
|
|
40
54
|
##
|
data/lib/ebnf/parser.rb
CHANGED
@@ -78,7 +78,7 @@ module EBNF
|
|
78
78
|
num, sym = num_sym.split(']', 2).map(&:strip)
|
79
79
|
num, sym = "", num if sym.nil?
|
80
80
|
num = num[1..-1]
|
81
|
-
r = Rule.new(sym && sym.to_sym, num, expression(expr).first, :
|
81
|
+
r = Rule.new(sym && sym.to_sym, num, expression(expr).first, ebnf: self)
|
82
82
|
debug("ruleParts") { r.inspect }
|
83
83
|
r
|
84
84
|
end
|
data/lib/ebnf/rule.rb
CHANGED
@@ -54,6 +54,9 @@ module EBNF
|
|
54
54
|
# @return [Boolean]
|
55
55
|
attr_accessor :start
|
56
56
|
|
57
|
+
# Determines preparation and cleanup rules for reconstituting EBNF ? * + from BNF
|
58
|
+
attr_accessor :cleanup
|
59
|
+
|
57
60
|
# @param [Integer] id
|
58
61
|
# @param [Symbol] sym
|
59
62
|
# @param [Array] expr
|
@@ -71,6 +74,7 @@ module EBNF
|
|
71
74
|
@first = options[:first]
|
72
75
|
@follow = options[:follow]
|
73
76
|
@start = options[:start]
|
77
|
+
@cleanup = options[:cleanup]
|
74
78
|
@kind = case
|
75
79
|
when options[:kind] then options[:kind]
|
76
80
|
when sym.to_s == sym.to_s.upcase then :terminal
|
@@ -97,10 +101,12 @@ module EBNF
|
|
97
101
|
first = first[1..-1] if first
|
98
102
|
follow = sxp.detect {|e| e.is_a?(Array) && e.first.to_sym == :follow}
|
99
103
|
follow = follow[1..-1] if follow
|
104
|
+
cleanup = sxp.detect {|e| e.is_a?(Array) && e.first.to_sym == :cleanup}
|
105
|
+
cleanup = cleanup[1..-1] if cleanup
|
100
106
|
start = sxp.any? {|e| e.is_a?(Array) && e.first.to_sym == :start}
|
101
107
|
sym = sxp[1] if sxp[1].is_a?(Symbol)
|
102
108
|
id = sxp[2] if sxp[2].is_a?(String)
|
103
|
-
Rule.new(sym, id, expr, :
|
109
|
+
Rule.new(sym, id, expr, kind: sxp.first, first: first, follow: follow, cleanup: cleanup, start: start)
|
104
110
|
end
|
105
111
|
|
106
112
|
# Build a new rule creating a symbol and numbering from the current rule
|
@@ -113,9 +119,10 @@ module EBNF
|
|
113
119
|
def build(expr, options = {})
|
114
120
|
new_sym, new_id = (@top_rule ||self).send(:make_sym_id)
|
115
121
|
Rule.new(new_sym, new_id, expr, {
|
116
|
-
:
|
117
|
-
:
|
118
|
-
:
|
122
|
+
kind: options[:kind],
|
123
|
+
ebnf: @ebnf,
|
124
|
+
top_rule: @top_rule || self,
|
125
|
+
cleanup: options[:cleanup],
|
119
126
|
}.merge(options))
|
120
127
|
end
|
121
128
|
|
@@ -127,6 +134,7 @@ module EBNF
|
|
127
134
|
elements << [:start, true] if start
|
128
135
|
elements << first.sort_by(&:to_s).unshift(:first) if first
|
129
136
|
elements << follow.sort_by(&:to_s).unshift(:follow) if follow
|
137
|
+
elements << [:cleanup, cleanup] if cleanup
|
130
138
|
elements << expr
|
131
139
|
elements
|
132
140
|
end
|
@@ -196,16 +204,19 @@ module EBNF
|
|
196
204
|
this = dup
|
197
205
|
# * Transform (a rule (opt b)) into (a rule (alt _empty b))
|
198
206
|
this.expr = [:alt, :_empty, expr.last]
|
207
|
+
this.cleanup = :opt
|
199
208
|
new_rules = this.to_bnf
|
200
209
|
elsif expr.first == :star
|
201
210
|
# * Transform (a rule (star b)) into (a rule (alt _empty (seq b a)))
|
202
211
|
this = dup
|
203
|
-
|
212
|
+
this.cleanup = :star
|
213
|
+
new_rule = this.build([:seq, expr.last, this.sym], cleanup: :merge)
|
204
214
|
this.expr = [:alt, :_empty, new_rule.sym]
|
205
215
|
new_rules = [this] + new_rule.to_bnf
|
206
216
|
elsif expr.first == :plus
|
207
217
|
# * Transform (a rule (plus b)) into (a rule (seq b (star b)
|
208
218
|
this = dup
|
219
|
+
this.cleanup = :plus
|
209
220
|
this.expr = [:seq, expr.last, [:star, expr.last]]
|
210
221
|
new_rules = this.to_bnf
|
211
222
|
elsif [:alt, :seq].include?(expr.first)
|
@@ -340,7 +351,7 @@ module EBNF
|
|
340
351
|
|
341
352
|
def inspect
|
342
353
|
"#<EBNF::Rule:#{object_id} " +
|
343
|
-
{:
|
354
|
+
{sym: sym, id: id, kind: kind, expr: expr}.inspect +
|
344
355
|
">"
|
345
356
|
end
|
346
357
|
|
@@ -387,7 +398,7 @@ module EBNF
|
|
387
398
|
private
|
388
399
|
def ttl_expr(expr, pfx, depth, is_obj = true)
|
389
400
|
indent = ' ' * depth
|
390
|
-
@ebnf.debug("ttl_expr", :
|
401
|
+
@ebnf.debug("ttl_expr", depth: depth) {expr.inspect}
|
391
402
|
op = expr.shift if expr.is_a?(Array)
|
392
403
|
statements = []
|
393
404
|
|
@@ -424,7 +435,7 @@ module EBNF
|
|
424
435
|
end
|
425
436
|
|
426
437
|
statements.last << " ." unless is_obj
|
427
|
-
@ebnf.debug("statements", :
|
438
|
+
@ebnf.debug("statements", depth: depth) {statements.join("\n")}
|
428
439
|
statements
|
429
440
|
end
|
430
441
|
|
@@ -461,10 +472,11 @@ module EBNF
|
|
461
472
|
end
|
462
473
|
|
463
474
|
# Make a new symbol/number combination
|
464
|
-
|
475
|
+
# @param [String] variation added to symbol to aid reconstitution from BNF to EBNF
|
476
|
+
def make_sym_id(variation = nil)
|
465
477
|
@id_seq ||= 0
|
466
478
|
@id_seq += 1
|
467
|
-
["_#{@sym}_#{@id_seq}".to_sym, "#{@id}.#{@id_seq}"]
|
479
|
+
["_#{@sym}_#{@id_seq}#{variation}".to_sym, "#{@id}.#{@id_seq}#{variation}"]
|
468
480
|
end
|
469
481
|
end
|
470
482
|
end
|
data/lib/ebnf/writer.rb
CHANGED
@@ -94,7 +94,7 @@ module EBNF
|
|
94
94
|
buffer = if rule.pass?
|
95
95
|
"%-#{lhs_length-2}s" % "@pass"
|
96
96
|
else
|
97
|
-
lhs_fmt % {:
|
97
|
+
lhs_fmt % {id: "[#{rule.id}]", sym: rule.sym}
|
98
98
|
end
|
99
99
|
formatted_expr = format(rule.expr)
|
100
100
|
if formatted_expr.length > rhs_length
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ebnf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregg Kellogg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sxp
|
@@ -169,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
169
169
|
version: '0'
|
170
170
|
requirements: []
|
171
171
|
rubyforge_project:
|
172
|
-
rubygems_version: 2.
|
172
|
+
rubygems_version: 2.6.8
|
173
173
|
signing_key:
|
174
174
|
specification_version: 4
|
175
175
|
summary: EBNF parser and parser generator.
|