Moving right along.
A little clunky but it seems to work so far.
This commit is contained in:
parent
c2dd7cca0a
commit
73d19b1f3d
|
|
@ -25,11 +25,12 @@ function.
|
|||
'''
|
||||
from inspect import getdoc
|
||||
from functools import wraps
|
||||
from itertools import count
|
||||
from inspect import getmembers, isfunction
|
||||
import operator, math
|
||||
|
||||
from .parser import text_to_expression, Symbol
|
||||
from .utils.stack import list_to_stack, iter_stack, pick, concat
|
||||
from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat
|
||||
from .utils.brutal_hackery import rename_code_object
|
||||
|
||||
from .utils import generated_library as genlib
|
||||
|
|
@ -38,16 +39,28 @@ from .utils.types import (
|
|||
ef,
|
||||
stack_effect,
|
||||
AnyJoyType,
|
||||
AnyStarJoyType,
|
||||
BooleanJoyType,
|
||||
NumberJoyType,
|
||||
NumberStarJoyType,
|
||||
StackJoyType,
|
||||
StackStarJoyType,
|
||||
FloatJoyType,
|
||||
IntJoyType,
|
||||
SymbolJoyType,
|
||||
TextJoyType,
|
||||
_functions,
|
||||
FUNCTIONS,
|
||||
infer,
|
||||
JoyTypeError,
|
||||
combinator_effect,
|
||||
)
|
||||
|
||||
|
||||
_SYM_NUMS = count().next
|
||||
_COMB_NUMS = count().next
|
||||
|
||||
|
||||
_R = range(10)
|
||||
A = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = map(AnyJoyType, _R)
|
||||
B = b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = map(BooleanJoyType, _R)
|
||||
|
|
@ -58,6 +71,12 @@ I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = map(IntJoyType, _R)
|
|||
T = t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 = map(TextJoyType, _R)
|
||||
|
||||
|
||||
_R = range(1, 11)
|
||||
As = map(AnyStarJoyType, _R)
|
||||
Ns = map(NumberStarJoyType, _R)
|
||||
Ss = map(StackStarJoyType, _R)
|
||||
|
||||
|
||||
sec0 = stack_effect(t1)()
|
||||
sec1 = stack_effect(s0, i1)(s1)
|
||||
sec2 = stack_effect(s0, i1)(a1)
|
||||
|
|
@ -67,7 +86,7 @@ sec_binary_logic = stack_effect(b1, b2)(b3)
|
|||
sec_binary_math = stack_effect(n1, n2)(n3)
|
||||
sec_unary_logic = stack_effect(a1)(b1)
|
||||
sec_unary_math = stack_effect(n1)(n2)
|
||||
|
||||
sec_Ns_math = stack_effect((Ns[1], s1),)(n0)
|
||||
|
||||
_dictionary = {}
|
||||
|
||||
|
|
@ -350,6 +369,14 @@ class DefinitionWrapper(object):
|
|||
Add the definition to the dictionary.
|
||||
'''
|
||||
F = class_.parse_definition(definition)
|
||||
try:
|
||||
#print F._body
|
||||
secs = infer(*F._body)
|
||||
except JoyTypeError:
|
||||
pass
|
||||
print F.name, '==', expression_to_string(F.body), ' --failed to infer stack effect.'
|
||||
else:
|
||||
FUNCTIONS[F.name] = SymbolJoyType(F.name, secs, _SYM_NUMS())
|
||||
dictionary[F.name] = F
|
||||
|
||||
|
||||
|
|
@ -357,23 +384,11 @@ def _text_to_defs(text):
|
|||
return (line.strip() for line in text.splitlines() if '==' in line)
|
||||
|
||||
|
||||
|
||||
## eh = compose(dup, bool_)
|
||||
## sqr = compose(dup, mul)
|
||||
## of = compose(swap, at)
|
||||
|
||||
|
||||
#
|
||||
# Functions
|
||||
#
|
||||
|
||||
|
||||
# Load the auto-generated primitives into the dictionary.
|
||||
_functions.update(yin_functions())
|
||||
for name, primitive in getmembers(genlib, isfunction):
|
||||
inscribe(SimpleFunctionWrapper(primitive))
|
||||
|
||||
|
||||
@inscribe
|
||||
@sec0
|
||||
@FunctionWrapper
|
||||
|
|
@ -528,6 +543,7 @@ def select(stack):
|
|||
|
||||
|
||||
@inscribe
|
||||
@sec_Ns_math
|
||||
@SimpleFunctionWrapper
|
||||
def max_(S):
|
||||
'''Given a list find the maximum.'''
|
||||
|
|
@ -536,6 +552,7 @@ def max_(S):
|
|||
|
||||
|
||||
@inscribe
|
||||
@sec_Ns_math
|
||||
@SimpleFunctionWrapper
|
||||
def min_(S):
|
||||
'''Given a list find the minimum.'''
|
||||
|
|
@ -544,6 +561,7 @@ def min_(S):
|
|||
|
||||
|
||||
@inscribe
|
||||
@sec_Ns_math
|
||||
@SimpleFunctionWrapper
|
||||
def sum_(S):
|
||||
'''Given a quoted sequence of numbers return the sum.
|
||||
|
|
@ -872,6 +890,7 @@ S_truthy = Symbol('truthy')
|
|||
|
||||
|
||||
@inscribe
|
||||
@combinator_effect(_COMB_NUMS(), s1)
|
||||
@FunctionWrapper
|
||||
def i(stack, expression, dictionary):
|
||||
'''
|
||||
|
|
@ -889,6 +908,7 @@ def i(stack, expression, dictionary):
|
|||
|
||||
|
||||
@inscribe
|
||||
@combinator_effect(_COMB_NUMS(), s1)
|
||||
@FunctionWrapper
|
||||
def x(stack, expression, dictionary):
|
||||
'''
|
||||
|
|
@ -906,6 +926,7 @@ def x(stack, expression, dictionary):
|
|||
|
||||
|
||||
@inscribe
|
||||
#@combinator_effect(_COMB_NUMS(), s7, s6)
|
||||
@FunctionWrapper
|
||||
def b(stack, expression, dictionary):
|
||||
'''
|
||||
|
|
@ -941,6 +962,7 @@ def dupdip(stack, expression, dictionary):
|
|||
|
||||
|
||||
@inscribe
|
||||
#@combinator_effect(_COMB_NUMS(), s7, s6)
|
||||
@FunctionWrapper
|
||||
def infra(stack, expression, dictionary):
|
||||
'''
|
||||
|
|
@ -1150,6 +1172,7 @@ def _cond(conditions, expression):
|
|||
|
||||
|
||||
@inscribe
|
||||
@combinator_effect(_COMB_NUMS(), a1, s1)
|
||||
@FunctionWrapper
|
||||
def dip(stack, expression, dictionary):
|
||||
'''
|
||||
|
|
@ -1437,7 +1460,31 @@ for F in (
|
|||
del F # Otherwise Sphinx autodoc will pick it up.
|
||||
|
||||
|
||||
YIN_STACK_EFFECTS = yin_functions()
|
||||
|
||||
# Load the auto-generated primitives into the dictionary.
|
||||
_functions.update(YIN_STACK_EFFECTS)
|
||||
# exec '''
|
||||
|
||||
# eh = compose(dup, bool)
|
||||
# sqr = compose(dup, mul)
|
||||
# of = compose(swap, at)
|
||||
|
||||
# ''' in dict(compose=compose), _functions
|
||||
|
||||
FUNCTIONS.update(
|
||||
(name, SymbolJoyType(name, [_functions[name]], _SYM_NUMS()))
|
||||
for name in sorted(_functions)
|
||||
)
|
||||
for name, primitive in getmembers(genlib, isfunction):
|
||||
inscribe(SimpleFunctionWrapper(primitive))
|
||||
|
||||
|
||||
add_aliases(_dictionary, ALIASES)
|
||||
add_aliases(_functions, ALIASES)
|
||||
add_aliases(FUNCTIONS, ALIASES)
|
||||
|
||||
|
||||
DefinitionWrapper.add_definitions(definitions, _dictionary)
|
||||
|
||||
#sec_Ns_math(_dictionary['product'])
|
||||
|
|
|
|||
|
|
@ -10,12 +10,6 @@ an unbounded sequence of other types.
|
|||
|
||||
'''
|
||||
import sys
|
||||
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, text_to_expression
|
||||
from joy.utils.stack import (
|
||||
|
|
@ -23,391 +17,22 @@ from joy.utils.stack import (
|
|||
expression_to_string,
|
||||
list_to_stack,
|
||||
)
|
||||
from joy.utils.types import (
|
||||
AnyJoyType, A,
|
||||
BooleanJoyType, B,
|
||||
DEFS,
|
||||
doc_from_stack_effect,
|
||||
FloatJoyType, F,
|
||||
JoyTypeError,
|
||||
NumberJoyType, N,
|
||||
StackJoyType, S,
|
||||
_stacky,
|
||||
_R,
|
||||
relabel, delabel,
|
||||
reify,
|
||||
)
|
||||
# from joy.utils.types import (
|
||||
# AnyJoyType, A,
|
||||
# BooleanJoyType, B,
|
||||
# DEFS,
|
||||
# doc_from_stack_effect,
|
||||
# FloatJoyType, F,
|
||||
# JoyTypeError,
|
||||
# NumberJoyType, N,
|
||||
# StackJoyType, S,
|
||||
# _stacky,
|
||||
# _R,
|
||||
# relabel, delabel,
|
||||
# reify,
|
||||
# )
|
||||
|
||||
|
||||
# We no longer want FloatJoyType to accept IntJoyType.
|
||||
class IntJoyType(NumberJoyType): prefix = 'i'
|
||||
|
||||
|
||||
class KleeneStar(object):
|
||||
u'''
|
||||
A sequence of zero or more `AnyJoyType` variables would be:
|
||||
|
||||
A*
|
||||
|
||||
The `A*` works by splitting the universe into two alternate histories:
|
||||
|
||||
A* → ∅
|
||||
|
||||
A* → A A*
|
||||
|
||||
The Kleene star variable disappears in one universe, and in the other
|
||||
it turns into an `AnyJoyType` variable followed by itself again.
|
||||
|
||||
We have to return all universes (represented by their substitution
|
||||
dicts, the "unifiers") that don't lead to type conflicts.
|
||||
'''
|
||||
|
||||
kind = AnyJoyType
|
||||
|
||||
def __init__(self, number):
|
||||
assert number
|
||||
self.number = number
|
||||
self.count = 0
|
||||
self.prefix = repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s%i*' % (self.kind.prefix, self.number)
|
||||
|
||||
def another(self):
|
||||
self.count += 1
|
||||
return self.kind(10000 * self.number + self.count)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and other.number == self.number
|
||||
)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.kind >= other.kind
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(self.number + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
|
||||
class AnyStarJoyType(KleeneStar): kind = AnyJoyType
|
||||
class NumberStarJoyType(KleeneStar): kind = NumberJoyType
|
||||
#class FloatStarJoyType(KleeneStar): kind = FloatJoyType
|
||||
#class IntStarJoyType(KleeneStar): kind = IntJoyType
|
||||
class StackStarJoyType(KleeneStar): kind = StackJoyType
|
||||
|
||||
|
||||
class FunctionJoyType(AnyJoyType):
|
||||
|
||||
def __init__(self, name, sec, number):
|
||||
self.name = name
|
||||
self.stack_effects = sec
|
||||
self.number = number
|
||||
|
||||
def __add__(self, other):
|
||||
return self
|
||||
__radd__ = __add__
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
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'
|
||||
|
||||
def __init__(self, name, sec, number, expect=None):
|
||||
super(CombinatorJoyType, self).__init__(name, sec, number)
|
||||
self.expect = expect
|
||||
|
||||
def enter_guard(self, f):
|
||||
if self.expect is None:
|
||||
return f
|
||||
g = self.expect, self.expect
|
||||
new_f = list(compose(f, g, ()))
|
||||
assert len(new_f) == 1, repr(new_f)
|
||||
return new_f[0][1]
|
||||
|
||||
|
||||
def _log_uni(U):
|
||||
def inner(u, v, s=None):
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s',
|
||||
len(inspect_stack()), u, v, s,
|
||||
)
|
||||
res = U(u, v, s)
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s => %s',
|
||||
len(inspect_stack()), u, v, s, res,
|
||||
)
|
||||
return res
|
||||
return inner
|
||||
|
||||
|
||||
@_log_uni
|
||||
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:
|
||||
u = reify(s, u)
|
||||
v = reify(s, v)
|
||||
|
||||
if u == v:
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple) and isinstance(v, tuple):
|
||||
if len(u) != 2 or len(v) != 2:
|
||||
if _that_one_special_case(u, v):
|
||||
return s,
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
|
||||
|
||||
(a, b), (c, d) = v, u
|
||||
if isinstance(a, KleeneStar):
|
||||
if isinstance(c, KleeneStar):
|
||||
s = _lil_uni(a, c, s) # Attempt to unify the two K-stars.
|
||||
res = unify(d, b, s[0])
|
||||
|
||||
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)
|
||||
|
||||
elif isinstance(c, KleeneStar):
|
||||
res = unify(v, d) + unify(v, (c.another(), u))
|
||||
for sn in res:
|
||||
sn.update(s)
|
||||
|
||||
else:
|
||||
res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
s[u] = v
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple):
|
||||
if not _stacky(v):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
|
||||
s[v] = u
|
||||
res = s,
|
||||
|
||||
else:
|
||||
res = _lil_uni(u, v, s)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _that_one_special_case(u, v):
|
||||
'''
|
||||
Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc...
|
||||
'''
|
||||
return (
|
||||
u == ()
|
||||
and len(v) == 2
|
||||
and isinstance(v[0], KleeneStar)
|
||||
and isinstance(v[1], StackJoyType)
|
||||
)
|
||||
|
||||
|
||||
def _lil_uni(u, v, s):
|
||||
if u >= v:
|
||||
s[u] = v
|
||||
return s,
|
||||
if v >= u:
|
||||
s[v] = u
|
||||
return s,
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
|
||||
def _compose(f, g, e):
|
||||
(f_in, f_out), (g_in, g_out) = f, g
|
||||
for s in unify(g_in, f_out):
|
||||
yield reify(s, (e, (f_in, g_out)))
|
||||
|
||||
|
||||
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):
|
||||
yield delabel(fg)
|
||||
|
||||
|
||||
def _meta_compose(F, G, e):
|
||||
for f, g in product(F, G):
|
||||
try:
|
||||
for result in compose(f, g, e): yield result
|
||||
except JoyTypeError:
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def flatten(g):
|
||||
return list(chain.from_iterable(g))
|
||||
|
||||
|
||||
ID = S[0], S[0] # Identity function.
|
||||
|
||||
|
||||
def _infer(e, F=ID):
|
||||
_log_it(e, F)
|
||||
if not e:
|
||||
return [F]
|
||||
|
||||
n, e = e
|
||||
|
||||
if isinstance(n, SymbolJoyType):
|
||||
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)
|
||||
res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
|
||||
|
||||
elif isinstance(n, Symbol):
|
||||
assert n not in FUNCTIONS, repr(n)
|
||||
func = joy.library._dictionary[n]
|
||||
res = _interpret(func, F[0], F[1], e)
|
||||
|
||||
else:
|
||||
fi, fo = F
|
||||
res = _infer(e, (fi, (n, fo)))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _interpret(f, fi, fo, e):
|
||||
new_fo, ee, _ = f(fo, e, {})
|
||||
ee = reify(FUNCTIONS, ee) # Fix Symbols.
|
||||
new_F = fi, new_fo
|
||||
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))))
|
||||
|
||||
|
||||
def infer_string(string):
|
||||
e = reify(FUNCTIONS, text_to_expression(string)) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def infer_expression(expression):
|
||||
e = reify(FUNCTIONS, expression) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def type_check(name, stack):
|
||||
'''
|
||||
Trinary predicate. True if named function type-checks, False if it
|
||||
fails, None if it's indeterminate (because I haven't entered it into
|
||||
the FUNCTIONS dict yet.)
|
||||
'''
|
||||
try:
|
||||
func = FUNCTIONS[name]
|
||||
except KeyError:
|
||||
return # None, indicating unknown
|
||||
|
||||
for fi, fo in infer(func):
|
||||
try:
|
||||
U = unify(fi, stack)
|
||||
except (JoyTypeError, ValueError), e:
|
||||
#print >> sys.stderr, name, e, stack
|
||||
continue
|
||||
#print U
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = A
|
||||
b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = B
|
||||
n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = N
|
||||
f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = F
|
||||
i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = I = map(IntJoyType, _R)
|
||||
s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = S
|
||||
|
||||
_R = range(1, 11)
|
||||
As = map(AnyStarJoyType, _R)
|
||||
Ns = map(NumberStarJoyType, _R)
|
||||
Ss = map(StackStarJoyType, _R)
|
||||
|
||||
|
||||
FUNCTIONS = {
|
||||
name: SymbolJoyType(name, [DEFS[name]], i)
|
||||
for i, name in enumerate(sorted(DEFS))
|
||||
}
|
||||
'''Docstring for functions in Sphinx?'''
|
||||
|
||||
|
||||
|
|
@ -452,6 +77,7 @@ FUNCTIONS.update({
|
|||
))
|
||||
})
|
||||
|
||||
|
||||
def branch_true(stack, expression, dictionary):
|
||||
(then, (else_, (flag, stack))) = stack
|
||||
return stack, CONCAT(then, expression), dictionary
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
# -*- coding: utf_8
|
||||
from logging import getLogger
|
||||
|
||||
_log = getLogger(__name__)
|
||||
|
||||
from collections import Counter
|
||||
from itertools import imap
|
||||
from joy.utils.stack import concat
|
||||
from joy.parser import Symbol
|
||||
from itertools import imap, chain, product
|
||||
from inspect import stack as inspect_stack
|
||||
from joy.utils.stack import concat, expression_to_string, list_to_stack
|
||||
from joy.parser import Symbol, text_to_expression
|
||||
|
||||
|
||||
class AnyJoyType(object):
|
||||
|
|
@ -49,7 +55,7 @@ class BooleanJoyType(AnyJoyType):
|
|||
|
||||
|
||||
class NumberJoyType(AnyJoyType):
|
||||
accept = int, float, long, complex
|
||||
accept = bool, int, float, long, complex
|
||||
prefix = 'n'
|
||||
|
||||
|
||||
|
|
@ -78,6 +84,118 @@ class StackJoyType(AnyJoyType):
|
|||
return False
|
||||
|
||||
|
||||
class KleeneStar(object):
|
||||
u'''
|
||||
A sequence of zero or more `AnyJoyType` variables would be:
|
||||
|
||||
A*
|
||||
|
||||
The `A*` works by splitting the universe into two alternate histories:
|
||||
|
||||
A* → ∅
|
||||
|
||||
A* → A A*
|
||||
|
||||
The Kleene star variable disappears in one universe, and in the other
|
||||
it turns into an `AnyJoyType` variable followed by itself again.
|
||||
|
||||
We have to return all universes (represented by their substitution
|
||||
dicts, the "unifiers") that don't lead to type conflicts.
|
||||
'''
|
||||
|
||||
kind = AnyJoyType
|
||||
|
||||
def __init__(self, number):
|
||||
assert number
|
||||
self.number = number
|
||||
self.count = 0
|
||||
self.prefix = repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s%i*' % (self.kind.prefix, self.number)
|
||||
|
||||
def another(self):
|
||||
self.count += 1
|
||||
return self.kind(10000 * self.number + self.count)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and other.number == self.number
|
||||
)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.kind >= other.kind
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(self.number + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
|
||||
class AnyStarJoyType(KleeneStar): kind = AnyJoyType
|
||||
class NumberStarJoyType(KleeneStar): kind = NumberJoyType
|
||||
class FloatStarJoyType(KleeneStar): kind = FloatJoyType
|
||||
class IntStarJoyType(KleeneStar): kind = IntJoyType
|
||||
class StackStarJoyType(KleeneStar): kind = StackJoyType
|
||||
class TextStarJoyType(KleeneStar): kind = TextJoyType
|
||||
|
||||
|
||||
class FunctionJoyType(AnyJoyType):
|
||||
|
||||
def __init__(self, name, sec, number):
|
||||
self.name = name
|
||||
self.stack_effects = sec
|
||||
self.number = number
|
||||
|
||||
def __add__(self, other):
|
||||
return self
|
||||
__radd__ = __add__
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
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'
|
||||
|
||||
def __init__(self, name, sec, number, expect=None):
|
||||
super(CombinatorJoyType, self).__init__(name, sec, number)
|
||||
self.expect = expect
|
||||
|
||||
def enter_guard(self, f):
|
||||
if self.expect is None:
|
||||
return f
|
||||
g = self.expect, self.expect
|
||||
new_f = list(poly_compose(f, g, ()))
|
||||
assert len(new_f) == 1, repr(new_f)
|
||||
return new_f[0][1]
|
||||
|
||||
|
||||
class JoyTypeError(Exception): pass
|
||||
|
||||
|
||||
|
|
@ -135,7 +253,7 @@ def delabel(f, seen=None, c=None):
|
|||
return tuple(delabel(inner, seen, c) for inner in f)
|
||||
|
||||
|
||||
def unify(u, v, s=None):
|
||||
def uni_unify(u, v, s=None):
|
||||
'''
|
||||
Return a substitution dict representing a unifier for u and v.
|
||||
'''
|
||||
|
|
@ -157,7 +275,7 @@ def unify(u, v, s=None):
|
|||
if len(u) != len(v) != 2:
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
(a, b), (c, d) = u, v
|
||||
s = unify(b, d, unify(a, c, s))
|
||||
s = uni_unify(b, d, uni_unify(a, c, s))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
|
|
@ -175,6 +293,112 @@ def unify(u, v, s=None):
|
|||
return s
|
||||
|
||||
|
||||
def _log_uni(U):
|
||||
def inner(u, v, s=None):
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s',
|
||||
len(inspect_stack()), u, v, s,
|
||||
)
|
||||
res = U(u, v, s)
|
||||
_log.debug(
|
||||
'%3i %s U %s w/ %s => %s',
|
||||
len(inspect_stack()), u, v, s, res,
|
||||
)
|
||||
return res
|
||||
return inner
|
||||
|
||||
|
||||
@_log_uni
|
||||
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:
|
||||
u = reify(s, u)
|
||||
v = reify(s, v)
|
||||
|
||||
if u == v:
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple) and isinstance(v, tuple):
|
||||
if len(u) != 2 or len(v) != 2:
|
||||
if _that_one_special_case(u, v):
|
||||
return s,
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
|
||||
|
||||
(a, b), (c, d) = v, u
|
||||
if isinstance(a, KleeneStar):
|
||||
if isinstance(c, KleeneStar):
|
||||
s = _lil_uni(a, c, s) # Attempt to unify the two K-stars.
|
||||
res = unify(d, b, s[0])
|
||||
|
||||
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)
|
||||
|
||||
elif isinstance(c, KleeneStar):
|
||||
res = unify(v, d) + unify(v, (c.another(), u))
|
||||
for sn in res:
|
||||
sn.update(s)
|
||||
|
||||
else:
|
||||
res = tuple(flatten(unify(d, b, sn) for sn in unify(c, a, s)))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
s[u] = v
|
||||
res = s,
|
||||
|
||||
elif isinstance(u, tuple):
|
||||
if not _stacky(v):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
|
||||
s[v] = u
|
||||
res = s,
|
||||
|
||||
else:
|
||||
res = _lil_uni(u, v, s)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _that_one_special_case(u, v):
|
||||
'''
|
||||
Handle e.g. ((), (n1*, s1)) when type-checking sum, product, etc...
|
||||
'''
|
||||
return (
|
||||
u == ()
|
||||
and len(v) == 2
|
||||
and isinstance(v[0], KleeneStar)
|
||||
and isinstance(v[1], StackJoyType)
|
||||
)
|
||||
|
||||
|
||||
def flatten(g):
|
||||
return list(chain.from_iterable(g))
|
||||
|
||||
|
||||
def _lil_uni(u, v, s):
|
||||
if u >= v:
|
||||
s[u] = v
|
||||
return s,
|
||||
if v >= u:
|
||||
s[v] = u
|
||||
return s,
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
|
||||
def _stacky(thing):
|
||||
return thing.__class__ in {AnyJoyType, StackJoyType}
|
||||
|
||||
|
|
@ -185,7 +409,7 @@ def _compose(f, g):
|
|||
'''
|
||||
# Relabel, unify, update, delabel.
|
||||
(f_in, f_out), (g_in, g_out) = relabel(f, g)
|
||||
fg = reify(unify(g_in, f_out), (f_in, g_out))
|
||||
fg = reify(uni_unify(g_in, f_out), (f_in, g_out))
|
||||
return delabel(fg)
|
||||
|
||||
|
||||
|
|
@ -275,7 +499,144 @@ def compile_(name, f, doc=None):
|
|||
return %s''' % (name, doc, i, o)
|
||||
|
||||
|
||||
_functions = {}
|
||||
def _poly_compose(f, g, e):
|
||||
(f_in, f_out), (g_in, g_out) = f, g
|
||||
for s in unify(g_in, f_out):
|
||||
yield reify(s, (e, (f_in, g_out)))
|
||||
|
||||
|
||||
def poly_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 _poly_compose(f, g, e):
|
||||
yield delabel(fg)
|
||||
|
||||
|
||||
def _meta_compose(F, G, e):
|
||||
for f, g in product(F, G):
|
||||
try:
|
||||
for result in poly_compose(f, g, e): yield result
|
||||
except JoyTypeError:
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
_S0 = StackJoyType(0)
|
||||
ID = _S0, _S0 # Identity function.
|
||||
|
||||
|
||||
def _infer(e, F=ID):
|
||||
_log_it(e, F)
|
||||
if not e:
|
||||
return [F]
|
||||
|
||||
n, e = e
|
||||
|
||||
if isinstance(n, SymbolJoyType):
|
||||
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)
|
||||
res = flatten(_interpret(f, fi, fo, e) for f in n.stack_effects)
|
||||
|
||||
elif isinstance(n, Symbol):
|
||||
if n in FUNCTIONS:
|
||||
res =_infer((FUNCTIONS[n], e), F)
|
||||
else:
|
||||
raise JoyTypeError
|
||||
# print n
|
||||
# func = joy.library._dictionary[n]
|
||||
# res = _interpret(func, F[0], F[1], e)
|
||||
|
||||
else:
|
||||
fi, fo = F
|
||||
res = _infer(e, (fi, (n, fo)))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def _interpret(f, fi, fo, e):
|
||||
new_fo, ee, _ = f(fo, e, {})
|
||||
ee = reify(FUNCTIONS, ee) # Fix Symbols.
|
||||
new_F = fi, new_fo
|
||||
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))))
|
||||
|
||||
|
||||
def infer_string(string):
|
||||
e = reify(FUNCTIONS, text_to_expression(string)) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def infer_expression(expression):
|
||||
e = reify(FUNCTIONS, expression) # Fix Symbols.
|
||||
return sorted(set(_infer(e)))
|
||||
|
||||
|
||||
def type_check(name, stack):
|
||||
'''
|
||||
Trinary predicate. True if named function type-checks, False if it
|
||||
fails, None if it's indeterminate (because I haven't entered it into
|
||||
the FUNCTIONS dict yet.)
|
||||
'''
|
||||
try:
|
||||
func = FUNCTIONS[name]
|
||||
except KeyError:
|
||||
return # None, indicating unknown
|
||||
|
||||
for fi, fo in infer(func):
|
||||
try:
|
||||
U = unify(fi, stack)
|
||||
except (JoyTypeError, ValueError), e:
|
||||
#print >> sys.stderr, name, e, stack
|
||||
continue
|
||||
#print U
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
FUNCTIONS = {} # Polytypes (lists of stack effects.)
|
||||
_functions = {} # plain ol' stack effects.
|
||||
|
||||
|
||||
def __(*seq):
|
||||
|
|
@ -303,6 +664,14 @@ def ef(*inputs):
|
|||
return _ef
|
||||
|
||||
|
||||
def combinator_effect(number, *expect):
|
||||
def _combinator_effect(c):
|
||||
C = FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number)
|
||||
if expect: C.expect = __(*expect)
|
||||
return c
|
||||
return _combinator_effect
|
||||
|
||||
|
||||
def show(DEFS):
|
||||
for name, stack_effect_comment in sorted(DEFS.iteritems()):
|
||||
t = ' *'[compilable(stack_effect_comment)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue