Type inference of Joy expressions.

More remains to be done but it seems to work.
This commit is contained in:
Simon Forman
2018-06-29 13:19:12 -07:00
parent 6ca59847ab
commit 4406a6620b
25 changed files with 1867 additions and 4593 deletions
+194 -73
View File
@@ -1,3 +1,4 @@
# -*- coding: utf_8
'''
Multiple Stack Effects
@@ -8,22 +9,29 @@ and we can introduce a kind of Kleene Star or sequence type that can stand for
an unbounded sequence of other types.
'''
from inspect import stack as inspect_stack
from itertools import chain, product
from logging import getLogger
_log = getLogger(__name__)
import joy.library
from joy.parser import Symbol
from joy.utils.stack import concat as CONCAT
from joy.utils.stack import (
concat as CONCAT,
expression_to_string,
list_to_stack,
)
from joy.utils.types import (
AnyJoyType, A,
BooleanJoyType, B,
C,
DEFS,
doc_from_stack_effect,
FloatJoyType, F,
JoyTypeError,
NumberJoyType, N,
StackJoyType, S,
stacky,
_stacky,
_R,
relabel, delabel,
update,
@@ -107,10 +115,28 @@ class FunctionJoyType(AnyJoyType):
return self.name
class SymbolJoyType(FunctionJoyType): prefix = 'F'
class SymbolJoyType(FunctionJoyType):
'''
Represent non-combinator functions.
These type variables carry the stack effect comments and can
appear in expressions (as in quoted programs.)
'''
prefix = 'F'
class CombinatorJoyType(FunctionJoyType):
'''
Represent combinators.
These type variables carry Joy functions that implement the
behaviour of Joy combinators and they can appear in expressions.
For simple combinators the implementation functions can be the
combinators themselves.
These types can also specify a stack effect (input side only) to
guard against being used on invalid types.
'''
prefix = 'C'
@@ -122,13 +148,15 @@ class CombinatorJoyType(FunctionJoyType):
if self.expect is None:
return f
g = self.expect, self.expect
new_f = list(C(f, g, ()))
new_f = list(compose(f, g, ()))
assert len(new_f) == 1, repr(new_f)
return new_f[0][1]
def unify(u, v, s=None):
'''
Return a tuple of substitution dicts representing unifiers for u and v.
'''
if s is None:
s = {}
elif s:
@@ -136,12 +164,9 @@ def unify(u, v, s=None):
v = update(s, v)
if u == v:
return s,
res = s,
if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
return _lil_uni(u, v, s)
if isinstance(u, tuple) and isinstance(v, tuple):
elif isinstance(u, tuple) and isinstance(v, tuple):
if len(u) != 2 or len(v) != 2:
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
@@ -149,41 +174,44 @@ def unify(u, v, s=None):
if isinstance(a, KleeneStar):
if isinstance(c, KleeneStar):
s = _lil_uni(a, c, s) # Attempt to unify the two K-stars.
return unify(d, b, s[0])
res = unify(d, b, s[0])
# Two universes, in one the Kleene star disappears and unification
# continues without it...
s0 = unify(u, b)
# In the other it spawns a new variable.
s1 = unify(u, (a.another(), v))
t = s0 + s1
for sn in t:
sn.update(s)
return t
else:
# Two universes, in one the Kleene star disappears and
# unification continues without it...
s0 = unify(u, b)
# In the other it spawns a new variable.
s1 = unify(u, (a.another(), v))
res = s0 + s1
for sn in res:
sn.update(s)
if isinstance(c, KleeneStar):
t = unify(v, d) + unify(v, (c.another(), u))
for sn in t:
elif isinstance(c, KleeneStar):
res = unify(v, d) + unify(v, (c.another(), u))
for sn in res:
sn.update(s)
return t
return tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
else:
res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
if isinstance(v, tuple):
if not stacky(u):
elif isinstance(v, tuple):
if not _stacky(u):
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
s[u] = v
return s,
res = s,
if isinstance(u, tuple):
if not stacky(v):
elif isinstance(u, tuple):
if not _stacky(v):
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
s[v] = u
return s,
res = s,
return _lil_uni(u, v, s)
else:
res = _lil_uni(u, v, s)
return res
def _lil_uni(u, v, s):
@@ -196,28 +224,36 @@ def _lil_uni(u, v, s):
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
def compose(f, g, e):
def _compose(f, g, e):
(f_in, f_out), (g_in, g_out) = f, g
for s in unify(g_in, f_out):
yield update(s, (e, (f_in, g_out)))
def C(f, g, e):
def compose(f, g, e):
'''
Yield the stack effects of the composition of two stack effects. An
expression is carried along and updated and yielded.
'''
f, g = relabel(f, g)
for fg in compose(f, g, e):
for fg in _compose(f, g, e):
yield delabel(fg)
def meta_compose(F, G, e):
def _meta_compose(F, G, e):
for f, g in product(F, G):
try:
for result in C(f, g, e): yield result
for result in compose(f, g, e): yield result
except JoyTypeError:
pass
def MC(F, G, e):
res = sorted(set(meta_compose(F, G, e)))
def meta_compose(F, G, e):
'''
Yield the stack effects of the composition of two lists of stack
effects. An expression is carried along and updated and yielded.
'''
res = sorted(set(_meta_compose(F, G, e)))
if not res:
raise JoyTypeError('Cannot unify %r and %r.' % (F, G))
return res
@@ -230,14 +266,16 @@ def flatten(g):
ID = S[0], S[0] # Identity function.
def infer(e, F=ID):
def _infer(e, F=ID):
_log_it(e, F)
if not e:
return [F]
n, e = e
if isinstance(n, SymbolJoyType):
res = flatten(infer(e, Fn) for e, Fn in MC([F], n.stack_effects, e))
eFG = meta_compose([F], n.stack_effects, e)
res = flatten(_infer(e, Fn) for e, Fn in eFG)
elif isinstance(n, CombinatorJoyType):
fi, fo = n.enter_guard(F)
@@ -250,7 +288,7 @@ def infer(e, F=ID):
else:
fi, fo = F
res = infer(e, (fi, (n, fo)))
res = _infer(e, (fi, (n, fo)))
return res
@@ -259,7 +297,34 @@ def _interpret(f, fi, fo, e):
new_fo, ee, _ = f(fo, e, {})
ee = update(FUNCTIONS, ee) # Fix Symbols.
new_F = fi, new_fo
return infer(ee, new_F)
return _infer(ee, new_F)
def _log_it(e, F):
_log.info(
u'%3i %s%s',
len(inspect_stack()),
doc_from_stack_effect(*F),
expression_to_string(e),
)
def infer(*expression):
'''
Return a list of stack effects for a Joy expression.
For example::
h = infer(pop, swap, rolldown, rest, rest, cons, cons)
for fi, fo in h:
print doc_from_stack_effect(fi, fo)
Prints::
([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
'''
return sorted(set(_infer(list_to_stack(expression))))
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = A
@@ -275,34 +340,76 @@ Ns = map(NumberStarJoyType, _R)
Ss = map(StackStarJoyType, _R)
'''Docstring for functions in Sphinx?'''
FUNCTIONS = {
name: SymbolJoyType(name, [DEFS[name]], i)
for i, name in enumerate('''
ccons cons divmod_ dup dupd dupdd first first_two fourth over pop
ccons cons divmod dup dupd dupdd first first_two fourth over pop
popd popdd popop popopd popopdd rest rrest rolldown rollup second
stack swaack swap swons third tuck uncons unswons stuncons
stununcons unit eq ge gt le lt ne and_ bool_ not_
stununcons unit eq ge gt le lt ne and bool not
_Tree_add_Ee _Tree_delete_R0 _Tree_delete_clear_stuff _Tree_get_E
'''.strip().split())
}
# sqrt succ pred pm
FUNCTIONS['sum'] = SymbolJoyType('sum', [(((Ns[1], s1), s0), (n0, s0))], 100)
FUNCTIONS['mul'] = SymbolJoyType('mul', [
((i2, (i1, s0)), (i3, s0)),
((f2, (i1, s0)), (f3, s0)),
((i2, (f1, s0)), (f3, s0)),
((f2, (f1, s0)), (f3, s0)),
], 101)
def defs():
'''
Return a dict of FunctionJoyType instances to be used with ``infer()``.
'''
sum_ = product = [(((Ns[1], s1), s0), (n0, s0))]
add = mul = sub = floordiv = modulus = [
((i2, (i1, s0)), (i3, s0)),
((f2, (i1, s0)), (f3, s0)),
((i2, (f1, s0)), (f3, s0)),
((f2, (f1, s0)), (f3, s0)),
]
div = truediv = pow_ = [
((i2, (i1, s0)), (f3, s0)),
((f2, (i1, s0)), (f3, s0)),
((i2, (f1, s0)), (f3, s0)),
((f2, (f1, s0)), (f3, s0)),
]
lshift = rshift = [((i2, (i1, s0)), (i3, s0))]
neg = pred = succ = [((n1, s0), (n2, s0))]
sqrt = [((n1, s0), (f2, s0))]
pm = divmod_ = [
((i2, (i1, s0)), (i3, (i4, s0))),
((f2, (i1, s0)), (f3, (f4, s0))),
((i2, (f1, s0)), (f3, (f4, s0))),
((f2, (f1, s0)), (f3, (f4, s0))),
]
return {
name.rstrip('_'): stack_effect
for name, stack_effect in locals().iteritems()
}
FUNCTIONS.update({
name: SymbolJoyType(name, stack_effect, i)
for i, (name, stack_effect) in enumerate(defs().iteritems())
})
FUNCTIONS.update({
combo.__name__: CombinatorJoyType(combo.__name__, [combo], i)
for i, combo in enumerate((
joy.library.i,
joy.library.b,
joy.library.concat_,
joy.library.dip,
joy.library.dipd,
joy.library.dipdd,
joy.library.dupdip,
joy.library.b,
joy.library.x,
joy.library.i,
joy.library.infra,
joy.library._dictionary['nullary'],
joy.library.x,
))
})
@@ -315,26 +422,40 @@ def branch_false(stack, expression, dictionary):
return stack, CONCAT(else_, expression), dictionary
FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
pop = FUNCTIONS['pop']
def loop_true(stack, expression, dictionary):
quote, (flag, stack) = stack
return stack, CONCAT(quote, (pop, expression)), dictionary
def loop_two_true(stack, expression, dictionary):
quote, (flag, stack) = stack
return stack, CONCAT(quote, (pop, CONCAT(quote, (pop, expression)))), dictionary
def loop_false(stack, expression, dictionary):
quote, (flag, stack) = stack
return stack, expression, dictionary
FUNCTIONS['loop'] = CombinatorJoyType('loop', [loop_two_true, loop_true, loop_false], 101)
globals().update(FUNCTIONS)
def set_expectations():
branch.expect = s7, (s6, (b1, s5))
loop.expect = s6, (b1, s5)
i.expect = x.expect = s7, s6
dip.expect = dupdip.expect = s8, (a8, s7)
dipd.expect = s8, (a8, (a7, s7))
dipdd.expect = s8, (a8, (a7, (a6, s7)))
b.expect = concat_.expect = infra.expect = s8, (s7, s6)
nullary.expect = s7, s6
scope = globals().copy()
scope.update(FUNCTIONS)
eval(set_expectations.func_code, scope)
branch.expect = s7, (s6, (b1, s5))
i.expect = x.expect = s7, s6
dip.expect = s8, (a8, s7)
dipd.expect = s8, (a8, (a7, s7))
infra.expect = s8, (s7, s6)
NULLARY = infer(((stack, s3), (dip, (infra, (first, ())))))
#NULLARY = infer(((stack, s3), (dip, (infra, (first, ())))))
##print NULLARY
nullary = FUNCTIONS['nullary'] = CombinatorJoyType(
'nullary',
[joy.library._dictionary['nullary']],
101,
)
nullary.expect = s7, s6
# Type Checking...
+8 -8
View File
@@ -156,7 +156,7 @@ def concat(quote, expression):
# RuntimeError: maximum recursion depth exceeded
# on quotes longer than sys.getrecursionlimit().
## return (quote[0], concat(quote[1], expression)) if quote else expression
return (quote[0], concat(quote[1], expression)) if quote else expression
# Original implementation.
@@ -165,13 +165,13 @@ def concat(quote, expression):
# In-lining is slightly faster (and won't break the
# recursion limit on long quotes.)
temp = []
while quote:
item, quote = quote
temp.append(item)
for item in reversed(temp):
expression = item, expression
return expression
## temp = []
## while quote:
## item, quote = quote
## temp.append(item)
## for item in reversed(temp):
## expression = item, expression
## return expression
+79 -66
View File
@@ -1,8 +1,12 @@
from collections import Counter
from itertools import imap
from joy.utils.stack import concat
class AnyJoyType(object):
'''
Joy type variable. Represents any Joy value.
'''
prefix = 'a'
@@ -46,12 +50,18 @@ class JoyTypeError(Exception): pass
def update(s, term):
'''
Apply substitution dict to term, returning new term.
'''
if not isinstance(term, tuple):
return s.get(term, term)
return tuple(update(s, inner) for inner in term)
def relabel(left, right):
'''
Re-number type variables to avoid collisions between stack effects.
'''
return left, _1000(right)
@@ -62,6 +72,9 @@ def _1000(right):
def delabel(f, seen=None, c=None):
'''
Fix up type variable numbers after relabel().
'''
if seen is None:
assert c is None
seen, c = {}, Counter()
@@ -83,88 +96,79 @@ def delabel(f, seen=None, c=None):
return tuple(delabel(inner, seen, c) for inner in f)
def stack_concat(q, e):
return (q[0], stack_concat(q[1], e)) if q else e
def unify(u, v, s=None):
'''
Return a substitution dict representing a unifier for u and v.
'''
if s is None:
s = {}
elif s:
u = update(s, u)
v = update(s, v)
if u == v:
return s
if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
if u >= v:
s[u] = v
return s
if v >= u:
elif v >= u:
s[v] = u
return s
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
else:
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
if isinstance(u, tuple) and isinstance(v, tuple):
elif isinstance(u, tuple) and isinstance(v, tuple):
if len(u) != len(v) != 2:
raise ValueError(repr((u, v)))
for uu, vv in zip(u, v):
s = unify(uu, vv, s)
if s == False: # (instead of a substitution dict.)
break
return s
raise ValueError(repr((u, v))) # Bad input.
(a, b), (c, d) = u, v
s = unify(b, d, unify(a, c, s))
if isinstance(v, tuple):
if not stacky(u):
elif isinstance(v, tuple):
if not _stacky(u):
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
s[u] = v
return s
if isinstance(u, tuple):
if not stacky(v):
elif isinstance(u, tuple):
if not _stacky(v):
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
s[v] = u
return s
return False
else:
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
return s
def stacky(thing):
def _stacky(thing):
return thing.__class__ in {AnyJoyType, StackJoyType}
def compose(f, g):
(f_in, f_out), (g_in, g_out) = f, g
if not g_in:
fg_in, fg_out = f_in, stack_concat(g_out, f_out)
elif not f_out:
fg_in, fg_out = stack_concat(f_in, g_in), g_out
else:
s = unify(g_in, f_out)
if s == False: # s can also be the empty dict, which is ok.
raise JoyTypeError('Cannot unify %r and %r.' % (fo, gi))
fg_in, fg_out = update(s, (f_in, g_out))
return fg_in, fg_out
def _C(f, g):
f, g = relabel(f, g)
fg = compose(f, g)
def _compose(f, g):
'''
Return the stack effect of the composition of two stack effects.
'''
# Relabel, unify, update, delabel.
(f_in, f_out), (g_in, g_out) = relabel(f, g)
fg = update(unify(g_in, f_out), (f_in, g_out))
return delabel(fg)
def C(*functions):
return reduce(_C, functions)
def compose(*functions):
'''
Return the stack effect of the composition of some of stack effects.
'''
return reduce(_compose, functions)
def compilable(f):
return isinstance(f, tuple) and all(imap(compilable, f)) or stacky(f)
'''
Return True if a stack effect represents a function that can be
automatically compiled (to Python), False otherwise.
'''
return isinstance(f, tuple) and all(imap(compilable, f)) or _stacky(f)
def doc_from_stack_effect(inputs, outputs):
'''
Return a crude string representation of a stack effect.
'''
switch = [False] # Do we need to display the '...' for the rest of the main stack?
i, o = _f(inputs, switch), _f(outputs, switch)
if switch[0]:
@@ -213,6 +217,11 @@ def _to_str(term, stack, switch):
def compile_(name, f, doc=None):
'''
Return a string of Python code implementing the function described
by the stack effect. If no doc string is passed doc_from_stack_effect()
is used to generate one.
'''
i, o = f
if doc is None:
doc = doc_from_stack_effect(i, o)
@@ -243,8 +252,11 @@ I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = map(IntJoyType, _R)
def defs():
'''
Return a dict of named stack effects.
'''
cons = __(a1, s0), __((a1, s0),)
ccons = C(cons, cons)
ccons = compose(cons, cons)
dup = __(a1,), __(a1, a1)
dupd = __(a2, a1), __(a2, a2, a1)
dupdd = __(a3, a2, a1), __(a3, a3, a2, a1)
@@ -259,18 +271,18 @@ def defs():
rest = __((a1, s0),), __(s0,)
rolldown = __(a1, a2, a3), __(a2, a3, a1)
rollup = __(a1, a2, a3), __(a3, a1, a2)
rrest = C(rest, rest)
second = C(rest, first)
rrest = compose(rest, rest)
second = compose(rest, first)
stack = s0, (s0, s0)
swaack = (s1, s0), (s0, s1)
swap = __(a1, a2), __(a2, a1)
swons = C(swap, cons)
third = C(rest, second)
swons = compose(swap, cons)
third = compose(rest, second)
tuck = __(a2, a1), __(a1, a2, a1)
uncons = __((a1, s0),), __(a1, s0)
unswons = C(uncons, swap)
stuncons = C(stack, uncons)
stununcons = C(stack, uncons, uncons)
unswons = compose(uncons, swap)
stuncons = compose(stack, uncons)
stununcons = compose(stack, uncons, uncons)
unit = __(a1), __((a1, ()))
eq = ge = gt = le = lt = ne = __(n1, n2), __(b1)
@@ -280,25 +292,26 @@ def defs():
add = div = floordiv = modulus = mul = pow_ = sub = truediv = \
lshift = rshift = __(n1, n2), __(n3,)
sqrt = C(dup, mul)
sqrt = compose(dup, mul)
succ = pred = neg = __(n1,), __(n2,)
divmod_ = pm = __(n2, n1), __(n4, n3)
first_two = C(uncons, uncons, pop)
fourth = C(rest, third)
first_two = compose(uncons, uncons, pop)
fourth = compose(rest, third)
_Tree_add_Ee = C(pop, swap, rolldown, rrest, ccons)
_Tree_get_E = C(popop, second)
_Tree_delete_clear_stuff = C(rollup, popop, rest)
_Tree_delete_R0 = C(over, first, swap, dup)
_Tree_add_Ee = compose(pop, swap, rolldown, rrest, ccons)
_Tree_get_E = compose(popop, second)
_Tree_delete_clear_stuff = compose(rollup, popop, rest)
_Tree_delete_R0 = compose(over, first, swap, dup)
return locals()
return {
name.rstrip('_'): stack_effect
for name, stack_effect in locals().iteritems()
}
DEFS = defs()
#globals().update(DEFS)
def show():
for name, stack_effect_comment in sorted(DEFS.iteritems()):