#!/usr/bin/env ruby -wKU

class Racc2JS
  
  def initialize(source, output = nil)
    @source = source
    @output = output || 
          File.join(File.dirname(source), File.basename(source, '.*') + '.js')
    
    @racc_output = File.join(
          File.dirname(source), File.basename(source, '.*') + '.rb')
  end
  
  def generate
    puts "generating racc parser..."
    `racc --output-file=#@racc_output #@source`
    puts "loading generated parser.."
    self.class.module_eval File.read(File.expand_path(@racc_output))
    # we rely on only constant under our class being our new generated parser..
    parser = self.class.constants[0]
    raise "no suitable parser found in #@racc_output" unless parser
    puts "using parser: #{parser}"
    @parser_name = parser.to_s
    @parser = self.class.const_get(parser)
    
    @parser_output = []
    handle_parser_runtime
    handle_racc_arg
    handle_racc_actions
    
    # write output to our destination
    File.open(@output, 'w') { |o| o.puts pre + @parser_output.join('') + post }
    
    # remove temp ruby file
    File.delete @racc_output
  end
  
  # pre code
  def pre
    %Q[var #@parser_name = (function() {\n]
  end
  
  # post code
  def post
    %Q[\n   return parser;
      })();
      
      if (typeof require !== 'undefined' && typeof module !== 'undefined') {
        exports.#@parser_name = #@parser_name;
      }
    ]
  end
  
  # add parser runtime to @parser_output
  def handle_parser_runtime
    source = File.read(File.join(File.dirname(__FILE__), 'parser.js'))
    @parser_output << source
  end
  
  # handle racc table
  def handle_racc_arg
    racc_arg = @parser::Racc_arg
    
    @parser_output << "parser.Racc_arg = [\n"
    
    # puts racc_arg
    racc_arg.each_with_index do |arg, idx|
      @parser_output.push "," if idx > 0
      
      case arg
      when Array
        @parser_output << "["
        
        arg.each_with_index do |ary_item, ary_idx|
          @parser_output.push "," if ary_idx > 0
          @parser_output.push ruby_to_javascript ary_item
        end
        
        @parser_output << "]"
      when Hash
        @parser_output << "{"
        done = false
        arg.each do |key, value|
          @parser_output.push "," if done
          done = true
          
          @parser_output.push key.to_s.inspect
          @parser_output.push ": "
          @parser_output.push ruby_to_javascript(value)
        end
        
        @parser_output << "}"
      else
        @parser_output << ruby_to_javascript(arg)
      end  
    end
    
    @parser_output << "];"
  end
  
  # output ruby values' javascript equivalent
  def ruby_to_javascript(literal)
    case literal
    when Numeric
      literal.to_s
    when NilClass
      # see parser.js - saves space once minimized. - removed.
      'null'
    when String, Symbol
      literal.to_s.inspect
    when true, false
      literal.to_s
    else
      raise "Error: bad native: #{literal}"
    end
  end
  
  # get all method from racc. we get all impl methods, and then output their
  # code to js. we (for now) rely on all methods basically being:
  # 
  #   result = "val[0] + .."
  # 
  # so we assign result a string, which we can then use to output js..hacky, but
  # only way (for now).
  def handle_racc_actions
    # get all methods and unique them.
    reduce_table = @parser::Racc_arg[9].select { |a| Symbol === a }.uniq!
    # remove racc_error and _reduce_none
    reduce_table.delete(:racc_error)
    reduce_table.delete(:_reduce_none)
    # we need a parser to use
    parser = @parser.new
    reduce_table.each do |reduction|
      code = parser.send(reduction, nil, nil, nil)
      @parser_output << %Q[
        parser.prototype.#{reduction} = function(val, result) {
          #{code}
          return result;
        };]
    end
  end
end

# 
# bin
# 
if ARGV.empty?
  raise "no filename given"
elsif ARGV.length == 1
  Racc2JS.new(ARGV[0]).generate
else
  Racc2JS.new(ARGV[0], ARGV[1]).generate
end
