ebnf 0.3.5 → 0.3.6
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/VERSION +1 -1
- data/bin/ebnf +2 -1
- data/etc/ebnf.ebnf +19 -19
- data/etc/ebnf.html +214 -0
- data/etc/ebnf.ll1.sxp +57 -58
- data/etc/ebnf.rb +0 -76
- data/etc/ebnf.sxp +18 -19
- data/lib/ebnf/base.rb +7 -0
- data/lib/ebnf/ll1/scanner.rb +17 -1
- data/lib/ebnf/parser.rb +10 -7
- data/lib/ebnf/rule.rb +5 -2
- data/lib/ebnf/writer.rb +120 -23
- metadata +56 -35
data/lib/ebnf/parser.rb
CHANGED
@@ -17,9 +17,12 @@ 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
|
20
|
+
# Eat comments /* .. */
|
21
|
+
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
22
|
+
when s = scanner.scan(%r(\(\*([^\*]|\*[^\)])*\*\))m)
|
23
|
+
# Eat comments (* .. *)
|
21
24
|
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
22
|
-
when s = scanner.scan(%r((
|
25
|
+
when s = scanner.scan(%r((#(?!x)|//).*$))
|
23
26
|
# Eat comments
|
24
27
|
cur_lineno += s.count("\n")
|
25
28
|
debug("eachRule(comment)") { "[#{cur_lineno}] #{s.inspect}" }
|
@@ -266,18 +269,18 @@ module EBNF
|
|
266
269
|
def terminal(s)
|
267
270
|
s = s.strip
|
268
271
|
case m = s[0,1]
|
269
|
-
when '"', "'" # STRING1 or STRING2
|
270
|
-
l, s = s[1..-1].split(m.rstrip
|
272
|
+
when '"', "'" # STRING1 or STRING2
|
273
|
+
l, s = s[1..-1].split(m.rstrip, 2)
|
271
274
|
[LL1::Lexer.unescape_string(l), s]
|
272
|
-
when '[' #
|
275
|
+
when '[' # RANGE, O_RANGE
|
273
276
|
l, s = s[1..-1].split(/(?<=[^\\])\]/, 2)
|
274
277
|
[[:range, LL1::Lexer.unescape_string(l)], s]
|
275
278
|
when '#' # HEX
|
276
|
-
s.match(/(
|
279
|
+
s.match(/(#x\h+)(.*)$/)
|
277
280
|
l, s = $1, $2
|
278
281
|
[[:hex, l], s]
|
279
282
|
when /[\w\.]/ # SYMBOL
|
280
|
-
s.match(/(\w+)(.*)$/)
|
283
|
+
s.match(/([\w\.]+)(.*)$/)
|
281
284
|
l, s = $1, $2
|
282
285
|
[l.to_sym, s]
|
283
286
|
when '@' # @pass or @terminals
|
data/lib/ebnf/rule.rb
CHANGED
@@ -161,11 +161,14 @@ module EBNF
|
|
161
161
|
# Transform EBNF rule to BNF rules:
|
162
162
|
#
|
163
163
|
# * Transform (a [n] rule (op1 (op2))) into two rules:
|
164
|
-
# (a [n] rule (op1
|
164
|
+
# (a [n] rule (op1 _a_1))
|
165
165
|
# (_a_1 [n.1] rule (op2))
|
166
|
-
# * Transform (a rule (opt b)) into (a rule (alt _empty
|
166
|
+
# * Transform (a rule (opt b)) into (a rule (alt _empty b))
|
167
167
|
# * Transform (a rule (star b)) into (a rule (alt _empty (seq b a)))
|
168
168
|
# * Transform (a rule (plus b)) into (a rule (seq b (star b)
|
169
|
+
#
|
170
|
+
# Transformation includes information used to re-construct non-transformed
|
171
|
+
# AST representation
|
169
172
|
# @return [Array<Rule>]
|
170
173
|
def to_bnf
|
171
174
|
return [self] unless rule?
|
data/lib/ebnf/writer.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require 'rdf'
|
3
|
+
require 'strscan' unless defined?(StringScanner)
|
3
4
|
|
4
5
|
##
|
5
6
|
# Serialize ruleset back to EBNF
|
@@ -38,12 +39,27 @@ module EBNF
|
|
38
39
|
Writer.new(rules, out: out)
|
39
40
|
end
|
40
41
|
|
42
|
+
##
|
43
|
+
# Write formatted rules to an IO like object as HTML
|
44
|
+
#
|
45
|
+
# @param [Array<Rule>] rules
|
46
|
+
# @return [Object]
|
47
|
+
def self.html(*rules)
|
48
|
+
require 'stringio' unless defined?(StringIO)
|
49
|
+
buf = StringIO.new
|
50
|
+
Writer.new(rules, out: buf, html: true)
|
51
|
+
buf.string
|
52
|
+
end
|
53
|
+
|
41
54
|
##
|
42
55
|
# @param [Array<Rule>] rules
|
43
56
|
# @param [Hash{Symbol => Object}] options
|
44
57
|
# @option options [Symbol] :format
|
45
58
|
# @option options [#write] :out ($stdout)
|
59
|
+
# @option options [Boolean] :html (false)
|
60
|
+
# Format as HTML
|
46
61
|
def initialize(rules, options = {})
|
62
|
+
@options = options.dup
|
47
63
|
out = options.fetch(:out, $stdio)
|
48
64
|
#fmt = options.fetch(:format, :ebnf)
|
49
65
|
|
@@ -51,13 +67,24 @@ module EBNF
|
|
51
67
|
max_id = rules.max_by {|r| r.id.to_s.length}.id.to_s.length
|
52
68
|
max_sym = rules.max_by {|r| r.sym.to_s.length}.sym.to_s.length
|
53
69
|
lhs_length = max_sym + 3
|
54
|
-
lhs_fmt = "
|
70
|
+
lhs_fmt = "%<sym>-#{max_sym}s ::= "
|
55
71
|
if max_id > 0
|
56
|
-
lhs_fmt = "
|
72
|
+
lhs_fmt = "%<id>-#{max_id+2}s " + lhs_fmt
|
57
73
|
lhs_length += max_id + 3
|
58
74
|
end
|
59
75
|
rhs_length = LINE_LENGTH - lhs_length
|
60
76
|
|
77
|
+
if @options[:html]
|
78
|
+
# Output as formatted HTML
|
79
|
+
require 'haml'
|
80
|
+
html = Haml::Engine.new(HAML_DESC).render(self, rules: rules) do |rule|
|
81
|
+
formatted_expr = format(rule.expr)
|
82
|
+
formatted_expr.length > rhs_length ? format(rule.expr, "\n") : formatted_expr
|
83
|
+
end
|
84
|
+
out.write html
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
61
88
|
# Format each rule, considering the available rhs size
|
62
89
|
rules.each do |rule|
|
63
90
|
buffer = if rule.pass?
|
@@ -78,48 +105,118 @@ module EBNF
|
|
78
105
|
protected
|
79
106
|
# Format the expression part of a rule
|
80
107
|
def format(expr, sep = nil)
|
81
|
-
return expr.to_s if expr.is_a?(Symbol)
|
82
|
-
|
108
|
+
return (@options[:html] ? %(<a href="#grammar-production-#{expr}">#{expr}</a>) : expr.to_s) if expr.is_a?(Symbol)
|
109
|
+
if expr.is_a?(String)
|
110
|
+
if expr.length == 1
|
111
|
+
return format_char(expr)
|
112
|
+
elsif expr =~ /\A#x\h+/
|
113
|
+
return (@options[:html] ? %(<code class="grammar-char-escape">#{expr}</code>) : expr)
|
114
|
+
elsif expr =~ /"/
|
115
|
+
return (@options[:html] ? %('<code class="grammar-literal">#{escape(expr, "'")}</code>') : %('#{escape(expr, "'")}'))
|
116
|
+
else
|
117
|
+
return (@options[:html] ? %("<code class="grammar-literal">#{escape(expr, '"')}</code>") : %("#{escape(expr, '"')}"))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
parts = {
|
121
|
+
alt: (@options[:html] ? "<code>|</code> " : "| "),
|
122
|
+
diff: (@options[:html] ? "<code>-</code> " : "- "),
|
123
|
+
star: (@options[:html] ? "<code>*</code> " : "*"),
|
124
|
+
plus: (@options[:html] ? "<code>+</code> " : "+"),
|
125
|
+
opt: (@options[:html] ? "<code>?</code> " : "?")
|
126
|
+
}
|
127
|
+
lparen = (@options[:html] ? "<code>(</code> " : "(")
|
128
|
+
rparen = (@options[:html] ? "<code>)</code> " : ")")
|
83
129
|
|
84
130
|
case expr.first
|
85
131
|
when :alt, :diff
|
86
|
-
this_sep = (sep ? sep : " ") +
|
132
|
+
this_sep = (sep ? sep : " ") + parts[expr.first.to_sym]
|
87
133
|
expr[1..-1].map {|e| format(e)}.join(this_sep)
|
88
134
|
when :star, :plus, :opt
|
89
135
|
raise "Expected star expression to have a single operand" unless expr.length == 2
|
90
|
-
char =
|
136
|
+
char = parts[expr.first.to_sym]
|
91
137
|
r = format(expr[1])
|
92
138
|
(r.start_with?("(") || Array(expr[1]).length == 1) ? "#{r}#{char}" : "(#{r})#{char}"
|
139
|
+
when :hex
|
140
|
+
(@options[:html] ? %(<code class="grammar-char-escape">#{expr.last}</code>) : expr.last)
|
93
141
|
when :range
|
94
|
-
|
95
|
-
"[" + parts.map {|e| format(e)[1..-2]}.join("-") + "]"
|
142
|
+
format_range(expr.last)
|
96
143
|
when :seq
|
97
144
|
this_sep = (sep ? sep : " ")
|
98
|
-
expr[1..-1].map {|e| r = format(e); Array(e).length > 2 ? "
|
145
|
+
expr[1..-1].map {|e| r = format(e); Array(e).length > 2 ? "#{lparen}#{r}#{rparen}" : r}.join(this_sep)
|
99
146
|
else
|
100
147
|
raise "Unknown operator: #{expr.first}"
|
101
148
|
end
|
102
149
|
end
|
103
150
|
|
104
|
-
|
151
|
+
# Format a single-character string, prefering hex for non-main ASCII
|
152
|
+
def format_char(c)
|
153
|
+
case c.ord
|
154
|
+
when 0x22 then (@options[:html] ? %('<code class="grammar-literal">"</code>') : %{'"'})
|
155
|
+
when (0x23..0x7e) then (@options[:html] ? %("<code class="grammar-literal">#{c}</code>") : %{"#{c}"})
|
156
|
+
else (@options[:html] ? %(<code class="grammar-char-escape">#{escape_hex(c)}</code>) : escape_hex(c))
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Format a range
|
161
|
+
def format_range(string)
|
162
|
+
lbrac = (@options[:html] ? "<code>[</code> " : "[")
|
163
|
+
rbrac = (@options[:html] ? "<code>]</code> " : "]")
|
164
|
+
dash = (@options[:html] ? "<code>-</code> " : "-")
|
165
|
+
|
166
|
+
buffer = lbrac
|
167
|
+
s = StringScanner.new(string)
|
168
|
+
while !s.eos?
|
169
|
+
case
|
170
|
+
when s.scan(/\A[!"\u0024-\u007e]+/)
|
171
|
+
buffer << (@options[:html] ? %(<code class="grammar-literal">#{s.matched}</code>) : s.matched)
|
172
|
+
when s.scan(/\A#x\h+/)
|
173
|
+
buffer << (@options[:html] ? %(<code class="grammar-char-escape">#{s.matched}</code>) : s.matched)
|
174
|
+
when s.scan(/\A-/)
|
175
|
+
buffer << dash
|
176
|
+
else
|
177
|
+
buffer << (@options[:html] ? %(<code class="grammar-char-escape">#{escape_hex(s.getch)}</code>) : escape_hex(s.getch))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
buffer + rbrac
|
181
|
+
end
|
182
|
+
|
183
|
+
# Escape a string, using as many UTF-8 characters as possible
|
184
|
+
def escape(string, quote = '"')
|
105
185
|
buffer = ""
|
106
186
|
string.each_char do |c|
|
107
|
-
buffer << case c.
|
108
|
-
when
|
109
|
-
when "
|
110
|
-
|
111
|
-
when "\\" then "\\\\"
|
112
|
-
#when "(" then "\\("
|
113
|
-
#when ")" then "\\)"
|
114
|
-
#when "[" then "\\["
|
115
|
-
#when "]" then "\\]"
|
116
|
-
#when "-" then "\\\\-"
|
117
|
-
when "'" then "\\'"
|
118
|
-
when '"' then "\\\""
|
119
|
-
else c
|
187
|
+
buffer << case (u = c.ord)
|
188
|
+
when (0x00..0x1f) then "#x%02X" % u
|
189
|
+
when quote.ord then "#x%02X" % u
|
190
|
+
else c
|
120
191
|
end
|
121
192
|
end
|
122
193
|
buffer
|
123
194
|
end
|
195
|
+
|
196
|
+
def escape_hex(u)
|
197
|
+
fmt = case u.ord
|
198
|
+
when 0x0000..0x00ff then "#x%02X"
|
199
|
+
when 0x0100..0xffff then "#x%04X"
|
200
|
+
else "#x%08X"
|
201
|
+
end
|
202
|
+
sprintf(fmt, u.ord)
|
203
|
+
end
|
204
|
+
|
205
|
+
HAML_DESC = %q(
|
206
|
+
%table.grammar
|
207
|
+
%tbody#grammar-productions
|
208
|
+
- rules.each do |rule|
|
209
|
+
%tr{id: "grammar-production-#{rule.sym}"}
|
210
|
+
- if rule.pass?
|
211
|
+
%td{colspan: 3}
|
212
|
+
%code<="@pass"
|
213
|
+
- else
|
214
|
+
%td<= "[#{rule.id}]"
|
215
|
+
%td<
|
216
|
+
%code<= rule.sym
|
217
|
+
%td<= "::="
|
218
|
+
%td
|
219
|
+
!= yield rule
|
220
|
+
).gsub(/^ /, '')
|
124
221
|
end
|
125
222
|
end
|
metadata
CHANGED
@@ -1,83 +1,103 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ebnf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregg Kellogg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2014-04-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sxp
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '0.1'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.1.3
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - "~>"
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
29
|
+
version: '0.1'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.1.3
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: rdf
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
|
-
- -
|
37
|
+
- - "~>"
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
39
|
+
version: '1.1'
|
34
40
|
type: :runtime
|
35
41
|
prerelease: false
|
36
42
|
version_requirements: !ruby/object:Gem::Requirement
|
37
43
|
requirements:
|
38
|
-
- -
|
44
|
+
- - "~>"
|
39
45
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
46
|
+
version: '1.1'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: haml
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '4.0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '4.0'
|
41
61
|
- !ruby/object:Gem::Dependency
|
42
62
|
name: rspec
|
43
63
|
requirement: !ruby/object:Gem::Requirement
|
44
64
|
requirements:
|
45
|
-
- -
|
65
|
+
- - "~>"
|
46
66
|
- !ruby/object:Gem::Version
|
47
|
-
version: 2.
|
67
|
+
version: '2.14'
|
48
68
|
type: :development
|
49
69
|
prerelease: false
|
50
70
|
version_requirements: !ruby/object:Gem::Requirement
|
51
71
|
requirements:
|
52
|
-
- -
|
72
|
+
- - "~>"
|
53
73
|
- !ruby/object:Gem::Version
|
54
|
-
version: 2.
|
74
|
+
version: '2.14'
|
55
75
|
- !ruby/object:Gem::Dependency
|
56
76
|
name: yard
|
57
77
|
requirement: !ruby/object:Gem::Requirement
|
58
78
|
requirements:
|
59
|
-
- -
|
79
|
+
- - "~>"
|
60
80
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.8
|
81
|
+
version: '0.8'
|
62
82
|
type: :development
|
63
83
|
prerelease: false
|
64
84
|
version_requirements: !ruby/object:Gem::Requirement
|
65
85
|
requirements:
|
66
|
-
- -
|
86
|
+
- - "~>"
|
67
87
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.8
|
88
|
+
version: '0.8'
|
69
89
|
- !ruby/object:Gem::Dependency
|
70
90
|
name: rake
|
71
91
|
requirement: !ruby/object:Gem::Requirement
|
72
92
|
requirements:
|
73
|
-
- -
|
93
|
+
- - ">="
|
74
94
|
- !ruby/object:Gem::Version
|
75
95
|
version: '0'
|
76
96
|
type: :development
|
77
97
|
prerelease: false
|
78
98
|
version_requirements: !ruby/object:Gem::Requirement
|
79
99
|
requirements:
|
80
|
-
- -
|
100
|
+
- - ">="
|
81
101
|
- !ruby/object:Gem::Version
|
82
102
|
version: '0'
|
83
103
|
description: EBNF is a Ruby parser for W3C EBNF and a parser generator for compliant
|
@@ -93,19 +113,10 @@ files:
|
|
93
113
|
- README.md
|
94
114
|
- UNLICENSE
|
95
115
|
- VERSION
|
96
|
-
-
|
97
|
-
- lib/ebnf/bnf.rb
|
98
|
-
- lib/ebnf/ll1/lexer.rb
|
99
|
-
- lib/ebnf/ll1/parser.rb
|
100
|
-
- lib/ebnf/ll1/scanner.rb
|
101
|
-
- lib/ebnf/ll1.rb
|
102
|
-
- lib/ebnf/parser.rb
|
103
|
-
- lib/ebnf/rule.rb
|
104
|
-
- lib/ebnf/version.rb
|
105
|
-
- lib/ebnf/writer.rb
|
106
|
-
- lib/ebnf.rb
|
116
|
+
- bin/ebnf
|
107
117
|
- etc/doap.ttl
|
108
118
|
- etc/ebnf.ebnf
|
119
|
+
- etc/ebnf.html
|
109
120
|
- etc/ebnf.ll1.sxp
|
110
121
|
- etc/ebnf.rb
|
111
122
|
- etc/ebnf.sxp
|
@@ -113,7 +124,17 @@ files:
|
|
113
124
|
- etc/turtle.ll1.sxp
|
114
125
|
- etc/turtle.rb
|
115
126
|
- etc/turtle.sxp
|
116
|
-
-
|
127
|
+
- lib/ebnf.rb
|
128
|
+
- lib/ebnf/base.rb
|
129
|
+
- lib/ebnf/bnf.rb
|
130
|
+
- lib/ebnf/ll1.rb
|
131
|
+
- lib/ebnf/ll1/lexer.rb
|
132
|
+
- lib/ebnf/ll1/parser.rb
|
133
|
+
- lib/ebnf/ll1/scanner.rb
|
134
|
+
- lib/ebnf/parser.rb
|
135
|
+
- lib/ebnf/rule.rb
|
136
|
+
- lib/ebnf/version.rb
|
137
|
+
- lib/ebnf/writer.rb
|
117
138
|
homepage: http://github.com/gkellogg/ebnf
|
118
139
|
licenses:
|
119
140
|
- Public Domain
|
@@ -124,17 +145,17 @@ require_paths:
|
|
124
145
|
- lib
|
125
146
|
required_ruby_version: !ruby/object:Gem::Requirement
|
126
147
|
requirements:
|
127
|
-
- -
|
148
|
+
- - ">="
|
128
149
|
- !ruby/object:Gem::Version
|
129
150
|
version: 1.9.2
|
130
151
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
152
|
requirements:
|
132
|
-
- -
|
153
|
+
- - ">="
|
133
154
|
- !ruby/object:Gem::Version
|
134
155
|
version: '0'
|
135
156
|
requirements: []
|
136
157
|
rubyforge_project:
|
137
|
-
rubygems_version: 2.
|
158
|
+
rubygems_version: 2.2.2
|
138
159
|
signing_key:
|
139
160
|
specification_version: 4
|
140
161
|
summary: EBNF parser and parser generator.
|