Remove the types stuff et. al.
This commit is contained in:
@@ -1,96 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2018 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
'''
|
||||
I really want tracebacks to show which function was being executed when
|
||||
an error in the wrapper function happens. In order to do that, you have
|
||||
to do this (the function in this module.)
|
||||
|
||||
Here's what it looks like when you pass too few arguments to e.g. "mul".
|
||||
|
||||
>>> from joy.library import _dictionary
|
||||
>>> m = _dictionary['*']
|
||||
>>> m((), (), {})
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "<pyshell#49>", line 1, in <module>
|
||||
m((), (), {})
|
||||
File "joy/library.py", line 185, in mul:inner
|
||||
(a, (b, stack)) = stack
|
||||
ValueError: need more than 0 values to unpack
|
||||
>>>
|
||||
|
||||
|
||||
Notice that line 185 in the library.py file is (as of this writing) in
|
||||
the BinaryBuiltinWrapper's inner() function, but this hacky code has
|
||||
managed to insert the name of the wrapped function ("mul") along with a
|
||||
colon into the wrapper function's reported name.
|
||||
|
||||
Normally I would frown on this sort of mad hackery, but... this is in
|
||||
the service of ease-of-debugging! Very valuable. And note that all the
|
||||
hideous patching is finished in the module-load-stage, it shouldn't cause
|
||||
issues of its own at runtime.
|
||||
|
||||
The main problem I see with this is that people coming to this code later
|
||||
might be mystified if they just see a traceback with a ':' in the
|
||||
function name! Hopefully they will discover this documentation.
|
||||
'''
|
||||
|
||||
|
||||
def rename_code_object(new_name):
|
||||
'''
|
||||
If you want to wrap a function in another function and have the wrapped
|
||||
function's name show up in the traceback when an exception occurs in
|
||||
the wrapper function, you must do this brutal hackery to change the
|
||||
func.__code__.co_name attribute. Just functools.wraps() is not enough.
|
||||
|
||||
See:
|
||||
|
||||
https://stackoverflow.com/questions/29919804/function-decorated-using-functools-wraps-raises-typeerror-with-the-name-of-the-w
|
||||
|
||||
https://stackoverflow.com/questions/29488327/changing-the-name-of-a-generator/29488561#29488561
|
||||
|
||||
I'm just glad it's possible.
|
||||
'''
|
||||
def inner(func):
|
||||
name = new_name + ':' + func.__name__
|
||||
code_object = func.__code__
|
||||
return type(func)(
|
||||
type(code_object)(
|
||||
code_object.co_argcount,
|
||||
code_object.co_nlocals,
|
||||
code_object.co_stacksize,
|
||||
code_object.co_flags,
|
||||
code_object.co_code,
|
||||
code_object.co_consts,
|
||||
code_object.co_names,
|
||||
code_object.co_varnames,
|
||||
code_object.co_filename,
|
||||
name,
|
||||
code_object.co_firstlineno,
|
||||
code_object.co_lnotab,
|
||||
code_object.co_freevars,
|
||||
code_object.co_cellvars
|
||||
),
|
||||
func.__globals__,
|
||||
name,
|
||||
func.__defaults__,
|
||||
func.__closure__
|
||||
)
|
||||
return inner
|
||||
@@ -1,241 +0,0 @@
|
||||
'''
|
||||
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'))
|
||||
@@ -1,4 +1,6 @@
|
||||
# GENERATED FILE. DO NOT EDIT.
|
||||
# The code that generated these functions is in the repo history
|
||||
# at the v0.4.0 tag.
|
||||
|
||||
|
||||
def _Tree_add_Ee(stack):
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
from builtins import str
|
||||
from joy.parser import Symbol
|
||||
|
||||
|
||||
def _names():
|
||||
n = 0
|
||||
while True:
|
||||
yield Symbol('a' + str(n))
|
||||
n += 1
|
||||
|
||||
|
||||
class InfiniteStack(tuple):
|
||||
|
||||
names = lambda n=_names(): next(n)
|
||||
|
||||
def __iter__(self):
|
||||
if not self:
|
||||
return iter((self.names(), self))
|
||||
|
||||
|
||||
i = InfiniteStack()
|
||||
|
||||
a, b = i
|
||||
|
||||
lambda u: (lambda fu, u: fu * fu * u)(
|
||||
(lambda u: (lambda fu, u: fu * fu)(
|
||||
(lambda u: (lambda fu, u: fu * fu * u)(
|
||||
(lambda u: 1)(u), u))(u), u))(u),
|
||||
u)
|
||||
|
||||
lambda u: (lambda fu, u: fu * fu * u)((lambda u: (lambda fu, u: fu * fu)((lambda u: (lambda fu, u: fu * fu * u)((lambda u: 1)(u), u))(u), u))(u), u)
|
||||
@@ -1,756 +0,0 @@
|
||||
# -*- coding: utf_8
|
||||
#
|
||||
# Copyright © 2018 Simon Forman
|
||||
#
|
||||
# This file is part of Thun
|
||||
#
|
||||
# Thun is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Thun is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from __future__ import print_function
|
||||
from builtins import str
|
||||
from builtins import map
|
||||
from past.builtins import basestring
|
||||
from builtins import object
|
||||
from logging import getLogger, addLevelName
|
||||
from functools import reduce
|
||||
|
||||
_log = getLogger(__name__)
|
||||
addLevelName(15, 'hmm')
|
||||
|
||||
from collections import Counter
|
||||
from itertools import chain, product
|
||||
from inspect import stack as inspect_stack
|
||||
from joy.utils.stack import (
|
||||
concat,
|
||||
expression_to_string,
|
||||
list_to_stack,
|
||||
stack_to_string,
|
||||
)
|
||||
from joy.parser import Symbol, text_to_expression
|
||||
|
||||
|
||||
class AnyJoyType(object):
|
||||
'''
|
||||
Joy type variable. Represents any Joy value.
|
||||
'''
|
||||
|
||||
accept = tuple, int, float, int, complex, str, bool, Symbol
|
||||
prefix = 'a'
|
||||
|
||||
def __init__(self, number):
|
||||
self.number = number
|
||||
|
||||
def __repr__(self):
|
||||
return self.prefix + str(self.number)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
isinstance(other, self.__class__)
|
||||
and other.prefix == self.prefix
|
||||
and other.number == self.number
|
||||
)
|
||||
|
||||
def __ge__(self, other):
|
||||
return (
|
||||
issubclass(other.__class__, self.__class__)
|
||||
or isinstance(other, self.accept)
|
||||
)
|
||||
|
||||
def __le__(self, other):
|
||||
# 'a string' >= AnyJoyType() should be False.
|
||||
return issubclass(self.__class__, other.__class__)
|
||||
|
||||
def __add__(self, other):
|
||||
return self.__class__(self.number + other)
|
||||
__radd__ = __add__
|
||||
|
||||
def __hash__(self):
|
||||
return hash(repr(self))
|
||||
|
||||
|
||||
class BooleanJoyType(AnyJoyType):
|
||||
accept = bool
|
||||
prefix = 'b'
|
||||
|
||||
|
||||
class NumberJoyType(AnyJoyType):
|
||||
accept = bool, int, float, complex
|
||||
prefix = 'n'
|
||||
|
||||
|
||||
class FloatJoyType(NumberJoyType):
|
||||
accept = float
|
||||
prefix = 'f'
|
||||
|
||||
|
||||
class IntJoyType(FloatJoyType):
|
||||
accept = int
|
||||
prefix = 'i'
|
||||
|
||||
|
||||
class TextJoyType(AnyJoyType):
|
||||
accept = basestring
|
||||
prefix = 't'
|
||||
|
||||
|
||||
class StackJoyType(AnyJoyType):
|
||||
|
||||
accept = tuple
|
||||
prefix = 's'
|
||||
|
||||
def __bool__(self):
|
||||
# Imitate () at the end of cons list.
|
||||
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
|
||||
|
||||
|
||||
def reify(meaning, name, seen=None):
|
||||
'''
|
||||
Apply substitution dict to term, returning new term.
|
||||
'''
|
||||
if isinstance(name, tuple):
|
||||
return tuple(reify(meaning, inner) for inner in name)
|
||||
safety = 101
|
||||
while name in meaning and safety:
|
||||
safety -= 1
|
||||
name = meaning[name]
|
||||
if not safety:
|
||||
raise ValueError('Cycle in substitution dict: %s' % (meaning,))
|
||||
return name
|
||||
|
||||
|
||||
def relabel(left, right):
|
||||
'''
|
||||
Re-number type variables to avoid collisions between stack effects.
|
||||
'''
|
||||
return left, _1000(right)
|
||||
|
||||
|
||||
def _1000(right):
|
||||
if isinstance(right, Symbol):
|
||||
return right
|
||||
if not isinstance(right, tuple):
|
||||
return 1000 + right
|
||||
return tuple(_1000(n) for n in 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()
|
||||
|
||||
try:
|
||||
return seen[f]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not isinstance(f, tuple):
|
||||
try:
|
||||
seen[f] = f.__class__(c[f.prefix] + 1)
|
||||
except (TypeError, # FunctionJoyTypes break this.
|
||||
AttributeError): # Symbol
|
||||
seen[f] = f
|
||||
else:
|
||||
c[f.prefix] += 1
|
||||
return seen[f]
|
||||
|
||||
return tuple(delabel(inner, seen, c) for inner in f)
|
||||
|
||||
|
||||
def uni_unify(u, v, s=None):
|
||||
'''
|
||||
Return a substitution dict representing a unifier for u and v.
|
||||
'''
|
||||
if s is None:
|
||||
s = {}
|
||||
elif s:
|
||||
u = reify(s, u)
|
||||
v = reify(s, v)
|
||||
|
||||
if isinstance(u, AnyJoyType) and isinstance(v, AnyJoyType):
|
||||
if u >= v:
|
||||
s[u] = v
|
||||
elif v >= u:
|
||||
s[v] = u
|
||||
else:
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
elif isinstance(u, tuple) and isinstance(v, tuple):
|
||||
if len(u) != len(v) != 2:
|
||||
raise ValueError(repr((u, v))) # Bad input.
|
||||
(a, b), (c, d) = u, v
|
||||
s = uni_unify(b, d, uni_unify(a, c, s))
|
||||
|
||||
elif isinstance(v, tuple):
|
||||
if not _stacky(u):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
s[u] = v
|
||||
|
||||
elif isinstance(u, tuple):
|
||||
if not _stacky(v):
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (v, u))
|
||||
s[v] = u
|
||||
|
||||
else:
|
||||
raise JoyTypeError('Cannot unify %r and %r.' % (u, v))
|
||||
|
||||
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}
|
||||
|
||||
|
||||
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 = reify(uni_unify(g_in, f_out), (f_in, g_out))
|
||||
return delabel(fg)
|
||||
|
||||
|
||||
def compose(*functions):
|
||||
'''
|
||||
Return the stack effect of the composition of some of stack effects.
|
||||
'''
|
||||
return reduce(_compose, functions)
|
||||
|
||||
|
||||
def compilable(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(map(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]:
|
||||
i.append('...')
|
||||
o.append('...')
|
||||
return '(%s--%s)' % (
|
||||
' '.join(reversed([''] + i)),
|
||||
' '.join(reversed(o + [''])),
|
||||
)
|
||||
|
||||
|
||||
def _f(term, switch):
|
||||
a = []
|
||||
while term and isinstance(term, tuple):
|
||||
item, term = term
|
||||
a.append(item)
|
||||
assert isinstance(term, (tuple, StackJoyType)), repr(term)
|
||||
a = [_to_str(i, term, switch) for i in a]
|
||||
return a
|
||||
|
||||
|
||||
def _to_str(term, stack, switch):
|
||||
if not isinstance(term, tuple):
|
||||
if term == stack:
|
||||
switch[0] = True
|
||||
return '[...]'
|
||||
return (
|
||||
'[...%i]' % term.number
|
||||
if isinstance(term, StackJoyType)
|
||||
else str(term)
|
||||
)
|
||||
|
||||
a = []
|
||||
while term and isinstance(term, tuple):
|
||||
item, term = term
|
||||
a.append(_to_str(item, stack, switch))
|
||||
assert isinstance(term, (tuple, StackJoyType)), repr(term)
|
||||
if term == stack:
|
||||
switch[0] = True
|
||||
end = '' if term == () else '...'
|
||||
#end = '...'
|
||||
else:
|
||||
end = '' if term == () else '...%i' % term.number
|
||||
a.append(end)
|
||||
return '[%s]' % ' '.join(a)
|
||||
|
||||
|
||||
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)
|
||||
return '''def %s(stack):
|
||||
"""
|
||||
::
|
||||
|
||||
%s
|
||||
|
||||
"""
|
||||
%s = stack
|
||||
return %s''' % (name, doc, i, o)
|
||||
|
||||
|
||||
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):
|
||||
if __debug__:
|
||||
_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(n)
|
||||
# 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.log(
|
||||
15,
|
||||
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
|
||||
if isinstance(func, SymbolJoyType):
|
||||
secs = func.stack_effects
|
||||
elif isinstance(func, CombinatorJoyType):
|
||||
if func.expect is None:
|
||||
return # None, indicating unknown
|
||||
secs = [(func.expect, ())]
|
||||
else:
|
||||
raise TypeError(repr(func)) # wtf?
|
||||
for fi, fo in secs:
|
||||
try:
|
||||
unify(fi, stack)
|
||||
except (JoyTypeError, ValueError):
|
||||
continue
|
||||
except:
|
||||
_log.exception(
|
||||
'Type-checking %s %s against %s',
|
||||
name,
|
||||
doc_from_stack_effect(fi, fo),
|
||||
stack_to_string(stack),
|
||||
)
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
FUNCTIONS = {} # Polytypes (lists of stack effects.)
|
||||
_functions = {} # plain ol' stack effects.
|
||||
|
||||
|
||||
def __(*seq):
|
||||
stack = StackJoyType(23)
|
||||
for item in seq: stack = item, stack
|
||||
return stack
|
||||
|
||||
|
||||
def stack_effect(*inputs):
|
||||
def _stack_effect(*outputs):
|
||||
def _apply_to(function):
|
||||
i, o = _functions[function.name] = __(*inputs), __(*outputs)
|
||||
d = doc_from_stack_effect(i, o)
|
||||
function.__doc__ += (
|
||||
'\nStack effect::\n\n ' # '::' for Sphinx docs.
|
||||
+ d
|
||||
)
|
||||
_log.info('Setting stack effect for %s := %s', function.name, d)
|
||||
return function
|
||||
return _apply_to
|
||||
return _stack_effect
|
||||
|
||||
|
||||
def ef(*inputs):
|
||||
def _ef(*outputs):
|
||||
return __(*inputs), __(*outputs)
|
||||
return _ef
|
||||
|
||||
|
||||
def combinator_effect(number, *expect):
|
||||
def _combinator_effect(c):
|
||||
e = __(*expect) if expect else None
|
||||
FUNCTIONS[c.name] = CombinatorJoyType(c.name, [c], number, e)
|
||||
if e:
|
||||
sec = doc_from_stack_effect(e)
|
||||
_log.info('Setting stack EXPECT for combinator %s := %s', c.name, sec)
|
||||
return c
|
||||
return _combinator_effect
|
||||
|
||||
|
||||
def show(DEFS):
|
||||
for name, stack_effect_comment in sorted(DEFS.items()):
|
||||
t = ' *'[compilable(stack_effect_comment)]
|
||||
print(name, '=', doc_from_stack_effect(*stack_effect_comment), t)
|
||||
|
||||
|
||||
def generate_library_code(DEFS, f=None):
|
||||
if f is None:
|
||||
import sys
|
||||
f = sys.stdout
|
||||
print('# GENERATED FILE. DO NOT EDIT.\n', file=f)
|
||||
for name, stack_effect_comment in sorted(DEFS.items()):
|
||||
if not compilable(stack_effect_comment):
|
||||
continue
|
||||
print(file=f)
|
||||
print(compile_(name, stack_effect_comment), file=f)
|
||||
print(file=f)
|
||||
|
||||
|
||||
def poly_combinator_effect(number, effect_funcs, *expect):
|
||||
def _poly_combinator_effect(c):
|
||||
e = __(*expect) if expect else None
|
||||
FUNCTIONS[c.name] = CombinatorJoyType(c.name, effect_funcs, number, e)
|
||||
if e:
|
||||
_log.info('Setting stack EXPECT for combinator %s := %s', c.name, e)
|
||||
return c
|
||||
return _poly_combinator_effect
|
||||
|
||||
#FUNCTIONS['branch'].expect = s7, (s6, (b1, s5))
|
||||
|
||||
Reference in New Issue
Block a user