#!/usr/local/bin/ruby
#
# r2yacc  --  racc to yacc converter
#
# Copyright (c) 1999-2005 Minero Aoki
#
# This program is feee software.
# You can distribute/modify this program under the terms of
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the LGPL, see the file "COPYING".
#

require 'getopts'
require 'racc/compiler'

RACC2Y_VERSION = '1.1.1'

def usage(stat = 0)
  $stderr.puts 'wrong option' unless stat == 0
  (stat == 0 ? $stdout : $stderr).print(<<EOS)
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

def main
  getopts('AHIF', 'o:', 'version', 'help', 'copyright') or usage 1
  if $OPT_version
    puts "racc2y version #{RACC2Y_VERSION}"
    exit 0
  end
  if $OPT_copyright
    puts 'Copyright (c) 1999-2003 Minero Aoki'
    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
  conv = Converter.new
  conv.parse str, fname
  File.open(outf, 'w') {|f|
    conv.output(f)
  }
end

class Converter

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

  attr_reader :ruletable
  attr_reader :symboltable
  attr_reader :parser

  attr_accessor :result_var

  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

  def parse(str, fname)
    @fname = fname
    @parser.parse str
    @ruletable.init
    u = Racc::GrammarFileParser.get_usercode(fname)
    @header, = *u['header']
    @inner,  = *u['inner']
    @footer, = *u['footer']
  end

  def output(f)
    rule = t = nil

    f.print(<<EOS)
/*

    generated from #{@fname} by racc2y version #{RACC2Y_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


main
