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 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
  - - '>='