ebnf 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/etc/ebnf.ebnf +19 -11
- data/etc/{ebnf.ll1 → ebnf.ll1.sxp} +460 -288
- data/etc/ebnf.rb +451 -507
- data/etc/ebnf.sxp +112 -0
- data/etc/{turtle.ll1 → turtle.ll1.sxp} +836 -66
- data/etc/turtle.rb +1030 -437
- data/etc/turtle.sxp +417 -0
- data/lib/ebnf/base.rb +5 -11
- data/lib/ebnf/bnf.rb +2 -2
- data/lib/ebnf/ll1.rb +92 -125
- data/lib/ebnf/ll1/parser.rb +12 -10
- data/lib/ebnf/parser.rb +6 -3
- data/lib/ebnf/rule.rb +61 -8
- metadata +7 -5
data/lib/ebnf/bnf.rb
CHANGED
@@ -19,8 +19,8 @@ module EBNF
|
|
19
19
|
|
20
20
|
# Consolodate equivalent terminal rules
|
21
21
|
to_rewrite = {}
|
22
|
-
new_ast.select {|r| r.
|
23
|
-
new_ast.select {|r| r.
|
22
|
+
new_ast.select {|r| r.terminal?}.each do |src_rule|
|
23
|
+
new_ast.select {|r| r.terminal?}.each do |dst_rule|
|
24
24
|
if src_rule.equivalent?(dst_rule) && src_rule != dst_rule
|
25
25
|
debug("make_bnf") {"equivalent rules: #{src_rule.inspect} and #{dst_rule.inspect}"}
|
26
26
|
(to_rewrite[src_rule] ||= []) << dst_rule
|
data/lib/ebnf/ll1.rb
CHANGED
@@ -48,143 +48,104 @@ module EBNF
|
|
48
48
|
start_rule.start = true
|
49
49
|
end
|
50
50
|
|
51
|
-
# Comprehnsion rule, create shorter versions of all non-terminal sequences
|
51
|
+
# Comprehnsion rule, create shorter versions of all non-terminal sequences. This is necessary as the FF rules reference w', which is a comprehension.
|
52
52
|
comprehensions = []
|
53
53
|
ittr = 0
|
54
54
|
depth do
|
55
55
|
begin
|
56
56
|
comprehensions = []
|
57
|
-
ast.select {|r| r.
|
57
|
+
ast.select {|r| r.rule? && r.seq? && r.comp.nil? && r.expr.length > 2}.each do |rule|
|
58
58
|
new_expr = rule.expr[2..-1].unshift(:seq)
|
59
|
-
|
60
|
-
|
59
|
+
if new_rule = ast.detect {|r| r.expr == new_expr}
|
60
|
+
# Link to existing comprehension used for another rules
|
61
|
+
debug("FF.c") {"(#{ittr}) link comprehension rule for #{rule.sym} => #{new_rule.sym}[#{new_expr.inspect}]"}
|
62
|
+
else
|
61
63
|
new_rule = rule.build(new_expr)
|
62
|
-
rule.
|
64
|
+
debug("FF.c") {"(#{ittr}) add comprehension rule for #{rule.sym} => #{new_rule.sym}[#{new_expr.inspect}]"}
|
63
65
|
comprehensions << new_rule
|
64
66
|
end
|
67
|
+
rule.comp = new_rule
|
65
68
|
end
|
66
|
-
|
69
|
+
|
67
70
|
@ast += comprehensions
|
68
71
|
progress("FF.c") {"(#{ittr}) comprehensions #{comprehensions.length}"}
|
72
|
+
#require 'debugger'; breakpoint
|
69
73
|
ittr += 1
|
70
74
|
end while !comprehensions.empty?
|
71
75
|
|
72
|
-
|
73
|
-
# For each rule who's expr's first element of a seq a terminal, or having any element of alt a terminal, add that terminal to the first set for this rule
|
74
|
-
each(:rule) do |rule|
|
75
|
-
each(:terminal) do |terminal|
|
76
|
-
if rule.starts_with?(terminal.sym)
|
77
|
-
debug("FF.t") {"(0) add first #{terminal.sym} to #{rule.sym}"}
|
78
|
-
rule.add_first([terminal.sym])
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Add strings to first for strings which are start elements
|
83
|
-
start_strs = rule.starts_with?(String)
|
84
|
-
if start_strs
|
85
|
-
debug("FF.t") {"(0) add firsts #{start_strs.join(", ")} to #{rule.sym}"}
|
86
|
-
rule.add_first(start_strs)
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
# # Fi(ε) = { ε }
|
91
|
-
# Add _eps as a first of _empty
|
92
|
-
find_rule(:_empty).add_first([:_eps])
|
93
|
-
|
94
|
-
# Loop until no more first elements are added
|
95
|
-
firsts, follows, ittr = 0, 0, 0
|
76
|
+
ittr = 0
|
96
77
|
begin
|
97
78
|
firsts, follows = 0, 0
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
# Fi(A w' ) = Fi(A) \ { ε } ∪ Fi(w' ) for every nonterminal A with ε in Fi(A)
|
111
|
-
# For each rule starting with eps, add the terminals for the comprehension of this rule
|
112
|
-
if rule.seq? &&
|
113
|
-
rule.expr.fetch(1, nil) == first_rule.sym &&
|
114
|
-
first_rule.first_includes_eps? &&
|
115
|
-
(comp = rule.comp) &&
|
116
|
-
comp.first &&
|
117
|
-
!(comp.first - [:_eps]).empty?
|
79
|
+
# add Fi(wi) to Fi(Ai) for every rule Ai → wi
|
80
|
+
#
|
81
|
+
# For sequences, this is the first rule in the sequence.
|
82
|
+
# For alts, this is every rule in the sequence
|
83
|
+
each(:rule) do |ai|
|
84
|
+
# Fi(a w' ) = { a } for every terminal a
|
85
|
+
ai.terminals(ast).each do |t|
|
86
|
+
debug("Fi.2.1") {"(#{ittr}) add terminal #{t} to #{ai.sym}"}
|
87
|
+
firsts += ai.add_first([t])
|
88
|
+
end
|
118
89
|
|
119
|
-
|
120
|
-
|
121
|
-
|
90
|
+
ai.non_terminals(ast).select(&:first).each do |a|
|
91
|
+
if !a.first_includes_eps?
|
92
|
+
# Fi(A w' ) = Fi(A) for every nonterminal A with ε not in Fi(A)
|
93
|
+
debug("Fi.2.2") {"(#{ittr}) add first from #{a.sym} to #{ai.sym}: #{a.first.inspect}"}
|
94
|
+
firsts += ai.add_first(a.first)
|
95
|
+
else
|
96
|
+
# Fi(A w' ) = Fi(A) \ { ε } ∪ Fi(w' ) for every nonterminal A with ε in Fi(A)
|
97
|
+
if ai.seq?
|
98
|
+
# w' is either comprehnsion of ai, or empty, if there is no comprehension
|
99
|
+
comp = ai.comp || find_rule(:_empty)
|
100
|
+
|
101
|
+
fi = a.first - [:_eps] + (comp.first || [])
|
102
|
+
debug("Fi.2.3a") {"(#{ittr}) add first #{fi.inspect} from #{a.sym} and #{comp.sym} to #{ai.sym}"}
|
103
|
+
firsts += ai.add_first(fi)
|
104
|
+
else
|
105
|
+
# ai is an alt, so there are no comprehensions of non-terminals, add Fi(A) including ε
|
106
|
+
debug("Fi.2.3b") {"(#{ittr}) add first #{a.first} from #{a.sym} to #{ai.sym}"}
|
107
|
+
firsts += ai.add_first(a.first)
|
108
|
+
end
|
122
109
|
end
|
123
110
|
end
|
111
|
+
end
|
124
112
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
# if first_rule.first == [T]
|
140
|
-
# => a.follow += [T]
|
141
|
-
debug("FF.3") {"(#{ittr}) add follow #{comp.first.inspect} from #{comp.sym} to #{ai.sym}"}
|
113
|
+
# # Fi(ε) = { ε }
|
114
|
+
# Add _eps as a first of _empty
|
115
|
+
find_rule(:_empty).add_first([:_eps])
|
116
|
+
|
117
|
+
# Add follows
|
118
|
+
# if there is a rule of the form Aj → wAiw' , then
|
119
|
+
# First do this for the case when Ai is the first rule
|
120
|
+
each(:rule) do |aj|
|
121
|
+
comp = aj.comp || find_rule(:_empty)
|
122
|
+
aj.non_terminals(ast).reject {|r| r.sym == :_empty}.each do |ai|
|
123
|
+
# if the terminal a is in Fi(w' ), then add a to Fo(Ai)
|
124
|
+
# Basically, this says that the firsts of a comprehension of a rule are the follows of the first non-terminal in the rule.
|
125
|
+
if comp.first
|
126
|
+
debug("Fo.2.1") {"(#{ittr}) add follow #{comp.first.inspect} from #{comp.sym} to #{ai.sym}"}
|
142
127
|
follows += ai.add_follow(comp.first)
|
143
128
|
end
|
144
129
|
|
145
|
-
#
|
146
|
-
if
|
147
|
-
|
148
|
-
|
130
|
+
# If there is no comprehension of this rule (meaning, it is a sequence of one non-terminal), then the follows of the non-terminal include the follows of the rule. This handles rules with multiple sequences because it will have a comprehension that includes the last element in the sequence
|
131
|
+
#require 'debugger'; breakpoint if ai.sym == :_predicateObjectList_1 && aj.sym == :_predicateObjectList_7
|
132
|
+
if !aj.comp && aj.follow
|
133
|
+
debug("Fo.2.1a") {"(#{ittr}) add follow #{aj.follow.inspect} from #{aj.sym} to #{ai.sym}"}
|
134
|
+
follows += ai.add_follow(aj.follow)
|
149
135
|
end
|
150
136
|
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
(member = find_rule(rule.expr.fetch(1, nil))) &&
|
156
|
-
member.kind == :rule
|
157
|
-
|
158
|
-
debug("FF.5") {"(#{ittr}) add follow #{rule.follow.inspect} from #{rule.sym} to #{member.sym}"}
|
159
|
-
follows += member.add_follow(rule.first)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Firsts of elements of an alt are firsts of the alt
|
164
|
-
if rule.alt?
|
165
|
-
rule.expr[1..-1].map {|s| find_rule(s)}.compact.select(&:first).each do |mem|
|
166
|
-
debug("FF.6") {"(#{ittr}) add first #{mem.first.inspect} from #{mem.sym} to #{rule.sym}"}
|
167
|
-
rule.add_first(mem.first)
|
137
|
+
# if ε is in Fi(w' ), then add Fo(Aj) to Fo(Ai)
|
138
|
+
if comp.first_includes_eps? && aj.follow
|
139
|
+
debug("Fo.2.2") {"(#{ittr}) add follow #{aj.follow.inspect} from #{aj.sym} to #{ai.sym}"}
|
140
|
+
follows += ai.add_follow(aj.follow)
|
168
141
|
end
|
169
142
|
end
|
170
143
|
|
171
|
-
#
|
172
|
-
if
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
debug("FF.7") {"(#{ittr}) add follow #{rule.follow.inspect} to #{member.sym}"}
|
177
|
-
follows += member.add_follow(rule.follow)
|
178
|
-
end
|
179
|
-
|
180
|
-
# For alts, anything that follows the rule follows each member of the rule
|
181
|
-
if rule.alt? && rule.follow
|
182
|
-
rule.expr[1..-1].map {|s| find_rule(s)}.each do |mem|
|
183
|
-
if mem && mem.kind == :rule
|
184
|
-
debug("FF.8") {"(#{ittr}) add follow #{rule.first.inspect} to #{mem.sym}"}
|
185
|
-
follows += mem.add_follow(rule.follow)
|
186
|
-
end
|
187
|
-
end
|
144
|
+
# Since the rules are of the form wAiw', and we've handled the case which is just Aiw', this leaves those cases that have rules prior to Ai. This basically says that the follows of a rule are added to the follows of the comprehension of the rule
|
145
|
+
#require 'debugger'; breakpoint if aj.sym == :_predicateObjectList_6 && aj.follow
|
146
|
+
if aj.comp && aj.follow
|
147
|
+
debug("Fo.2.3") {"(#{ittr}) add follow #{aj.follow.inspect} from #{aj.sym} to #{aj.comp.sym}"}
|
148
|
+
follows += aj.comp.add_follow(aj.follow)
|
188
149
|
end
|
189
150
|
end
|
190
151
|
|
@@ -198,8 +159,8 @@ module EBNF
|
|
198
159
|
# Generate parser tables, {#branch}, {#first}, {#follow}, and {#terminals}
|
199
160
|
def build_tables
|
200
161
|
progress("build_tables") {
|
201
|
-
"Terminals: #{ast.count {|r| r.
|
202
|
-
"Non-Terminals: #{ast.count {|r| r.
|
162
|
+
"Terminals: #{ast.count {|r| r.terminal?}} " +
|
163
|
+
"Non-Terminals: #{ast.count {|r| r.rule?}}"
|
203
164
|
}
|
204
165
|
|
205
166
|
@first = ast.
|
@@ -211,13 +172,13 @@ module EBNF
|
|
211
172
|
@follow = ast.
|
212
173
|
select(&:follow).
|
213
174
|
inject({}) {|memo, r|
|
214
|
-
memo[r.sym] = r.
|
175
|
+
memo[r.sym] = r.follow if r.follow
|
215
176
|
memo
|
216
177
|
}
|
217
178
|
@terminals = ast.map do |r|
|
218
179
|
(r.first || []) + (r.follow || [])
|
219
180
|
end.flatten.uniq
|
220
|
-
@terminals = (@terminals - [:_eps, :_eof
|
181
|
+
@terminals = (@terminals - [:_eps, :_eof]).sort_by{|t| t.to_s.sub(/^_/, '')}
|
221
182
|
|
222
183
|
@branch = {}
|
223
184
|
@already = []
|
@@ -250,14 +211,14 @@ module EBNF
|
|
250
211
|
|
251
212
|
if table.is_a?(Hash)
|
252
213
|
io.puts "#{ind0}#{name} = {"
|
253
|
-
table.keys.sort_by(
|
214
|
+
table.keys.sort_by{|t| t.to_s.sub(/^_/, '')}.each do |prod|
|
254
215
|
case table[prod]
|
255
216
|
when Array
|
256
217
|
list = table[prod].map(&:inspect).join(",\n#{ind2}")
|
257
218
|
io.puts "#{ind1}#{prod.inspect} => [\n#{ind2}#{list}],"
|
258
219
|
when Hash
|
259
220
|
io.puts "#{ind1}#{prod.inspect} => {"
|
260
|
-
table[prod].keys.sort_by(
|
221
|
+
table[prod].keys.sort_by{|t| t.to_s.sub(/^_/, '')}.each do |term|
|
261
222
|
list = table[prod][term].map(&:inspect).join(", ")
|
262
223
|
io.puts "#{ind2}#{term.inspect} => [#{list}],"
|
263
224
|
end
|
@@ -269,7 +230,7 @@ module EBNF
|
|
269
230
|
io.puts "#{ind0}}.freeze\n"
|
270
231
|
else
|
271
232
|
io.puts "#{ind0}#{name} = [\n#{ind1}" +
|
272
|
-
table.sort_by(
|
233
|
+
table.sort_by{|t| t.to_s.sub(/^_/, '')}.map(&:inspect).join(",\n#{ind1}") +
|
273
234
|
"\n#{ind0}].freeze\n"
|
274
235
|
end
|
275
236
|
end
|
@@ -277,7 +238,7 @@ module EBNF
|
|
277
238
|
private
|
278
239
|
def do_production(lhs)
|
279
240
|
rule = find_rule(lhs)
|
280
|
-
if rule.nil? || rule.
|
241
|
+
if rule.nil? || !rule.rule? || rule.sym == :_empty
|
281
242
|
progress("prod") {"Skip: #{lhs.inspect}"}
|
282
243
|
return
|
283
244
|
end
|
@@ -302,12 +263,12 @@ module EBNF
|
|
302
263
|
@agenda << prod unless @already.include?(prod) || @agenda.include?(prod)
|
303
264
|
if prod == :_empty
|
304
265
|
debug(" empty")
|
305
|
-
|
266
|
+
# Skip empty, rules added bellow for follows
|
306
267
|
elsif prod_rule.nil? || prod_rule.first.nil?
|
307
268
|
debug(" no first =>", prod)
|
308
269
|
branchDict[prod] = [prod]
|
309
270
|
else
|
310
|
-
prod_rule.first.each do |f|
|
271
|
+
prod_rule.first.reject{|f| f == :_eps}.each do |f|
|
311
272
|
if branchDict.has_key?(f)
|
312
273
|
error("First/First Conflict: #{f} is also the condition for #{branchDict[f]}")
|
313
274
|
end
|
@@ -321,8 +282,12 @@ module EBNF
|
|
321
282
|
debug(" Seq", rule)
|
322
283
|
# Entries for each first element referencing the sequence
|
323
284
|
(rule.first || []).each do |f|
|
324
|
-
|
325
|
-
|
285
|
+
if [:_eps, :_eof].include?(f)
|
286
|
+
# Skip eps/eof, rules added below for follows
|
287
|
+
else
|
288
|
+
debug(" seq") {"[#{f}] => #{rule.expr[1..-1].inspect}"}
|
289
|
+
branchDict[f] = rule.expr[1..-1]
|
290
|
+
end
|
326
291
|
end
|
327
292
|
|
328
293
|
# Add each production to the agenda
|
@@ -330,11 +295,13 @@ module EBNF
|
|
330
295
|
@agenda << prod unless @already.include?(prod) || @agenda.include?(prod)
|
331
296
|
end
|
332
297
|
end
|
333
|
-
|
334
|
-
# Add follow rules
|
335
|
-
|
336
|
-
|
337
|
-
|
298
|
+
|
299
|
+
# Add follow rules, if first includes eps
|
300
|
+
if rule.first_includes_eps?
|
301
|
+
(rule.follow || []).reject {|f| f == :_eof}.each do |f|
|
302
|
+
debug(" Follow") {f.inspect}
|
303
|
+
branchDict[f] ||= []
|
304
|
+
end
|
338
305
|
end
|
339
306
|
|
340
307
|
@branch[lhs] = branchDict
|
data/lib/ebnf/ll1/parser.rb
CHANGED
@@ -282,7 +282,7 @@ module EBNF::LL1
|
|
282
282
|
debug("parse(token)") {"token #{token.inspect}, term #{term.inspect}"}
|
283
283
|
onToken(term, token)
|
284
284
|
elsif terminals.include?(term)
|
285
|
-
# If term is a terminal, then it is an error
|
285
|
+
# If term is a terminal, then it is an error if token does not
|
286
286
|
# match it
|
287
287
|
skip_until_valid(todo_stack)
|
288
288
|
else
|
@@ -301,7 +301,7 @@ module EBNF::LL1
|
|
301
301
|
while !pushed &&
|
302
302
|
!todo_stack.empty? &&
|
303
303
|
( (terms = todo_stack.last.fetch(:terms, [])).empty? ||
|
304
|
-
(@recovering && @follow.fetch(terms.last, []).none? {|t| token == t}))
|
304
|
+
(@recovering && @follow.fetch(terms.last, []).none? {|t| (token || :_eps) == t}))
|
305
305
|
debug("parse(pop)", :level => 2) {"todo #{todo_stack.last.inspect}, depth #{depth}"}
|
306
306
|
if terms.empty?
|
307
307
|
prod = todo_stack.last[:prod]
|
@@ -403,7 +403,6 @@ module EBNF::LL1
|
|
403
403
|
self.class.eval_with_binding(self) {
|
404
404
|
handler.call(@prod_data.last, data, @parse_callback)
|
405
405
|
}
|
406
|
-
#require 'debugger'; breakpoint
|
407
406
|
progress("#{prod}(:finish):#{@prod_data.length}") {@prod_data.last}
|
408
407
|
else
|
409
408
|
progress("#{prod}(:finish)", "recovering: #{@recovering.inspect}")
|
@@ -440,18 +439,20 @@ module EBNF::LL1
|
|
440
439
|
cur_prod = todo_stack.last[:prod]
|
441
440
|
token = get_token
|
442
441
|
first = @first[cur_prod] || []
|
443
|
-
|
442
|
+
expected = @branch.fetch(cur_prod, {}).keys
|
443
|
+
expected << :_eps if first.include?(:_eps) # Helps when testing
|
444
|
+
|
445
|
+
# If we've reached EOF, token is nil. This is fine, if _eof is in @follow
|
446
|
+
return if token.nil? && @follow.fetch(cur_prod, []).include?(:_eof)
|
447
|
+
|
444
448
|
# If this token can be used by the top production, return it
|
445
449
|
# Otherwise, if the banch table allows empty, also return the token
|
446
|
-
return token if !@recovering && (
|
447
|
-
|
448
|
-
first.any? {|t| token === t})
|
449
|
-
|
450
|
+
return token if !@recovering && (expected.any? {|t| (token || :_eps) === t})
|
451
|
+
|
450
452
|
# Otherwise, it's an error condition, and skip either until
|
451
453
|
# we find a valid token for this production, or until we find
|
452
454
|
# something that can follow this production
|
453
|
-
expected
|
454
|
-
error("skip_until_valid", "expected one of #{expected}",
|
455
|
+
error("skip_until_valid", "expected one of #{expected.map(&:inspect).join(", ")}, found #{token.inspect}",
|
455
456
|
:production => cur_prod, :token => token)
|
456
457
|
|
457
458
|
debug("recovery", "stack follows:")
|
@@ -495,6 +496,7 @@ module EBNF::LL1
|
|
495
496
|
@error_log << message unless @recovering
|
496
497
|
@recovering = true
|
497
498
|
debug(node, message, options.merge(:level => 0))
|
499
|
+
$stderr.puts("[#{@lineno}]#{' ' * depth}#{node}: #{message}")
|
498
500
|
end
|
499
501
|
|
500
502
|
##
|
data/lib/ebnf/parser.rb
CHANGED
@@ -17,6 +17,9 @@ module EBNF
|
|
17
17
|
cur_lineno += s.count("\n")
|
18
18
|
#debug("eachRule(ws)") { "[#{cur_lineno}] #{s.inspect}" }
|
19
19
|
when s = scanner.scan(%r(/\*([^\*]|\*[^\/])*\*/)m)
|
20
|
+
# Eat comments
|
21
|
+
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
22
|
+
when s = scanner.scan(%r((#|//).*$))
|
20
23
|
# Eat comments
|
21
24
|
cur_lineno += s.count("\n")
|
22
25
|
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
@@ -32,7 +35,7 @@ module EBNF
|
|
32
35
|
yield r unless r.empty?
|
33
36
|
@lineno = cur_lineno
|
34
37
|
r = s
|
35
|
-
when s = scanner.scan(/\[(
|
38
|
+
when s = scanner.scan(/\[(?=[\w\.]+\])/)
|
36
39
|
# Found rule start, if we've already collected a rule, yield it
|
37
40
|
yield r unless r.empty?
|
38
41
|
#debug("eachRule(rule)") { "[#{cur_lineno}] #{s.inspect}" }
|
@@ -268,7 +271,7 @@ module EBNF
|
|
268
271
|
s.match(/(#\w+)(.*)$/)
|
269
272
|
l, s = $1, $2
|
270
273
|
[[:hex, l], s]
|
271
|
-
when /[
|
274
|
+
when /[\w\.]/
|
272
275
|
s.match(/(\w+)(.*)$/)
|
273
276
|
l, s = $1, $2
|
274
277
|
[l.to_sym, s]
|
@@ -290,7 +293,7 @@ module EBNF
|
|
290
293
|
[[m.to_sym], s[1..-1]]
|
291
294
|
else
|
292
295
|
error("terminal", "unrecognized terminal: #{s.inspect}")
|
293
|
-
raise "Syntax Error"
|
296
|
+
raise "Syntax Error, unrecognized terminal: #{s.inspect}"
|
294
297
|
end
|
295
298
|
end
|
296
299
|
end
|
data/lib/ebnf/rule.rb
CHANGED
@@ -113,7 +113,7 @@ module EBNF
|
|
113
113
|
%{ rdfs:comment #{comment.inspect};},
|
114
114
|
]
|
115
115
|
|
116
|
-
statements += ttl_expr(expr,
|
116
|
+
statements += ttl_expr(expr, terminal? ? "re" : "g", 1, false)
|
117
117
|
"\n" + statements.join("\n")
|
118
118
|
end
|
119
119
|
|
@@ -128,7 +128,7 @@ module EBNF
|
|
128
128
|
# * Transform (a rule (plus b)) into (a rule (seq b (star b)
|
129
129
|
# @return [Array<Rule>]
|
130
130
|
def to_bnf
|
131
|
-
return [self] unless
|
131
|
+
return [self] unless rule?
|
132
132
|
new_rules = []
|
133
133
|
|
134
134
|
# Look for rules containing recursive definition and rewrite to multiple rules. If `expr` contains elements which are in array form, where the first element of that array is a symbol, create a new rule for it.
|
@@ -170,7 +170,7 @@ module EBNF
|
|
170
170
|
new_rules << self
|
171
171
|
elsif [:diff, :hex, :range].include?(expr.first)
|
172
172
|
# This rules are fine, the just need to be terminals
|
173
|
-
raise "Encountered #{expr.first.inspect}, which is a #{self.kind}, not :terminal" unless self.
|
173
|
+
raise "Encountered #{expr.first.inspect}, which is a #{self.kind}, not :terminal" unless self.terminal?
|
174
174
|
new_rules << self
|
175
175
|
else
|
176
176
|
# Some case we didn't think of
|
@@ -180,6 +180,42 @@ module EBNF
|
|
180
180
|
return new_rules
|
181
181
|
end
|
182
182
|
|
183
|
+
# Return the non-terminals for this rule. For seq, this is the first
|
184
|
+
# non-terminals in the seq. For alt, this is every non-terminal ni the alt
|
185
|
+
# @param [Array<Rule>] ast
|
186
|
+
# The set of rules, used to turn symbols into rules
|
187
|
+
# @return [Array<Rule>]
|
188
|
+
def non_terminals(ast)
|
189
|
+
@non_terms ||= (alt? ? expr[1..-1] : expr[1,1]).map do |sym|
|
190
|
+
case sym
|
191
|
+
when Symbol
|
192
|
+
r = ast.detect {|r| r.sym == sym}
|
193
|
+
r if r && r.rule?
|
194
|
+
else
|
195
|
+
nil
|
196
|
+
end
|
197
|
+
end.compact
|
198
|
+
end
|
199
|
+
|
200
|
+
# Return the terminals for this rule. For seq, this is the first
|
201
|
+
# terminals or strings in the seq. For alt, this is every non-terminal ni the alt
|
202
|
+
# @param [Array<Rule>] ast
|
203
|
+
# The set of rules, used to turn symbols into rules
|
204
|
+
# @return [Array<Rule>]
|
205
|
+
def terminals(ast)
|
206
|
+
@terms ||= (alt? ? expr[1..-1] : expr[1,1]).map do |sym|
|
207
|
+
case sym
|
208
|
+
when Symbol
|
209
|
+
r = ast.detect {|r| r.sym == sym}
|
210
|
+
r if r && r.terminal?
|
211
|
+
when String
|
212
|
+
sym
|
213
|
+
else
|
214
|
+
nil
|
215
|
+
end
|
216
|
+
end.compact
|
217
|
+
end
|
218
|
+
|
183
219
|
# Does this rule start with a sym? It does if expr is that sym,
|
184
220
|
# expr starts with alt and contains that sym, or
|
185
221
|
# expr starts with seq and the next element is that sym
|
@@ -203,22 +239,22 @@ module EBNF
|
|
203
239
|
end
|
204
240
|
|
205
241
|
# Add terminal as proceding this rule
|
206
|
-
# @param [Array<Rule>] terminals
|
242
|
+
# @param [Array<Rule, Symbol, String>] terminals
|
207
243
|
# @return [Integer] if number of terminals added
|
208
244
|
def add_first(terminals)
|
209
245
|
@first ||= []
|
210
|
-
terminals
|
246
|
+
terminals = terminals.map {|t| t.is_a?(Rule) ? t.sym : t} - @first
|
211
247
|
@first += terminals
|
212
248
|
terminals.length
|
213
249
|
end
|
214
250
|
|
215
251
|
# Add terminal as following this rule. Don't add _eps as a follow
|
216
252
|
#
|
217
|
-
# @param [Array<Rule>] terminals
|
253
|
+
# @param [Array<Rule, Symbol, String>] terminals
|
218
254
|
# @return [Integer] if number of terminals added
|
219
255
|
def add_follow(terminals)
|
220
|
-
|
221
|
-
terminals
|
256
|
+
# Remove terminals already in follows, and empty string
|
257
|
+
terminals = terminals.map {|t| t.is_a?(Rule) ? t.sym : t} - (@follow || []) - [:_eps]
|
222
258
|
unless terminals.empty?
|
223
259
|
@follow ||= []
|
224
260
|
@follow += terminals
|
@@ -226,6 +262,23 @@ module EBNF
|
|
226
262
|
terminals.length
|
227
263
|
end
|
228
264
|
|
265
|
+
# Is this a terminal?
|
266
|
+
# @return [Boolean]
|
267
|
+
def terminal?
|
268
|
+
kind == :terminal
|
269
|
+
end
|
270
|
+
|
271
|
+
# Is this a rule?
|
272
|
+
# @return [Boolean]
|
273
|
+
def rule?
|
274
|
+
kind == :rule
|
275
|
+
end
|
276
|
+
|
277
|
+
# Is this rule of the form (alt ...)?
|
278
|
+
def alt?
|
279
|
+
expr.is_a?(Array) && expr.first == :alt
|
280
|
+
end
|
281
|
+
|
229
282
|
# Is this rule of the form (seq ...)?
|
230
283
|
def seq?
|
231
284
|
expr.is_a?(Array) && expr.first == :seq
|