class Rexical::Generator


Constants

REX_HEADER

REX_STUB
REX_UTIL

Attributes

class_name[RW]
debug[RW]
exclusive_states[RW]
grammar_file[RW]

grammar_lines[RW]
ignorecase[RW]
independent[RW]
lineno[RW]
module_name[RW]
rules[RW]
scanner_file[RW]

Public Class Methods

new(opts) click to toggle source

# File lib/rexical/generator.rb, line 34
def initialize(opts)
  @lineno  =  0
  @macro  =  {}
  @rules  =  []
  @exclusive_states = [nil]
  @grammar_lines  =  nil
  @scanner_header  =  ""
  @scanner_footer  =  ""
  @scanner_inner  =  ""
  @opt  =  opts
end

Public Instance Methods

add_header( st ) click to toggle source

# File lib/rexical/generator.rb, line 47
def add_header( st )
  @scanner_header  +=  "#{st}\n"
end
add_inner( st ) click to toggle source

# File lib/rexical/generator.rb, line 57
def add_inner( st )
  @scanner_inner  +=  "#{st}\n"
end
add_macro( st ) click to toggle source

# File lib/rexical/generator.rb, line 77
def add_macro( st )
  ss  =  StringScanner.new(st)
  ss.scan(/\s+/)
  key = ss.scan(/\S+/)
  ss.scan(/\s+/)
  st = ss.post_match
  len  =  st.size
  ndx  =  0
  while ndx <= len
    c  =  st[ndx,1]
    ndx  +=  1
    case  c
    when '\'
      ndx  +=  1
      next
    when '#', ' '
      ndx  -=  1
      break
    end
  end
  expr = st[0,ndx]
  expr.gsub!('\ ', ' ')
  key  =  '{' + key + '}'
  @macro.each_pair do |k, e|
    expr.gsub!(k) { |m| e }
  end
  @macro[key]  =  expr
rescue
  raise ParseError, "parse error in add_macro:'#{st}'"
end
add_option( st ) click to toggle source

# File lib/rexical/generator.rb, line 62
def add_option( st )
  opts = st.split
  opts.each do |opt|
    case opt
    when /ignorecase/i
      @opt['--ignorecase'] = true
    when /stub/i
      @opt['--stub'] = true
    when /independent/i
      @opt['--independent'] = true
    end
  end
end
add_rule( rule_state, rule_expr, rule_action=nil ) click to toggle source

# File lib/rexical/generator.rb, line 109
def add_rule( rule_state, rule_expr, rule_action=nil )
  st = rule_expr.dup
  @macro.each_pair do |k, e|
    rule_expr.gsub!(k) { |m| e }
  end
  if rule_state.to_s[1,1] =~ /[A-Z]/
    @exclusive_states << rule_state  unless @exclusive_states.include?(rule_state)
    exclusive_state = rule_state
    start_state = nil
  else
    exclusive_state = nil
    start_state = rule_state
  end
  rule = [exclusive_state, start_state, rule_expr, rule_action]
  @rules << rule
rescue
  raise ParseError, "parse error in add_rule:'#{st}'"
end
next_line() click to toggle source
# File lib/rexical/generator.rb, line 132
def next_line
  @lineno += 1
  @grammar_lines.scan_until(/\n/).chomp
rescue
  nil
end
parse() click to toggle source
# File lib/rexical/generator.rb, line 139
def parse
  state1  =  :HEAD
  state2  =  nil
  state3  =  nil
  lastmodes  =  []
  while st = next_line
    case state1
    when :FOOT
      add_footer  st

    when :HEAD
      ss  =  StringScanner.new(st)
      if ss.scan(/class/)
        state1  =  :CLASS
        st  =  ss.post_match.strip
        @class_name  =  st
      else
        add_header  st
      end

    when :CLASS
      s = st.strip
      next  if s.size == 0 or s[0,1] == '#'

      ss  =  StringScanner.new(st)
      if ss.scan(/option.*$/)
        state2 = :OPTION
        next
      end
      if ss.scan(/inner.*$/)
        state2 = :INNER
        next
      end
      if ss.scan(/macro.*$/)
        state2 = :MACRO
        next
      end
      if ss.scan(/rule.*$/)
        state2 = :RULE
        next
      end
      if ss.scan(/end.*$/)
        state1 = :FOOT
        next
      end

      case state2
      when :OPTION
        add_option  st

      when :INNER
        add_inner  st

      when :MACRO
        add_macro  st

      when :RULE
        case state3
        when nil
          rule_state, rule_expr, rule_action  =  parse_rule(st)
          if rule_action =~ /\s*\{/
            lastmodes = parse_action(rule_action, lastmodes)
            if lastmodes.empty?
              add_rule  rule_state, rule_expr, rule_action
            else
              state3  =  :CONT
              rule_action  +=  "\n"
            end
          else
            add_rule  rule_state, rule_expr
          end

        when :CONT
          rule_action  +=  "#{st}\n"
          lastmodes = parse_action(st, lastmodes)
          if lastmodes.empty?
            state3  =  nil
            add_rule  rule_state, rule_expr, rule_action
          else
          end

        end # case state3

      end # case state2

    end # case state1

  end # while

end
parse_action(st, lastmodes=[]) click to toggle source

# File lib/rexical/generator.rb, line 244
def parse_action(st, lastmodes=[])
  modes  =  lastmodes
  mode  =  lastmodes[-1]
  ss  =  StringScanner.new(st)
  until ss.eos?
    c  =  ss.scan(/./)
    case  c
    when '#'
      if (mode == :brace) or (mode == nil)
        #p [c, mode, modes]
        return  modes
      end
    when '{'
      if (mode == :brace) or (mode == nil)
        mode = :brace
        modes.push  mode
      end
    when '}'
      if (mode == :brace)
        modes.pop
        mode = modes[0]
      end
    when "'"
      if (mode == :brace)
        mode = :quote
        modes.push  mode
      elsif (mode == :quote)
        modes.pop
        mode = modes[0]
      end
    when '"'
      if (mode == :brace)
        mode = :doublequote
        modes.push  mode
      elsif (mode == :doublequote)
        modes.pop
        mode = modes[0]
      end
    when '`'
      if (mode == :brace)
        mode = :backquote
        modes.push  mode
      elsif (mode == :backquote)
        modes.pop
        mode = modes[0]
      end
    end
  end
  #p [c, mode, modes]
  return  modes
end
parse_rule(st) click to toggle source

# File lib/rexical/generator.rb, line 231
def parse_rule(st)
  st.strip!
  return  if st.size == 0 or st[0,1] == '#'
  ss  =  StringScanner.new(st)
  ss.scan(/\s+/)
  rule_state  =  ss.scan(/\:\S+/)
  ss.scan(/\s+/)
  rule_expr  =  ss.scan(/\S+/)
  ss.scan(/\s+/)
  [rule_state, rule_expr, ss.post_match]
end
read_grammar() click to toggle source
# File lib/rexical/generator.rb, line 128
def read_grammar
  @grammar_lines = StringScanner.new File.read(grammar_file)
end
write_scanner(f = scanner_io) click to toggle source
# File lib/rexical/generator.rb, line 374
  def write_scanner f = scanner_io
    ## scan flag
    flag = ""
    flag += "i"  if @opt['--ignorecase']
    ## header
    f.printf REX_HEADER, Rexical::VERSION, grammar_file

    unless @opt['--independent']
      f.printf "require 'racc/parser'\n"
    end

    @scanner_header.each_line do |s|
      f.print s
    end
    if @opt['--independent']
      f.puts "class #{@class_name}"
    else
      f.puts "class #{@class_name} < Racc::Parser"
    end

    ## utility method
    f.print REX_UTIL

    ## scanner method

    f.print <<-REX_EOT

def next_token
  return if @ss.eos?
  
  # skips empty actions
  until token = _next_token or @ss.eos?; end
  token
end

def _next_token
  text = @ss.peek(1)
  @lineno  +=  1  if text == "\\n"
  token = case @state
  REX_EOT

  exclusive_states.each do |es|
    f.printf <<-REX_EOT
  when #{es ? es.to_s : "nil"}
    case
    REX_EOT
    rules.each do |rule|
      exclusive_state, start_state, rule_expr, rule_action = *rule
      if es == exclusive_state

        if rule_action
          if start_state
            f.print <<-REX_EOT
    when((state == #{start_state}) and (text = @ss.scan(/#{rule_expr}/#{flag})))
       action #{rule_action}

            REX_EOT
          else
            f.print <<-REX_EOT
    when (text = @ss.scan(/#{rule_expr}/#{flag}))
       action #{rule_action}

            REX_EOT
          end
        else
          if start_state
            f.print <<-REX_EOT
    when (state == #{start_state}) and (text = @ss.scan(/#{rule_expr}/#{flag}))
      ;

            REX_EOT
          else
            f.print <<-REX_EOT
    when (text = @ss.scan(/#{rule_expr}/#{flag}))
      ;

            REX_EOT
          end
        end

      end
    end
    f.print <<-REX_EOT
    else
      text = @ss.string[@ss.pos .. -1]
      raise  ScanError, "can not match: '" + text + "'"
    end  # if

    REX_EOT
  end
  f.print <<-REX_EOT
  else
    raise  ScanError, "undefined state: '" + state.to_s + "'"
  end  # case state
    REX_EOT
  if @opt['--debug']
    f.print <<-REX_EOT
  p token
    REX_EOT
  end
  f.print <<-REX_EOT
  token
end  # def _next_token

    REX_EOT

    ## inner method
    @scanner_inner.each_line do |s|
      f.print s
    end
    f.puts "end # class"

    ## footer
    @scanner_footer.each_line do |s|
      f.print s
    end # case

    ## stub main
    f.printf REX_STUB, @class_name, '"%s:%d:%s\n"'  if @opt['--stub']
    f.close

  end

Private Instance Methods

scanner_io() click to toggle source

# File lib/rexical/generator.rb, line 366
def scanner_io
  unless  scanner_file = @opt['--output-file']
    scanner_file  =  grammar_file + ".rb"
  end
  f = File.open(scanner_file, 'w')
end