
"""
Varius microopcodes for different ootypesystem based backends

These microopcodes are used to translate from the ootype operations to
the operations of a particular backend.  For an example, see
cli/opcodes.py which maps from ootype opcodes to sets of metavm
instructions.

See the MicroInstruction class for discussion on the methods of a
micro-op.
"""

from pypy.rpython.ootypesystem import ootype
from pypy.rpython.extfunc import ExtFuncEntry, is_external

class Generator(object):
    
    def add_comment(self, text):
        """
        Called w/in a function w/ a text string that could be
        usefully added to the output.
        """
        pass

    def add_section(self, text):
        """
        Prints a distinguished comment
        """
        self.add_comment("_" * 70)
        self.add_comment(text)

    def pop(self, TYPE):
        """ Pops a value off the top of the stack, which is of the
        given TYPE.

        Stack: val, ... -> ..."""
        raise NotImplementedError

    def dup(self, TYPE):
        """ Duplicates the top of the stack, which is of the given TYPE.

        Stack: val, ... -> val, val, ..."""
        raise NotImplementedError

    def emit(self, instr, *args):
        """
        Invoked by InstructionList.render() when we encounter a
        non-MicroInstruction in the list of instructions.  This is
        typically used to encode small single operands as strings.
        """
        pass

    def load(self, v):
        """
        Loads an item 'v' onto the stack
        
        Stack: ... -> v, ...
        """
        pass

    def store(self, v):
        """
        Stores an item from the stack into 'v'
        
        Stack: value, ... -> ...
        """
        pass

    def set_field(self, CONCRETETYPE, fieldname):
        """
        Stores a value into a field.
        
        'CONCRETETYPE' should be the type of the class that has the field
        'fieldname' is a string with the name of the field
        
        Stack: value, item, ... -> ...
        """
        raise NotImplementedError

    def get_field(self, CONCRETETYPE, fieldname):
        """
        Gets a value from a specified field.

        'CONCRETETYPE' should be the type of the class that has the field
        'fieldname' is the name of the field

        Stack: item, ... -> ...
        """
        raise NotImplementedError

    def downcast(self, TYPE):
        """
        Casts the object on the top of the stack to be of the specified
        ootype.  Assumed to raise an exception on failure.
        
        Stack: obj, ... -> obj, ...
        """
        raise NotImplementedError

    def getclassobject(self, OOINSTANCE):
        """
        Gets the class object for the OOINSTANCE.  The type of the class
        object will depend on the backend, of course; for example in JVM
        it is java.lang.Class.
        """
        raise NotImplementedError

    def instantiate(self):
        """
        Instantiates an instance of the Class object that is on top of
        the stack.  Class objects refers to an object representing a
        class.  Used to implement RuntimeNew.

        Stack: class_obj, ... -> instance_obj, ...
        """
        raise NotImplementedError

    def instanceof(self, TYPE):
        """
        Determines whether the object on the top of the stack is an
        instance of TYPE (an ootype).
        
        Stack: obj, ... -> boolean, ...
        """
        pass

    def branch_unconditionally(self, target_label):
        """ Branches to target_label unconditionally """
        raise NotImplementedError

    def branch_conditionally(self, iftrue, target_label):
        """ Branches to target_label depending on the value on the top of
        the stack.  If iftrue is True, then the branch occurs if the value
        on top of the stack is true; if iftrue is false, then the branch
        occurs if the value on the top of the stack is false

        Stack: cond, ... -> ... """
        raise NotImplementedError

    def branch_if_equal(self, target_label):
        """
        Pops two values from the stack and branches to target_label if
        they are equal.

        Stack: obj1, obj2, ... -> ...
        """
        raise NotImplementedError

    def call_graph(self, graph):
        """ Invokes the function corresponding to the given graph.  The
        arguments to the graph have already been pushed in order
        (i.e., first argument pushed first, etc).  Pushes the return
        value.

        Stack: argN...arg2, arg1, arg0, ... -> ret, ... """
        raise NotImplementedError

    def prepare_generic_argument(self, ITEMTYPE):
        """
        Invoked after a generic argument has been pushed onto the stack.
        May not need to do anything, but some backends, *cough*Java*cough*,
        require boxing etc.
        """
        return # by default do nothing

    def call_method(self, OOCLASS, method_name):
        """ Invokes the given method on the object on the stack.  The
        this ptr and all arguments have already been pushed.

        Stack: argN, arg2, arg1, this, ... -> ret, ... """
        raise NotImplementedError

    def prepare_call_primitive(self, op, module, name):
        """ see call_primitive: by default does nothing """
        pass
        
    def call_primitive(self, op, module, name):
        """ Like call_graph, but it has been suggested that the method be
        rendered as a primitive.  The full sequence for invoking a primitive:

          self.prepare_call_primitive(op, module, name)
          for each arg: self.load(arg)
          self.call_primitive(op, module, name)

        Stack: argN...arg2, arg1, arg0, ... -> ret, ... """
        raise NotImplementedError

    def prepare_call_oostring(self, OOTYPE):
        " see call_oostring "
        pass

    def call_oostring(self, OOTYPE):
        """ Invoked for the oostring opcode with both operands
        (object, int base) already pushed onto the stack.
        prepare_call_oostring() is invoked before the operands are
        pushed."""
        raise NotImplementedError

    def prepare_call_oounicode(self, OOTYPE):
        " see call_oounicode "
        pass

    def call_oounicode(self, OOTYPE):
        """ Invoked for the oounicode opcode with the operand already
        pushed onto the stack.  prepare_call_oounicode() is invoked
        before the operand is pushed. """
        raise NotImplementedError

    def new(self, TYPE):
        """ Creates a new object of the given type.

        Stack: ... -> newobj, ... """
        raise NotImplementedError

    def oonewarray(self, TYPE, length):
        """ Creates a new array of the given type with the given length.

        Stack: ... -> newobj, ... """
        raise NotImplementedError


    def push_null(self, TYPE):
        """ Push a NULL value onto the stack (the NULL value represents
        a pointer to an instance of OOType TYPE, if it matters to you). """
        raise NotImplementedError

    def push_primitive_constant(self, TYPE, value):
        """ Push an instance of TYPE onto the stack with the given
        value.  TYPE will be one of the types enumerated in
        oosupport.constant.PRIMITIVE_TYPES.  value will be its
        corresponding ootype implementation. """
        raise NotImplementedError

    def get_instrution_count(self):
        """
        Return the number of opcodes in the current function, or -1
        if the backend doesn't care about it. Default is -1
        """
        return -1

class InstructionList(list):
    def render(self, generator, op):
        for instr in self:
            if isinstance(instr, MicroInstruction):
                instr.render(generator, op)
            else:
                generator.emit(instr)
    
    def __call__(self, *args):
        return self.render(*args)


class MicroInstruction(object):
    def render(self, generator, op):
        """
        Generic method which emits code to perform this microinstruction.
        
        'generator' -> the class which generates actual code emitted
        'op' -> the instruction from the FlowIR
        """
        pass

    def __str__(self):
        return self.__class__.__name__
    
    def __call__(self, *args):
        return self.render(*args)

class _DoNothing(MicroInstruction):
    def render(self, generator, op):
        pass
        
class PushArg(MicroInstruction):
    """ Pushes a given operand onto the stack. """
    def __init__(self, n):
        self.n = n

    def render(self, generator, op):
        generator.load(op.args[self.n])

class _PushAllArgs(MicroInstruction):
    """ Pushes all arguments of the instruction onto the stack in order. """
    def __init__(self, slice=None):
        """ Eventually slice args
        """
        self.slice = slice
    
    def render(self, generator, op):
        if self.slice is not None:
            args = op.args[self.slice]
        else:
            args = op.args
        for arg in args:
            generator.load(arg)

class PushPrimitive(MicroInstruction):
    def __init__(self, TYPE, value):
        self.TYPE = TYPE
        self.value = value

    def render(self, generator, op):
        generator.push_primitive_constant(self.TYPE, self.value)
        
class _StoreResult(MicroInstruction):
    def render(self, generator, op):
        generator.store(op.result)

class _SetField(MicroInstruction):
    def render(self, generator, op):
        this, field, value = op.args
##        if field.value == 'meta':
##            return # TODO
        
        if value.concretetype is ootype.Void:
            return
        generator.load(this)
        generator.load(value)
        generator.set_field(this.concretetype, field.value)

class _GetField(MicroInstruction):
    def render(self, generator, op):
        # OOType produces void values on occassion that can safely be ignored
        if op.result.concretetype is ootype.Void:
            return
        this, field = op.args
        generator.load(this)
        generator.get_field(this.concretetype, field.value)

class _DownCast(MicroInstruction):
    """ Push the argument op.args[0] and cast it to the desired type, leaving
    result on top of the stack. """
    def render(self, generator, op):
        RESULTTYPE = op.result.concretetype
        generator.load(op.args[0])
        generator.downcast(RESULTTYPE)

class _InstanceOf(MicroInstruction):
    """ Push the argument op.args[0] and cast it to the desired type, leaving
    result on top of the stack. """
    def render(self, generator, op):
        RESULTTYPE = op.result.concretetype
        generator.load(op.args[0])
        generator.instanceof(RESULTTYPE)

# There are three distinct possibilities where we need to map call differently:
# 1. Object is marked with rpython_hints as a builtin, so every attribut access
#    and function call goes as builtin
# 2. Function called is a builtin, so it might be mapped to attribute access, builtin function call
#    or even method call
# 3. Object on which method is called is primitive object and method is mapped to some
#    method/function/attribute access
class _GeneralDispatcher(MicroInstruction):
    def __init__(self, builtins, class_map):
        self.builtins = builtins
        self.class_map = class_map
    
    def render(self, generator, op):
        raise NotImplementedError("pure virtual class")
    
    def check_builtin(self, this):
        if not isinstance(this, ootype.Instance):
            return False
        return this._hints.get('_suggested_external')
    
class _MethodDispatcher(_GeneralDispatcher):
    def render(self, generator, op):
        method = op.args[0].value
        this = op.args[1].concretetype
        if self.check_builtin(this):
            return self.class_map['CallBuiltinObject'].render(generator, op)
        try:
            self.builtins.builtin_obj_map[this.__class__][method](generator, op)
        except KeyError:
            return self.class_map['CallMethod'].render(generator, op)

class _CallDispatcher(_GeneralDispatcher):
    def render(self, generator, op):
        func = op.args[0]
        # XXX we need to sort out stuff here at some point
        if is_external(func):
            func_name = func.value._name.split("__")[0]
            try:
                return self.builtins.builtin_map[func_name](generator, op)
            except KeyError:
                return self.class_map['CallBuiltin'](func_name)(generator, op)
        return self.class_map['Call'].render(generator, op)
    
class _GetFieldDispatcher(_GeneralDispatcher):
    def render(self, generator, op):
        if self.check_builtin(op.args[0].concretetype):
            return self.class_map['GetBuiltinField'].render(generator, op)
        else:
            return self.class_map['GetField'].render(generator, op)
    
class _SetFieldDispatcher(_GeneralDispatcher):
    def render(self, generator, op):
        if self.check_builtin(op.args[0].concretetype):
            return self.class_map['SetBuiltinField'].render(generator, op)
        else:
            return self.class_map['SetField'].render(generator, op)

class _New(MicroInstruction):
    def render(self, generator, op):
        try:
            op.args[0].value._hints['_suggested_external']
            generator.ilasm.new(op.args[0].value._name.split('.')[-1])
        except (KeyError, AttributeError):
            if op.args[0].value is ootype.Void:
                return
            generator.new(op.args[0].value)


class _OONewArray(MicroInstruction):
    def render(self, generator, op):
        if op.args[0].value is ootype.Void:
            return
        generator.oonewarray(op.args[0].value, op.args[1])


class BranchUnconditionally(MicroInstruction):
    def __init__(self, label):
        self.label = label
    def render(self, generator, op):
        generator.branch_unconditionally(self.label)

class BranchIfTrue(MicroInstruction):
    def __init__(self, label):
        self.label = label
    def render(self, generator, op):
        generator.branch_conditionally(True, self.label)

class BranchIfFalse(MicroInstruction):
    def __init__(self, label):
        self.label = label
    def render(self, generator, op):
        generator.branch_conditionally(False, self.label)


def get_primitive_name(sm):
    try:
        sm.graph
        return None
    except AttributeError:
        pass
    try:
        return 'rffi', sm._obj.oo_primitive
    except AttributeError:
        pass
    return sm._name.rsplit('.', 1)

class _Call(MicroInstruction):
        
    def render(self, generator, op):
        callee = op.args[0].value
        is_primitive = get_primitive_name(callee)

        if is_primitive:
            module, name = is_primitive
            generator.prepare_call_primitive(op, module, name)

        for arg in op.args[1:]:
            generator.load(arg)

        if is_primitive:
            generator.call_primitive(op, module, name)
        else:
            generator.call_graph(callee.graph)

            
class _CallMethod(MicroInstruction):
    def render(self, generator, op):
        method = op.args[0] # a FlowConstant string...
        this = op.args[1]
        for arg in op.args[1:]:
            generator.load(arg)
        generator.call_method(this.concretetype, method.value)

class _RuntimeNew(MicroInstruction):
    def render(self, generator, op):
        generator.load(op.args[0])
        generator.instantiate()
        generator.downcast(op.result.concretetype)

class _OOString(MicroInstruction):
    def render(self, generator, op):
        ARGTYPE = op.args[0].concretetype
        generator.prepare_call_oostring(ARGTYPE)
        generator.load(op.args[0])
        generator.load(op.args[1])
        generator.call_oostring(ARGTYPE)

class _OOUnicode(MicroInstruction):
    def render(self, generator, op):
        v_base = op.args[1]
        assert v_base.value == -1, "The second argument of oounicode must be -1"

        ARGTYPE = op.args[0].concretetype
        generator.prepare_call_oounicode(ARGTYPE)
        generator.load(op.args[0])
        generator.call_oounicode(ARGTYPE)

class _CastTo(MicroInstruction):
    def render(self, generator, op):
        generator.load(op.args[0])
        INSTANCE = op.args[1].value
        class_name = generator.db.pending_class(INSTANCE)
        generator.isinstance(class_name)

New = _New()
OONewArray = _OONewArray()

PushAllArgs = _PushAllArgs()
StoreResult = _StoreResult()
SetField = _SetField()
GetField = _GetField()
DownCast = _DownCast()
DoNothing = _DoNothing()
Call = _Call()
CallMethod = _CallMethod()
RuntimeNew = _RuntimeNew()
OOString = _OOString()
OOUnicode = _OOUnicode()
CastTo = _CastTo()


