require 'voodoo/generators/generator_api1'

module Voodoo
  # Common base class for code generators.
  #
  # Code generators are expected to implement the following methods:
  #
  # - #new
  # - #add
  # - #add_function
  # - #gensym
  # - #output_file_name
  # - #output_file_suffix
  # - #wordsize
  # - #write
  #
  # This class contains base implementations of some of these methods,
  # which can be used and/or overridden by subclasses.
  #
  # An example of how to use the code generators provided by this module
  # is provided on the main page of the documentation of the Voodoo module.
  #
  class CommonCodeGenerator
    # Provide compatibility with old API
    include GeneratorApi1

    # Initializes the code generator.
    # _params_ shall be a hash containing parameters to the code generator,
    # and shall at least contain the keys <tt>:architecture</tt> and
    # <tt>:format</tt>, specifying the target architecture and output
    # format, respectively.
    def initialize params = {}
      @architecture = params[:architecture] || Config.default_architecture
      @format = params[:format] || Config.default_format
      @sections = {}
      @section_aliases = {}
      # Default section aliases. Subclasses can start from scratch by
      # doing @section_aliases = {}
      section_alias :code, ".text"
      section_alias :functions, :code
      section_alias :data, ".data"
      self.section = :code
      @top_level = Environment.initial_environment
      @environment = @top_level
      @output_file_suffix = '.o'
    end

    # Adds code to the given section.
    #
    # Examples:
    #   add :code, [:return, 0]
    #   add :data, [:align], [:label, :xyzzy], [:word, 42]
    #
    # This method implements the required functionality in terms
    # of the following methods, which must be implemented by subclasses:
    #
    # - #align
    # - #byte
    # - #call
    # - #end_if
    # - #export
    # - #begin_function
    # - #ifelse
    # - #ifeq
    # - #ifge
    # - #ifgt
    # - #ifle
    # - #iflt
    # - #ifne
    # - #import
    # - #label
    # - #let
    # - #ret
    # - #set
    # - #set_byte
    # - #set_word
    # - #string
    # - #word
    #
    def add section, *code
      in_section section do
        code.each do |action|
          keyword, args = action[0], action[1..-1]
          case keyword
          when :function
            function args[0], *args[1..-1]
          when :ifeq, :ifge, :ifgt, :ifle, :iflt, :ifne
            truebody = action[2]
            falsebody = action[3]
            send keyword, action[1][0], action[1][1]
            add section, *truebody
            if falsebody && !falsebody.empty?
              ifelse
              add section, *falsebody
            end
            end_if
          when :return
            send :ret, *args
          when :'set-word'
            send :set_word, *args
          when :'set-byte'
            send :set_byte, *args
          when :'tail-call'
            send :tail_call, *args
          else
            send *action
          end
        end
      end
    end

    # Add function.
    #
    # Parameters:
    # [formals] an Array of formal parameter names
    # [code] an Array of actions to be used as the function's body
    #
    # Example:
    #   add_function [:n], [:return, :add, :n, 1]
    def add_function formals, *code
      add :functions, [:function, formals] + code
    end

    # Add a function to the current section
    def function formals, *code
      begin_function *formals
      code.each { |action| add section, action }
      end_function
    end

    # Generate a new, unused symbol
    def gensym
      Environment.gensym
    end

    # Add code to the current section
    def emit code
      @sections[real_section_name(@section)] << code
    end

    # Get the real name of a section.
    # Given a section name which may be an alias, this method returns the
    # real name of the section.
    def real_section_name name
      given_name = name
      while true
        x = @section_aliases[name]
        break if x == nil       # Not an alias, exit loop and return name
        name = x
        # If name == given_name, we're back where we started. Continuing
        # would have us loop forever. Just return what we have now.
        break if name == given_name
      end
      name
    end

    # Set the current section
    def section= name
      real_name = real_section_name name
      @section = name
      unless @sections.has_key? real_name
        @sections[real_name] = ''
      end
    end

    def section name = nil
      self.section = name if name
      @section
    end

    # Set up +alias_name+ to refer to the same section as +original_name+.
    def section_alias alias_name, original_name
      @section_aliases[alias_name] = original_name
    end

    def in_section name, &block
      oldsection = @section
      self.section = name
      begin
        yield
      ensure
        self.section = oldsection
      end
    end

    # Given an input file name, returns the canonical output file name
    # for this code generator.
    def output_file_name input_name
      input_name.sub(/\.voo$/, '') + @output_file_suffix
    end

    # Returns the canonical output file suffix for this code generator
    def output_file_suffix
      @output_file_suffix
    end

    class Environment
      @@gensym_counter = 0

      attr_reader :args, :locals, :symbols

      def initialize parent = nil
        ## Parent environment
        @parent = parent
        ## Symbol lookup table
        @symbols = parent ? parent.symbols.dup : {}
        ## Number of arguments
        @args = parent ? parent.args : 0
        ## Number of local variables
        @locals = parent ? parent.locals : 0
      end

      def add_arg symbol
        @symbols[symbol] = [:arg, @args]
        @args = @args + 1
      end

      def add_args symbols
        symbols.each { |sym| add_arg sym }
      end

      def add_local symbol
        @symbols[symbol] = [:local, @locals]
        @locals = @locals + 1
      end

      def add_locals symbols
        symbols.each { |sym| add_local sym }
      end

      def gensym
        Environment.gensym
      end

      def [] symbol
        @symbols[symbol]
      end

      def self.gensym
        @@gensym_counter = @@gensym_counter + 1
        "_G#{@@gensym_counter}".to_sym
      end

      def self.initial_environment
        Environment.new
      end
    end

  end
end
