#!/usr/local/bin/ruby
#
# $Id$
#
# Copyright (c) 1999-2006 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 'racc/grammarfileparser'
require 'racc/info'
require 'optparse'

def main
  @with_action = true
  with_header = false
  with_inner = false
  with_footer = false
  output = nil
  parser = OptionParser.new
  parser.banner = "Usage: #{File.basename($0)} [-AHIF] [-oFILENAME] GRAMMARFILE"
  parser.on('-o', '--output=FILENAME', 'output file name [<input>.yacc]') {|name|
    output = name
  }
  parser.on('-A', '--without-action', 'Does not include actions.') {
    @with_action = false
  }
  parser.on('-H', '--with-header', 'Includes header part.') {
    with_header = true
  }
  parser.on('-I', '--with-inner', 'Includes inner part.') {
    with_inner = true
  }
  parser.on('-F', '--with-footer', 'Includes footer part.') {
    with_footer = true
  }
  parser.on('--version', 'Prints version and quit.') {
    puts "racc2y version #{Racc::Version}"
    exit 0
  }
  parser.on('--copyright', 'Prints copyright and quit.') {
    puts Racc::Copyright
    exit 0
  }
  parser.on('--help', 'Prints this message and quit.') {
    puts parser.help
    exit 1
  }
  begin
    parser.parse!
  rescue OptionParser::ParseError => err
    $stderr.puts err.message
    $stderr.puts parser.help
    exit 1
  end
  if ARGV.empty?
    $stderr.puts "no input file"
    exit 1
  end
  unless ARGV.size == 1
    $stderr.puts "too many inputs"
    exit 1
  end
  input = ARGV[0]

  begin
    result = Racc::GrammarFileParser.parse_file(input)
    result.grammar.init
    File.open(output || "#{input}.yacc", 'w') {|f|
      f.puts "/* generated from #{input} */"
      if with_header
        f.puts
        f.puts '%{'
        print_user_codes f, result.params.header
        f.puts '%}'
      end
      f.puts
      print_terminals f, result.grammar
      f.puts
      print_precedence_table f, precedence_table(result.grammar)
      f.puts
      f.puts '%%'
      print_grammar f, result.grammar
      f.puts '%%'
      if with_inner
        f.puts '/*---- inner ----*/'
        print_user_codes f, result.params.inner
      end
      if with_footer
        f.puts '/*---- footer ----*/'
        print_user_codes f, result.params.footer
      end
    }
  rescue SystemCallError => err
    $stderr.puts err.message
    exit 1
  end
end

def print_terminals(f, grammar)
  init_indent = '%token'.size
  f.print '%token'
  columns = init_indent
  grammar.symboltable.each_terminal do |t|
    next unless t.terminal?
    next if t.dummy?
    next if t == grammar.symboltable.anchor
    next if t == grammar.symboltable.error
    unless t.value.kind_of?(String)
      if columns > 60
        f.puts
        f.print ' ' * init_indent
        columns = init_indent
      end
      columns += f.write(" #{yacc_symbol(t)}")
    end
  end
  f.puts
end

def precedence_table(grammar)
  table = []
  grammar.symboltable.select {|sym| sym.precedence }.each do |sym|
    (table[sym.prec] ||= [sym.assoc]).push sym
  end
  table.compact
end

def print_precedence_table(f, table)
  return if table.empty?
  f.puts '/* precedance table */'
  table.each do |syms|
    assoc = syms.shift
    f.printf '%%%-8s ', assoc.to_s.downcase
    f.puts syms.map {|s| yacc_symbol(s) }.join(' ')
  end
  f.puts
end

def print_grammar(f, grammar)
  prev_target = nil
  indent = 10
  embactions = []
  grammar.each do |rule|
    if rule.target.dummy?
      embactions.push rule.action  unless rule.action.empty?
      next
    end
    if rule.target == prev_target
      f.print ' ' * indent, '|'
    else
      prev_target = rule.target
      f.printf "\n%-10s:", yacc_symbol(prev_target)
    end
    rule.symbols.each do |s|
      if s.dummy?   # target of dummy rule for embedded action
        f.puts
        print_action f, embactions.shift, indent
        f.print ' ' * (indent + 1)
      else
        f.print ' ', yacc_symbol(s)
      end
    end
    if rule.specified_prec
      f.print ' %prec ', yacc_symbol(rule.specified_prec)
    end
    f.puts
    unless rule.action.empty?
      print_action f, rule.action, indent
    end
  end
end

def print_action(f, action, indent)
  return unless @with_action
  f.print ' ' * (indent + 4), "{\n"
  f.print ' ' * (indent + 6), action.source.text.strip, "\n"
  f.print ' ' * (indent + 4) , "}\n"
end

def print_user_codes(f, srcs)
  return if srcs.empty?
  srcs.each do |src|
    f.puts src.text
  end
end

def yacc_symbol(s)
  s.to_s.gsub('"', "'")
end

main
