# -*- test-case-name: pymeta.test.test_builder -*-
import linecache, sys
from types import ModuleType as module

import itertools, linecache, sys

class TreeBuilder(object):
    """
    Produce an abstract syntax tree of OMeta operations.
    """

    def __init__(self, name, grammar=None, *args):
        self.name = name


    def makeGrammar(self, rules):
        return ["Grammar", self.name, rules]

    def rule(self, name, expr):
        return ["Rule", name, expr]

    def apply(self, ruleName, codeName, *exprs):
        return ["Apply", ruleName, codeName, exprs]

    def exactly(self, expr):
        return ["Exactly", expr]

    def many(self, expr):
        return ["Many", expr]

    def many1(self, expr):
        return ["Many1", expr]

    def optional(self, expr):
        return ["Optional", expr]

    def _or(self, exprs):
        return ["Or", exprs]

    def _not(self, expr):
        return ["Not", expr]

    def lookahead(self, expr):
        return ["Lookahead", expr]

    def sequence(self, exprs):
        return ["And", exprs]

    def bind(self, expr, name):
        return ["Bind", name, expr]

    def pred(self, expr):
        return ["Predicate", expr]

    def action(self, expr):
        return ["Action", expr]

    def expr(self, expr):
        return ["Python", expr]

    def listpattern(self, exprs):
        return ["List", exprs]



class PythonWriter(object):
    """
    Converts an OMeta syntax tree into Python source.
    """
    def __init__(self, tree):
        self.tree = tree
        self.lines = []
        self.gensymCounter = 0


    def _generate(self, retrn=False):
        result = self._generateNode(self.tree)
        if retrn:
            self.lines.append("return (%s, self.currentError)" % (result,))
        elif result:
            self.lines.append(result)
        return self.lines


    def output(self):
        return '\n'.join(self._generate())


    def _generateNode(self, node):
        name = node[0]
        args =  node[1:]
        return getattr(self, "generate_"+name)(*args)


    def _gensym(self, name):
        """
        Produce a unique name for a variable in generated code.
        """
        self.gensymCounter += 1
        return "_G_%s_%s" % (name, self.gensymCounter)


    def _newThunkFor(self, name, expr):
        """
        Define a new function of no arguments.
        @param name: The name of the rule generating this thunk.
        @param expr: A list of lines of Python code.
        """
        
        subwriter = self.__class__(expr)
        flines  = subwriter._generate(retrn=True)
        fname = self._gensym(name)
        self._writeFunction(fname, (),  flines)
        return fname


    def _expr(self, typ, e):
        """
        Generate the code needed to execute the expression, and return the
        variable name bound to its value.
        """
        name = self._gensym(typ)
        self.lines.append("%s, lastError = %s" % (name, e))
        self.lines.append("self.considerError(lastError)")
        return name


    def _writeFunction(self, fname, arglist, flines):
        """
        Generate a function.
        @param head: The initial line defining the function.
        @param body: A list of lines for the function body.
        """

        self.lines.append("def %s(%s):" % (fname, ", ".join(arglist)))
        for line in flines:
            self.lines.append((" " * 4) + line)
        return fname


    def compilePythonExpr(self, expr):
        """
        Generate code for running embedded Python expressions.
        """
        
        return self._expr('python', 'eval(%r, self.globals, _locals), None' %(expr,))


    def generate_Apply(self, ruleName, codeName, rawArgs):
        """
        Create a call to self.apply(ruleName, *args).
        """
        args = [self._generateNode(x) for x in rawArgs]
        if ruleName == 'super':
            return self._expr('apply', 'self.superApply("%s", %s)' % (codeName,
                                                              ', '.join(args)))
        return self._expr('apply', 'self._apply(self.rule_%s, "%s", [%s])' % (ruleName,
                                                                              ruleName,
                                                             ', '.join(args)))

    def generate_Exactly(self, literal):
        """
        Create a call to self.exactly(expr).
        """
        return self._expr('exactly', 'self.exactly(%r)' % (literal,))


    def generate_Many(self, expr):
        """
        Create a call to self.many(lambda: expr).
        """
        fname = self._newThunkFor("many", expr)
        return self._expr('many', 'self.many(%s)' % (fname,))


    def generate_Many1(self, expr):
        """
        Create a call to self.many(lambda: expr).
        """
        fname = self._newThunkFor("many1", expr)
        return self._expr('many1', 'self.many(%s, %s())' % (fname, fname))


    def generate_Optional(self, expr):
        """
        Try to parse an expr and continue if it fails.
        """
        realf = self._newThunkFor("optional", expr)
        passf = self._gensym("optional")
        self._writeFunction(passf, (), ["return (None, self.input.nullError())"])
        return self._expr('or', 'self._or([%s])' % (', '.join([realf, passf])))


    def generate_Or(self, exprs):
        """
        Create a call to
        self._or([lambda: expr1, lambda: expr2, ... , lambda: exprN]).
        """
        if len(exprs) > 1:
            fnames = [self._newThunkFor("or", expr) for expr in exprs]
            return self._expr('or', 'self._or([%s])' % (', '.join(fnames)))
        else:
            return self._generateNode(exprs[0])


    def generate_Not(self, expr):
        """
        Create a call to self._not(lambda: expr).
        """
        fname = self._newThunkFor("not", expr)
        return self._expr("not", "self._not(%s)" % (fname,))


    def generate_Lookahead(self, expr):
        """
        Create a call to self.lookahead(lambda: expr).
        """
        fname = self._newThunkFor("lookahead", expr)
        return self._expr("lookahead", "self.lookahead(%s)" %(fname,))


    def generate_And(self, exprs):
        """
        Generate code for each statement in order.
        """
        v = None
        for ex in exprs:
            v = self._generateNode(ex)
        return v


    def generate_Bind(self, name, expr):
        """
        Bind the value of 'expr' to a name in the _locals dict.
        """
        v = self._generateNode(expr)
        ref = "_locals['%s']" % (name,)
        self.lines.append("%s = %s" %(ref, v))
        return ref


    def generate_Predicate(self, expr):
        """
        Generate a call to self.pred(lambda: expr).
        """

        fname = self._newThunkFor("pred", expr)
        return self._expr("pred", "self.pred(%s)" %(fname,))


    def generate_Action(self, expr):
        """
        Generate this embedded Python expression on its own line.
        """
        return self.compilePythonExpr(expr)


    def generate_Python(self, expr):
        """
        Generate this embedded Python expression on its own line.
        """
        return self.compilePythonExpr(expr)


    def generate_List(self, expr):
        """
        Generate a call to self.listpattern(lambda: expr).
        """
        fname = self._newThunkFor("listpattern", expr)
        return  self._expr("listpattern", "self.listpattern(%s)" %(fname,))


    def generate_Rule(self, name, expr):
        rulelines = ["_locals = {'self': self}",
                     "self.locals[%r] = _locals" % (name,)]
        subwriter = self.__class__(expr)
        flines  = subwriter._generate(retrn=True)
        rulelines.extend(flines)
        self._writeFunction("rule_" + name, ("self",), rulelines)


    def generate_Grammar(self, name, rules):
        self.lines.append("class %s(GrammarBase):" % (name,))
        for rule in rules:
            self._generateNode(rule)
            self.lines.extend(['', ''])
        self.lines[1:] = [line and (' ' * 4 + line) for line in self.lines[1:]]
        del self.lines[-2:]



def writePython(tree):
    pw = PythonWriter(tree)
    return pw.output()


class GeneratedCodeLoader(object):
    """
    Object for use as a module's __loader__, to display generated
    source.
    """
    def __init__(self, source):
        self.source = source
    def get_source(self, name):
        return self.source



def moduleFromGrammar(tree, className, superclass, globalsDict):
    source = writePython(tree)
    modname = "pymeta_grammar__" + className
    filename = "/pymeta_generated_code/" + modname + ".py"
    mod = module(modname)
    mod.__dict__.update(globalsDict)
    mod.__name__ = modname
    mod.__dict__[superclass.__name__] = superclass
    mod.__dict__["GrammarBase"] = superclass
    mod.__loader__ = GeneratedCodeLoader(source)
    code = compile(source, filename, "exec")
    eval(code, mod.__dict__)
    fullGlobals = dict(getattr(mod.__dict__[className], "globals", None) or {})
    fullGlobals.update(globalsDict)
    mod.__dict__[className].globals = fullGlobals
    sys.modules[modname] = mod
    linecache.getlines(filename, mod.__dict__)
    return mod.__dict__[className]
