cake.py   [plain text]


"""
Cake is like eacher, but upside down and on fire. >:)
"""

import types
from cStringIO import StringIO

class _later:
    pass
later = _later()

def curry(fn, *args, **kwargs):
    if later not in args:
        def curried(*_args, **_kwargs):
            d = kwargs.copy()
            d.update(_kwargs)
            return fn(*(args+_args), **d)
    else:
        def curried(*_args, **_kwargs):
            d = kwargs.copy()
            d.update(_kwargs)
            nargs, _args = list(args), list(_args)
            for x in xrange(len(nargs)):
                if nargs[x] is later:
                    nargs[x] = _args.pop(0)
            while _args:
                nargs.append(_args.pop(0))
            return fn(*tuple(nargs), **d)
    return curried

class Cake(object):
    _prevcake_ = None
    _meth_ = None
    _args_ = None
    _kwargs_ = None
    def __init__(self, *args, **kwargs):
        if len(args) == 0:
            pass
        elif len(args) == 2:
            self._prevcake_, self._meth_ = args
        else:
            self._prevcake_, self._meth_, self._args_ = args
            self._kwargs_ = kwargs
        
    def __repr__(self):
        return '<Cake _meth_=%r _args_=%r _kwargs_=%r>' % (self._meth_, self._args_, self._kwargs_)
        return object.__repr__(self)

    __str__ = __repr__

    def __getattr__(self, arg):
        return Cake(self, arg)

    def __call__(self, *args, **kwargs):
        return Cake(self, None, args, **kwargs)
        

class Yeast(object):
    def __init__(self, klassmethod, *args, **kwargs):
        self.ferment = curry(klassmethod, later, *args, **kwargs)

    def __get__(self, sugar, klass=None):
        if klass is None:
            return None
        return self.ferment(sugar)

def call_fmt(fn, args, kwargs):
    return (fn or '') + '(' + ', '.join(map(repr, args or ()) + [('%s=%r' % item) for item in (kwargs and kwargs.items() or ())]) + ')'

class Recipe(Cake):
    def __init__(self, cake):
        self.callstack = preheat(cake)

    def __call__(self, res):
        for fn, args, kwargs in self.callstack:
            if isinstance(fn, Recipe):
                res = fn(res)
            else:
                if fn == '__getitem__' and isinstance(res, list) and isinstance(args[0], types.SliceType):
                    res = res[args[0].start:args[0].stop]
                else:
                    _res = getattr(res, fn)(*args, **kwargs)
                    res = _res is None and res or _res
        return res

    def __repr__(self):
        return 'input.' + '.'.join([call_fmt(*tpl) for tpl in self.callstack])

    __str__ = __repr__
        
class Compiler:
    def __init__(self, recipe, indent = '    '):
        self.code = StringIO()
        self.recipe = recipe
        self.local_num = 0
        self.locals = {}
        self.locals_cache = {}
        self.indent = indent
    
    def newlocal(self, obj):
        if id(obj) in self.locals_cache:
            return self.locals_cache[id(obj)]
        name = 'local_' + str(self.local_num)
        self.local_num += 1
        self.locals_cache[id(obj)] = name
        self.locals[name] = obj
        return name

    def compile(self):
        for fn, args, kwargs in self.recipe.callstack:
            self.compile_step(fn, args, kwargs)
        self.writeln('return res')
        return self.code.getvalue(), self.locals

    def compile_step(self, fn, args, kwargs):
        if (fn[:2] == fn[-2:] == '__'):
            return getattr(self, 'handle_' + fn[2:-2], self.write_code)(fn, args, kwargs)
        return self.write_code(fn, args, kwargs)
    
    def writeln(self, s):
        self.code.write(self.indent + s + '\n')

    # optimize all you like here, isn't this really f'ing scary?

    def handle_getitem(self, fn, args, kwargs):
        if isinstance(args[0], types.SliceType):
            if args[0].step is None:
               name = self.newlocal(args[0])
               self.writeln('res = res[%s.start:%s.stop]' % (name, name))
               return 
        self.writeln('res = res[%s]' % self.newlocal(args[0]))

    def handle_add(self, fn, args, kwargs):
        self.writeln('res = res + %s' % self.newlocal(args[0]))

    def write_code(self, fn, args, kwargs):
        if not (args or kwargs):
            self.writeln('_res = res.%s()' % fn)
        elif not kwargs:
            self.writeln('_res = res.%s(*%s)' % (fn, self.newlocal(args)))
        elif not args:
            self.writeln('_res = res.%s(**%s)' % (fn, self.newlocal(kwargs)))
        else:
            self.writeln('_res = res.%s(*%s, **%s)' % (fn, self.newlocal(args), self.newlocal(kwargs)))
        self.writeln('res = _res is None and res or _res')

def code_recipe(recipe, indent=''):
    return Compiler(recipe, indent).compile()

def compile_recipe(recipe):
    if not isinstance(recipe, Recipe):
        recipe = Recipe(recipe)
    res = []
    codestring, locals = code_recipe(recipe, indent='    ')
    locals['callback'] = res.append
    codeobject = compile('def bake(res):\n' + codestring + 'callback(bake)', '<almost-baked cake>', 'exec')
    eval(codeobject, locals, globals())
    return res[0]

def preheat(cake):
    stack = []
    while cake is not None:
        stack.append(cake)
        cake = cake._prevcake_
    callstack = []
    while stack:
        cake = stack.pop()
        if isinstance(cake, Recipe):
            callstack.append((cake, None, None))
            continue
        if cake._args_ is not None:
            prevcake = cake
            while prevcake._meth_ is None:
                prevcake = prevcake._prevcake_
            callstack.append((prevcake._meth_, cake._args_, cake._kwargs_))
    return callstack
 
def bake(recipe, res):
    if not isinstance(recipe, Recipe):
        recipe = Recipe(recipe)
    return recipe(res)

_SPECIALS = """
    __hash__
    __len__ __getitem__ __setitem__ __delitem__ __iter__ __contains__
    __add__ __sub__ __mul__ __floordiv__ __mod__ __divmod__ __pow__
    __lshift__ __rshift__ __and__ __xor__ __or__ __div__ __truediv__
    __radd__ __rsub__ __rmul__ __rdiv__ __rtruediv__ __rfloordiv__
    __rmod__ __rdivmod__ __rpow__ __rlshift__ __rrshift__ __rand__
    __rxor__ __ror__ __iadd__ __isub__ __imul__ __idiv__ __itruediv__
    __ifloordiv__ __imod__ __ipow__ __ilshift__ __irshift__ __iand__
    __ixor__ __ior__ __neg__ __pos__ __abs__ __invert__ 
    __lt__ __le__ __eq__ __ne__ __gt__ __ge__ __cmp__ __nonzero__
    __complex__ __int__ __long__ __float__ __oct__ __hex__ __coerce__
    """.split()

#_EXCLUDEDSPECIALS = """__getattr__ __setattr__ __delattr__ __repr__ __str__ __call__""".split()

def _makeClassSpecial(klass):
    for meth in [_m for _m in _SPECIALS]:
        setattr(klass, meth, Yeast(klass.__getattr__, meth))
_makeClassSpecial(Cake)

if __name__=='__main__':
    cake = Cake()
    cake1 = cake.append(12)
    cake2 = cake1 + [1, 2, 3]
    cake3 = cake2.sort()
    cake4 = cake3[2:4]
    cake5 = Recipe(cake4)
    print cake5
    cake6 = (cake5 + [12, 13, 14]).sort().pop()
    for x in (cake6, cake5, cake4, cake3, cake2, cake1):
        print bake(x, [60])
    cakes = [x + 10 for x in map(cake + 1, [1, 2, 3, 4])]
    print map(curry(bake, later, 4), cakes)
    print code_recipe(cake5)[0].strip()
    fn = compile_recipe(cake5)
    fn2 = compile_recipe(cake4)
    print fn([60])
    print fn2([60])
    print fn([12, 100, 40, 1, 2, 3, 4, 5, 6, 9, 7, 10, 10.1, 10.2, 10.3, 11, 12, 13, 14])