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.
@@ -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 Terminated by line-end or whitespace
270
- l, s = s[1..-1].split(m.rstrip , 2)
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 '[' # ENUM, RANGE, O_ENUM, or O_RANGE
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(/(#\w+)(.*)$/)
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
@@ -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 a.2))
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 "foo"))
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?
@@ -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 = "%-#{max_sym}{sym} ::= "
70
+ lhs_fmt = "%<sym>-#{max_sym}s ::= "
55
71
  if max_id > 0
56
- lhs_fmt = "%-#{max_id+2}{id} " + 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
- return %("#{escape(expr)}") if expr.is_a?(String)
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 : " ") + {alt: "| ", diff: "- "}[expr.first.to_sym]
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 = {star: "*", plus: "+", opt: "?"}[expr.first.to_sym]
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
- parts = expr.last.split(/(?!\\)-/, 2)
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 ? "(#{r})" : r}.join(this_sep)
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
- def escape(string)
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.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
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.5
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: 2013-12-22 00:00:00.000000000 Z
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: '0'
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: '0'
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.12.0
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.12.0
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.3
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.3
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
- - lib/ebnf/base.rb
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
- - bin/ebnf
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.1.11
158
+ rubygems_version: 2.2.2
138
159
  signing_key:
139
160
  specification_version: 4
140
161
  summary: EBNF parser and parser generator.