ebnf 0.3.2 → 0.3.3
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 +2 -0
- data/VERSION +1 -1
- data/bin/ebnf +2 -1
- data/etc/ebnf.ebnf +13 -11
- data/etc/ebnf.ll1.sxp +11 -11
- data/etc/ebnf.sxp +11 -11
- data/lib/ebnf.rb +1 -0
- data/lib/ebnf/base.rb +7 -1
- data/lib/ebnf/ll1/lexer.rb +0 -7
- data/lib/ebnf/ll1/parser.rb +27 -23
- data/lib/ebnf/parser.rb +3 -2
- data/lib/ebnf/rule.rb +2 -1
- data/lib/ebnf/writer.rb +125 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1e418c3276c9f6252edf2fb8e89b815a95731629
|
4
|
+
data.tar.gz: 1f9fa2b8690bb7d8e5491a94b0f7f0fcc781af99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 597dcc0e0f174981fc7d8285238f1b74bec9c0e8b4e3294de792d555af36fac9ae4983e112b31534d42f8a24a05fb250535a5c0594d425c15a662d4e97a93187
|
7
|
+
data.tar.gz: b1df195a3f03dada05472fb222a8df83236c12de389e8566d914b847f85b1be66dd5b8e3e6efcf5c52420794336b185f8fb4e8af9663be455f34f7a325a0c078
|
data/README.md
CHANGED
@@ -84,6 +84,8 @@ to each `terminal` and `production`. A trivial parser loop can be described as f
|
|
84
84
|
The [EBNF][] variant used here is based on [W3C](http://w3.org/) [EBNF][] (see {file:etc/ebnf.ebnf EBNF grammar}) as defined in the
|
85
85
|
[XML 1.0 recommendation](http://www.w3.org/TR/REC-xml/), with minor extensions.
|
86
86
|
|
87
|
+
Parsing this grammar yields an S-Expression version: {file:etc/ebnf.ll1.sxp}.
|
88
|
+
|
87
89
|
## Example parser
|
88
90
|
For an example parser built using this gem, see {file:examples/ebnf-parser/README EBNF Parser example}. This example creates a parser for the [EBNF][] grammar which generates the same Abstract Syntax Tree as the built-in parser in the gem.
|
89
91
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.3.
|
1
|
+
0.3.3
|
data/bin/ebnf
CHANGED
@@ -48,7 +48,7 @@ OPT_ARGS = [
|
|
48
48
|
["--bnf", GetoptLong::NO_ARGUMENT, "Transform EBNF to BNF"],
|
49
49
|
["--evaluate","-e", GetoptLong::REQUIRED_ARGUMENT,"Evaluate argument as a JSON-LD document"],
|
50
50
|
["--ll1", GetoptLong::REQUIRED_ARGUMENT,"Generate First/Follow rules, argument is start symbol"],
|
51
|
-
["--format", "-f", GetoptLong::REQUIRED_ARGUMENT,"Specify output format one of ttl, sxp, or rb"],
|
51
|
+
["--format", "-f", GetoptLong::REQUIRED_ARGUMENT,"Specify output format one of ebnf, ttl, sxp, or rb"],
|
52
52
|
["--input-format", GetoptLong::REQUIRED_ARGUMENT,"Specify input format one of ebnf or sxp"],
|
53
53
|
["--mod-name", GetoptLong::REQUIRED_ARGUMENT,"Module name used when creating ruby tables"],
|
54
54
|
["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output to the specified file path"],
|
@@ -104,6 +104,7 @@ ebnf.make_bnf if options[:bnf] || options[:ll1]
|
|
104
104
|
ebnf.first_follow(options[:ll1]) if options[:ll1]
|
105
105
|
|
106
106
|
res = case options[:output_format]
|
107
|
+
when :ebnf then ebnf.to_s
|
107
108
|
when :sxp then ebnf.to_sxp
|
108
109
|
when :ttl then ebnf.to_ttl(options[:prefix], options[:namespace])
|
109
110
|
when :rb then dump_tables(ARGV[0], ebnf, out, options)
|
data/etc/ebnf.ebnf
CHANGED
@@ -32,32 +32,34 @@
|
|
32
32
|
|
33
33
|
@terminals
|
34
34
|
|
35
|
-
[11] LHS ::= ENUM SYMBOL "::="
|
35
|
+
[11] LHS ::= ENUM? SYMBOL "::="
|
36
36
|
|
37
37
|
[12] SYMBOL ::= ([a-z] | [A-Z] | [0-9] | "_" | ".")+
|
38
38
|
|
39
39
|
[13] HEX ::= '#x' ([0-9]|[a-f]|[A-F]) ([0-9]|[a-f]|[A-F])
|
40
40
|
|
41
|
-
[14] RANGE ::= '['
|
41
|
+
[14] RANGE ::= '[' R_CHAR '-' R_CHAR ']'
|
42
42
|
|
43
|
-
[15] ENUM ::= '['
|
43
|
+
[15] ENUM ::= '[' R_CHAR+ ']'
|
44
44
|
|
45
|
-
[16] O_RANGE ::= '[^'
|
45
|
+
[16] O_RANGE ::= '[^' R_CHAR '-' R_CHAR ']'
|
46
46
|
|
47
|
-
[17] O_ENUM ::= '[^'
|
47
|
+
[17] O_ENUM ::= '[^' R_CHAR+ ']'
|
48
48
|
|
49
|
-
[18] STRING1 ::= '"' (CHAR |
|
49
|
+
[18] STRING1 ::= '"' ((CHAR - '"') | "\t")* '"'
|
50
50
|
|
51
|
-
[19] STRING2 ::= "'" (CHAR |
|
51
|
+
[19] STRING2 ::= "'" ((CHAR - "'") | "\t")* "'"
|
52
52
|
|
53
53
|
[20] CHAR ::= HEX
|
54
|
-
|
55
|
-
|
54
|
+
| ('\\' [\\trn"'])
|
55
|
+
| [^\t\r\n]
|
56
|
+
|
57
|
+
[21] R_CHAR ::= CHAR - "]"
|
56
58
|
|
57
59
|
# Should be able to do this inline, but not until terminal regular expressions are created automatically
|
58
|
-
[
|
60
|
+
[22] POSTFIX ::= [?*+]
|
59
61
|
|
60
|
-
[
|
62
|
+
[23] PASS ::= ( [#x20\t\r\n]
|
61
63
|
| ( '#' | '//' ) [^\r\n]*
|
62
64
|
| '/*' (( '*' [^/] )? | [^*] )* '*/'
|
63
65
|
)+
|
data/etc/ebnf.ll1.sxp
CHANGED
@@ -141,22 +141,22 @@
|
|
141
141
|
(first "(" ENUM HEX O_ENUM O_RANGE RANGE STRING1 STRING2 SYMBOL)
|
142
142
|
(follow "@pass" "@terminals" LHS _eof)
|
143
143
|
(seq expression))
|
144
|
-
(terminal LHS "11" (seq ENUM SYMBOL "::="))
|
144
|
+
(terminal LHS "11" (seq (opt ENUM) SYMBOL "::="))
|
145
145
|
(terminal SYMBOL "12" (plus (alt (range "a-z") (range "A-Z") (range "0-9") "_" ".")))
|
146
146
|
(terminal HEX "13"
|
147
147
|
(seq "#x"
|
148
148
|
(alt (range "0-9") (range "a-f") (range "A-F"))
|
149
149
|
(alt (range "0-9") (range "a-f") (range "A-F"))) )
|
150
|
-
(terminal RANGE "14" (seq "["
|
151
|
-
(terminal ENUM "15" (seq "[" (plus
|
152
|
-
(terminal O_RANGE "16" (seq "[^"
|
153
|
-
(terminal O_ENUM "17" (seq "[^" (plus
|
154
|
-
(terminal STRING1 "18" (seq "\"" (star (alt CHAR
|
155
|
-
(terminal STRING2 "19" (seq "'" (star (alt CHAR
|
156
|
-
(terminal CHAR "20"
|
157
|
-
|
158
|
-
(terminal POSTFIX "
|
159
|
-
(terminal PASS "
|
150
|
+
(terminal RANGE "14" (seq "[" R_CHAR "-" R_CHAR "]"))
|
151
|
+
(terminal ENUM "15" (seq "[" (plus R_CHAR) "]"))
|
152
|
+
(terminal O_RANGE "16" (seq "[^" R_CHAR "-" R_CHAR "]"))
|
153
|
+
(terminal O_ENUM "17" (seq "[^" (plus R_CHAR) "]"))
|
154
|
+
(terminal STRING1 "18" (seq "\"" (star (alt (diff CHAR "\"") "\t")) "\""))
|
155
|
+
(terminal STRING2 "19" (seq "'" (star (alt (diff CHAR "'") "\t")) "'"))
|
156
|
+
(terminal CHAR "20" (alt HEX (seq "\\" (range "\\trn\"'")) (range "^\t\r\n")))
|
157
|
+
(terminal R_CHAR "21" (diff CHAR "]"))
|
158
|
+
(terminal POSTFIX "22" (range "?*+"))
|
159
|
+
(terminal PASS "23"
|
160
160
|
(plus
|
161
161
|
(alt
|
162
162
|
(range "#x20\t\r\n")
|
data/etc/ebnf.sxp
CHANGED
@@ -11,22 +11,22 @@
|
|
11
11
|
(rule primary "9"
|
12
12
|
(alt HEX SYMBOL RANGE ENUM O_RANGE O_ENUM STRING1 STRING2 (seq "(" expression ")")))
|
13
13
|
(rule pass "10" (seq "@pass" expression))
|
14
|
-
(terminal LHS "11" (seq ENUM SYMBOL "::="))
|
14
|
+
(terminal LHS "11" (seq (opt ENUM) SYMBOL "::="))
|
15
15
|
(terminal SYMBOL "12" (plus (alt (range "a-z") (range "A-Z") (range "0-9") "_" ".")))
|
16
16
|
(terminal HEX "13"
|
17
17
|
(seq "#x"
|
18
18
|
(alt (range "0-9") (range "a-f") (range "A-F"))
|
19
19
|
(alt (range "0-9") (range "a-f") (range "A-F"))) )
|
20
|
-
(terminal RANGE "14" (seq "["
|
21
|
-
(terminal ENUM "15" (seq "[" (plus
|
22
|
-
(terminal O_RANGE "16" (seq "[^"
|
23
|
-
(terminal O_ENUM "17" (seq "[^" (plus
|
24
|
-
(terminal STRING1 "18" (seq "\"" (star (alt CHAR
|
25
|
-
(terminal STRING2 "19" (seq "'" (star (alt CHAR
|
26
|
-
(terminal CHAR "20"
|
27
|
-
|
28
|
-
(terminal POSTFIX "
|
29
|
-
(terminal PASS "
|
20
|
+
(terminal RANGE "14" (seq "[" R_CHAR "-" R_CHAR "]"))
|
21
|
+
(terminal ENUM "15" (seq "[" (plus R_CHAR) "]"))
|
22
|
+
(terminal O_RANGE "16" (seq "[^" R_CHAR "-" R_CHAR "]"))
|
23
|
+
(terminal O_ENUM "17" (seq "[^" (plus R_CHAR) "]"))
|
24
|
+
(terminal STRING1 "18" (seq "\"" (star (alt (diff CHAR "\"") "\t")) "\""))
|
25
|
+
(terminal STRING2 "19" (seq "'" (star (alt (diff CHAR "'") "\t")) "'"))
|
26
|
+
(terminal CHAR "20" (alt HEX (seq "\\" (range "\\trn\"'")) (range "^\t\r\n")))
|
27
|
+
(terminal R_CHAR "21" (diff CHAR "]"))
|
28
|
+
(terminal POSTFIX "22" (range "?*+"))
|
29
|
+
(terminal PASS "23"
|
30
30
|
(plus
|
31
31
|
(alt
|
32
32
|
(range "#x20\t\r\n")
|
data/lib/ebnf.rb
CHANGED
data/lib/ebnf/base.rb
CHANGED
@@ -175,7 +175,13 @@ module EBNF
|
|
175
175
|
require 'sxp' unless defined?(SXP)
|
176
176
|
SXP::Generator.string(ast.sort_by{|r| r.id.to_f}.map(&:for_sxp))
|
177
177
|
end
|
178
|
-
|
178
|
+
|
179
|
+
##
|
180
|
+
# Output formatted EBNF
|
181
|
+
# @return [String]
|
182
|
+
def to_s
|
183
|
+
Writer.string(*ast)
|
184
|
+
end
|
179
185
|
|
180
186
|
def dup
|
181
187
|
new_obj = super
|
data/lib/ebnf/ll1/lexer.rb
CHANGED
@@ -507,12 +507,5 @@ module EBNF::LL1
|
|
507
507
|
super(message.to_s)
|
508
508
|
end
|
509
509
|
end # class Error
|
510
|
-
|
511
|
-
unless "".respond_to?(:force_encoding)
|
512
|
-
# Compatibility with 1.9 Encoding
|
513
|
-
module Encoding
|
514
|
-
class CompatibilityError < StandardError; end
|
515
|
-
end
|
516
|
-
end
|
517
510
|
end # class Lexer
|
518
511
|
end # module EBNF
|
data/lib/ebnf/ll1/parser.rb
CHANGED
@@ -281,8 +281,8 @@ module EBNF::LL1
|
|
281
281
|
end
|
282
282
|
elsif prod_branch = @branch[cur_prod]
|
283
283
|
sequence = prod_branch.fetch(token.representation) do
|
284
|
-
raise Error.new("
|
285
|
-
:production => cur_prod)
|
284
|
+
raise Error.new("Expected one of #{@first[cur_prod].inspect}",
|
285
|
+
:token => token, :production => cur_prod)
|
286
286
|
end
|
287
287
|
debug("parse(production)") do
|
288
288
|
"token #{token.representation.inspect} " +
|
@@ -292,7 +292,7 @@ module EBNF::LL1
|
|
292
292
|
end
|
293
293
|
todo_stack.last[:terms] += sequence
|
294
294
|
else
|
295
|
-
raise Error.new("
|
295
|
+
raise Error.new("Unexpected",
|
296
296
|
:production => cur_prod, :token => token)
|
297
297
|
end
|
298
298
|
end
|
@@ -309,8 +309,8 @@ module EBNF::LL1
|
|
309
309
|
elsif terminals.include?(term)
|
310
310
|
# If term is a terminal, then it is an error if token does not
|
311
311
|
# match it
|
312
|
-
raise Error.new("
|
313
|
-
:production => cur_prod)
|
312
|
+
raise Error.new("Expected #{term.inspect}",
|
313
|
+
:token => get_token, :production => cur_prod)
|
314
314
|
else
|
315
315
|
token = get_token
|
316
316
|
|
@@ -339,12 +339,12 @@ module EBNF::LL1
|
|
339
339
|
if e.is_a?(Lexer::Error)
|
340
340
|
# Skip to the next valid terminal
|
341
341
|
@lexer.recover
|
342
|
-
error("parse(#{e.class})", "With input '#{e.input}': #{e.message}
|
342
|
+
error("parse(#{e.class})", "With input '#{e.input}': #{e.message}",
|
343
343
|
:production => @productions.last)
|
344
344
|
else
|
345
345
|
# Otherwise, the terminal is fine, just not for this production.
|
346
346
|
@lexer.shift
|
347
|
-
error("parse(#{e.class})", "#{e.message}
|
347
|
+
error("parse(#{e.class})", "#{e.message}",
|
348
348
|
:production => @productions.last, :token => e.token)
|
349
349
|
end
|
350
350
|
|
@@ -422,7 +422,7 @@ module EBNF::LL1
|
|
422
422
|
|
423
423
|
# When all is said and done, raise the error log
|
424
424
|
unless @error_log.empty?
|
425
|
-
raise Error, @error_log.join("\n
|
425
|
+
raise Error, @error_log.join("\n")
|
426
426
|
end
|
427
427
|
end
|
428
428
|
|
@@ -468,12 +468,14 @@ module EBNF::LL1
|
|
468
468
|
# @option options [Token] :token
|
469
469
|
# @see {#debug}
|
470
470
|
def error(node, message, options = {})
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
471
|
+
m = "ERROR "
|
472
|
+
m += "[line: #{@lineno}] " if @lineno
|
473
|
+
m += message
|
474
|
+
m += " (found #{options[:token].inspect})" if options[:token]
|
475
|
+
m += ", production = #{options[:production].inspect}" if options[:production]
|
476
|
+
@error_log << m unless @recovering
|
475
477
|
@recovering = true
|
476
|
-
debug(node,
|
478
|
+
debug(node, m, options.merge(:level => 0))
|
477
479
|
end
|
478
480
|
|
479
481
|
##
|
@@ -486,11 +488,13 @@ module EBNF::LL1
|
|
486
488
|
# @option options [Token] :token
|
487
489
|
# @see {#debug}
|
488
490
|
def warn(node, message, options = {})
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
491
|
+
m = "WARNING "
|
492
|
+
m += "[line: #{@lineno}] " if @lineno
|
493
|
+
m += message
|
494
|
+
m += " (found #{options[:token].inspect})" if options[:token]
|
495
|
+
m += ", production = #{options[:production].inspect}" if options[:production]
|
496
|
+
@error_log << m unless @recovering
|
497
|
+
debug(node, m, options.merge(:level => 1))
|
494
498
|
end
|
495
499
|
|
496
500
|
##
|
@@ -570,7 +574,7 @@ module EBNF::LL1
|
|
570
574
|
# Make sure we push as many was we pop, even if there is no
|
571
575
|
# explicit start handler
|
572
576
|
@prod_data << {} if self.class.production_handlers[prod]
|
573
|
-
progress("#{prod}(:start)") { get_token.inspect + (@recovering ? ' recovering' : '')}
|
577
|
+
progress("#{prod}(:start:#{@prod_data.length})") { get_token.inspect + (@recovering ? ' recovering' : '')}
|
574
578
|
end
|
575
579
|
#puts "prod_data(s): " + @prod_data.inspect
|
576
580
|
end
|
@@ -593,7 +597,7 @@ module EBNF::LL1
|
|
593
597
|
end
|
594
598
|
progress("#{prod}(:finish):#{@prod_data.length}") {@prod_data.last}
|
595
599
|
else
|
596
|
-
progress("#{prod}(:finish)") { "recovering" if @recovering }
|
600
|
+
progress("#{prod}(:finish):#{@prod_data.length}") { "recovering" if @recovering }
|
597
601
|
end
|
598
602
|
@productions.pop
|
599
603
|
end
|
@@ -611,7 +615,7 @@ module EBNF::LL1
|
|
611
615
|
handler.call(parentProd, token, @prod_data.last, @parse_callback)
|
612
616
|
}
|
613
617
|
rescue ArgumentError, Error => e
|
614
|
-
error("terminal", "#{e.class}: #{e.message}", :production => prod)
|
618
|
+
error("terminal", "#{e.class}: #{e.message}", :token => token, :production => prod)
|
615
619
|
@recovering = false
|
616
620
|
end
|
617
621
|
progress("#{prod}(:terminal)", "", :depth => (depth + 1)) {"#{token}: #{@prod_data.last}"}
|
@@ -619,7 +623,7 @@ module EBNF::LL1
|
|
619
623
|
progress("#{prod}(:terminal)", "", :depth => (depth + 1)) {token.to_s}
|
620
624
|
end
|
621
625
|
else
|
622
|
-
error("#{parentProd}(:terminal)", "Terminal has no parent production", :production => prod)
|
626
|
+
error("#{parentProd}(:terminal)", "Terminal has no parent production", :token => token, :production => prod)
|
623
627
|
end
|
624
628
|
end
|
625
629
|
|
@@ -729,7 +733,7 @@ module EBNF::LL1
|
|
729
733
|
def initialize(message, options = {})
|
730
734
|
@production = options[:production]
|
731
735
|
@token = options[:token]
|
732
|
-
@lineno = options[:lineno]
|
736
|
+
@lineno = options[:lineno] || (@token.lineno if @token.respond_to?(:lineno))
|
733
737
|
super(message.to_s)
|
734
738
|
end
|
735
739
|
end # class Error
|
data/lib/ebnf/parser.rb
CHANGED
@@ -35,7 +35,7 @@ module EBNF
|
|
35
35
|
yield r unless r.empty?
|
36
36
|
@lineno = cur_lineno
|
37
37
|
r = s
|
38
|
-
when s = scanner.scan(
|
38
|
+
when s = scanner.scan(/(?:\[[\w\.]+\])\s*[\w\.]+\s*::=/)
|
39
39
|
# Found rule start, if we've already collected a rule, yield it
|
40
40
|
yield r unless r.empty?
|
41
41
|
#debug("eachRule(rule)") { "[#{cur_lineno}] #{s.inspect}" }
|
@@ -53,13 +53,14 @@ module EBNF
|
|
53
53
|
end
|
54
54
|
|
55
55
|
##
|
56
|
-
# Parse a rule into
|
56
|
+
# Parse a rule into an optional rule number, a symbol and an expression
|
57
57
|
#
|
58
58
|
# @param [String] rule
|
59
59
|
# @return [Rule]
|
60
60
|
def ruleParts(rule)
|
61
61
|
num_sym, expr = rule.split('::=', 2).map(&:strip)
|
62
62
|
num, sym = num_sym.split(']', 2).map(&:strip)
|
63
|
+
num, sym = "", num if sym.nil?
|
63
64
|
num = num[1..-1]
|
64
65
|
r = Rule.new(sym && sym.to_sym, num, expression(expr).first, :ebnf => self)
|
65
66
|
debug("ruleParts") { r.inspect }
|
data/lib/ebnf/rule.rb
CHANGED
@@ -122,7 +122,8 @@ module EBNF
|
|
122
122
|
# Return representation for building S-Expressions
|
123
123
|
# @return [Array]
|
124
124
|
def for_sxp
|
125
|
-
elements = [kind, sym
|
125
|
+
elements = [kind, sym]
|
126
|
+
elements << id if id
|
126
127
|
elements << [:start, true] if start
|
127
128
|
elements << first.sort_by(&:to_s).unshift(:first) if first
|
128
129
|
elements << follow.sort_by(&:to_s).unshift(:follow) if follow
|
data/lib/ebnf/writer.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'rdf'
|
3
|
+
|
4
|
+
##
|
5
|
+
# Serialize ruleset back to EBNF
|
6
|
+
module EBNF
|
7
|
+
class Writer
|
8
|
+
LINE_LENGTH = 80
|
9
|
+
|
10
|
+
##
|
11
|
+
# Format rules to a String
|
12
|
+
#
|
13
|
+
# @param [Array<Rule>] rules
|
14
|
+
# @return [Object]
|
15
|
+
def self.string(*rules)
|
16
|
+
require 'stringio' unless defined?(StringIO)
|
17
|
+
buf = StringIO.new
|
18
|
+
write(buf, *rules)
|
19
|
+
buf.string
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Format rules to $stdout
|
24
|
+
#
|
25
|
+
# @param [Array<Rule>] rules
|
26
|
+
# @return [Object]
|
27
|
+
def self.print(*rules)
|
28
|
+
write($stdout, *rules)
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Write formatted rules to an IO like object
|
33
|
+
#
|
34
|
+
# @param [Object] out
|
35
|
+
# @param [Array<Rule>] rules
|
36
|
+
# @return [Object]
|
37
|
+
def self.write(out, *rules)
|
38
|
+
Writer.new(rules, out: out)
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# @param [Array<Rule>] rules
|
43
|
+
# @param [Hash{Symbol => Object}] options
|
44
|
+
# @option options [Symbol] :format
|
45
|
+
# @option options [#write] :out ($stdout)
|
46
|
+
def initialize(rules, options = {})
|
47
|
+
out = options.fetch(:out, $stdio)
|
48
|
+
#fmt = options.fetch(:format, :ebnf)
|
49
|
+
|
50
|
+
# Determine max LHS length
|
51
|
+
max_id = rules.max_by {|r| r.id.to_s.length}.id.to_s.length
|
52
|
+
max_sym = rules.max_by {|r| r.sym.to_s.length}.sym.to_s.length
|
53
|
+
lhs_length = max_sym + 3
|
54
|
+
lhs_fmt = "%-#{max_sym}{sym} ::= "
|
55
|
+
if max_id > 0
|
56
|
+
lhs_fmt = "%-#{max_id+2}{id} " + lhs_fmt
|
57
|
+
lhs_length += max_id + 3
|
58
|
+
end
|
59
|
+
rhs_length = LINE_LENGTH - lhs_length
|
60
|
+
|
61
|
+
# Format each rule, considering the available rhs size
|
62
|
+
rules.each do |rule|
|
63
|
+
buffer = if rule.pass?
|
64
|
+
"%-#{lhs_length-2}s" % "@pass"
|
65
|
+
else
|
66
|
+
lhs_fmt % {:id => "[#{rule.id}]", :sym => rule.sym}
|
67
|
+
end
|
68
|
+
formatted_expr = format(rule.expr)
|
69
|
+
if formatted_expr.length > rhs_length
|
70
|
+
buffer << format(rule.expr, ("\n" + " " * lhs_length))
|
71
|
+
else
|
72
|
+
buffer << formatted_expr
|
73
|
+
end
|
74
|
+
out.puts(buffer)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
# Format the expression part of a rule
|
80
|
+
def format(expr, sep = nil)
|
81
|
+
return expr.to_s if expr.is_a?(Symbol)
|
82
|
+
return %("#{escape(expr)}") if expr.is_a?(String)
|
83
|
+
|
84
|
+
case expr.first
|
85
|
+
when :alt, :diff
|
86
|
+
this_sep = (sep ? sep : " ") + {alt: "| ", diff: "- "}[expr.first.to_sym]
|
87
|
+
expr[1..-1].map {|e| format(e)}.join(this_sep)
|
88
|
+
when :star, :plus, :opt
|
89
|
+
raise "Expected star expression to have a single operand" unless expr.length == 2
|
90
|
+
char = {star: "*", plus: "+", opt: "?"}[expr.first.to_sym]
|
91
|
+
r = format(expr[1])
|
92
|
+
(r.start_with?("(") || Array(expr[1]).length == 1) ? "#{r}#{char}" : "(#{r})#{char}"
|
93
|
+
when :range
|
94
|
+
parts = expr.last.split(/(?!\\)-/, 2)
|
95
|
+
"[" + parts.map {|e| format(e)[1..-2]}.join("-") + "]"
|
96
|
+
when :seq
|
97
|
+
this_sep = (sep ? sep : " ")
|
98
|
+
expr[1..-1].map {|e| r = format(e); Array(e).length > 2 ? "(#{r})" : r}.join(this_sep)
|
99
|
+
else
|
100
|
+
raise "Unknown operator: #{expr.first}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def escape(string)
|
105
|
+
buffer = ""
|
106
|
+
string.each_char do |c|
|
107
|
+
buffer << case c.to_s
|
108
|
+
when "\t" then "\\t"
|
109
|
+
when "\n" then "\\n"
|
110
|
+
when "\r" then "\\r"
|
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
|
120
|
+
end
|
121
|
+
end
|
122
|
+
buffer
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
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: 0.3.
|
4
|
+
version: 0.3.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gregg Kellogg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-12-
|
11
|
+
date: 2013-12-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sxp
|
@@ -102,6 +102,7 @@ files:
|
|
102
102
|
- lib/ebnf/parser.rb
|
103
103
|
- lib/ebnf/rule.rb
|
104
104
|
- lib/ebnf/version.rb
|
105
|
+
- lib/ebnf/writer.rb
|
105
106
|
- lib/ebnf.rb
|
106
107
|
- etc/doap.ttl
|
107
108
|
- etc/ebnf.ebnf
|
@@ -125,7 +126,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
126
|
requirements:
|
126
127
|
- - '>='
|
127
128
|
- !ruby/object:Gem::Version
|
128
|
-
version: 1.
|
129
|
+
version: 1.9.2
|
129
130
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
130
131
|
requirements:
|
131
132
|
- - '>='
|