Class: EBNF::Writer
- Inherits:
-
Object
- Object
- EBNF::Writer
- Defined in:
- lib/ebnf/writer.rb
Constant Summary collapse
- LINE_LENGTH =
80
- LINE_LENGTH_HTML =
200
- UNICODE_ESCAPE_NAMES =
UNICODE escape names From en.wikipedia.org/wiki/List_of_Unicode_characters
{ 0x00 => 'null', 0x01 => 'start of heading', 0x02 => 'start of text', 0x03 => 'end of text', 0x04 => 'end of transmission', 0x05 => 'enquiry', 0x06 => 'acknowledge', 0x07 => 'bell', 0x08 => 'backspace', 0x09 => 'horizontal tab', 0x0A => 'new line', 0x0B => 'vertical tab', 0x0C => 'form feed', 0x0D => 'carriage return', 0x0E => 'shift out', 0x0F => 'shift in', 0x10 => 'data link escape', 0x11 => 'device control 1', 0x12 => 'device control 2', 0x13 => 'device control 3', 0x14 => 'device control 4', 0x15 => 'negative acknowledge', 0x16 => 'synchronous idle', 0x17 => 'end of trans. block', 0x18 => 'cancel', 0x19 => 'end of medium', 0x1A => 'substitute', 0x1B => 'escape', 0x1C => 'file separator', 0x1D => 'group separator', 0x1E => 'record separator', 0x1F => 'unit separator', 0x20 => 'space', 0x22 => 'dquote', 0x27 => 'apos', 0x2F => 'slash', 0x5C => 'backslash', 0x60 => 'grave', 0x7F => 'delete', 0x80 => 'padding character', 0x81 => 'high octet preset', 0x82 => 'break permitted here', 0x83 => 'no break here', 0x84 => 'index', 0x85 => 'next line', 0x86 => 'start of selected area', 0x87 => 'end of selected area', 0x88 => 'character tabulation set', 0x89 => 'character tabulation with justification', 0x8A => 'line tabulation set', 0x8B => 'partial line forward', 0x8C => 'partial line backward', 0x8D => 'reverse line feed', 0x8E => 'single-shift two', 0x8F => 'single-shift three', 0x90 => 'device control string', 0x91 => 'private use 1', 0x92 => 'private use 2', 0x93 => 'set transmit state', 0x94 => 'cancel character', 0x95 => 'message waiting', 0x96 => 'start of protected area', 0x97 => 'end of protected area', 0x98 => 'start of string', 0x99 => 'single graphic character introducer', 0x9A => 'single character intro introducer', 0x9B => 'control sequence introducer', 0x9C => 'string terminator', 0x9D => 'operating system command', 0x9E => 'private message', 0x9F => 'application program command', }
- ERB_DESC =
%(<!-- Generated with ebnf version #{EBNF::VERSION}. See https://github.com/dryruby/ebnf. -->\n) + %q(<table class="grammar"> <tbody id="grammar-productions" class="<%= @format %>"> <% for rule in @rules %> <tr<%= %{ id="grammar-#{rule[:class]}-#{rule.sym}"} unless %w(=/ |).include?(rule.assign) || rule.sym.nil?%>> <% if rule.id %> <td<%= " colspan=2" unless rule.sym %>><%= rule.id %></td> <% end %> <% if rule.sym %> <td><code><%== (rule.sym unless rule.class == :declaration) %></code></td> <% end %> <td><%= rule.assign %></td> <td><%= rule.formatted %></td> </tr> <% end %> </tbody> </table> ).gsub(/^ /, '')
Class Method Summary collapse
-
.html(*rules, format: :ebnf, validate: false) ⇒ Object
Write formatted rules to an IO like object as HTML.
-
.print(*rules, format: :ebnf) ⇒ Object
Format rules to $stdout.
-
.string(*rules, format: :ebnf) ⇒ Object
Format rules to a String.
-
.write(out, *rules, format: :ebnf) ⇒ Object
Write formatted rules to an IO like object.
Instance Method Summary collapse
- #escape_abnf_hex(u) ⇒ Object protected
- #escape_ebnf_hex(u) ⇒ Object protected
-
#format_abnf(expr, sep: nil, embedded: false, sensitive: true) ⇒ Object
protected
Format the expression part of a rule.
-
#format_abnf_char(c) ⇒ Object
protected
Format a single-character string, prefering hex for non-main ASCII.
-
#format_abnf_range(string) ⇒ Object
protected
Format a range.
-
#format_ebnf(expr, sep: nil, embedded: false) ⇒ Object
protected
Format the expression part of a rule.
-
#format_ebnf_char(c) ⇒ Object
protected
Format a single-character string, prefering hex for non-main ASCII.
-
#format_ebnf_range(string) ⇒ Object
protected
Format a range.
-
#format_ebnf_string(string) ⇒ Object
protected
Escape a string, using as many UTF-8 characters as possible.
-
#format_isoebnf(expr, sep: nil, embedded: false) ⇒ Object
protected
Format the expression part of a rule.
-
#format_isoebnf_range(string) ⇒ Object
protected
Format a range Range is formatted as a aliteration of characters.
-
#initialize(rules, out: $stdout, html: false, format: :ebnf, validate: false, **options) ⇒ Writer
constructor
A new instance of Writer.
Constructor Details
#initialize(rules, out: $stdout, html: false, format: :ebnf, validate: false, **options) ⇒ Writer
Returns a new instance of Writer.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/ebnf/writer.rb', line 145 def initialize(rules, out: $stdout, html: false, format: :ebnf, validate: false, **) @options = .merge(html: html) return if rules.empty? # Determine max LHS length format_meth = "format_#{format}".to_sym max_id = rules.max_by {|r| r.id.to_s.length}.id.to_s.length max_sym = rules.max_by {|r| r.sym.to_s.length}.sym.to_s.length lhs_length = max_sym + 1 lhs_fmt = case format when :abnf then "%<sym>-#{max_sym}s = " when :ebnf then "%<sym>-#{max_sym}s ::= " when :isoebnf then "%<sym>-#{max_sym}s = " end if format == :ebnf && max_id > 0 lhs_fmt = "%<id>-#{max_id+2}s " + lhs_fmt lhs_length += max_id + 3 end rhs_length = (html ? LINE_LENGTH_HTML : LINE_LENGTH) - lhs_length if html # Output as formatted HTML begin require 'erubis' require 'htmlentities' @coder = HTMLEntities.new eruby = Erubis::Eruby.new(ERB_DESC) formatted_rules = rules.map do |rule| if rule.kind == :terminals || rule.kind == :pass OpenStruct.new(id: ("@#{rule.kind}"), class: :declaration, sym: rule.kind, assign: nil, formatted: ( rule.kind == :terminals ? "<strong># Productions for terminals</strong>" : self.send(format_meth, rule.expr))) else formatted_expr = self.send(format_meth, rule.expr) # Measure text without markup formatted_expr_text = formatted_expr.gsub(%r{</?\w+[^>]*>}, '') if formatted_expr_text.length > rhs_length && (format != :abnf || rule.alt?) lines = [] # Can only reasonably split apart alts self.send(format_meth, rule.expr, sep: "--rule-extensions--"). split(/\s*--rule-extensions--\s*/).each_with_index do |formatted, ndx| assign = case format when :ebnf formatted.sub!(%r{\s*<code[^>]*>\|</code>\s*}, '') (ndx > 0 ? (rule.alt? ? '<code class="grammar-alt">|</code>' : '') : '::=') when :abnf formatted.sub!(%r{\s*<code[^>]>/</code>\s*}, '') (ndx > 0 ? '<code class="grammar-alt">=/</code>' : '=') else formatted.sub!(%r{\s*<code[^>]>\|</code>\s*}, '') (ndx > 0 ? (rule.alt? ? '<code class="grammar-alt">|</code>' : '') : '=') end lines << OpenStruct.new(id: ((ndx == 0 ? "[#{rule.id}]" : "") if rule.id), sym: (rule.sym if ndx == 0 || format == :abnf), class: :production, assign: assign, formatted: formatted) end if format == :isoebnf lines << OpenStruct.new(assign: ';') end lines else OpenStruct.new(id: ("[#{rule.id}]" if rule.id), class: :production, sym: rule.sym, assign: (format == :ebnf ? '::=' : '='), formatted: (formatted_expr + (format == :isoebnf ? ' ;' : ''))) end end end.flatten html_result = eruby.evaluate(format: format, rules: formatted_rules) if validate begin require 'nokogiri' # Validate the output HTML doc = ::Nokogiri::HTML5("<!DOCTYPE html>" + html_result, max_errors: 10) raise EncodingError, "Errors found in generated HTML:\n " + doc.errors.map(&:to_s).join("\n ") unless doc.errors.empty? rescue LoadError, NoMethodError # Skip end end out.write html_result return rescue LoadError $stderr.puts "Generating HTML requires erubis and htmlentities gems to be loaded" end end # Format each rule, considering the available rhs size rules.each do |rule| buffer = if rule.pass? "\n%-#{lhs_length-2}s " % '@pass' elsif rule.kind == :terminals "\n%-#{lhs_length-2}s" % '@terminals' else lhs_fmt % {id: "[#{rule.id}]", sym: rule.sym} end formatted_expr = self.send(format_meth, rule.expr) if formatted_expr.length > rhs_length && (format != :abnf || rule.alt?) if format == :abnf # No whitespace, use =/ self.send(format_meth, rule.expr, sep: "--rule-extensions--"). split(/\s*--rule-extensions--\s*/).each_with_index do |formatted, ndx| if ndx > 0 buffer << "\n" + lhs_fmt.sub('= ', '=/') % {id: "[#{rule.id}]", sym: rule.sym} end buffer << formatted.sub(/\s*\/\s*/, '') end else # Space out past "= " buffer << self.send(format_meth, rule.expr, sep: ("\n" + " " * (lhs_length + (rule.alt? ? 2 : 4) - (format == :ebnf ? 0 : 2)))) buffer << ("\n" + " " * (lhs_length) + ';') if format == :isoebnf end else buffer << formatted_expr + (format == :isoebnf ? ' ;' : '') end buffer << "\n\n" if [:terminals, :pass].include?(rule.kind) out.puts(buffer) end end |
Class Method Details
.html(*rules, format: :ebnf, validate: false) ⇒ Object
Write formatted rules to an IO like object as HTML
131 132 133 134 135 136 |
# File 'lib/ebnf/writer.rb', line 131 def self.html(*rules, format: :ebnf, validate: false) require 'stringio' unless defined?(StringIO) buf = StringIO.new Writer.new(rules, out: buf, html: true, format: format, validate: validate) buf.string end |
.print(*rules, format: :ebnf) ⇒ Object
Format rules to $stdout
109 110 111 |
# File 'lib/ebnf/writer.rb', line 109 def self.print(*rules, format: :ebnf) write($stdout, *rules, format: format) end |
.string(*rules, format: :ebnf) ⇒ Object
Format rules to a String
96 97 98 99 100 101 |
# File 'lib/ebnf/writer.rb', line 96 def self.string(*rules, format: :ebnf) require 'stringio' unless defined?(StringIO) buf = StringIO.new write(buf, *rules, format: format) buf.string end |
Instance Method Details
#escape_abnf_hex(u) ⇒ Object (protected)
589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 |
# File 'lib/ebnf/writer.rb', line 589 def escape_abnf_hex(u) fmt = case u.ord when 0x0000..0x00ff then "%02X" when 0x0100..0xffff then "%04X" else "%08X" end char = "%x" + (fmt % u.ord).upcase if @options[:html] char = if UNICODE_ESCAPE_NAMES.include?(u.ord) %(<abbr title="#{UNICODE_ESCAPE_NAMES[u.ord]}">#{char}</abbr>) elsif ([::Unicode::Types.of(u)] - %w(Control Private-use Surrogate Noncharacter Reserved)).empty? %(<abbr title="unicode '#{@coder.encode u}'">#{char}</abbr>) else uni_esc = "U+%04X" % u.ord %(<abbr title="unicode #{uni_esc}">#{char}</abbr>) end %(<code class="grammar-char-escape">#{char}</code>) else char end end |
#escape_ebnf_hex(u) ⇒ Object (protected)
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/ebnf/writer.rb', line 402 def escape_ebnf_hex(u) fmt = case u.ord when 0x0000..0x00ff then "#x%02X" when 0x0100..0xffff then "#x%04X" else "#x%08X" end char = fmt % u.ord if @options[:html] char = if UNICODE_ESCAPE_NAMES.include?(u.ord) %(<abbr title="#{UNICODE_ESCAPE_NAMES[u.ord]}">#{char}</abbr>) elsif ([::Unicode::Types.of(u)] - %w(Control Private-use Surrogate Noncharacter Reserved)).empty? %(<abbr title="unicode '#{@coder.encode u}'">#{char}</abbr>) else uni_esc = "U+%04X" % u.ord %(<abbr title="unicode #{uni_esc}">#{char}</abbr>) end %(<code class="grammar-char-escape">#{char}</code>) else char end end |
#format_abnf(expr, sep: nil, embedded: false, sensitive: true) ⇒ Object (protected)
Format the expression part of a rule
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 |
# File 'lib/ebnf/writer.rb', line 429 def format_abnf(expr, sep: nil, embedded: false, sensitive: true) return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol) if expr.is_a?(String) if expr.length == 1 return format_abnf_char(expr) elsif expr.start_with?('%') # Already encoded return expr elsif expr =~ /"/ # Split into segments segments = expr.split('"') return format_abnf_char(expr) if segments.empty? seq = segments.inject([]) {|memo, s| memo.concat([[:hex, "#x22"], s])}[1..-1] seq.unshift(:seq) return format_abnf(seq, sep: nil, embedded: false) else return (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode expr}</code>") : %("#{expr}")) end end parts = { alt: (@options[:html] ? %(<code class="grammar-alt">/</code>) : "/ "), star: (@options[:html] ? %(<code class="grammar-star">*</code>) : "*"), plus: (@options[:html] ? %(<code class="grammar-plus">+</code>) : "1*"), opt: (@options[:html] ? %(<code class="grammar-opt">?</code>) : "?") } lbrac = (@options[:html] ? %(<code class="grammar-brac">[</code>) : "[") rbrac = (@options[:html] ? %(<code class="grammar-brac">]</code>) : "]") lparen = (@options[:html] ? %[<code class="grammar-paren">(</code>] : "(") rparen = (@options[:html] ? %[<code class="grammar-paren">)</code>] : ")") case expr.first when :istr # FIXME: if string part is segmented, need to do something different format_abnf(expr.last, embedded: true, sensitive: false) when :alt this_sep = (sep ? sep : " ") + parts[expr.first.to_sym] res = expr[1..-1].map {|e| format_abnf(e, embedded: true)}.join(this_sep) ? (lparen + res + rparen) : res when :diff raise RangeError, "ABNF does not support the diff operator" when :opt char = parts[expr.first.to_sym] r = format_abnf(expr[1], embedded: true) "#{lbrac}#{r}#{rbrac}" when :plus, :star char = parts[expr.first.to_sym] r = format_abnf(expr[1], embedded: true) "#{char}#{r}" when :hex escape_abnf_hex(expr.last[2..-1].hex.chr) when :range # Returns an [:alt] or [:not [:alt]] if composed of multiple sequences # Note: ABNF does not support the `not` operator res = format_abnf_range(expr.last) res.is_a?(Array) ? format_abnf(res, embedded: true) : res when :seq this_sep = (sep ? sep : " ") res = expr[1..-1].map do |e| format_abnf(e, embedded: true) end.join(this_sep) ? (lparen + res + rparen) : res when :rept # Expand repetition min, max, value = expr[1..-1] r = format_abnf(value, embedded: true) if min == max "#{min}#{r}" elsif min == 0 && max == '*' "#{parts[:star]}#{r}" elsif min > 0 && max == '*' "#{min}#{parts[:star]}#{r}" elsif min == 0 "#{parts[:star]}#{max}#{r}" else "#{min}#{parts[:star]}#{max}#{r}" end else raise "Unknown operator: #{expr.first}" end end |
#format_abnf_char(c) ⇒ Object (protected)
Format a single-character string, prefering hex for non-main ASCII
515 516 517 518 519 520 521 |
# File 'lib/ebnf/writer.rb', line 515 def format_abnf_char(c) if /[\x20-\x21\x23-\x7E]/.match?(c) @options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : c.inspect else escape_abnf_hex(c) end end |
#format_abnf_range(string) ⇒ Object (protected)
Format a range
Presumes range has already been validated
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 |
# File 'lib/ebnf/writer.rb', line 526 def format_abnf_range(string) alt, o_dash = [:alt], false raise RangeError, "cannot format #{string.inspect} an ABNF range" if string.start_with?('^') if string.end_with?('-') o_dash = true string = string[0..-2] end scanner = StringScanner.new(string) hexes, deces = [], [] in_range = false # Build op (alt) from different ranges/enums while !scanner.eos? if hex = scanner.scan(Terminals::HEX) # Append any decimal values alt << "%d" + deces.join(".") unless deces.empty? deces = [] hex = hex.upcase if in_range # Add "." sequences for any previous hexes alt << "%x" + hexes[0..-2].join(".") if hexes.length > 1 alt << "%x#{hexes.last}-#{hex[2..-1]}" in_range, hexes = false, [] else hexes << hex[2..-1] end elsif dec = scanner.scan(Terminals::R_CHAR) # Append any hexadecimal values alt << "%x" + hexes.join(".") unless hexes.empty? hexes = [] if in_range # Add "." sequences for any previous hexes alt << "%d" + deces[0..-2].join(".") if deces.length > 1 alt << "%d#{deces.last}-#{dec.codepoints.first}" in_range, deces = false, [] else deces << dec.codepoints.first.to_s end end in_range = true if scanner.scan(/\-/) end deces << '45' if o_dash # Append hexes and deces as "." sequences (should be only one) alt << "%d" + deces.join(".") unless deces.empty? alt << "%x" + hexes.join(".") unless hexes.empty? # FIXME: HTML abbreviations? if alt.length == 2 # Just return the range or enum alt.last else # Return the alt, which will be further formatted alt end end |
#format_ebnf(expr, sep: nil, embedded: false) ⇒ Object (protected)
Format the expression part of a rule
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 |
# File 'lib/ebnf/writer.rb', line 283 def format_ebnf(expr, sep: nil, embedded: false) return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol) if expr.is_a?(String) return expr.length == 1 ? format_ebnf_char(expr) : format_ebnf_string(expr) end parts = { alt: (@options[:html] ? %(<code class="grammar-alt">|</code> ) : "| "), diff: (@options[:html] ? %(<code class="grammar-diff">-</code> ) : "- "), star: (@options[:html] ? %(<code class="grammar-star">*</code>) : "*"), plus: (@options[:html] ? %(<code class="grammar-plus">+</code>) : "+"), opt: (@options[:html] ? %(<code class="grammar-opt">?</code>) : "?") } lparen = (@options[:html] ? %[<code class="grammar-paren">(</code>] : "(") rparen = (@options[:html] ? %[<code class="grammar-paren">)</code>] : ")") case expr.first when :istr # Looses fidelity, but, oh well ... format_ebnf(expr.last, embedded: true) when :alt, :diff this_sep = (sep ? sep : " ") + parts[expr.first.to_sym] res = expr[1..-1].map {|e| format_ebnf(e, embedded: true)}.join(this_sep) ? (lparen + res + rparen) : res when :star, :plus, :opt char = parts[expr.first.to_sym] r = format_ebnf(expr[1], embedded: true) "#{r}#{char}" when :hex escape_ebnf_hex(expr.last[2..-1].hex.chr(Encoding::UTF_8)) when :range format_ebnf_range(expr.last) when :seq this_sep = (sep ? sep : " ") res = expr[1..-1].map do |e| format_ebnf(e, embedded: true) end.join(this_sep) ? (lparen + res + rparen) : res when :rept # Expand repetition min, max, value = expr[1..-1] if min == 0 && max == 1 format_ebnf([:opt, value], sep: sep, embedded: ) elsif min == 0 && max == '*' format_ebnf([:star, value], sep: sep, embedded: ) elsif min == 1 && max == '*' format_ebnf([:plus, value], sep: sep, embedded: ) else val2 = [:seq] while min > 0 val2 << value min -= 1 max -= 1 unless max == '*' end if max == '*' val2 << [:star, value] else opt = nil while max > 0 opt = [:opt, opt ? [:seq, value, opt] : value] max -= 1 end val2 << opt if opt end format_ebnf(val2, sep: sep, embedded: ) end else raise "Unknown operator: #{expr.first}" end end |
#format_ebnf_char(c) ⇒ Object (protected)
Format a single-character string, prefering hex for non-main ASCII
356 357 358 359 360 361 362 363 364 365 |
# File 'lib/ebnf/writer.rb', line 356 def format_ebnf_char(c) quote = c.as_dquote? ? '"' : "'" case c.ord when 0x21 then (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode c}</code>") : %{"#{c}"}) when 0x22 then (@options[:html] ? %('<code class="grammar-literal">"</code>') : %{'"'}) when (0x23..0x7e) then (@options[:html] ? %(#{quote}<code class="grammar-literal">#{@coder.encode c}</code>#{quote}) : %{#{quote}#{c}#{quote}}) when (0x80..0xFFFD) then (@options[:html] ? %(#{quote}<code class="grammar-literal">#{@coder.encode c}</code>#{quote}) : %{#{quote}#{c}#{quote}}) else escape_ebnf_hex(c) end end |
#format_ebnf_range(string) ⇒ Object (protected)
Format a range
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/ebnf/writer.rb', line 368 def format_ebnf_range(string) lbrac = (@options[:html] ? %(<code class="grammar-brac">[</code>) : "[") rbrac = (@options[:html] ? %(<code class="grammar-brac">]</code>) : "]") buffer = lbrac s = StringScanner.new(string) while !s.eos? case when s.scan(/\A[!"\u0024-\u007e]+/) buffer << (@options[:html] ? %(<code class="grammar-literal">#{@coder.encode s.matched}</code>) : s.matched) when s.scan(/\A#x\h+/) buffer << escape_ebnf_hex(s.matched[2..-1].hex.chr(Encoding::UTF_8)) else buffer << escape_ebnf_hex(s.getch) end end buffer + rbrac end |
#format_ebnf_string(string) ⇒ Object (protected)
Escape a string, using as many UTF-8 characters as possible
388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/ebnf/writer.rb', line 388 def format_ebnf_string(string) quote = string.as_dquote? ? '"' : "'" string.each_char do |c| case c.ord when 0x00..0x19, quote.ord raise RangeError, "cannot format #{string.inspect} as an EBNF String: #{c.inspect} is out of range" unless ISOEBNF::TERMINAL_CHARACTER.match?(c) end end res = @options[:html] ? %(<code class="grammar-literal">#{@coder.encode(string)}</code>) : string res = "#{quote}#{res}#{quote}" end |
#format_isoebnf(expr, sep: nil, embedded: false) ⇒ Object (protected)
Format the expression part of a rule
616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 |
# File 'lib/ebnf/writer.rb', line 616 def format_isoebnf(expr, sep: nil, embedded: false) return (@options[:html] ? %(<a href="#grammar-production-#{@coder.encode expr}">#{@coder.encode expr}</a>) : expr.to_s) if expr.is_a?(Symbol) if expr.is_a?(String) expr = expr[2..-1].hex.chr if expr =~ /\A#x\h+/ expr.chars.each do |c| raise RangeError, "cannot format #{expr.inspect} as an ISO EBNF String: #{c.inspect} is out of range" unless ISOEBNF::TERMINAL_CHARACTER.match?(c) end if expr =~ /"/ return (@options[:html] ? %('<code class="grammar-literal">#{@coder.encode expr}</code>') : %('#{expr}')) else return (@options[:html] ? %("<code class="grammar-literal">#{@coder.encode expr}</code>") : %("#{expr}")) end end parts = { alt: (@options[:html] ? %(<code class="grammar-alt">|</code> ) : "| "), diff: (@options[:html] ? %(<code class="grammar-diff">-</code> ) : "- "), } lparen = (@options[:html] ? %[<code class="grammar-paren">(</code>] : "(") rparen = (@options[:html] ? %[<code class="grammar-paren">)</code>] : ")") case expr.first when :istr # Looses fidelity, but, oh well ... format_isoebnf(expr.last, embedded: true) when :alt, :diff this_sep = (sep ? sep : " ") + parts[expr.first.to_sym] res = expr[1..-1].map {|e| format_isoebnf(e, embedded: true)}.join(this_sep) ? (lparen + res + rparen) : res when :opt r = format_isoebnf(expr[1], embedded: true) "[#{r}]" when :star r = format_isoebnf(expr[1], embedded: true) "{#{r}}" when :plus r = format_isoebnf(expr[1], embedded: true) "#{r}, {#{r}}" when :hex format_isoebnf(expr[1], embedded: true) when :range res = format_isoebnf_range(expr.last) res.is_a?(Array) ? format_isoebnf(res, embedded: true) : res when :seq this_sep = "," + (sep ? sep : " ") res = expr[1..-1].map do |e| format_isoebnf(e, embedded: true) end.join(this_sep) ? (lparen + res + rparen) : res when :rept # Expand repetition min, max, value = expr[1..-1] if min == 0 && max == 1 format_isoebnf([:opt, value], sep: sep, embedded: ) elsif min == 0 && max == '*' format_isoebnf([:star, value], sep: sep, embedded: ) elsif min == 1 && max == '*' format_isoebnf([:plus, value], sep: sep, embedded: ) else val2 = [:seq] while min > 0 val2 << value min -= 1 max -= 1 unless max == '*' end if max == '*' val2 << [:star, value] else opt = nil while max > 0 opt = [:opt, opt ? [:seq, value, opt] : value] max -= 1 end val2 << opt if opt end format_isoebnf(val2, sep: sep, embedded: ) end else raise "Unknown operator: #{expr.first}" end end |
#format_isoebnf_range(string) ⇒ Object (protected)
Format a range Range is formatted as a aliteration of characters
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 |
# File 'lib/ebnf/writer.rb', line 702 def format_isoebnf_range(string) chars = [] o_dash = false raise RangeError, "cannot format #{string.inspect} an ABNF range" if string.start_with?('^') if string.end_with?('-') o_dash = true string = string[0..-2] end scanner = StringScanner.new(string) in_range = false # Build chars from different ranges/enums while !scanner.eos? char = if hex = scanner.scan(Terminals::HEX) hex[2..-1].hex.ord.char(Encoding::UTF_8) else scanner.scan(Terminals::R_CHAR) end raise RangeError, "cannot format #{string.inspect} as an ISO EBNF Aliteration: #{char.inspect} is out of range" unless char && ISOEBNF::TERMINAL_CHARACTER.match?(char) if in_range # calculate characters from chars.last to this char raise RangeError, "cannot format #{string.inspect} as an ISO EBNF Aliteration" unless chars.last < char chars.concat (chars.last..char).to_a[1..-1] in_range = false else chars << char end in_range = true if scanner.scan(/\-/) end chars << '-' if o_dash # Possibly only a single character (no character?) chars.length == 1 ? chars.last.inspect : chars.unshift(:alt) end |