class Racc::ParserFileGenerator

Constants

RE_CACHE
RUBY_PATH

Public Class Methods

new(states, params) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 70
def initialize(states, params)
  @states = states
  @grammar = states.grammar
  @params = params
end

Public Instance Methods

generate_parser() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 76
def generate_parser
  string_io = StringIO.new

  init_line_conversion_system
  @f = string_io
  parser_file

  string_io.rewind
  string_io.read
end
generate_parser_file(destpath) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 87
def generate_parser_file(destpath)
  init_line_conversion_system
  File.open(destpath, 'w') {|f|
    @f = f
    parser_file
  }
  File.chmod 0755, destpath if @params.make_executable?
end
put_state_transition_table(f) click to toggle source

State Transition Table Serialization

# File lib/racc/parserfilegenerator.rb, line 252
def put_state_transition_table(f)
  @f = f
  state_transition_table
end

Private Instance Methods

actions() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 404
    def actions
      @grammar.each do |rule|
        unless rule.action.source?
          raise "racc: fatal: cannot generate parser file when any action is a Proc"
        end
      end

      if @params.result_var?
        decl = ', result'
        retval = "\n    result"
        default_body = ''
      else
        decl = ''
        retval = ''
        default_body = 'val[0]'
      end
      @grammar.each do |rule|
        line
        if rule.action.empty? and @params.omit_action_call?
          line "# reduce #{rule.ident} omitted"
        else
          src0 = rule.action.source || SourceText.new(default_body, __FILE__, 0)
          if @params.convert_line?
            src = remove_blank_lines(src0)
            delim = make_delimiter(src.text)
            @f.printf unindent_auto(<<-End),
              module_eval(<<'%s', '%s', %d)
                def _reduce_%d(val, _values%s)
                  %s%s
                end
              %s
            End
                      delim, src.filename, src.lineno - 1,
                        rule.ident, decl,
                        src.text, retval,
                      delim
          else
            src = remove_blank_lines(src0)
            @f.printf unindent_auto(<<-End),
              def _reduce_%d(val, _values%s)
              %s%s
              end
            End
                      rule.ident, decl,
                      src.text, retval
          end
        end
      end
      line
      @f.printf unindent_auto(<<-'End'), decl
        def _reduce_none(val, _values%s)
          val[0]
        end
      End
      line
    end
cref_pop() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 211
def cref_pop
  @cref.pop
end
cref_push(name) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 207
def cref_push(name)
  @cref.push name
end
detab(str, ts = 8) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 498
def detab(str, ts = 8)
  add = 0
  len = nil
  str.gsub(/\t/) {
    len = ts - ($`.size + add) % ts
    add += len - 1
    ' ' * len
  }
end
embed_library(src) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 134
def embed_library(src)
  line %[###### #{src.filename} begin]
  line %[unless $".index '#{src.filename}']
  line %[$".push '#{src.filename}']
  put src, @params.convert_line?
  line %[end]
  line %[###### #{src.filename} end]
end
header() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 165
def header
  @params.header.each do |src|
    line
    put src, @params.convert_line_all?
  end
end
i_i_sym_list(name, table) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 374
def i_i_sym_list(name, table)
  sep = ''
  line "#{name} = ["
  table.each_slice(3) do |len, target, mid|
    @f.print sep; sep = ",\n"
    @f.printf '  %d, %d, %s', len, target, mid.inspect
  end
  line " ]"
end
indent() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 215
def indent
  @f.print '  ' * @cref.size
end
indent_re(n) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 494
def indent_re(n)
  RE_CACHE[n] ||= /\A {#{n}}/
end
init_line_conversion_system() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 202
def init_line_conversion_system
  @cref = []
  @used_separator = {}
end
inner() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 172
def inner
  @params.inner.each do |src|
    line
    put src, @params.convert_line?
  end
end
integer_list(name, table) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 320
def integer_list(name, table)
  if table.size > 2000
    serialize_integer_list_compressed name, table
  else
    serialize_integer_list_std name, table
  end
end
line(str = '') click to toggle source
# File lib/racc/parserfilegenerator.rb, line 198
def line(str = '')
  @f.puts str
end
make_delimiter(body) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 470
def make_delimiter(body)
  delim = '.,.,'
  while body.index(delim)
    delim *= 2
  end
  delim
end
make_separator(src) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 231
def make_separator(src)
  sep = unique_separator(src.filename)
  sep *= 2 while src.text.index(sep)
  sep
end
minimum_indent(lines) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 484
def minimum_indent(lines)
  lines.map {|line| n_indent(line) }.min
end
n_indent(line) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 488
def n_indent(line)
  line.slice(/\A\s+/).size
end
notice() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 122
def notice
  line %q[#]
  line %q[# DO NOT MODIFY!!!!]
  line %Q[# This file is automatically generated by Racc #{Racc::Version}]
  line %Q[# from Racc grammer file "#{@params.filename}".]
  line %q[#]
end
parser_class(classname, superclass) { || ... } click to toggle source
# File lib/racc/parserfilegenerator.rb, line 147
def parser_class(classname, superclass)
  mods = classname.split('::')
  classid = mods.pop
  mods.each do |mod|
    indent; line "module #{mod}"
    cref_push mod
  end
  indent; line "class #{classid} < #{superclass}"
  cref_push classid
  yield
  cref_pop
  indent; line "end   \# class #{classid}"
  mods.reverse_each do |mod|
    indent; line "end   \# module #{mod}"
    cref_pop
  end
end
parser_file() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 98
def parser_file
  shebang @params.interpreter if @params.make_executable?
  notice
  line
  if @params.embed_runtime?
    embed_library runtime_source()
  else
    require 'racc/parser.rb'
  end
  header
  parser_class(@params.classname, @params.superclass) {
    inner
    state_transition_table
  }
  footer
end
put(src, convert_line = false) click to toggle source

Low Level Routines

# File lib/racc/parserfilegenerator.rb, line 188
def put(src, convert_line = false)
  if convert_line
    replace_location(src) {
      @f.puts src.text
    }
  else
    @f.puts src.text
  end
end
remove_blank_lines(src) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 461
def remove_blank_lines(src)
  body = src.text.dup
  line = src.lineno
  while body.slice!(/\A[ \t\f]*(?:\n|\r\n|\r)/)
    line += 1
  end
  SourceText.new(body, src.filename, line)
end
replace_location(src) { || ... } click to toggle source
# File lib/racc/parserfilegenerator.rb, line 223
def replace_location(src)
  sep = make_separator(src)
  @f.print 'self.class.' if toplevel?
  @f.puts "module_eval(<<'#{sep}', '#{src.filename}', #{src.lineno})"
  yield
  @f.puts sep
end
require(feature) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 143
def require(feature)
  line "require '#{feature}'"
end
runtime_source() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 130
def runtime_source
  SourceText.new(::Racc::PARSER_TEXT, 'racc/parser.rb', 1)
end
serialize_integer_list_compressed(name, table) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 328
    def serialize_integer_list_compressed(name, table)
      # TODO: this can be made a LOT more clean with a simple split/map
      sep  = "\n"
      nsep = ",\n"
      buf  = ''
      com  = ''
      ncom = ','
      co   = com
      @f.print 'clist = ['
      table.each do |i|
        buf << co << i.to_s; co = ncom
        if buf.size > 66
          @f.print sep; sep = nsep
          @f.print "'", buf, "'"
          buf = ''
          co = com
        end
      end
      unless buf.empty?
        @f.print sep
        @f.print "'", buf, "'"
      end
      line ' ]'

      @f.print(<<-End)
        #{name} = arr = ::Array.new(#{table.size}, nil)
        idx = 0
        clist.each do |str|
          str.split(',', -1).each do |i|
            arr[idx] = i.to_i unless i.empty?
            idx += 1
          end
        end
      End
    end
serialize_integer_list_std(name, table) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 364
def serialize_integer_list_std(name, table)
  sep = ''
  line "#{name} = ["
  table.each_slice(10) do |ns|
    @f.print sep; sep = ",\n"
    @f.print ns.map {|n| sprintf('%6s', n ? n.to_s : 'nil') }.join(',')
  end
  line ' ]'
end
shebang(path) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 118
def shebang(path)
  line '#!' + (path == 'ruby' ? RUBY_PATH : path)
end
state_transition_table() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 259
    def state_transition_table
      table = @states.state_transition_table
      table.use_result_var = @params.result_var?
      table.debug_parser = @params.debug_parser?

      line "##### State transition tables begin ###"
      line
      integer_list 'racc_action_table', table.action_table
      line
      integer_list 'racc_action_check', table.action_check
      line
      integer_list 'racc_action_pointer', table.action_pointer
      line
      integer_list 'racc_action_default', table.action_default
      line
      integer_list 'racc_goto_table', table.goto_table
      line
      integer_list 'racc_goto_check', table.goto_check
      line
      integer_list 'racc_goto_pointer', table.goto_pointer
      line
      integer_list 'racc_goto_default', table.goto_default
      line
      i_i_sym_list 'racc_reduce_table', table.reduce_table
      line
      line "racc_reduce_n = #{table.reduce_n}"
      line
      line "racc_shift_n = #{table.shift_n}"
      line
      sym_int_hash 'racc_token_table', table.token_table
      line
      line "racc_nt_base = #{table.nt_base}"
      line
      line "racc_use_result_var = #{table.use_result_var}"
      line
      @f.print(unindent_auto(<<-End))
        Racc_arg = [
          racc_action_table,
          racc_action_check,
          racc_action_default,
          racc_action_pointer,
          racc_goto_table,
          racc_goto_check,
          racc_goto_default,
          racc_goto_pointer,
          racc_nt_base,
          racc_reduce_table,
          racc_token_table,
          racc_shift_n,
          racc_reduce_n,
          racc_use_result_var ]
      End
      line
      string_list 'Racc_token_to_s_table', table.token_to_s_table
      line
      line "Racc_debug_parser = #{table.debug_parser}"
      line
      line '##### State transition tables end #####'
      actions
    end
string_list(name, list) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 394
def string_list(name, list)
  sep = "  "
  line "#{name} = ["
  list.each do |s|
    @f.print sep; sep = ",\n  "
    @f.print s.dump
  end
  line ' ]'
end
sym_int_hash(name, h) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 384
def sym_int_hash(name, h)
  sep = "\n"
  @f.print "#{name} = {"
  h.to_a.sort_by {|sym, i| i }.each do |sym, i|
    @f.print sep; sep = ",\n"
    @f.printf "  %s => %d", sym.serialize, i
  end
  line " }"
end
toplevel?() click to toggle source
# File lib/racc/parserfilegenerator.rb, line 219
def toplevel?
  @cref.empty?
end
unindent_auto(str) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 478
def unindent_auto(str)
  lines = str.lines.to_a
  n = minimum_indent(lines)
  lines.map {|line| detab(line).sub(indent_re(n), '').rstrip + "\n" }.join('')
end
unique_separator(id) click to toggle source
# File lib/racc/parserfilegenerator.rb, line 237
def unique_separator(id)
  sep = "...end #{id}/module_eval..."
  while @used_separator.key?(sep)
    sep.concat sprintf('%02x', rand(255))
  end
  @used_separator[sep] = true
  sep
end