import sys
import types

import log
import util
import knodes
import values
import refine
import codegen

import ppc # for now

class Compiler:

    """ Currently largely a set of globals """

    def __init__ (self):
        # Debug options:
        #
        #   dump_pass is a list of pass names ("basic", "refine", "codegen")
        #   for which dumping is turned on.  dump_level is the level at which
        #   it is enabled.  You can set these through set_dump_options()
        
        self.log = log.Log (sys.stderr)
        self.dump_pass = ()
        self.dump_level = log.NOTHING

        # trace_enabled:
        #
        #   if this is set to 1, then we will instrument the generated
        #   code to dump messages to stderr

        self.trace_enabled = 0
        self.instruction_counter = 0

        # visit_tag:
        #
        #   used when doing a depth first search to cheaply tag those blocks
        #   we have seen.
        
        self.visit_tag = 0

        # worklist:
        #
        #   used in _basic_module and _refine_module to keep track of what
        #   we have left to process
        
        self.worklist = util.WorkList (None)

        # root:
        #
        #   Root of the knowledge tree
        
        self.root = knodes.RootKNode ()

        # undefinedvalue:
        # 
        #   The initial Value object bound to locals
        
        self.undefinedvalue = values.UndefinedValue ()

        # knodes:
        #
        #   copy a reference to the module so that methods defined in
        #   compiler_methods can access it without directly importing
        #   the module and causing a circular conflict
        
        self.knodes = knodes

        # md:
        #
        #   the machine description used to generate the actual
        #   machine-specific assembly.  You must set this with a call
        #   to set_machine_desc()
        
        self.md = None

        # monitor:
        #
        #   If this is non-None, it is used to monitor our progress
        
        self.monitor = None

        return

    def set_machine_desc (self, md):
        self.md = md
        return

    def set_monitor (self, mon):
        self.monitor = mon
        pass

    def set_trace_options (self, enabled):
        self.trace_enabled = enabled
        return

    def next_instruction_id (self):
        id = self.instruction_counter
        self.instruction_counter += 1
        return id

    def set_dump_options (self, passes, level):
        self.dump_pass = passes
        self.dump_level = level
        return

    def jit (self, module):
        
        self._basic_module (module)
        if self.monitor: self.monitor.basic ()
        
        self._refine_module (module)
        if self.monitor: self.monitor.refine ()
        
        self._codegen_module (module)
        if self.monitor: self.monitor.codegen ()

        return

    # Internal

    def get_visit_tag (self):
        self.visit_tag += 1
        return self.visit_tag

    def get_root (self):
        return self.root

    def get_constant (self, constant):

        """ Returns a symbol for a given constant by finding the appropriate
        DataType object for its type and adding the constant to its list of
        constants. """
        
        return self.root.get_typed_constant (type(constant), constant)

    def get_undefined_value (self):
        return self.undefinedvalue

    def _basic_module (self, module):

        """ A poorly named routine; performs the basic setup for a
        module.  This involves constructing a knode for it and
        processing all of the knode children that it has and the knode
        children that they generate.  When you're done we'll have a
        subtree for the module with simple IR fragments but little to
        no optimization; this is why it is called "basic" module. """

        if "basic" in self.dump_pass:
            self.log.set_level (self.dump_level)
            pass
        
        self._construct_module (module)
        
        # Now drain the worklist
        for work in self.worklist: work ()

        self.log.set_level (log.NOTHING)
        return

    def _construct_module (self, module):

        """ Jits the contents of a module.  Anything that is modified
        is modified in place. """

        # Have we already added this module?  If so, return the
        # corresponding knode.
        kmod = self.root.get_child (module)
        if kmod: return kmod

        # Otherwise, construct a knode for it
        kmod = knodes.ModuleKNode (self.root)
        kedge = knodes.KEdge (module, kmod)
        self.root.add_child (kedge)

        # Add in knodes for all the items in the module
        return self._construct_child_knodes (kmod, module.__dict__.items())

    def _construct_child_knodes (self, parent, items):
        for name, value in items:
            knode = self._construct_knode (parent, value)
            if knode:
                knode.basic (self, self.worklist)
                kedge = knodes.KEdge (name, knode)
                parent.add_child (kedge)
                pass
            pass
        return parent

    def _construct_knode (self, parent, value):

        functypes = (types.FunctionType,)
        modtypes = (types.ModuleType,)
        classtypes = (types.ClassType,)

        # Certain types of pointers we assume won't change:
        if isinstance (value, modtypes):
            return self._construct_module (value)
        if isinstance (value, functypes):
            return knodes.FunctionKNode (parent, value.func_code)
        if isinstance (value, classtypes):
            return self._construct_class (parent, value)

        # Otherwise we just return an instance type:
        return knodes.InstanceKNode (parent)

    def _construct_class (self, parent, klass):

        # Otherwise, construct a knode for it
        knode = knodes.ClassKNode (parent, klass.__name__, False)

        # Add in knodes for all the items in the class
        self._construct_child_knodes (knode, klass.__dict__.items())

        return knode

    def _refine_module (self, module):

        """ Refinement takes the IR from the module and performs
        local refinement on all of its parts. """
        
        if "refine" in self.dump_pass:
            self.log.set_level (self.dump_level)
            pass

        modobj = self.root.get_child (module).node
        modobj.refine (self, self.worklist)
        for work in self.worklist: work ()

        self.log.set_level (log.NOTHING)
        return

    def _codegen_module (self, module):

        """ Code Generation generates machine code for a module.  It
        uses the current machine description (self.md) to do it.  """

        if "codegen" in self.dump_pass:
            self.log.set_level (self.dump_level)
            pass

        # Indicate that low level dumping is about to start:
        if self.monitor: self.monitor.low_level_start ()

        # Perform code generation as appropriate
        modobj = self.root.get_child (module).node
        try:
            modobj.codegen (self, self.md, module)

            # Find any named entites and replace them in the module object
            for kedge in modobj.get_children():
                knode = kedge.node
                if knode.is_function ():
                    jitted = knode.external_jitted_version ()
                    if jitted:
                        callable = jitted.get_callable_object ()
                        self.log.high ("replacing entry %s with %s (0x%x)",
                                       kedge.label, callable, id(callable))
                        setattr (module, kedge.label, callable)
                        pass
                    pass
                pass
            pass
        finally:
            self.log.set_level (log.NOTHING)
            pass
            
        return

    pass
