"""PyPy Translator Frontend

The Translator is a glue class putting together the various pieces of the
translation-related code.  It can be used for interactive testing of the
translator; see pypy/bin/translatorshell.py.
"""
import autopath, os, sys, types, copy

from pypy.objspace.flow.model import *
from pypy.translator import simplify
from pypy.objspace.flow import FlowObjSpace
from pypy.tool.ansi_print import ansi_log
from pypy.tool.sourcetools import nice_repr_for_func
from pypy.config.pypyoption import pypy_optiondescription
from pypy.config.translationoption import get_combined_translation_config
import py
log = py.log.Producer("flowgraph")
py.log.setconsumer("flowgraph", ansi_log)
 
class TranslationContext(object):
    FLOWING_FLAGS = {
        'verbose': False,
        'simplifying': True,
        'builtins_can_raise_exceptions': False,
        'list_comprehension_operations': False,   # True, - not super-tested
        }

    def __init__(self, config=None, **flowing_flags):
        if config is None:
            from pypy.config.pypyoption import get_pypy_config
            config = get_pypy_config(translating=True)
        # ZZZ should go away in the end
        for attr in ['verbose', 'simplifying',
                     'builtins_can_raise_exceptions',
                     'list_comprehension_operations']:
            if attr in flowing_flags:
                setattr(config.translation, attr, flowing_flags[attr])
        self.config = config
        self.create_flowspace_config()
        self.annotator = None
        self.rtyper = None
        self.exceptiontransformer = None
        self.graphs = []      # [graph]
        self.callgraph = {}   # {opaque_tag: (caller-graph, callee-graph)}
        self._prebuilt_graphs = {}   # only used by the pygame viewer

        self._implicitly_called_by_externals = []

    def create_flowspace_config(self):
        # XXX this is a hack: we create a new config, which is only used
        # for the flow object space. The problem is that the flow obj space
        # needs an objspace config, but the thing we are translating might not
        # have one (or worse we are translating pypy and the flow space picks
        # up strange options of the pypy we are translating). Therefore we need
        # to construct this new config
        self.flowconfig = get_combined_translation_config(
                pypy_optiondescription, self.config, translating=True)
        self.flowconfig.objspace.name = "flow"

    def buildflowgraph(self, func, mute_dot=False):
        """Get the flow graph for a function."""
        if not isinstance(func, types.FunctionType):
            raise TypeError("buildflowgraph() expects a function, "
                            "got %r" % (func,))
        if func in self._prebuilt_graphs:
            graph = self._prebuilt_graphs.pop(func)
        else:
            if self.config.translation.verbose:
                log.start(nice_repr_for_func(func))
            space = FlowObjSpace(self.flowconfig)
            if self.annotator:
                # ZZZ
                self.annotator.policy._adjust_space_config(space)
            elif hasattr(self, 'no_annotator_but_do_imports_immediately'):
                space.do_imports_immediately = (
                    self.no_annotator_but_do_imports_immediately)
            graph = space.build_flow(func)
            if self.config.translation.simplifying:
                simplify.simplify_graph(graph)
            if self.config.translation.list_comprehension_operations:
                simplify.detect_list_comprehension(graph)
            if self.config.translation.verbose:
                log.done(func.__name__)
            elif not mute_dot:
                log.dot()
            self.graphs.append(graph)   # store the graph in our list
        return graph

    def update_call_graph(self, caller_graph, callee_graph, position_tag):
        # update the call graph
        key = caller_graph, callee_graph, position_tag
        self.callgraph[key] = caller_graph, callee_graph

    def buildannotator(self, policy=None):
        if self.annotator is not None:
            raise ValueError("we already have an annotator")
        from pypy.annotation.annrpython import RPythonAnnotator
        self.annotator = RPythonAnnotator(self, policy=policy)
        return self.annotator

    def buildrtyper(self, type_system="lltype"):
        if self.annotator is None:
            raise ValueError("no annotator")
        if self.rtyper is not None:
            raise ValueError("we already have an rtyper")
        from pypy.rpython.rtyper import RPythonTyper
        self.rtyper = RPythonTyper(self.annotator,
                                   type_system = type_system)
        return self.rtyper

    def getexceptiontransformer(self):
        if self.rtyper is None:
            raise ValueError("no rtyper")
        if self.exceptiontransformer is not None:
            return self.exceptiontransformer
        from pypy.translator.c.exceptiontransform import ExceptionTransformer
        self.exceptiontransformer = ExceptionTransformer(self)
        return self.exceptiontransformer

    def checkgraphs(self):
        for graph in self.graphs:
            checkgraph(graph)

    # debug aids

    def about(self, x, f=None):
        """Interactive debugging helper """
        if f is None:
            f = sys.stdout
        if isinstance(x, Block):
            for graph in self.graphs:
                if x in graph.iterblocks():
                    print >>f, '%s is a %s' % (x, x.__class__)
                    print >>f, 'in %s' % (graph,)
                    break
            else:
                print >>f, '%s is a %s at some unknown location' % (
                    x, x.__class__.__name__)
            print >>f, 'containing the following operations:'
            for op in x.operations:
                print >>f, "   ",op
            print >>f, '--end--'
            return
        raise TypeError, "don't know about %r" % x


    def view(self):
        """Shows the control flow graph with annotations if computed.
        Requires 'dot' and pygame."""
        from pypy.translator.tool.graphpage import FlowGraphPage
        FlowGraphPage(self).display()

    def viewcg(self):
        """Shows the whole call graph and the class hierarchy, based on
        the computed annotations."""
        from pypy.translator.tool.graphpage import TranslatorPage
        TranslatorPage(self).display()



# _______________________________________________________________
# testing helper

def graphof(translator, func):
    if isinstance(func, FunctionGraph):
        return func
    result = []
    for graph in translator.graphs:
        if getattr(graph, 'func', None) is func:
            result.append(graph)
    assert len(result) == 1
    return result[0]

TranslationContext._graphof = graphof
