Thun/joy/utils/compiler.py

242 lines
4.8 KiB
Python

'''
A crude compiler for a subset of Joy functions.
I think I'm going about this the wrong way.
The inference algorithm can "collapse" Yin function sequences into
single stack effects which can then be written out as Python functions.
Why not keep track of the new variables introduced as results of Yang
functions, during inference? Could I write out better code that way?
In any event, I am proceeding with this sort of ad hoc way for now.
'''
from __future__ import print_function
from builtins import next
from builtins import str
from builtins import object
from joy.parser import text_to_expression, Symbol
from joy.utils.stack import concat, iter_stack, list_to_stack
from joy.library import SimpleFunctionWrapper, YIN_STACK_EFFECTS
from functools import reduce
def import_yin():
from joy.utils.generated_library import *
return locals()
class InfiniteStack(tuple):
def _names():
n = 0
while True:
m = yield Symbol('a' + str(n))
n = n + 1 if m is None else m
_NAMES = _names()
next(_NAMES)
names = lambda: next(_NAMES)
reset = lambda _self, _n=_NAMES: _n.send(-1)
def __init__(self, code):
self.reset()
self.code = code
def __iter__(self):
if not self:
new_var = self.names()
self.code.append(('pop', new_var))
return iter((new_var, self))
def I(expression):
code = []
stack = InfiniteStack(code)
while expression:
term, expression = expression
if isinstance(term, Symbol):
func = D[term]
stack, expression, _ = func(stack, expression, code)
else:
stack = term, stack
code.append(tuple(['ret'] + list(iter_stack(stack))))
return code
strtup = lambda a, b: '(%s, %s)' % (b, a)
strstk = lambda rest: reduce(strtup, rest, 'stack')
def code_gen(code):
#for p in code: print p
coalesce_pops(code)
lines = []
emit = lines.append
for t in code:
tag, rest = t[0], t[1:]
if tag == 'pop': emit(strstk(rest) + ' = stack')
elif tag == 'call': emit('%s = %s%s' % rest)
elif tag == 'ret': emit('return ' + strstk(rest[::-1]))
else:
raise ValueError(tag)
return '\n'.join(' ' + line for line in lines)
def coalesce_pops(code):
code.sort(key=lambda p: p[0] != 'pop') # All pops to the front.
try: index = next((i for i, t in enumerate(code) if t[0] != 'pop'))
except StopIteration: return
code[:index] = [tuple(['pop'] + [t for _, t in code[:index][::-1]])]
def compile_yinyang(name, text):
return '''
def %s(stack):
%s
''' % (name, code_gen(I(text_to_expression(text))))
def q():
memo = {}
def bar(type_var):
try:
res = memo[type_var]
except KeyError:
res = memo[type_var] = InfiniteStack.names()
return res
return bar
def type_vars_to_labels(thing, map_):
if not thing:
return thing
if not isinstance(thing, tuple):
return map_(thing)
return tuple(type_vars_to_labels(inner, map_) for inner in thing)
def remap_inputs(in_, stack, code):
map_ = q()
while in_:
term, in_ = in_
arg0, stack = stack
term = type_vars_to_labels(term, map_)
code.append(('call', term, '', arg0))
return stack, map_
class BinaryBuiltin(object):
def __init__(self, name):
self.name = name
def __call__(self, stack, expression, code):
in1, (in0, stack) = stack
out = InfiniteStack.names()
code.append(('call', out, self.name, (in0, in1)))
return (out, stack), expression, code
YIN = import_yin()
D = {
name: SimpleFunctionWrapper(YIN[name])
for name in '''
ccons
cons
dup
dupd
dupdd
over
pop
popd
popdd
popop
popopd
popopdd
rolldown
rollup
swap
swons
tuck
unit
'''.split()
}
for name in '''
first
first_two
fourth
rest
rrest
second
third
uncons
unswons
'''.split():
def foo(stack, expression, code, name=name):
in_, out = YIN_STACK_EFFECTS[name]
stack, map_ = remap_inputs(in_, stack, code)
out = type_vars_to_labels(out, map_)
return concat(out, stack), expression, code
foo.__name__ = name
D[name] = foo
for name in '''
eq
ge
gt
le
lt
ne
xor
lshift
rshift
and_
or_
add
floordiv
mod
mul
pow
sub
truediv
'''.split():
D[name.rstrip('-')] = BinaryBuiltin(name)
'''
stack
stuncons
stununcons
swaack
'''
for name in sorted(D):
print(name, end=' ')
## print compile_yinyang(name, name)
print('-' * 100)
print(compile_yinyang('mul_', 'mul'))
print(compile_yinyang('pop', 'pop'))
print(compile_yinyang('ppm', 'popop mul'))
print(compile_yinyang('sqr', 'dup mul'))
print(compile_yinyang('foo', 'dup 23 sub mul'))
print(compile_yinyang('four_mul', 'mul mul mul mul'))
print(compile_yinyang('baz', 'mul dup sub dup'))
print(compile_yinyang('to_the_fifth_power', 'dup dup mul dup mul mul'))
print(compile_yinyang('dup3', 'dup dup dup'))
print(compile_yinyang('df2m', 'dup first_two mul'))
print(compile_yinyang('sqr_first', 'uncons swap dup mul swons'))
print(compile_yinyang('0BAD', 'uncons dup mul'))
print(compile_yinyang('uncons', 'uncons'))