Class: EBNF::Writer
- Inherits:
-
Object
- Object
- EBNF::Writer
- Defined in:
- lib/ebnf/writer.rb
Constant Summary collapse
- LINE_LENGTH =
80
- LINE_LENGTH_HTML =
200
- ASCII_ESCAPE_NAMES =
ASCII escape names
[ "null", #x00 "start of heading", #x01 "start of text", #x02 "end of text", #x03 "end of transmission", #x04 "enquiry", #x05 "acknowledge", #x06 "bell", #x07 "backspace", #x08 "horizontal tab", #x09 "new line", #x0A "vertical tab", #x0B "form feed", #x0C "carriage return", #x0D "shift out", #x0E "shift in", #x0F "data link escape", #x10 "device control 1", #x11 "device control 2", #x12 "device control 3", #x13 "device control 4", #x14 "negative acknowledge", #x15 "synchronous idle", #x16 "end of trans. block", #x17 "cancel", #x18 "end of medium", #x19 "substitute", #x1A "escape", #x1B "file separator", #x1C "group separator", #x1D "record separator", #x1E "unit separator", #x1F "space" #x20 ]
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.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 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 |
# File 'lib/ebnf/writer.rb', line 106 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}"), sym: nil, 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? ? '|' : '') : '::=') when :abnf formatted.sub!(%r{\s*<code>/</code>\s*}, '') (ndx > 0 ? '=/' : '=') else formatted.sub!(%r{\s*<code>\|</code>\s*}, '') (ndx > 0 ? (rule.alt? ? '|' : '') : '=') end lines << OpenStruct.new(id: ((ndx == 0 ? "[#{rule.id}]" : "") if rule.id), sym: (rule.sym if ndx == 0 || format == :abnf), assign: assign, formatted: formatted) end if format == :isoebnf lines << OpenStruct.new(assign: ';') end lines else OpenStruct.new(id: ("[#{rule.id}]" if rule.id), 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 # 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
92 93 94 95 96 97 |
# File 'lib/ebnf/writer.rb', line 92 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
70 71 72 |
# File 'lib/ebnf/writer.rb', line 70 def self.print(*rules, format: :ebnf) write($stdout, *rules, format: format) end |
.string(*rules, format: :ebnf) ⇒ Object
Format rules to a String
57 58 59 60 61 62 |
# File 'lib/ebnf/writer.rb', line 57 def self.string(*rules, format: :ebnf) require 'stringio' unless defined?(StringIO) buf = StringIO.new write(buf, *rules, format: format) buf.string end |