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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3c3b6668384bfe503728e795abf4350ec4a327c9
4
- data.tar.gz: d2b8ed718f483766f3e7756dd2feaac637d29dbe
3
+ metadata.gz: 1e418c3276c9f6252edf2fb8e89b815a95731629
4
+ data.tar.gz: 1f9fa2b8690bb7d8e5491a94b0f7f0fcc781af99
5
5
  SHA512:
6
- metadata.gz: 0271fdc7808467c76a0c95a66982e6d97efdd9bcac52b5bcfde66fb1b763fab7a82eea344a98bd4fd81ef95de5a7865b88a2b912876ef69ed4814ba05cdbe635
7
- data.tar.gz: 47e4b1314eb8068da0a4802d04206d47cfa2d52fe46b8b740cc00cd6187fea9b3e658441af3c2848d3b1a3bccfc614b31f95ffa3130f88cc42ade09aca0e86df
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.2
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 ::= '[' CHAR '-' CHAR ']'
41
+ [14] RANGE ::= '[' R_CHAR '-' R_CHAR ']'
42
42
 
43
- [15] ENUM ::= '[' CHAR+ ']'
43
+ [15] ENUM ::= '[' R_CHAR+ ']'
44
44
 
45
- [16] O_RANGE ::= '[^' CHAR '-' CHAR ']'
45
+ [16] O_RANGE ::= '[^' R_CHAR '-' R_CHAR ']'
46
46
 
47
- [17] O_ENUM ::= '[^' CHAR+ ']'
47
+ [17] O_ENUM ::= '[^' R_CHAR+ ']'
48
48
 
49
- [18] STRING1 ::= '"' (CHAR | [\t\'\[\]\(\)\-])* '"'
49
+ [18] STRING1 ::= '"' ((CHAR - '"') | "\t")* '"'
50
50
 
51
- [19] STRING2 ::= "'" (CHAR | [\t\"\[\]\(\)\-])* "'"
51
+ [19] STRING2 ::= "'" ((CHAR - "'") | "\t")* "'"
52
52
 
53
53
  [20] CHAR ::= HEX
54
- | ('\\' [\\trn\'\"\[\]\(\)\-])
55
- | [^\t\r\n\'\"\[\]\(\)\-]
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
- [21] POSTFIX ::= [?*+]
60
+ [22] POSTFIX ::= [?*+]
59
61
 
60
- [22] PASS ::= ( [#x20\t\r\n]
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 "[" CHAR "-" CHAR "]"))
151
- (terminal ENUM "15" (seq "[" (plus CHAR) "]"))
152
- (terminal O_RANGE "16" (seq "[^" CHAR "-" CHAR "]"))
153
- (terminal O_ENUM "17" (seq "[^" (plus CHAR) "]"))
154
- (terminal STRING1 "18" (seq "\"" (star (alt CHAR (range "\t'[]()-"))) "\""))
155
- (terminal STRING2 "19" (seq "'" (star (alt CHAR (range "\t\"[]()-"))) "'"))
156
- (terminal CHAR "20"
157
- (alt HEX (seq "\\" (range "\\trn'\"[]()-")) (range "^\t\r\n'\"[]()-")))
158
- (terminal POSTFIX "21" (range "?*+"))
159
- (terminal PASS "22"
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 "[" CHAR "-" CHAR "]"))
21
- (terminal ENUM "15" (seq "[" (plus CHAR) "]"))
22
- (terminal O_RANGE "16" (seq "[^" CHAR "-" CHAR "]"))
23
- (terminal O_ENUM "17" (seq "[^" (plus CHAR) "]"))
24
- (terminal STRING1 "18" (seq "\"" (star (alt CHAR (range "\t'[]()-"))) "\""))
25
- (terminal STRING2 "19" (seq "'" (star (alt CHAR (range "\t\"[]()-"))) "'"))
26
- (terminal CHAR "20"
27
- (alt HEX (seq "\\" (range "\\trn'\"[]()-")) (range "^\t\r\n'\"[]()-")))
28
- (terminal POSTFIX "21" (range "?*+"))
29
- (terminal PASS "22"
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
@@ -4,6 +4,7 @@ module EBNF
4
4
  autoload :LL1, "ebnf/ll1"
5
5
  autoload :Parser, "ebnf/parser"
6
6
  autoload :Rule, "ebnf/rule"
7
+ autoload :Writer, "ebnf/writer"
7
8
  autoload :VERSION, "ebnf/version"
8
9
 
9
10
  ##
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
- def to_s; to_sxp; end
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
@@ -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
@@ -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("#{token.inspect} does not match production #{cur_prod.inspect}",
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("No branches found for #{cur_prod.inspect}",
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("#{get_token.inspect} does not match terminal #{term.inspect}",
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}, skipped to #{(get_token(:recover) || :eof).inspect}",
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}, skipped to #{(get_token(:recover) || :eof).inspect}",
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\t")
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
- message += ", found #{options[:token].inspect}" if options[:token]
472
- message += " at line #{@lineno}" if @lineno
473
- message += ", production = #{options[:production].inspect}" if options[:production]
474
- @error_log << message unless @recovering
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, message, options.merge(:level => 0))
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
- message += ", with token #{options[:token].inspect}" if options[:token]
490
- message += " at line #{@lineno}" if @lineno
491
- message += ", production = #{options[:production].inspect}" if options[:production]
492
- @error_log << message unless @recovering
493
- debug(node, message, options.merge(:level => 1))
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(/\[(?=[\w\.]+\])/)
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 a rule number, a symbol and an expression
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, id]
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
@@ -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.2
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-10 00:00:00.000000000 Z
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.8.7
129
+ version: 1.9.2
129
130
  required_rubygems_version: !ruby/object:Gem::Requirement
130
131
  requirements:
131
132
  - - '>='