ebnf 1.1.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +13 -12
- 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 +84 -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 +554 -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 +554 -85
- metadata +98 -20
- data/etc/sparql.rb +0 -45773
data/lib/ebnf/parser.rb
CHANGED
@@ -1,322 +1,305 @@
|
|
1
|
+
require_relative 'ebnf/meta'
|
2
|
+
require 'logger'
|
3
|
+
|
1
4
|
module EBNF
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
# a line that starts with '\[' or '@' starts a new rule
|
6
|
-
#
|
7
|
-
# @param [StringScanner] scanner
|
8
|
-
# @yield rule_string
|
9
|
-
# @yieldparam [String] rule_string
|
10
|
-
def eachRule(scanner)
|
11
|
-
cur_lineno = 1
|
12
|
-
r = ''
|
13
|
-
until scanner.eos?
|
14
|
-
case
|
15
|
-
when s = scanner.scan(%r(\s+)m)
|
16
|
-
# Eat whitespace
|
17
|
-
cur_lineno += s.count("\n")
|
18
|
-
#debug("eachRule(ws)") { "[#{cur_lineno}] #{s.inspect}" }
|
19
|
-
when s = scanner.scan(%r(/\*([^\*]|\*[^\/])*\*/)m)
|
20
|
-
# Eat comments /* .. */
|
21
|
-
cur_lineno += s.count("\n")
|
22
|
-
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
23
|
-
when s = scanner.scan(%r(\(\*([^\*]|\*[^\)])*\*\))m)
|
24
|
-
# Eat comments (* .. *)
|
25
|
-
cur_lineno += s.count("\n")
|
26
|
-
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
27
|
-
when s = scanner.scan(%r((#(?!x)|//).*$))
|
28
|
-
# Eat comments // & #
|
29
|
-
cur_lineno += s.count("\n")
|
30
|
-
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
31
|
-
when s = scanner.scan(/\A["']/)
|
32
|
-
# Found a quote, scan until end of matching quote
|
33
|
-
s += scanner.scan_until(/#{scanner.matched}|$/)
|
34
|
-
r += s
|
35
|
-
when s = scanner.scan(%r(^@terminals))
|
36
|
-
#debug("eachRule(@terminals)") { "[#{cur_lineno}] #{s.inspect}" }
|
37
|
-
yield(r) unless r.empty?
|
38
|
-
@lineno = cur_lineno
|
39
|
-
yield(s)
|
40
|
-
r = ''
|
41
|
-
when s = scanner.scan(/@pass/)
|
42
|
-
# Found rule start, if we've already collected a rule, yield it
|
43
|
-
#debug("eachRule(@pass)") { "[#{cur_lineno}] #{s.inspect}" }
|
44
|
-
yield r unless r.empty?
|
45
|
-
@lineno = cur_lineno
|
46
|
-
r = s
|
47
|
-
when s = scanner.scan(/(?:\[[\w\.]+\])\s*[\w\.]+\s*::=/)
|
48
|
-
# Found rule start, if we've already collected a rule, yield it
|
49
|
-
yield r unless r.empty?
|
50
|
-
#debug("eachRule(rule)") { "[#{cur_lineno}] #{s.inspect}" }
|
51
|
-
@lineno = cur_lineno
|
52
|
-
r = s
|
53
|
-
else
|
54
|
-
# Collect until end of line, or start of comment or quote
|
55
|
-
s = scanner.scan_until(%r{(?:[/\(]\*)|#(?!x)|//|["']|$})
|
56
|
-
if scanner.matched.length > 0
|
57
|
-
# Back up scan head before ending match
|
58
|
-
scanner.pos = scanner.pos - scanner.matched.length
|
5
|
+
class Parser
|
6
|
+
include EBNF::PEG::Parser
|
7
|
+
include EBNF::Terminals
|
59
8
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
9
|
+
# Abstract syntax tree from parse
|
10
|
+
#
|
11
|
+
# @return [Array<EBNF::Rule>]
|
12
|
+
attr_reader :ast
|
13
|
+
|
14
|
+
# ## Terminals
|
15
|
+
# Define rules for Terminals, placing results on the input stack, making them available to upstream non-Terminal rules.
|
16
|
+
#
|
17
|
+
# Terminals are defined with a symbol matching the associated rule name, and an optional (although strongly encouraged) regular expression used to match the head of the input stream.
|
18
|
+
#
|
19
|
+
# The result of the terminal block is the semantic value of that terminal, which if often a string, but may be any instance which reflects the semantic interpretation of that terminal.
|
20
|
+
#
|
21
|
+
# The `value` parameter is the value matched by the regexp, if defined, or by the sub-terminal rules otherwise.
|
22
|
+
#
|
23
|
+
# The `prod` parameter is the name of the parent rule for which this terminal is matched, which may have a bearing in some circumstances, although not used in this example.
|
24
|
+
#
|
25
|
+
# If no block is provided, then the value which would have been passed to the block is used as the result directly.
|
26
|
+
|
27
|
+
# Match the Left hand side of a rule or terminal
|
28
|
+
#
|
29
|
+
# [11] LHS ::= ('[' SYMBOL+ ']' ' '+)? SYMBOL ' '* '::='
|
30
|
+
terminal(:LHS, LHS) do |value, prod|
|
31
|
+
value.to_s.scan(/(?:\[([^\]]+)\])?\s*(\w+)\s*::=/).first
|
32
|
+
end
|
33
|
+
|
34
|
+
# Match `SYMBOL` terminal
|
35
|
+
#
|
36
|
+
# [12] SYMBOL ::= ([a-z] | [A-Z] | [0-9] | '_' | '.')+
|
37
|
+
terminal(:SYMBOL, SYMBOL) do |value|
|
38
|
+
value.to_sym
|
69
39
|
end
|
70
|
-
|
71
|
-
|
72
|
-
#
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
def ruleParts(rule)
|
77
|
-
num_sym, expr = rule.split('::=', 2).map(&:strip)
|
78
|
-
num, sym = num_sym.split(']', 2).map(&:strip)
|
79
|
-
num, sym = "", num if sym.nil?
|
80
|
-
num = num[1..-1]
|
81
|
-
r = Rule.new(sym && sym.to_sym, num, expression(expr).first, ebnf: self)
|
82
|
-
debug("ruleParts") { r.inspect }
|
83
|
-
r
|
40
|
+
|
41
|
+
# Match `HEX` terminal
|
42
|
+
#
|
43
|
+
# [13] HEX ::= #x' ([a-f] | [A-F] | [0-9])+
|
44
|
+
terminal(:HEX, HEX) do |value|
|
45
|
+
[:hex, value]
|
84
46
|
end
|
85
47
|
|
86
|
-
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
# ((seq a b c) '')
|
92
|
-
#
|
93
|
-
# >>> expression("a? b+ c*")
|
94
|
-
# ((seq (opt a) (plus b) (star c)) '')
|
95
|
-
#
|
96
|
-
# >>> expression(" | x xlist")
|
97
|
-
# ((alt (seq) (seq x xlist)) '')
|
98
|
-
#
|
99
|
-
# >>> expression("a | (b - c)")
|
100
|
-
# ((alt a (diff b c)) '')
|
101
|
-
#
|
102
|
-
# >>> expression("a b | c d")
|
103
|
-
# ((alt (seq a b) (seq c d)) '')
|
104
|
-
#
|
105
|
-
# >>> expression("a | b | c")
|
106
|
-
# ((alt a b c) '')
|
107
|
-
#
|
108
|
-
# >>> expression("a) b c")
|
109
|
-
# (a ' b c')
|
110
|
-
#
|
111
|
-
# >>> expression("BaseDecl? PrefixDecl*")
|
112
|
-
# ((seq (opt BaseDecl) (star PrefixDecl)) '')
|
113
|
-
#
|
114
|
-
# >>> expression("NCCHAR1 | diff | [0-9] | #x00B7 | [#x0300-#x036F] | \[#x203F-#x2040\]")
|
115
|
-
# ((alt NCCHAR1 diff
|
116
|
-
# (range '0-9')
|
117
|
-
# (hex '#x00B7')
|
118
|
-
# (range '#x0300-#x036F')
|
119
|
-
# (range, '#x203F-#x2040')) '')
|
120
|
-
#
|
121
|
-
# @param [String] s
|
122
|
-
# @return [Array]
|
123
|
-
def expression(s)
|
124
|
-
debug("expression") {"(#{s.inspect})"}
|
125
|
-
e, s = depth {alt(s)}
|
126
|
-
debug {"=> alt returned #{[e, s].inspect}"}
|
127
|
-
unless s.to_s.empty?
|
128
|
-
t, ss = depth {terminal(s)}
|
129
|
-
debug {"=> terminal returned #{[t, ss].inspect}"}
|
130
|
-
return [e, ss] if t.is_a?(Array) && t.first == :")"
|
131
|
-
end
|
132
|
-
[e, s]
|
48
|
+
# Terminal for `RANGE` is matched as part of a `primary` rule.
|
49
|
+
#
|
50
|
+
# [14] RANGE ::= '[' ((R_CHAR '-' R_CHAR) | (HEX '-' HEX) | R_CHAR | HEX)+ '-'? ']' - LHS
|
51
|
+
terminal(:RANGE, RANGE) do |value|
|
52
|
+
[:range, value[1..-2]]
|
133
53
|
end
|
134
|
-
|
135
|
-
|
136
|
-
#
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
# @return [Array]
|
141
|
-
def alt(s)
|
142
|
-
debug("alt") {"(#{s.inspect})"}
|
143
|
-
args = []
|
144
|
-
while !s.to_s.empty?
|
145
|
-
e, s = depth {seq(s)}
|
146
|
-
debug {"=> seq returned #{[e, s].inspect}"}
|
147
|
-
if e.to_s.empty?
|
148
|
-
break unless args.empty?
|
149
|
-
e = [:seq, []] # empty sequence
|
150
|
-
end
|
151
|
-
args << e
|
152
|
-
unless s.to_s.empty?
|
153
|
-
t, ss = depth {terminal(s)}
|
154
|
-
break unless t[0] == :alt
|
155
|
-
s = ss
|
156
|
-
end
|
157
|
-
end
|
158
|
-
args.length > 1 ? [args.unshift(:alt), s] : [e, s]
|
54
|
+
|
55
|
+
# Terminal for `O_RANGE` is matched as part of a `primary` rule.
|
56
|
+
#
|
57
|
+
# [15] O_RANGE ::= '[^' ((R_CHAR '-' R_CHAR) | (HEX '-' HEX) | R_CHAR | HEX)+ '-'? ']'
|
58
|
+
terminal(:O_RANGE, O_RANGE) do |value|
|
59
|
+
[:range, value[1..-2]]
|
159
60
|
end
|
160
|
-
|
161
|
-
|
162
|
-
#
|
163
|
-
#
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
61
|
+
|
62
|
+
# Match double quote string
|
63
|
+
#
|
64
|
+
# [16] STRING1 ::= '"' (CHAR - '"')* '"'
|
65
|
+
terminal(:STRING1, STRING1) do |value|
|
66
|
+
value[1..-2]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Match single quote string
|
70
|
+
#
|
71
|
+
# [17] STRING2 ::= "'" (CHAR - "'")* "'"
|
72
|
+
terminal(:STRING2, STRING2) do |value|
|
73
|
+
value[1..-2]
|
74
|
+
end
|
75
|
+
|
76
|
+
# The `CHAR` and `R_CHAR` productions are not used explicitly
|
77
|
+
|
78
|
+
# Match `POSTFIX` terminal
|
79
|
+
#
|
80
|
+
# [20] POSTFIX ::= [?*+]
|
81
|
+
terminal(:POSTFIX, POSTFIX)
|
82
|
+
|
83
|
+
# The `PASS` productions is not used explicitly
|
84
|
+
|
85
|
+
# ## Non-terminal productions
|
86
|
+
# Define productions for non-Termainals. This can include `start_production` as well as `production` to hook into rule start and end. In some cases, we need to use sub-productions as generated when turning EBNF into PEG.
|
87
|
+
#
|
88
|
+
# Productions are defined with a symbol matching the associated rule name.
|
89
|
+
#
|
90
|
+
# The result of the productions is typically the abstract syntax tree matched by the rule, so far, but could be a specific semantic value, or could be ignored with the result being returned via the `callback`.
|
91
|
+
#
|
92
|
+
# The `value` parameter is the result returned from child productions
|
93
|
+
#
|
94
|
+
# The `data` parameter other data which may be returned by child productions placing information onto their input (unused in this example).
|
95
|
+
#
|
96
|
+
# The `callback` parameter provides access to a callback defined in the call to `parse`).
|
97
|
+
|
98
|
+
# Production for end of `declaration` non-terminal.
|
99
|
+
#
|
100
|
+
# Look for `@terminals` to change parser state to parsing terminals.
|
101
|
+
#
|
102
|
+
# Clears the packrat parser when called.
|
103
|
+
#
|
104
|
+
# `@pass` is ignored here.
|
105
|
+
#
|
106
|
+
# [2] declaration ::= '@terminals' | pass
|
107
|
+
production(:declaration, clear_packrat: true) do |value, data, callback|
|
108
|
+
# value contains a declaration.
|
109
|
+
# Invoke callback
|
110
|
+
callback.call(:terminals) if value == '@terminals'
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Production for end of `rule` non-terminal.
|
115
|
+
#
|
116
|
+
# By setting `as_hash: true` in the `start_production`, the `value` parameter will be in the form `{LHS: "v", expression: "v"}`. Otherwise, it would be expressed using an array of hashes of the form `[{LHS: "v"}, {expression: "v"}]`.
|
117
|
+
#
|
118
|
+
# Clears the packrat parser when called.
|
119
|
+
#
|
120
|
+
# Create rule from expression value and pass to callback
|
121
|
+
#
|
122
|
+
# [3] rule ::= LHS expression
|
123
|
+
start_production(:rule, as_hash: true)
|
124
|
+
production(:rule, clear_packrat: true) do |value, data, callback|
|
125
|
+
# value contains an expression.
|
126
|
+
# Invoke callback
|
127
|
+
id, sym = value[:LHS]
|
128
|
+
expression = value[:expression]
|
129
|
+
callback.call(:rule, EBNF::Rule.new(sym.to_sym, id, expression))
|
130
|
+
nil
|
131
|
+
end
|
132
|
+
|
133
|
+
# Production for end of `expression` non-terminal.
|
134
|
+
# Passes through the optimized value of the alt production as follows:
|
135
|
+
#
|
136
|
+
# The `value` parameter, is of the form `[{alt: "v"}]`.
|
137
|
+
#
|
138
|
+
# [:alt foo] => foo
|
139
|
+
# [:alt foo bar] => [:alt foo bar]
|
140
|
+
#
|
141
|
+
# [4] expression ::= alt
|
142
|
+
production(:expression) do |value|
|
143
|
+
value.first[:alt]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Production for end of `alt` non-terminal.
|
147
|
+
# Passes through the optimized value of the seq production as follows:
|
148
|
+
#
|
149
|
+
# The `value` parameter, is of the form `{seq: "v", _alt_1: "v"}`.
|
150
|
+
#
|
151
|
+
# [:seq foo] => foo
|
152
|
+
# [:seq foo bar] => [:seq foo bar]
|
153
|
+
#
|
154
|
+
# Note that this also may just pass through from `_alt_1`
|
155
|
+
#
|
156
|
+
# [5] alt ::= seq ('|' seq)*
|
157
|
+
start_production(:alt, as_hash: true)
|
158
|
+
production(:alt) do |value|
|
159
|
+
if value[:_alt_1].length > 0
|
160
|
+
[:alt, value[:seq]] + value[:_alt_1]
|
186
161
|
else
|
187
|
-
[
|
162
|
+
value[:seq]
|
188
163
|
end
|
189
164
|
end
|
190
|
-
|
191
|
-
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
debug {"=> postfix returned #{[e1, s].inspect}"}
|
200
|
-
unless e1.to_s.empty?
|
201
|
-
unless s.to_s.empty?
|
202
|
-
t, ss = depth {terminal(s)}
|
203
|
-
debug {"diff #{[t, ss].inspect}"}
|
204
|
-
if t.is_a?(Array) && t.first == :diff
|
205
|
-
s = ss
|
206
|
-
e2, s = primary(s)
|
207
|
-
unless e2.to_s.empty?
|
208
|
-
return [[:diff, e1, e2], s]
|
209
|
-
else
|
210
|
-
error("diff", "Syntax Error")
|
211
|
-
raise "Syntax Error"
|
212
|
-
end
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
[e1, s]
|
165
|
+
|
166
|
+
# Production for end of `_alt_1` non-terminal.
|
167
|
+
# Used to collect the `('|' seq)*` portion of the `alt` non-terminal:
|
168
|
+
#
|
169
|
+
# The `value` parameter, is of the form `[{seq: ["v"]}]`.
|
170
|
+
#
|
171
|
+
# [5] _alt_1 ::= ('|' seq)*
|
172
|
+
production(:_alt_1) do |value|
|
173
|
+
value.map {|a1| a1.last[:seq]}.compact # Get rid of '|'
|
217
174
|
end
|
218
|
-
|
219
|
-
|
220
|
-
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
if !s.to_s.empty?
|
233
|
-
t, ss = depth {terminal(s)}
|
234
|
-
debug {"=> #{[t, ss].inspect}"}
|
235
|
-
if t.is_a?(Array) && [:opt, :star, :plus].include?(t.first)
|
236
|
-
return [[t.first, e], ss]
|
237
|
-
end
|
238
|
-
end
|
239
|
-
[e, s]
|
175
|
+
|
176
|
+
# Production for end of `seq` non-terminal.
|
177
|
+
# Passes through the optimized value of the `diff` production as follows:
|
178
|
+
#
|
179
|
+
# The `value` parameter, is an array of values, which cannot be empty.
|
180
|
+
#
|
181
|
+
# [:diff foo] => foo
|
182
|
+
# [:diff foo bar] => [:diff foo bar]
|
183
|
+
#
|
184
|
+
# Note that this also may just pass through from `_seq_1`
|
185
|
+
#
|
186
|
+
# [6] seq ::= diff+
|
187
|
+
production(:seq) do |value|
|
188
|
+
value.length == 1 ? value.first : ([:seq] + value)
|
240
189
|
end
|
241
190
|
|
242
|
-
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
if t.is_a?(Symbol) || t.is_a?(String)
|
252
|
-
[t, s]
|
253
|
-
elsif %w(range hex).map(&:to_sym).include?(t.first)
|
254
|
-
[t, s]
|
255
|
-
elsif t.first == :"("
|
256
|
-
e, s = depth {expression(s)}
|
257
|
-
debug {"=> expression returned #{[e, s].inspect}"}
|
258
|
-
[e, s]
|
191
|
+
# `Diff` production returns concatenated postfix values
|
192
|
+
#
|
193
|
+
# The `value` parameter, is of the form `{postfix: "v", _diff_1: "v"}`.
|
194
|
+
#
|
195
|
+
# [7] diff ::= postfix ('-' postfix)?
|
196
|
+
start_production(:diff, as_hash: true)
|
197
|
+
production(:diff) do |value|
|
198
|
+
if value[:_diff_1]
|
199
|
+
[:diff, value[:postfix], value[:_diff_1]]
|
259
200
|
else
|
260
|
-
[
|
201
|
+
value[:postfix]
|
261
202
|
end
|
262
203
|
end
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
#
|
269
|
-
#
|
270
|
-
#
|
271
|
-
#
|
272
|
-
#
|
273
|
-
#
|
274
|
-
#
|
275
|
-
#
|
276
|
-
#
|
277
|
-
#
|
278
|
-
#
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
204
|
+
|
205
|
+
production(:_diff_1) do |value|
|
206
|
+
value.last[:postfix] if value
|
207
|
+
end
|
208
|
+
|
209
|
+
# Production for end of `postfix` non-terminal.
|
210
|
+
# Either returns the `primary` production value, or as modified by the `postfix`.
|
211
|
+
#
|
212
|
+
# The `value` parameter, is of the form `{primary: "v", _postfix_1: "v"}`.
|
213
|
+
#
|
214
|
+
# [:primary] => [:primary]
|
215
|
+
# [:primary, '*'] => [:star, :primary]
|
216
|
+
# [:primary, '+'] => [:plus, :primary]
|
217
|
+
# [:primary, '?'] => [:opt, :primary]
|
218
|
+
#
|
219
|
+
# [8] postfix ::= primary POSTFIX?
|
220
|
+
start_production(:postfix, as_hash: true)
|
221
|
+
production(:postfix) do |value|
|
222
|
+
# Push result onto input stack, as the `diff` production can have some number of `postfix` values that are applied recursively
|
223
|
+
case value[:_postfix_1]
|
224
|
+
when "*" then [:star, value[:primary]]
|
225
|
+
when "+" then [:plus, value[:primary]]
|
226
|
+
when "?" then [:opt, value[:primary]]
|
227
|
+
else value[:primary]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Production for end of `primary` non-terminal.
|
232
|
+
# Places `:primary` on the stack
|
233
|
+
#
|
234
|
+
# The `value` parameter, is either a string (for a terminal) or an array of the form `['(': '(', expression: "v", ')', ')']`.
|
235
|
+
#
|
236
|
+
# This may either be a terminal, or the result of an `expression`.
|
237
|
+
#
|
238
|
+
# [9] primary ::= HEX
|
239
|
+
# | SYMBOL
|
240
|
+
# | RANGE
|
241
|
+
# | O_RANGE
|
242
|
+
# | STRING1
|
243
|
+
# | STRING2
|
244
|
+
# | '(' expression ')'
|
245
|
+
production(:primary) do |value|
|
246
|
+
Array(value).length > 2 ? value[1][:expression] : value
|
247
|
+
end
|
248
|
+
|
249
|
+
# Production for end of pass non-terminal.
|
250
|
+
#
|
251
|
+
# [10] pass ::= '@pass' expression
|
252
|
+
production(:pass) do |value, data, callback|
|
253
|
+
# Invoke callback
|
254
|
+
callback.call(:pass, value.last[:expression])
|
255
|
+
end
|
256
|
+
|
257
|
+
# ## Parser invocation.
|
258
|
+
# On start, yield ourselves if a block is given, otherwise, return this parser instance
|
259
|
+
#
|
260
|
+
# @param [#read, #to_s] input
|
261
|
+
# @param [Hash{Symbol => Object}] options
|
262
|
+
# @option options [Boolean] :level
|
263
|
+
# Trace level. 0(debug), 1(info), 2(warn), 3(error).
|
264
|
+
# @return [EBNFParser]
|
265
|
+
def initialize(input, **options, &block)
|
266
|
+
# If the `level` option is set, instantiate a logger for collecting trace information.
|
267
|
+
if options.has_key?(:level)
|
268
|
+
options[:logger] = Logger.new(STDERR)
|
269
|
+
options[:logger].level = options[:level]
|
270
|
+
options[:logger].formatter = lambda {|severity, datetime, progname, msg| "#{severity} #{msg}\n"}
|
271
|
+
end
|
272
|
+
|
273
|
+
# Read input, if necessary, which will be used in a Scanner.
|
274
|
+
@input = input.respond_to?(:read) ? input.read : input.to_s
|
275
|
+
|
276
|
+
parsing_terminals = false
|
277
|
+
@ast = []
|
278
|
+
parse(@input, :ebnf, EBNFMeta::RULES,
|
279
|
+
# Use an optimized Regexp for whitespace
|
280
|
+
whitespace: EBNF::Terminals::PASS,
|
281
|
+
**options
|
282
|
+
) do |context, *data|
|
283
|
+
rule = case context
|
284
|
+
when :terminals
|
285
|
+
# After parsing `@terminals`
|
286
|
+
# This changes the state of the parser to treat subsequent rules as terminals.
|
287
|
+
parsing_terminals = true
|
288
|
+
rule = EBNF::Rule.new(nil, nil, data.first, kind: :terminals)
|
289
|
+
when :pass
|
290
|
+
# After parsing `@pass`
|
291
|
+
# This defines a specific rule for whitespace.
|
292
|
+
rule = EBNF::Rule.new(nil, nil, data.first, kind: :pass)
|
293
|
+
when :rule
|
294
|
+
# A rule which has already been turned into a `Rule` object.
|
295
|
+
rule = data.first
|
296
|
+
rule.kind = :terminal if parsing_terminals
|
297
|
+
rule
|
298
|
+
end
|
299
|
+
@ast << rule if rule
|
319
300
|
end
|
301
|
+
rescue EBNF::PEG::Parser::Error => e
|
302
|
+
raise SyntaxError, e.message
|
320
303
|
end
|
321
304
|
end
|
322
305
|
end
|