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 29
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 41
def add_header( st )
  @scanner_header  +=  "#{st}\n"
end
add_inner( st ) click to toggle source
# File lib/rexical/generator.rb, line 49
def add_inner( st )
  @scanner_inner  +=  "#{st}\n"
end
add_macro( st ) click to toggle source
# File lib/rexical/generator.rb, line 69
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 53
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
    when /matcheos/i
      @opt['--matcheos'] = true
    end
  end
end
add_rule( rule_state, rule_expr, rule_action=nil ) click to toggle source
# File lib/rexical/generator.rb, line 100
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 123
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 130
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 229
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 217
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 119
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 354
    def write_scanner f = scanner_io
      flag = ""
      flag += "i"  if @opt['--ignorecase']

      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

      f.print REX_UTIL

      eos_check = @opt["--matcheos"] ? "" : "return if @ss.eos?"

      ## scanner method
      f.print <<-REX_EOT

        def next_token
          #{eos_check}

          # 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|
        if es.nil?
          f.printf <<-REX_EOT
            when #{(["nil"] + rules.collect{ |rule| rule[1].nil? ? "nil" : rule[1] }).uniq.join(', ')}
          REX_EOT
        else
          f.printf <<-REX_EOT
            when #{es}
          REX_EOT
        end
        f.printf <<-REX_EOT
          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}) && (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 # rules.each

        if @opt["--matcheos"]
          eos_check = <<-REX_EOT
            when @@ss.scan(/$/)
               ;
          REX_EOT
        else
          eos_check = ""
        end

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

        REX_EOT
      end # exclusive_states.each

      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

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

      @scanner_footer.each_line do |s|
        f.print s
      end

      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 346
def scanner_io
  unless  scanner_file = @opt['--output-file']
    scanner_file  =  grammar_file + ".rb"
  end
  File.open(scanner_file, 'wb')
end