ebnf 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
- - '>='
|