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', }
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
-
#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 |