X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=joy%2Flibrary.py;h=800ff3b76fca339294dcdcdd2bc7a1ff3b616c29;hb=fcd4c613e4a049a21fe0ed5bed42770390bc1b80;hp=ac9e473a1b1ef0f92f6628579dde49c2ea673d9d;hpb=1863cba7f774f8325e9d5abde726df6e17a373db;p=joypy%2FThun.git diff --git a/joy/library.py b/joy/library.py index ac9e473..800ff3b 100644 --- a/joy/library.py +++ b/joy/library.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright © 2014, 2015, 2017, 2018 Simon Forman +# Copyright © 2014-2020 Simon Forman # # This file is part of Thun # @@ -23,380 +23,187 @@ functions. Its main export is a Python function initialize() that returns a dictionary of Joy functions suitable for use with the joy() function. ''' -from __future__ import print_function -from builtins import map, object, range, zip -from logging import getLogger - -_log = getLogger(__name__) -_log.info('Loading library.') - -from inspect import getdoc +from pkg_resources import resource_stream +from io import TextIOWrapper +from inspect import getdoc, getmembers, isfunction from functools import wraps from itertools import count -from inspect import getmembers, isfunction import operator, math +from . import __name__ as _joy_package_name from .parser import text_to_expression, Symbol -from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat -import sys -if sys.version_info.major < 3: - from .utils.brutal_hackery import rename_code_object -else: - rename_code_object = lambda _: lambda f: f - from .utils import generated_library as genlib -from .utils.types import ( - compose, - ef, - stack_effect, - AnyJoyType, - AnyStarJoyType, - BooleanJoyType, - NumberJoyType, - NumberStarJoyType, - StackJoyType, - StackStarJoyType, - FloatJoyType, - IntJoyType, - SymbolJoyType, - CombinatorJoyType, - TextJoyType, - _functions, - FUNCTIONS, - infer, - infer_expression, - JoyTypeError, - combinator_effect, - poly_combinator_effect, - doc_from_stack_effect, - ) - - -_SYM_NUMS = lambda c=count(): next(c) -_COMB_NUMS = lambda c=count(): next(c) - - -_R = list(range(10)) -A = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = list(map(AnyJoyType, _R)) -B = b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = list(map(BooleanJoyType, _R)) -N = n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = list(map(NumberJoyType, _R)) -S = s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = list(map(StackJoyType, _R)) -F = f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = list(map(FloatJoyType, _R)) -I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = list(map(IntJoyType, _R)) -T = t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 = list(map(TextJoyType, _R)) - - -_R = list(range(1, 11)) -As = list(map(AnyStarJoyType, _R)) -Ns = list(map(NumberStarJoyType, _R)) -Ss = list(map(StackStarJoyType, _R)) - - -sec0 = stack_effect(t1)() -sec1 = stack_effect(s0, i1)(s1) -sec2 = stack_effect(s0, i1)(a1) -sec_binary_cmp = stack_effect(n1, n2)(b1) -sec_binary_ints = stack_effect(i1, i2)(i3) -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) +from .utils.errors import ( + NotAListError, + NotAnIntError, + StackUnderflowError, + ) +from .utils.stack import ( + concat, + expression_to_string, + iter_stack, + list_to_stack, + pick, + ) + + +def default_defs(dictionary): + def_stream = TextIOWrapper( + resource_stream(_joy_package_name, 'defs.txt'), + encoding='UTF_8', + ) + Def.load_definitions(def_stream, dictionary) + + +HELP_TEMPLATE = '''\ + +==== Help on %s ==== + +%s +---- end (%s) +''' + + +# This is the main dict we're building. _dictionary = {} -def inscribe(function): - '''A decorator to inscribe functions into the default dictionary.''' - _dictionary[function.name] = function - return function +def inscribe(function, d=_dictionary): + '''A decorator to inscribe functions into the default dictionary.''' + d[function.name] = function + return function def initialize(): - '''Return a dictionary of Joy functions for use with joy().''' - return _dictionary.copy() + '''Return a dictionary of Joy functions for use with joy().''' + return _dictionary.copy() ALIASES = ( - ('add', ['+']), - ('and', ['&']), - ('bool', ['truthy']), - ('mul', ['*']), - ('floordiv', ['/floor', '//']), - ('floor', ['round']), - ('truediv', ['/']), - ('mod', ['%', 'rem', 'remainder', 'modulus']), - ('eq', ['=']), - ('ge', ['>=']), - ('getitem', ['pick', 'at']), - ('gt', ['>']), - ('le', ['<=']), - ('lshift', ['<<']), - ('lt', ['<']), - ('ne', ['<>', '!=']), - ('rshift', ['>>']), - ('sub', ['-']), - ('xor', ['^']), - ('succ', ['++']), - ('pred', ['--']), - ('rolldown', ['roll<']), - ('rollup', ['roll>']), - ('eh', ['?']), - ('id', [u'•']), - ) + ('add', ['+']), + ('and', ['&']), + ('bool', ['truthy']), + ('mul', ['*']), + ('floordiv', ['/floor', '//', '/', 'div']), + ('mod', ['%', 'rem', 'remainder', 'modulus']), + ('eq', ['=']), + ('ge', ['>=']), + ('getitem', ['pick', 'at']), + ('gt', ['>']), + ('le', ['<=']), + ('lshift', ['<<']), + ('lt', ['<']), + ('ne', ['<>', '!=']), + ('rshift', ['>>']), + ('sub', ['-']), + ('xor', ['^']), + ('succ', ['++']), + ('pred', ['--']), + ('rolldown', ['roll<']), + ('rollup', ['roll>']), + ('eh', ['?']), + ('id', [u'•']), + ) def add_aliases(D, A): - ''' - Given a dict and a iterable of (name, [alias, ...]) pairs, create - additional entries in the dict mapping each alias to the named function - if it's in the dict. Aliases for functions not in the dict are ignored. - ''' - for name, aliases in A: - try: - F = D[name] - except KeyError: - continue - for alias in aliases: - D[alias] = F - - -def yin_functions(): - ''' - Return a dict of named stack effects. - - "Yin" functions are those that only rearrange items in stacks and - can be defined completely by their stack effects. This means they - can be auto-compiled. - ''' - # pylint: disable=unused-variable - cons = ef(a1, s0)((a1, s0)) - ccons = compose(cons, cons) - dup = ef(a1)(a1, a1) - dupd = ef(a2, a1)(a2, a2, a1) - dupdd = ef(a3, a2, a1)(a3, a3, a2, a1) - first = ef((a1, s1),)(a1,) - over = ef(a2, a1)(a2, a1, a2) - pop = ef(a1)() - popd = ef(a2, a1,)(a1) - popdd = ef(a3, a2, a1,)(a2, a1,) - popop = ef(a2, a1,)() - popopd = ef(a3, a2, a1,)(a1) - popopdd = ef(a4, a3, a2, a1,)(a2, a1) - rest = ef((a1, s0),)(s0,) - rolldown = ef(a1, a2, a3)(a2, a3, a1) - rollup = ef(a1, a2, a3)(a3, a1, a2) - rrest = compose(rest, rest) - second = compose(rest, first) - stack = s0, (s0, s0) - swaack = (s1, s0), (s0, s1) - swap = ef(a1, a2)(a2, a1) - swons = compose(swap, cons) - third = compose(rest, second) - tuck = ef(a2, a1)(a1, a2, a1) - uncons = ef((a1, s0),)(a1, s0) - unswons = compose(uncons, swap) - stuncons = compose(stack, uncons) - stununcons = compose(stack, uncons, uncons) - unit = ef(a1)((a1, ())) - - first_two = compose(uncons, uncons, pop) - fourth = compose(rest, third) - - _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() - - -definitions = ('''\ -ii == [dip] dupdip i -of == swap at -product == 1 swap [*] step -flatten == [] swap [concat] step -quoted == [unit] dip -unquoted == [i] dip -enstacken == stack [clear] dip -? == dup truthy -disenstacken == ? [uncons ?] loop pop -dinfrirst == dip infra first -nullary == [stack] dinfrirst -unary == nullary popd -binary == nullary [popop] dip -ternary == unary [popop] dip -pam == [i] map -run == [] swap infra -sqr == dup mul -size == 0 swap [pop ++] step -fork == [i] app2 -cleave == fork [popd] dip -average == [sum 1.0 *] [size] cleave / -gcd == 1 [tuck modulus dup 0 >] loop pop -least_fraction == dup [gcd] infra [div] concat map -*fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons -*fraction0 == concat [[swap] dip * [*] dip] infra -down_to_zero == [0 >] [dup --] while -range_to_zero == unit [down_to_zero] infra -anamorphism == [pop []] swap [dip swons] genrec -range == [0 <=] [1 - dup] anamorphism -while == swap [nullary] cons dup dipd concat loop -dupdipd == dup dipd -primrec == [i] genrec -step_zero == 0 roll> step -codireco == cons dip rest cons -make_generator == [codireco] ccons -ifte == [nullary not] dipd branch -swoncat == swap concat -''' -# -# -# ifte == [nullary] dipd swap branch -# genrec == [[genrec] cons cons cons cons] nullary swons concat ifte - -# Another definition for while. FWIW -# while == over [[i] dip nullary] ccons [nullary] dip loop - -##ccons == cons cons -##unit == [] cons -##second == rest first -##third == rest rest first -##swons == swap cons - -##Zipper -##z-down == [] swap uncons swap -##z-up == swons swap shunt -##z-right == [swons] cons dip uncons swap -##z-left == swons [uncons swap] dip swap - -##Quadratic Formula -##divisor == popop 2 * -##minusb == pop neg -##radical == swap dup * rollup * 4 * - sqrt -##root1 == + swap / -##root2 == - swap / -##q0 == [[divisor] [minusb] [radical]] pam -##q1 == [[root1] [root2]] pam -##quadratic == [q0] ternary i [q1] ternary - -# Project Euler -##'''\ -##PE1.1 == + dup [+] dip -##PE1.2 == dup [3 & PE1.1] dip 2 >> -##PE1.3 == 14811 swap [PE1.2] times pop -##PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop -##''' -#PE1.2 == [PE1.1] step -#PE1 == 0 0 66 [[3 2 1 3 1 2 3] PE1.2] times [3 2 1 3] PE1.2 pop -) + ''' + Given a dict and a iterable of (name, [alias, ...]) pairs, create + additional entries in the dict mapping each alias to the named function + if it's in the dict. Aliases for functions not in the dict are ignored. + ''' + for name, aliases in A: + try: + F = D[name] + except KeyError: + continue + for alias in aliases: + D[alias] = F def FunctionWrapper(f): - '''Set name attribute.''' - if not f.__doc__: - raise ValueError('Function %s must have doc string.' % f.__name__) - f.name = f.__name__.rstrip('_') # Don't shadow builtins. - return f + '''Set name attribute.''' + if not f.__doc__: + raise ValueError('Function %s must have doc string.' % f.__name__) + f.name = f.__name__.rstrip('_') # Don't shadow builtins. + return f def SimpleFunctionWrapper(f): - ''' - Wrap functions that take and return just a stack. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - return f(stack), expression, dictionary - return inner + ''' + Wrap functions that take and return just a stack. + ''' + @FunctionWrapper + @wraps(f) + def inner(stack, expression, dictionary): + return f(stack), expression, dictionary + return inner def BinaryBuiltinWrapper(f): - ''' - Wrap functions that take two arguments and return a single result. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - (a, (b, stack)) = stack - result = f(b, a) - return (result, stack), expression, dictionary - return inner + ''' + Wrap functions that take two arguments and return a single result. + ''' + @FunctionWrapper + @wraps(f) + def inner(stack, expression, dictionary): + try: + (a, (b, stack)) = stack + except ValueError: + raise StackUnderflowError('Not enough values on stack.') + # Boolean predicates like "or" fail here. :( +## if ( not isinstance(a, int) +## or not isinstance(b, int) +## or isinstance(a, bool) # Because bools are ints in Python. +## or isinstance(b, bool) +## ): +## raise NotAnIntError + result = f(b, a) + return (result, stack), expression, dictionary + return inner def UnaryBuiltinWrapper(f): - ''' - Wrap functions that take one argument and return a single result. - ''' - @FunctionWrapper - @wraps(f) - @rename_code_object(f.__name__) - def inner(stack, expression, dictionary): - (a, stack) = stack - result = f(a) - return (result, stack), expression, dictionary - return inner - - -class DefinitionWrapper(object): - ''' - Provide implementation of defined functions, and some helper methods. - ''' - - def __init__(self, name, body_text, doc=None): - self.name = self.__name__ = name - self.body = text_to_expression(body_text) - self._body = tuple(iter_stack(self.body)) - self.__doc__ = doc or body_text - self._compiled = None - - def __call__(self, stack, expression, dictionary): - if self._compiled: - return self._compiled(stack, expression, dictionary) # pylint: disable=E1102 - expression = list_to_stack(self._body, expression) - return stack, expression, dictionary - - @classmethod - def parse_definition(class_, defi): - ''' - Given some text describing a Joy function definition parse it and - return a DefinitionWrapper. - ''' - name, proper, body_text = (n.strip() for n in defi.partition('==')) - if not proper: - raise ValueError('Definition %r failed' % (defi,)) - return class_(name, body_text) - - @classmethod - def add_definitions(class_, defs, dictionary): - ''' - Scan multi-line string defs for definitions and add them to the - dictionary. - ''' - for definition in _text_to_defs(defs): - class_.add_def(definition, dictionary) - - @classmethod - def add_def(class_, definition, dictionary, fail_fails=False): - ''' - Add the definition to the dictionary. - ''' - F = class_.parse_definition(definition) - _log.info('Adding definition %s := %s', F.name, expression_to_string(F.body)) - dictionary[F.name] = F - - @classmethod - def load_definitions(class_, filename, dictionary): - with open(filename) as f: - lines = [line for line in f if '==' in line] - for line in lines: - class_.add_def(line, dictionary) - - -def _text_to_defs(text): - return (line.strip() for line in text.splitlines() if '==' in line) + ''' + Wrap functions that take one argument and return a single result. + ''' + @FunctionWrapper + @wraps(f) + def inner(stack, expression, dictionary): + (a, stack) = stack + result = f(a) + return (result, stack), expression, dictionary + return inner + + +class Def(object): + ''' + Definitions created by inscribe. + ''' + + def __init__(self, name, body): + self.name = name + self.body = body + self._body = tuple(iter_stack(body)) + self.__doc__ = expression_to_string(body) + self._compiled = None + + def __call__(self, stack, expression, dictionary): + if self._compiled: + return self._compiled(stack, expression, dictionary) # pylint: disable=E1102 + expression = list_to_stack(self._body, expression) + return stack, expression, dictionary + + @classmethod + def load_definitions(class_, stream, dictionary): + for line in stream: + if line.lstrip().startswith('#'): + continue + name, body = text_to_expression(line) + if name not in dictionary: + inscribe(class_(name, body), dictionary) +## inscribe(class_(name, body), dictionary) # @@ -405,363 +212,369 @@ def _text_to_defs(text): @inscribe -@sec0 @FunctionWrapper def inscribe_(stack, expression, dictionary): - ''' - Create a new Joy function definition in the Joy dictionary. A - definition is given as a string with a name followed by a double - equal sign then one or more Joy functions, the body. for example: + ''' + Create a new Joy function definition in the Joy dictionary. A + definition is given as a quote with a name followed by a Joy + expression. for example: - sqr == dup mul + [sqr dup mul] inscribe - If you want the definition to persist over restarts, enter it into - the definitions.txt resource. - ''' - definition, stack = stack - DefinitionWrapper.add_def(definition, dictionary, fail_fails=True) - return stack, expression, dictionary + ''' + (name, body), stack = stack + inscribe(Def(name, body), dictionary) + return stack, expression, dictionary @inscribe @SimpleFunctionWrapper def parse(stack): - '''Parse the string on the stack to a Joy expression.''' - text, stack = stack - expression = text_to_expression(text) - return expression, stack + '''Parse the string on the stack to a Joy expression.''' + text, stack = stack + expression = text_to_expression(text) + return expression, stack -@inscribe -@SimpleFunctionWrapper -def infer_(stack): - '''Attempt to infer the stack effect of a Joy expression.''' - E, stack = stack - effects = infer_expression(E) - e = list_to_stack([(fi, (fo, ())) for fi, fo in effects]) - return e, stack +# @inscribe +# @SimpleFunctionWrapper +# def infer_(stack): +# '''Attempt to infer the stack effect of a Joy expression.''' +# E, stack = stack +# effects = infer_expression(E) +# e = list_to_stack([(fi, (fo, ())) for fi, fo in effects]) +# return e, stack @inscribe -@sec2 @SimpleFunctionWrapper def getitem(stack): - ''' - :: + ''' + :: - getitem == drop first + getitem == drop first - Expects an integer and a quote on the stack and returns the item at the - nth position in the quote counting from 0. - :: + Expects an integer and a quote on the stack and returns the item at the + nth position in the quote counting from 0. + :: - [a b c d] 0 getitem - ------------------------- - a + [a b c d] 0 getitem + ------------------------- + a - ''' - n, (Q, stack) = stack - return pick(Q, n), stack + ''' + n, (Q, stack) = stack + return pick(Q, n), stack @inscribe -@sec1 @SimpleFunctionWrapper def drop(stack): - ''' - :: + ''' + :: - drop == [rest] times + drop == [rest] times - Expects an integer and a quote on the stack and returns the quote with - n items removed off the top. - :: + Expects an integer and a quote on the stack and returns the quote with + n items removed off the top. + :: - [a b c d] 2 drop - ---------------------- - [c d] + [a b c d] 2 drop + ---------------------- + [c d] - ''' - n, (Q, stack) = stack - while n > 0: - try: - _, Q = Q - except ValueError: - raise IndexError - n -= 1 - return Q, stack + ''' + n, (Q, stack) = stack + while n > 0: + try: + _, Q = Q + except ValueError: + raise IndexError + n -= 1 + return Q, stack @inscribe -@sec1 @SimpleFunctionWrapper def take(stack): - ''' - Expects an integer and a quote on the stack and returns the quote with - just the top n items in reverse order (because that's easier and you can - use reverse if needed.) - :: - - [a b c d] 2 take - ---------------------- - [b a] - - ''' - n, (Q, stack) = stack - x = () - while n > 0: - try: - item, Q = Q - except ValueError: - raise IndexError - x = item, x - n -= 1 - return x, stack + ''' + Expects an integer and a quote on the stack and returns the quote with + just the top n items in reverse order (because that's easier and you can + use reverse if needed.) + :: + + [a b c d] 2 take + ---------------------- + [b a] + + ''' + n, (Q, stack) = stack + x = () + while n > 0: + try: + item, Q = Q + except ValueError: + raise IndexError + x = item, x + n -= 1 + return x, stack + + +@inscribe +@FunctionWrapper +def gcd2(stack, expression, dictionary): + '''Compiled GCD function.''' + (v1, (v2, stack)) = stack + tos = True + while tos: + v3 = v2 % v1 + tos = v3 > 0 + (v1, (v2, stack)) = (v3, (v1, stack)) + return (v2, stack), expression, dictionary @inscribe @SimpleFunctionWrapper def choice(stack): - ''' - Use a Boolean value to select one of two items. - :: + ''' + Use a Boolean value to select one of two items. + :: - A B False choice - ---------------------- - A + A B False choice + ---------------------- + A - A B True choice - --------------------- - B + A B True choice + --------------------- + B - Currently Python semantics are used to evaluate the "truthiness" of the - Boolean value (so empty string, zero, etc. are counted as false, etc.) - ''' - (if_, (then, (else_, stack))) = stack - return then if if_ else else_, stack + Currently Python semantics are used to evaluate the "truthiness" of the + Boolean value (so empty string, zero, etc. are counted as false, etc.) + ''' + (if_, (then, (else_, stack))) = stack + return then if if_ else else_, stack @inscribe @SimpleFunctionWrapper def select(stack): - ''' - Use a Boolean value to select one of two items from a sequence. - :: + ''' + Use a Boolean value to select one of two items from a sequence. + :: - [A B] False select - ------------------------ - A + [A B] False select + ------------------------ + A - [A B] True select - ----------------------- - B + [A B] True select + ----------------------- + B - The sequence can contain more than two items but not fewer. - Currently Python semantics are used to evaluate the "truthiness" of the - Boolean value (so empty string, zero, etc. are counted as false, etc.) - ''' - (flag, (choices, stack)) = stack - (else_, (then, _)) = choices - return then if flag else else_, stack + The sequence can contain more than two items but not fewer. + Currently Python semantics are used to evaluate the "truthiness" of the + Boolean value (so empty string, zero, etc. are counted as false, etc.) + ''' + (flag, (choices, stack)) = stack + (else_, (then, _)) = choices + return then if flag else else_, stack @inscribe -@sec_Ns_math @SimpleFunctionWrapper def max_(S): - '''Given a list find the maximum.''' - tos, stack = S - return max(iter_stack(tos)), stack + '''Given a list find the maximum.''' + tos, stack = S + return max(iter_stack(tos)), stack @inscribe -@sec_Ns_math @SimpleFunctionWrapper def min_(S): - '''Given a list find the minimum.''' - tos, stack = S - return min(iter_stack(tos)), stack + '''Given a list find the minimum.''' + tos, stack = S + return min(iter_stack(tos)), stack @inscribe -@sec_Ns_math @SimpleFunctionWrapper def sum_(S): - '''Given a quoted sequence of numbers return the sum. + ''' + Given a quoted sequence of numbers return the sum. + :: - sum == 0 swap [+] step - ''' - tos, stack = S - return sum(iter_stack(tos)), stack + sum == 0 swap [+] step + + ''' + tos, stack = S + return sum(iter_stack(tos)), stack @inscribe @SimpleFunctionWrapper def remove(S): - ''' - Expects an item on the stack and a quote under it and removes that item - from the the quote. The item is only removed once. - :: + ''' + Expects an item on the stack and a quote under it and removes that item + from the the quote. The item is only removed once. + :: - [1 2 3 1] 1 remove - ------------------------ - [2 3 1] + [1 2 3 1] 1 remove + ------------------------ + [2 3 1] - ''' - (tos, (second, stack)) = S - l = list(iter_stack(second)) - l.remove(tos) - return list_to_stack(l), stack + ''' + (tos, (second, stack)) = S + l = list(iter_stack(second)) + l.remove(tos) + return list_to_stack(l), stack @inscribe @SimpleFunctionWrapper def unique(S): - '''Given a list remove duplicate items.''' - tos, stack = S - I = list(iter_stack(tos)) - return list_to_stack(sorted(set(I), key=I.index)), stack + '''Given a list remove duplicate items.''' + tos, stack = S + I = list(iter_stack(tos)) + return list_to_stack(sorted(set(I), key=I.index)), stack @inscribe @SimpleFunctionWrapper def sort_(S): - '''Given a list return it sorted.''' - tos, stack = S - return list_to_stack(sorted(iter_stack(tos))), stack + '''Given a list return it sorted.''' + tos, stack = S + return list_to_stack(sorted(iter_stack(tos))), stack -_functions['clear'] = s0, s1 @inscribe @SimpleFunctionWrapper def clear(stack): - '''Clear everything from the stack. - :: + '''Clear everything from the stack. + :: - clear == stack [pop stack] loop + clear == stack [pop stack] loop - ... clear - --------------- + ... clear + --------------- - ''' - return () + ''' + return () @inscribe @SimpleFunctionWrapper -def unstack(stack): - ''' - The unstack operator expects a list on top of the stack and makes that - the stack discarding the rest of the stack. - ''' - return stack[0] +def disenstacken(stack): + ''' + The disenstacken operator expects a list on top of the stack and makes that + the stack discarding the rest of the stack. + ''' + return stack[0] @inscribe @SimpleFunctionWrapper def reverse(S): - '''Reverse the list on the top of the stack. - :: + ''' + Reverse the list on the top of the stack. + :: - reverse == [] swap shunt - ''' - (tos, stack) = S - res = () - for term in iter_stack(tos): - res = term, res - return res, stack + reverse == [] swap shunt + ''' + (tos, stack) = S + res = () + for term in iter_stack(tos): + res = term, res + return res, stack @inscribe -@combinator_effect(_COMB_NUMS(), s7, s6) @SimpleFunctionWrapper def concat_(S): - '''Concatinate the two lists on the top of the stack. - :: + ''' + Concatinate the two lists on the top of the stack. + :: - [a b c] [d e f] concat - ---------------------------- - [a b c d e f] + [a b c] [d e f] concat + ---------------------------- + [a b c d e f] -''' - (tos, (second, stack)) = S - return concat(second, tos), stack + ''' + (tos, (second, stack)) = S + return concat(second, tos), stack @inscribe @SimpleFunctionWrapper def shunt(stack): - '''Like concat but reverses the top list into the second. - :: + ''' + Like concat but reverses the top list into the second. + :: - shunt == [swons] step == reverse swap concat + shunt == [swons] step == reverse swap concat - [a b c] [d e f] shunt - --------------------------- - [f e d a b c] + [a b c] [d e f] shunt + --------------------------- + [f e d a b c] - ''' - (tos, (second, stack)) = stack - while tos: - term, tos = tos - second = term, second - return second, stack + ''' + (tos, (second, stack)) = stack + while tos: + term, tos = tos + second = term, second + return second, stack @inscribe @SimpleFunctionWrapper def zip_(S): - ''' - Replace the two lists on the top of the stack with a list of the pairs - from each list. The smallest list sets the length of the result list. - ''' - (tos, (second, stack)) = S - accumulator = [ - (a, (b, ())) - for a, b in zip(iter_stack(tos), iter_stack(second)) - ] - return list_to_stack(accumulator), stack + ''' + Replace the two lists on the top of the stack with a list of the pairs + from each list. The smallest list sets the length of the result list. + ''' + (tos, (second, stack)) = S + accumulator = [ + (a, (b, ())) + for a, b in zip(iter_stack(tos), iter_stack(second)) + ] + return list_to_stack(accumulator), stack @inscribe -@sec_unary_math @SimpleFunctionWrapper def succ(S): - '''Increment TOS.''' - (tos, stack) = S - return tos + 1, stack + '''Increment TOS.''' + (tos, stack) = S + return tos + 1, stack @inscribe -@sec_unary_math @SimpleFunctionWrapper def pred(S): - '''Decrement TOS.''' - (tos, stack) = S - return tos - 1, stack + '''Decrement TOS.''' + (tos, stack) = S + return tos - 1, stack @inscribe @SimpleFunctionWrapper def pm(stack): - ''' - Plus or minus - :: + ''' + Plus or minus + :: - a b pm - ------------- - a+b a-b + a b pm + ------------- + a+b a-b - ''' - a, (b, stack) = stack - p, m, = b + a, b - a - return m, (p, stack) + ''' + a, (b, stack) = stack + p, m, = b + a, b - a + return m, (p, stack) def floor(n): - return int(math.floor(n)) + return int(math.floor(n)) floor.__doc__ = math.floor.__doc__ @@ -769,27 +582,27 @@ floor.__doc__ = math.floor.__doc__ @inscribe @SimpleFunctionWrapper def divmod_(S): - ''' - divmod(x, y) -> (quotient, remainder) + ''' + divmod(x, y) -> (quotient, remainder) - Return the tuple (x//y, x%y). Invariant: div*y + mod == x. - ''' - a, (b, stack) = S - d, m = divmod(a, b) - return d, (m, stack) + Return the tuple (x//y, x%y). Invariant: q * y + r == x. + ''' + a, (b, stack) = S + d, m = divmod(a, b) + return d, (m, stack) def sqrt(a): - ''' - Return the square root of the number a. - Negative numbers return complex roots. - ''' - try: - r = math.sqrt(a) - except ValueError: - assert a < 0, repr(a) - r = math.sqrt(-a) * 1j - return r + ''' + Return the square root of the number a. + Negative numbers return complex roots. + ''' + try: + r = math.sqrt(a) + except ValueError: + assert a < 0, repr(a) + r = math.sqrt(-a) * 1j + return r #def execute(S): @@ -802,20 +615,20 @@ def sqrt(a): @inscribe @SimpleFunctionWrapper def id_(stack): - '''The identity function.''' - return stack + '''The identity function.''' + return stack @inscribe @SimpleFunctionWrapper def void(stack): - '''True if the form on TOS is void otherwise False.''' - form, stack = stack - return _void(form), stack + '''True if the form on TOS is void otherwise False.''' + form, stack = stack + return _void(form), stack def _void(form): - return any(not _void(i) for i in iter_stack(form)) + return any(not _void(i) for i in iter_stack(form)) @@ -827,42 +640,42 @@ def _void(form): @inscribe @FunctionWrapper def words(stack, expression, dictionary): - '''Print all the words in alphabetical order.''' - print(' '.join(sorted(dictionary))) - return stack, expression, dictionary + '''Print all the words in alphabetical order.''' + print(' '.join(sorted(dictionary))) + return stack, expression, dictionary @inscribe @FunctionWrapper def sharing(stack, expression, dictionary): - '''Print redistribution information.''' - print("You may convey verbatim copies of the Program's source code as" - ' you receive it, in any medium, provided that you conspicuously' - ' and appropriately publish on each copy an appropriate copyright' - ' notice; keep intact all notices stating that this License and' - ' any non-permissive terms added in accord with section 7 apply' - ' to the code; keep intact all notices of the absence of any' - ' warranty; and give all recipients a copy of this License along' - ' with the Program.' - ' You should have received a copy of the GNU General Public License' - ' along with Thun. If not see .') - return stack, expression, dictionary + '''Print redistribution information.''' + print("You may convey verbatim copies of the Program's source code as" + ' you receive it, in any medium, provided that you conspicuously' + ' and appropriately publish on each copy an appropriate copyright' + ' notice; keep intact all notices stating that this License and' + ' any non-permissive terms added in accord with section 7 apply' + ' to the code; keep intact all notices of the absence of any' + ' warranty; and give all recipients a copy of this License along' + ' with the Program.' + ' You should have received a copy of the GNU General Public License' + ' along with Thun. If not see .') + return stack, expression, dictionary @inscribe @FunctionWrapper def warranty(stack, expression, dictionary): - '''Print warranty information.''' - print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY' - ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE' - ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM' - ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR' - ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES' - ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE' - ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS' - ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE' - ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.') - return stack, expression, dictionary + '''Print warranty information.''' + print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY' + ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE' + ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM' + ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR' + ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES' + ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE' + ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS' + ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE' + ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.') + return stack, expression, dictionary # def simple_manual(stack): @@ -887,11 +700,11 @@ def warranty(stack, expression, dictionary): @inscribe @FunctionWrapper def help_(S, expression, dictionary): - '''Accepts a quoted symbol on the top of the stack and prints its docs.''' - ((symbol, _), stack) = S - word = dictionary[symbol] - print(getdoc(word)) - return stack, expression, dictionary + '''Accepts a quoted symbol on the top of the stack and prints its docs.''' + ((symbol, _), stack) = S + word = dictionary[symbol] + print(HELP_TEMPLATE % (symbol, getdoc(word), symbol)) + return stack, expression, dictionary # @@ -904,194 +717,250 @@ def help_(S, expression, dictionary): # could change the word in the dictionary to use different semantics. S_choice = Symbol('choice') S_first = Symbol('first') -S_getitem = Symbol('getitem') S_genrec = Symbol('genrec') -S_loop = Symbol('loop') +S_getitem = Symbol('getitem') S_i = Symbol('i') S_ifte = Symbol('ifte') S_infra = Symbol('infra') +S_loop = Symbol('loop') S_pop = Symbol('pop') +S_primrec = Symbol('primrec') S_step = Symbol('step') -S_times = Symbol('times') S_swaack = Symbol('swaack') +S_times = Symbol('times') @inscribe -@combinator_effect(_COMB_NUMS(), s1) @FunctionWrapper def i(stack, expression, dictionary): - ''' - The i combinator expects a quoted program on the stack and unpacks it - onto the pending expression for evaluation. - :: + ''' + The i combinator expects a quoted program on the stack and unpacks it + onto the pending expression for evaluation. + :: - [Q] i - ----------- - Q + [Q] i + ----------- + Q - ''' - quote, stack = stack - return stack, concat(quote, expression), dictionary + ''' + try: + quote, stack = stack + except ValueError: + raise StackUnderflowError('Not enough values on stack.') + return stack, concat(quote, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), s1) @FunctionWrapper def x(stack, expression, dictionary): - ''' - :: + ''' + :: - x == dup i + x == dup i - ... [Q] x = ... [Q] dup i - ... [Q] x = ... [Q] [Q] i - ... [Q] x = ... [Q] Q + ... [Q] x = ... [Q] dup i + ... [Q] x = ... [Q] [Q] i + ... [Q] x = ... [Q] Q - ''' - quote, _ = stack - return stack, concat(quote, expression), dictionary + ''' + quote, _ = stack + return stack, concat(quote, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def b(stack, expression, dictionary): - ''' - :: + ''' + :: - b == [i] dip i + b == [i] dip i - ... [P] [Q] b == ... [P] i [Q] i - ... [P] [Q] b == ... P Q + ... [P] [Q] b == ... [P] i [Q] i + ... [P] [Q] b == ... P Q - ''' - q, (p, (stack)) = stack - return stack, concat(p, concat(q, expression)), dictionary + ''' + q, (p, (stack)) = stack + return stack, concat(p, concat(q, expression)), dictionary + + +@inscribe +@FunctionWrapper +def ii(stack, expression, dictionary): + ''' + :: + + ... a [Q] ii + ------------------ + ... Q a Q + + ''' + quote, (a, stack) = stack + expression = concat(quote, (a, concat(quote, expression))) + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def dupdip(stack, expression, dictionary): - ''' - :: + ''' + :: - [F] dupdip == dup [F] dip + [F] dupdip == dup [F] dip - ... a [F] dupdip - ... a dup [F] dip - ... a a [F] dip - ... a F a + ... a [F] dupdip + ... a dup [F] dip + ... a a [F] dip + ... a F a - ''' - F, stack = stack - a = stack[0] - return stack, concat(F, (a, expression)), dictionary + ''' + F, stack = stack + a = stack[0] + return stack, concat(F, (a, expression)), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def infra(stack, expression, dictionary): - ''' - Accept a quoted program and a list on the stack and run the program - with the list as its stack. Does not affect the rest of the stack. - :: + ''' + Accept a quoted program and a list on the stack and run the program + with the list as its stack. Does not affect the rest of the stack. + :: - ... [a b c] [Q] . infra - ----------------------------- - c b a . Q [...] swaack + ... [a b c] [Q] . infra + ----------------------------- + c b a . Q [...] swaack - ''' - (quote, (aggregate, stack)) = stack - return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary + ''' + (quote, (aggregate, stack)) = stack + return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary @inscribe -#@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4) @FunctionWrapper def genrec(stack, expression, dictionary): - ''' - General Recursion Combinator. - :: + ''' + General Recursion Combinator. + :: - [if] [then] [rec1] [rec2] genrec - --------------------------------------------------------------------- - [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte + [if] [then] [rec1] [rec2] genrec + --------------------------------------------------------------------- + [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte - From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun: - "The genrec combinator takes four program parameters in addition to - whatever data parameters it needs. Fourth from the top is an if-part, - followed by a then-part. If the if-part yields true, then the then-part - is executed and the combinator terminates. The other two parameters are - the rec1-part and the rec2-part. If the if-part yields false, the - rec1-part is executed. Following that the four program parameters and - the combinator are again pushed onto the stack bundled up in a quoted - form. Then the rec2-part is executed, where it will find the bundled - form. Typically it will then execute the bundled form, either with i or - with app2, or some other combinator." + From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun: + "The genrec combinator takes four program parameters in addition to + whatever data parameters it needs. Fourth from the top is an if-part, + followed by a then-part. If the if-part yields true, then the then-part + is executed and the combinator terminates. The other two parameters are + the rec1-part and the rec2-part. If the if-part yields false, the + rec1-part is executed. Following that the four program parameters and + the combinator are again pushed onto the stack bundled up in a quoted + form. Then the rec2-part is executed, where it will find the bundled + form. Typically it will then execute the bundled form, either with i or + with app2, or some other combinator." - The way to design one of these is to fix your base case [then] and the - test [if], and then treat rec1 and rec2 as an else-part "sandwiching" - a quotation of the whole function. + The way to design one of these is to fix your base case [then] and the + test [if], and then treat rec1 and rec2 as an else-part "sandwiching" + a quotation of the whole function. - For example, given a (general recursive) function 'F': - :: + For example, given a (general recursive) function 'F': + :: - F == [I] [T] [R1] [R2] genrec + F == [I] [T] [R1] [R2] genrec - If the [I] if-part fails you must derive R1 and R2 from: - :: + If the [I] if-part fails you must derive R1 and R2 from: + :: - ... R1 [F] R2 + ... R1 [F] R2 - Just set the stack arguments in front, and figure out what R1 and R2 - have to do to apply the quoted [F] in the proper way. In effect, the - genrec combinator turns into an ifte combinator with a quoted copy of - the original definition in the else-part: - :: + Just set the stack arguments in front, and figure out what R1 and R2 + have to do to apply the quoted [F] in the proper way. In effect, the + genrec combinator turns into an ifte combinator with a quoted copy of + the original definition in the else-part: + :: - F == [I] [T] [R1] [R2] genrec - == [I] [T] [R1 [F] R2] ifte + F == [I] [T] [R1] [R2] genrec + == [I] [T] [R1 [F] R2] ifte - Primitive recursive functions are those where R2 == i. - :: + Primitive recursive functions are those where R2 == i. + :: - P == [I] [T] [R] primrec - == [I] [T] [R [P] i] ifte - == [I] [T] [R P] ifte + P == [I] [T] [R] tailrec + == [I] [T] [R [P] i] ifte + == [I] [T] [R P] ifte - ''' - (rec2, (rec1, stack)) = stack - (then, (if_, _)) = stack - F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) - else_ = concat(rec1, (F, rec2)) - return (else_, stack), (S_ifte, expression), dictionary + ''' + (rec2, (rec1, stack)) = stack + (then, (if_, _)) = stack + F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) + else_ = concat(rec1, (F, rec2)) + return (else_, stack), (S_ifte, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def map_(S, expression, dictionary): - ''' - Run the quoted program on TOS on the items in the list under it, push a - new list with the results in place of the program and original list. - ''' -# (quote, (aggregate, stack)) = S -# results = list_to_stack([ -# joy((term, stack), quote, dictionary)[0][0] -# for term in iter_stack(aggregate) -# ]) -# return (results, stack), expression, dictionary - (quote, (aggregate, stack)) = S - if not aggregate: - return (aggregate, stack), expression, dictionary - batch = () - for term in iter_stack(aggregate): - s = term, stack - batch = (s, (quote, (S_infra, (S_first, batch)))) - stack = (batch, ((), stack)) - return stack, (S_infra, expression), dictionary + ''' + Run the quoted program on TOS on the items in the list under it, push a + new list with the results in place of the program and original list. + ''' + # (quote, (aggregate, stack)) = S + # results = list_to_stack([ + # joy((term, stack), quote, dictionary)[0][0] + # for term in iter_stack(aggregate) + # ]) + # return (results, stack), expression, dictionary + (quote, (aggregate, stack)) = S + if not aggregate: + return (aggregate, stack), expression, dictionary + batch = () + for term in iter_stack(aggregate): + s = term, stack + batch = (s, (quote, (S_infra, (S_first, batch)))) + stack = (batch, ((), stack)) + return stack, (S_infra, expression), dictionary + + +@inscribe +@FunctionWrapper +def primrec(stack, expression, dictionary): + ''' + From the "Overview of the language JOY": + + > The primrec combinator expects two quoted programs in addition to a + data parameter. For an integer data parameter it works like this: If + the data parameter is zero, then the first quotation has to produce + the value to be returned. If the data parameter is positive then the + second has to combine the data parameter with the result of applying + the function to its predecessor.:: + + 5 [1] [*] primrec + + > Then primrec tests whether the top element on the stack (initially + the 5) is equal to zero. If it is, it pops it off and executes one of + the quotations, the [1] which leaves 1 on the stack as the result. + Otherwise it pushes a decremented copy of the top element and + recurses. On the way back from the recursion it uses the other + quotation, [*], to multiply what is now a factorial on top of the + stack by the second element on the stack.:: + + n [Base] [Recur] primrec + + 0 [Base] [Recur] primrec + ------------------------------ + Base + + n [Base] [Recur] primrec + ------------------------------------------ n > 0 + n (n-1) [Base] [Recur] primrec Recur + + ''' + recur, (base, (n, stack)) = stack + if n <= 0: + expression = concat(base, expression) + else: + expression = S_primrec, concat(recur, expression) + stack = recur, (base, (n - 1, (n, stack))) + return stack, expression, dictionary #def cleave(S, expression, dictionary): @@ -1108,45 +977,29 @@ def map_(S, expression, dictionary): # return (q, (p, stack)), expression, dictionary -def branch_true(stack, expression, dictionary): - # pylint: disable=unused-variable - (then, (else_, (flag, stack))) = stack - return stack, concat(then, expression), dictionary - - -def branch_false(stack, expression, dictionary): - # pylint: disable=unused-variable - (then, (else_, (flag, stack))) = stack - return stack, concat(else_, expression), dictionary - - @inscribe -@poly_combinator_effect(_COMB_NUMS(), [branch_true, branch_false], b1, s7, s6) @FunctionWrapper def branch(stack, expression, dictionary): - ''' - Use a Boolean value to select one of two quoted programs to run. - - :: - - branch == roll< choice i + ''' + Use a Boolean value to select one of two quoted programs to run. - :: + :: - False [F] [T] branch - -------------------------- - F + branch == roll< choice i - True [F] [T] branch - ------------------------- - T + :: - ''' - (then, (else_, (flag, stack))) = stack - return stack, concat(then if flag else else_, expression), dictionary + False [F] [T] branch + -------------------------- + F + True [F] [T] branch + ------------------------- + T -#FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100) + ''' + (then, (else_, (flag, stack))) = stack + return stack, concat(then if flag else else_, expression), dictionary ##@inscribe @@ -1180,231 +1033,227 @@ def branch(stack, expression, dictionary): @inscribe @FunctionWrapper def cond(stack, expression, dictionary): - ''' - This combinator works like a case statement. It expects a single quote - on the stack that must contain zero or more condition quotes and a - default quote. Each condition clause should contain a quoted predicate - followed by the function expression to run if that predicate returns - true. If no predicates return true the default function runs. - - It works by rewriting into a chain of nested `ifte` expressions, e.g.:: - - [[[B0] T0] [[B1] T1] [D]] cond - ----------------------------------------- - [B0] [T0] [[B1] [T1] [D] ifte] ifte - - ''' - conditions, stack = stack - if conditions: - expression = _cond(conditions, expression) - try: - # Attempt to preload the args to first ifte. - (P, (T, (E, expression))) = expression - except ValueError: - # If, for any reason, the argument to cond should happen to contain - # only the default clause then this optimization will fail. - pass - else: - stack = (E, (T, (P, stack))) - return stack, expression, dictionary + ''' + This combinator works like a case statement. It expects a single quote + on the stack that must contain zero or more condition quotes and a + default quote. Each condition clause should contain a quoted predicate + followed by the function expression to run if that predicate returns + true. If no predicates return true the default function runs. + + It works by rewriting into a chain of nested `ifte` expressions, e.g.:: + + [[[B0] T0] [[B1] T1] [D]] cond + ----------------------------------------- + [B0] [T0] [[B1] [T1] [D] ifte] ifte + + ''' + conditions, stack = stack + if conditions: + expression = _cond(conditions, expression) + try: + # Attempt to preload the args to first ifte. + (P, (T, (E, expression))) = expression + except ValueError: + # If, for any reason, the argument to cond should happen to contain + # only the default clause then this optimization will fail. + pass + else: + stack = (E, (T, (P, stack))) + return stack, expression, dictionary def _cond(conditions, expression): - (clause, rest) = conditions - if not rest: # clause is [D] - return clause - P, T = clause - return (P, (T, (_cond(rest, ()), (S_ifte, expression)))) + (clause, rest) = conditions + if not rest: # clause is [D] + return clause + P, T = clause + return (P, (T, (_cond(rest, ()), (S_ifte, expression)))) @inscribe -@combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def dip(stack, expression, dictionary): - ''' - The dip combinator expects a quoted program on the stack and below it - some item, it hoists the item into the expression and runs the program - on the rest of the stack. - :: + ''' + The dip combinator expects a quoted program on the stack and below it + some item, it hoists the item into the expression and runs the program + on the rest of the stack. + :: - ... x [Q] dip - ------------------- - ... Q x + ... x [Q] dip + ------------------- + ... Q x - ''' - (quote, (x, stack)) = stack - expression = (x, expression) - return stack, concat(quote, expression), dictionary + ''' + try: + (quote, (x, stack)) = stack + except ValueError: + raise StackUnderflowError('Not enough values on stack.') + expression = (x, expression) + return stack, concat(quote, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a2, a1, s1) @FunctionWrapper def dipd(S, expression, dictionary): - ''' - Like dip but expects two items. - :: + ''' + Like dip but expects two items. + :: - ... y x [Q] dip - --------------------- - ... Q y x + ... y x [Q] dip + --------------------- + ... Q y x - ''' - (quote, (x, (y, stack))) = S - expression = (y, (x, expression)) - return stack, concat(quote, expression), dictionary + ''' + (quote, (x, (y, stack))) = S + expression = (y, (x, expression)) + return stack, concat(quote, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a3, a2, a1, s1) @FunctionWrapper def dipdd(S, expression, dictionary): - ''' - Like dip but expects three items. - :: + ''' + Like dip but expects three items. + :: - ... z y x [Q] dip - ----------------------- - ... Q z y x + ... z y x [Q] dip + ----------------------- + ... Q z y x - ''' - (quote, (x, (y, (z, stack)))) = S - expression = (z, (y, (x, expression))) - return stack, concat(quote, expression), dictionary + ''' + (quote, (x, (y, (z, stack)))) = S + expression = (z, (y, (x, expression))) + return stack, concat(quote, expression), dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a1, s1) @FunctionWrapper def app1(S, expression, dictionary): - ''' - Given a quoted program on TOS and anything as the second stack item run - the program and replace the two args with the first result of the - program. - :: - - ... x [Q] . app1 - ----------------------------------- - ... [x ...] [Q] . infra first - ''' - (quote, (x, stack)) = S - stack = (quote, ((x, stack), stack)) - expression = (S_infra, (S_first, expression)) - return stack, expression, dictionary + ''' + Given a quoted program on TOS and anything as the second stack item run + the program and replace the two args with the first result of the + program. + :: + + ... x [Q] . app1 + ----------------------------------- + ... [x ...] [Q] . infra first + + ''' + (quote, (x, stack)) = S + stack = (quote, ((x, stack), stack)) + expression = (S_infra, (S_first, expression)) + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a2, a1, s1) @FunctionWrapper def app2(S, expression, dictionary): - '''Like app1 with two items. - :: + '''Like app1 with two items. + :: - ... y x [Q] . app2 - ----------------------------------- - ... [y ...] [Q] . infra first - [x ...] [Q] infra first + ... y x [Q] . app2 + ----------------------------------- + ... [y ...] [Q] . infra first + [x ...] [Q] infra first - ''' - (quote, (x, (y, stack))) = S - expression = (S_infra, (S_first, - ((x, stack), (quote, (S_infra, (S_first, - expression)))))) - stack = (quote, ((y, stack), stack)) - return stack, expression, dictionary + ''' + (quote, (x, (y, stack))) = S + expression = (S_infra, (S_first, + ((x, stack), (quote, (S_infra, (S_first, + expression)))))) + stack = (quote, ((y, stack), stack)) + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a3, a2, a1, s1) @FunctionWrapper def app3(S, expression, dictionary): - '''Like app1 with three items. - :: + '''Like app1 with three items. + :: - ... z y x [Q] . app3 - ----------------------------------- - ... [z ...] [Q] . infra first - [y ...] [Q] infra first - [x ...] [Q] infra first + ... z y x [Q] . app3 + ----------------------------------- + ... [z ...] [Q] . infra first + [y ...] [Q] infra first + [x ...] [Q] infra first - ''' - (quote, (x, (y, (z, stack)))) = S - expression = (S_infra, (S_first, - ((y, stack), (quote, (S_infra, (S_first, - ((x, stack), (quote, (S_infra, (S_first, - expression)))))))))) - stack = (quote, ((z, stack), stack)) - return stack, expression, dictionary + ''' + (quote, (x, (y, (z, stack)))) = S + expression = (S_infra, (S_first, + ((y, stack), (quote, (S_infra, (S_first, + ((x, stack), (quote, (S_infra, (S_first, + expression)))))))))) + stack = (quote, ((z, stack), stack)) + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), s7, s6) @FunctionWrapper def step(S, expression, dictionary): - ''' - Run a quoted program on each item in a sequence. - :: + ''' + Run a quoted program on each item in a sequence. + :: - ... [] [Q] . step - ----------------------- - ... . + ... [] [Q] . step + ----------------------- + ... . - ... [a] [Q] . step - ------------------------ - ... a . Q + ... [a] [Q] . step + ------------------------ + ... a . Q - ... [a b c] [Q] . step - ---------------------------------------- - ... a . Q [b c] [Q] step + ... [a b c] [Q] . step + ---------------------------------------- + ... a . Q [b c] [Q] step - The step combinator executes the quotation on each member of the list - on top of the stack. - ''' - (quote, (aggregate, stack)) = S - if not aggregate: - return stack, expression, dictionary - head, tail = aggregate - stack = quote, (head, stack) - if tail: - expression = tail, (quote, (S_step, expression)) - expression = S_i, expression - return stack, expression, dictionary + The step combinator executes the quotation on each member of the list + on top of the stack. + ''' + (quote, (aggregate, stack)) = S + if not aggregate: + return stack, expression, dictionary + head, tail = aggregate + stack = quote, (head, stack) + if tail: + expression = tail, (quote, (S_step, expression)) + expression = S_i, expression + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), i1, s6) @FunctionWrapper def times(stack, expression, dictionary): - ''' - times == [-- dip] cons [swap] infra [0 >] swap while pop - :: + ''' + times == [-- dip] cons [swap] infra [0 >] swap while pop + :: - ... n [Q] . times - --------------------- w/ n <= 0 - ... . + ... n [Q] . times + --------------------- w/ n <= 0 + ... . - ... 1 [Q] . times - --------------------------------- - ... . Q + ... 1 [Q] . times + ----------------------- + ... . Q - ... n [Q] . times - --------------------------------- w/ n > 1 - ... . Q (n - 1) [Q] times + ... n [Q] . times + ------------------------------------- w/ n > 1 + ... . Q (n - 1) [Q] times - ''' - # times == [-- dip] cons [swap] infra [0 >] swap while pop - (quote, (n, stack)) = stack - if n <= 0: - return stack, expression, dictionary - n -= 1 - if n: - expression = n, (quote, (S_times, expression)) - expression = concat(quote, expression) - return stack, expression, dictionary + ''' + # times == [-- dip] cons [swap] infra [0 >] swap while pop + (quote, (n, stack)) = stack + if n <= 0: + return stack, expression, dictionary + n -= 1 + if n: + expression = n, (quote, (S_times, expression)) + expression = concat(quote, expression) + return stack, expression, dictionary # The current definition above works like this: @@ -1413,7 +1262,7 @@ def times(stack, expression, dictionary): # -------------------------------------- # [P] nullary [Q [P] nullary] loop -# while == [pop i not] [popop] [dudipd] primrec +# while == [pop i not] [popop] [dudipd] tailrec #def while_(S, expression, dictionary): # '''[if] [body] while''' @@ -1423,66 +1272,60 @@ def times(stack, expression, dictionary): # return stack, expression, dictionary -def loop_true(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, concat(quote, (S_pop, expression)), dictionary - -def loop_two_true(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, concat(quote, (S_pop, concat(quote, (S_pop, expression)))), dictionary - -def loop_false(stack, expression, dictionary): - quote, (flag, stack) = stack # pylint: disable=unused-variable - return stack, expression, dictionary - - @inscribe -@poly_combinator_effect(_COMB_NUMS(), [loop_two_true, loop_true, loop_false], b1, s6) @FunctionWrapper def loop(stack, expression, dictionary): - ''' - Basic loop combinator. - :: - - ... True [Q] loop - ----------------------- - ... Q [Q] loop - - ... False [Q] loop - ------------------------ - ... - - ''' - quote, (flag, stack) = stack - if flag: - expression = concat(quote, (quote, (S_loop, expression))) - return stack, expression, dictionary + ''' + Basic loop combinator. + :: + + ... True [Q] loop + ----------------------- + ... Q [Q] loop + + ... False [Q] loop + ------------------------ + ... + + ''' + try: + quote, stack = stack + except ValueError: + raise StackUnderflowError('Not enough values on stack.') + if not isinstance(quote, tuple): + raise NotAListError('Loop body not a list.') + try: + (flag, stack) = stack + except ValueError: + raise StackUnderflowError('Not enough values on stack.') + if flag: + expression = concat(quote, (quote, (S_loop, expression))) + return stack, expression, dictionary @inscribe -@combinator_effect(_COMB_NUMS(), a1, a2, s6, s7, s8) @FunctionWrapper def cmp_(stack, expression, dictionary): - ''' - cmp takes two values and three quoted programs on the stack and runs - one of the three depending on the results of comparing the two values: - :: + ''' + cmp takes two values and three quoted programs on the stack and runs + one of the three depending on the results of comparing the two values: + :: - a b [G] [E] [L] cmp - ------------------------- a > b - G + a b [G] [E] [L] cmp + ------------------------- a > b + G - a b [G] [E] [L] cmp - ------------------------- a = b - E + a b [G] [E] [L] cmp + ------------------------- a = b + E - a b [G] [E] [L] cmp - ------------------------- a < b - L - ''' - L, (E, (G, (b, (a, stack)))) = stack - expression = concat(G if a > b else L if a < b else E, expression) - return stack, expression, dictionary + a b [G] [E] [L] cmp + ------------------------- a < b + L + ''' + L, (E, (G, (b, (a, stack)))) = stack + expression = concat(G if a > b else L if a < b else E, expression) + return stack, expression, dictionary # FunctionWrapper(cleave), @@ -1491,137 +1334,46 @@ def cmp_(stack, expression, dictionary): for F in ( - #divmod_ = pm = __(n2, n1), __(n4, n3) - - sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.le)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)), - sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)), - - sec_binary_ints(BinaryBuiltinWrapper(operator.xor)), - sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)), - sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)), - - sec_binary_logic(BinaryBuiltinWrapper(operator.and_)), - sec_binary_logic(BinaryBuiltinWrapper(operator.or_)), - - sec_binary_math(BinaryBuiltinWrapper(operator.add)), - sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)), - sec_binary_math(BinaryBuiltinWrapper(operator.mod)), - sec_binary_math(BinaryBuiltinWrapper(operator.mul)), - sec_binary_math(BinaryBuiltinWrapper(operator.pow)), - sec_binary_math(BinaryBuiltinWrapper(operator.sub)), - sec_binary_math(BinaryBuiltinWrapper(operator.truediv)), - - sec_unary_logic(UnaryBuiltinWrapper(bool)), - sec_unary_logic(UnaryBuiltinWrapper(operator.not_)), - - sec_unary_math(UnaryBuiltinWrapper(abs)), - sec_unary_math(UnaryBuiltinWrapper(operator.neg)), - sec_unary_math(UnaryBuiltinWrapper(sqrt)), - - stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)), - ): - inscribe(F) + #divmod_ = pm = __(n2, n1), __(n4, n3) + + BinaryBuiltinWrapper(operator.eq), + BinaryBuiltinWrapper(operator.ge), + BinaryBuiltinWrapper(operator.gt), + BinaryBuiltinWrapper(operator.le), + BinaryBuiltinWrapper(operator.lt), + BinaryBuiltinWrapper(operator.ne), + + BinaryBuiltinWrapper(operator.xor), + BinaryBuiltinWrapper(operator.lshift), + BinaryBuiltinWrapper(operator.rshift), + + BinaryBuiltinWrapper(operator.and_), + BinaryBuiltinWrapper(operator.or_), + + BinaryBuiltinWrapper(operator.add), + BinaryBuiltinWrapper(operator.floordiv), + BinaryBuiltinWrapper(operator.mod), + BinaryBuiltinWrapper(operator.mul), + BinaryBuiltinWrapper(operator.pow), + BinaryBuiltinWrapper(operator.sub), +## BinaryBuiltinWrapper(operator.truediv), + + UnaryBuiltinWrapper(bool), + UnaryBuiltinWrapper(operator.not_), + + UnaryBuiltinWrapper(abs), + UnaryBuiltinWrapper(operator.neg), + UnaryBuiltinWrapper(sqrt), + + UnaryBuiltinWrapper(floor), + UnaryBuiltinWrapper(round), + ): + inscribe(F) del F # Otherwise Sphinx autodoc will pick it up. -YIN_STACK_EFFECTS = yin_functions() -add_aliases(YIN_STACK_EFFECTS, ALIASES) - -# 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 -for name in sorted(_functions): - sec = _functions[name] - F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS()) - if name in YIN_STACK_EFFECTS: - _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec)) - for name, primitive in getmembers(genlib, isfunction): - inscribe(SimpleFunctionWrapper(primitive)) + inscribe(SimpleFunctionWrapper(primitive)) add_aliases(_dictionary, ALIASES) -add_aliases(_functions, ALIASES) -add_aliases(FUNCTIONS, ALIASES) - - -DefinitionWrapper.add_definitions(definitions, _dictionary) - - -EXPECTATIONS = dict( - ifte=(s7, (s6, (s5, s4))), - nullary=(s7, s6), - run=(s7, s6), -) -EXPECTATIONS['while'] = (s7, (s6, s5)) - - -for name in ''' - dinfrirst - nullary - ifte - run - dupdipd codireco - while - '''.split(): - C = _dictionary[name] - expect = EXPECTATIONS.get(name) - if expect: - sec = doc_from_stack_effect(expect) - _log.info('Setting stack EXPECT for combinator %s := %s', C.name, sec) - else: - _log.info('combinator %s', C.name) - FUNCTIONS[name] = CombinatorJoyType(name, [C], _COMB_NUMS(), expect) - - -for name in (''' - of quoted enstacken ? - unary binary ternary - sqr unquoted - '''.split()): - of_ = _dictionary[name] - secs = infer_expression(of_.body) - assert len(secs) == 1, repr(secs) - _log.info( - 'Setting stack effect for definition %s := %s', - name, - doc_from_stack_effect(*secs[0]), - ) - FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS()) - - -#sec_Ns_math(_dictionary['product']) - -## product == 1 swap [*] step -## flatten == [] swap [concat] step -## disenstacken == ? [uncons ?] loop pop -## pam == [i] map -## size == 0 swap [pop ++] step -## fork == [i] app2 -## cleave == fork [popd] dip -## average == [sum 1.0 *] [size] cleave / -## gcd == 1 [tuck modulus dup 0 >] loop pop -## least_fraction == dup [gcd] infra [div] concat map -## *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons -## *fraction0 == concat [[swap] dip * [*] dip] infra -## down_to_zero == [0 >] [dup --] while -## range_to_zero == unit [down_to_zero] infra -## anamorphism == [pop []] swap [dip swons] genrec -## range == [0 <=] [1 - dup] anamorphism -## while == swap [nullary] cons dup dipd concat loop -## dupdipd == dup dipd -## primrec == [i] genrec -## step_zero == 0 roll> step -## codireco == cons dip rest cons -## make_generator == [codireco] ccons -## ifte == [nullary not] dipd branch