#!/usr/bin/ruby
#
# r2yacc  ---  racc to yacc converter
#
#   Copyright (c) 1999-2001 Minero Aoki <aamine@loveruby.net>
#
#   This program is feee software.
#   You can distribute/modify this program under the terms of
#   the GNU Lesser General Public License version 2 or later.
#

require 'getopts'
require 'racc/compiler'


class R

  Version = '1.1.0'


  def initialize
    @symboltable = Racc::SymbolTable.new(self)
    @ruletable   = Racc::RuleTable.new(self)
    @parser      = Racc::GrammarFileParser.new(self)
  end

  attr_reader :ruletable
  attr_reader :symboltable
  attr_reader :parser

  def debug()   false end
  def verbose() false end
  def d_parse() false end
  def d_token() false end
  def d_rule()  false end
  def d_state() false end


  ###
  ### parse
  ###

  def parse( str, fname )
    @fname = fname
    @parser.parse str
    @ruletable.init

    u = Racc::GrammarFileParser.get_ucode(fname)
    @header, = u['header']
    @inner,  = u['inner']
    @footer, = u['footer']
  end


  ###
  ### output
  ###

  def output( f )
    rule = t = nil

    f.print(<<EOS)
/*

    generated from #{@fname} by racc2y version #{R::Version}

*/

EOS
    if $OPT_H and @header
      f.puts '%{'
      f.puts '/*---- header ----*/'
      f.puts @header
      f.puts '%}'
      f.puts
    end

    output_defs f
    output_grammer f

    if $OPT_I and @inner
      f.puts
      f.puts '/*---- inner ----*/'
      f.puts
      f.puts @inner
    end
    if $OPT_F and @footer
      f.puts
      f.puts '/*---- footer ----*/'
      f.puts
      f.puts @footer
    end
  end

  def output_defs( f )
    output_token f
    f.puts
    prec = getprecs
    unless prec.empty?
      output_prec f, prec
    end
  end

  def output_token( f )
    f.puts '/* tokens */'
    anc = @symboltable.anchor
    err = @symboltable.error
    total = 6
    f.print '%token'
    @symboltable.each do |t|
      next unless t.terminal?
      next if t.dummy?
      next if t == err
      next if t == anc

      unless String === t.value
        if total > 60
          f.print "\n     "
          total = 0
        end
        total += f.write(" #{tok t}")
      end
    end
    f.puts
  end

  def getprecs
    prec = []
    @symboltable.each do |t|
      next unless t.prec
      if a = prec[t.prec]
        a.push t
      else
        prec[t.prec] = [t.assoc, t]
      end
    end

    prec
  end

  def output_prec( f, tab )
    f.puts '/* precedance table */'
    tab.each do |a|
      if a
        f.printf '%%%-8s', a.shift.id2name.downcase
        a.each do |t|
          f.print ' ', tok(t)
        end
        f.puts
      end
    end
    f.puts
  end


  def output_grammer( f )
    f.puts '%%'

    targ   = nil
    indent = 10
    fmt    = "\n%-10s:"
    emb    = []

    @ruletable.each do |rule|
      if rule.target.dummy?
        emb.push rule.action if rule.action
        next
      end

      if rule.target == targ
        f.print ' ' * indent, '|'
      else
        targ = rule.target
        f.printf fmt, tok(targ)
      end
      rule.symbols.each do |t|
        if t.dummy?   # target of dummy rule for embedded action
          f.puts
          output_act f,
                     emb.shift,
                     indent
          f.print ' ' * (indent + 1)
        else
          f.print ' ', tok(t)
        end
      end
      if rule.specified_prec
        f.print ' %prec ', tok(rule.specified_prec)
      end
      f.puts
      if rule.action
        output_act f, rule.action, indent
      end
    end

    f.puts "\n%%"
  end

  def output_act( f, str, indent )
    f.print ' ' * (indent + 4), "{\n"
    f.print ' ' * (indent + 6), str, "\n" unless $OPT_A
    f.print ' ' * (indent + 4), "}\n"
  end

  def tok( t )
    s = t.to_s
    s.gsub '"', "'"
  end

end   # class R


def usage( stat = 0 )
  $stderr.puts 'wrong option' unless stat == 0
  (stat == 0 ? $stdout : $stderr).print(<<EOS)

racc2y version #{R::Version}

usage:

    racc2y [-AHIF] [-o outfile] raccfile

    -o <file>  output file name  [y.<inputfile>]
    -A         did not output actions
    -H         output 'header'
    -I         output 'inner'
    -F         output 'footer'

EOS
  exit stat
end

getopts('AHIF', 'o:', 'version', 'help', 'copyright') or usage 1
if $OPT_version
  puts "racc2y version #{R::Version}"
  exit 0
end
if $OPT_copyright
  puts "Copyright (c) 2000 Minero Aoki <aamine@loveruby.net>"
  exit 0
end
usage 0 if $OPT_help
usage 1 unless ARGV.size == 1


fname = ARGV[0]
outf = $OPT_o || 'y.' + fname
begin
  str = nil
  File.open(fname) {|f| str = f.read }
rescue Errno::ENOENT
  $stderr.puts "#{File.basename $0}: no such file: #{fname}"
  exit 1
end
r = R.new
r.parse str, fname
File.open(outf, 'w') {|f|
    r.output f
}
