From 65b2b4a7e35017f83dba5462473840b51cf6c02a Mon Sep 17 00:00:00 2001 From: Simon Forman Date: Fri, 9 Apr 2021 16:16:34 -0700 Subject: [PATCH] Switch back to spaces for indentation. For better or worse, Python 3 won. No need to be shitty about it, eh? --- joy/__main__.py | 6 +- joy/joy.py | 163 ++-- joy/library.py | 1529 +++++++++++++++++++------------------ joy/parser.py | 104 +-- joy/utils/pretty_print.py | 124 +-- joy/utils/stack.py | 236 +++--- 6 files changed, 1088 insertions(+), 1074 deletions(-) diff --git a/joy/__main__.py b/joy/__main__.py index 73b6ddc..e1f7b80 100644 --- a/joy/__main__.py +++ b/joy/__main__.py @@ -26,10 +26,10 @@ from .utils.pretty_print import trace inscribe(trace) if '-q' in sys.argv: - j = interp + j = interp else: - j = repl - print('''\ + j = repl + print('''\ Thun - Copyright © 2017 Simon Forman This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty". This is free software, and you are welcome to redistribute it diff --git a/joy/joy.py b/joy/joy.py index 88a9cbf..dfa4572 100644 --- a/joy/joy.py +++ b/joy/joy.py @@ -27,113 +27,118 @@ from builtins import input from traceback import print_exc from .parser import text_to_expression, ParseError, Symbol from .utils.stack import stack_to_string +from .library import NotAnIntError, StackUnderflowError class UnknownSymbolError(KeyError): pass def joy(stack, expression, dictionary, viewer=None): - '''Evaluate a Joy expression on a stack. + '''Evaluate a Joy expression on a stack. This function iterates through a sequence of terms which are either literals (strings, numbers, sequences of terms) or function symbols. Literals are put onto the stack and functions are looked up in the dictionary and executed. - The viewer is a function that is called with the stack and expression - on every iteration, its return value is ignored. + The viewer is a function that is called with the stack and expression + on every iteration, its return value is ignored. - :param stack stack: The stack. - :param stack expression: The expression to evaluate. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :param function viewer: Optional viewer function. - :rtype: (stack, (), dictionary) + :param stack stack: The stack. + :param stack expression: The expression to evaluate. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :param function viewer: Optional viewer function. + :rtype: (stack, (), dictionary) - ''' - while expression: + ''' + while expression: - if viewer: viewer(stack, expression) + if viewer: viewer(stack, expression) - term, expression = expression - if isinstance(term, Symbol): - try: - term = dictionary[term] - except KeyError: - raise UnknownSymbolError(term) - stack, expression, dictionary = term(stack, expression, dictionary) - else: - stack = term, stack + term, expression = expression + if isinstance(term, Symbol): + try: + term = dictionary[term] + except KeyError: + raise UnknownSymbolError(term) + stack, expression, dictionary = term(stack, expression, dictionary) + else: + stack = term, stack - if viewer: viewer(stack, expression) - return stack, expression, dictionary + if viewer: viewer(stack, expression) + return stack, expression, dictionary def run(text, stack, dictionary, viewer=None): - ''' - Return the stack resulting from running the Joy code text on the stack. + ''' + Return the stack resulting from running the Joy code text on the stack. - :param str text: Joy code. - :param stack stack: The stack. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :param function viewer: Optional viewer function. - :rtype: (stack, (), dictionary) + :param str text: Joy code. + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :param function viewer: Optional viewer function. + :rtype: (stack, (), dictionary) - ''' - expression = text_to_expression(text) - return joy(stack, expression, dictionary, viewer) + ''' + expression = text_to_expression(text) + return joy(stack, expression, dictionary, viewer) def repl(stack=(), dictionary=None): - ''' - Read-Evaluate-Print Loop + ''' + Read-Evaluate-Print Loop - Accept input and run it on the stack, loop. + Accept input and run it on the stack, loop. - :param stack stack: The stack. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :rtype: stack + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: stack - ''' - if dictionary is None: - dictionary = {} - try: - while True: - print() - print(stack_to_string(stack), '<-top') - print() - try: - text = input('joy? ') - except (EOFError, KeyboardInterrupt): - break - try: - stack, _, dictionary = run(text, stack, dictionary) - except: - print_exc() - except: - print_exc() - print() - return stack + ''' + if dictionary is None: + dictionary = {} + try: + while True: + print() + print(stack_to_string(stack), '<-top') + print() + try: + text = input('joy? ') + except (EOFError, KeyboardInterrupt): + break + try: + stack, _, dictionary = run(text, stack, dictionary) + except: + print_exc() + except: + print_exc() + print() + return stack def interp(stack=(), dictionary=None): - ''' - Simple REPL with no extra output, suitable for use in scripts. - ''' - if dictionary is None: - dictionary = {} - try: - while True: - try: - text = input() - except (EOFError, KeyboardInterrupt): - break - try: - stack, _, dictionary = run(text, stack, dictionary) - except UnknownSymbolError as sym: - print('Unknown:', sym) - except: - print_exc() - print(stack_to_string(stack)) - except: - print_exc() - return stack + ''' + Simple REPL with no extra output, suitable for use in scripts. + ''' + if dictionary is None: + dictionary = {} + try: + while True: + try: + text = input() + except (EOFError, KeyboardInterrupt): + break + try: + stack, _, dictionary = run(text, stack, dictionary) + except UnknownSymbolError as sym: + print('Unknown:', sym) + except StackUnderflowError: + print('Not enough values on stack.') + except NotAnIntError: + print('Not an integer.') + except: + print_exc() + print(stack_to_string(stack)) + except: + print_exc() + return stack diff --git a/joy/library.py b/joy/library.py index 5d778d6..123de6e 100644 --- a/joy/library.py +++ b/joy/library.py @@ -31,12 +31,12 @@ import operator, math from .parser import text_to_expression, Symbol from .utils import generated_library as genlib from .utils.stack import ( - concat, - expression_to_string, - iter_stack, - list_to_stack, - pick, - ) + concat, + expression_to_string, + iter_stack, + list_to_stack, + pick, + ) HELP_TEMPLATE = '''\ @@ -54,57 +54,57 @@ _dictionary = {} def inscribe(function): - '''A decorator to inscribe functions into the default dictionary.''' - _dictionary[function.name] = function - return function + '''A decorator to inscribe functions into the default dictionary.''' + _dictionary[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', '//']), - ('truediv', ['/', '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'•']), - ) + ('add', ['+']), + ('and', ['&']), + ('bool', ['truthy']), + ('mul', ['*']), + ('floordiv', ['/floor', '//']), + ('truediv', ['/', '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 + ''' + 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 definitions = ('''\ @@ -189,122 +189,131 @@ while == swap [nullary] cons dup dipd concat loop ) +class NotAnIntError(Exception): pass +class StackUnderflowError(Exception): pass + + 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) - 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) - 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 + if not isinstance(a, int) or not isinstance(b, int): + 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) - def inner(stack, expression, dictionary): - (a, stack) = stack - result = f(a) - return (result, stack), expression, dictionary - return inner + ''' + 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 DefinitionWrapper(object): - ''' - Provide implementation of defined functions, and some helper methods. - ''' + ''' + 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 __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 + 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. - ''' - # At some point I decided that the definitions file should NOT - # use '==' to separate the name from the body. But somehow the - # xerblin\gui\default_joy_home\definitions.txt file didn't get - # the memo. Nor did the load_definitions() method. - # So I think the simplest way forward at the moment will be to - # edit this function to expect '=='. + @classmethod + def parse_definition(class_, defi): + ''' + Given some text describing a Joy function definition parse it and + return a DefinitionWrapper. + ''' + # At some point I decided that the definitions file should NOT + # use '==' to separate the name from the body. But somehow the + # xerblin\gui\default_joy_home\definitions.txt file didn't get + # the memo. Nor did the load_definitions() method. + # So I think the simplest way forward at the moment will be to + # edit this function to expect '=='. - name, part, body = defi.partition('==') - if part: - return class_(name.strip(), body.strip()) - raise ValueError("No '==' in definition text %r" % (defi,)) + name, part, body = defi.partition('==') + if part: + return class_(name.strip(), body.strip()) + raise ValueError("No '==' in definition text %r" % (defi,)) - # return class_(*(n.strip() for n in defi.split(None, 1))) + # return class_(*(n.strip() for n in defi.split(None, 1))) - @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_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) - dictionary[F.name] = F + @classmethod + def add_def(class_, definition, dictionary, fail_fails=False): + ''' + Add the definition to the dictionary. + ''' + F = class_.parse_definition(definition) + 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) + @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 line - and not line.startswith('#') - and '==' in line - ) + return ( + line.strip() + for line in text.splitlines() + if line + and not line.startswith('#') + and '==' in line + ) # @@ -315,356 +324,356 @@ def _text_to_defs(text): @inscribe @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 string with a name followed by a double + equal sign then one or more Joy functions, the body. for example: - sqr == dup mul + sqr == dup mul - 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 + 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 @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 +# '''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 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 @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 @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.) - :: + ''' + 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] + [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 + ''' + 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 @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 @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 @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 @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 + sum == 0 swap [+] step - ''' - tos, stack = S - return sum(iter_stack(tos)), stack + ''' + 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 @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 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] + ''' + 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 @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 @SimpleFunctionWrapper def succ(S): - '''Increment TOS.''' - (tos, stack) = S - return tos + 1, stack + '''Increment TOS.''' + (tos, stack) = S + return tos + 1, stack @inscribe @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__ @@ -672,27 +681,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: div*y + mod == 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): @@ -705,20 +714,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)) @@ -730,42 +739,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): @@ -790,11 +799,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(HELP_TEMPLATE % (symbol, getdoc(word), symbol)) - 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 # @@ -823,215 +832,215 @@ S_times = Symbol('times') @inscribe @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 + ''' + quote, stack = stack + return stack, concat(quote, expression), dictionary @inscribe @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 @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 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 @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 @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] tailrec - == [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 @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": + ''' + 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.:: + > 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 + 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.:: + > 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 + n [Base] [Recur] primrec - 0 [Base] [Recur] primrec - ------------------------------ - Base + 0 [Base] [Recur] primrec + ------------------------------ + Base - n [Base] [Recur] primrec - ------------------------------------------ n > 0 - n (n-1) [Base] [Recur] primrec Recur + 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 + ''' + 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): @@ -1051,26 +1060,26 @@ def primrec(stack, expression, dictionary): @inscribe @FunctionWrapper def branch(stack, expression, dictionary): - ''' - Use a Boolean value to select one of two quoted programs to run. + ''' + Use a Boolean value to select one of two quoted programs to run. - :: + :: - branch == roll< choice i + branch == roll< choice i - :: + :: - False [F] [T] branch - -------------------------- - F + False [F] [T] branch + -------------------------- + F - True [F] [T] branch - ------------------------- - T + True [F] [T] branch + ------------------------- + T - ''' - (then, (else_, (flag, stack))) = stack - return stack, concat(then if flag else else_, expression), dictionary + ''' + (then, (else_, (flag, stack))) = stack + return stack, concat(then if flag else else_, expression), dictionary ##@inscribe @@ -1104,224 +1113,224 @@ 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. + ''' + 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.:: + 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 + [[[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 + ''' + 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 @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 + ''' + (quote, (x, stack)) = stack + expression = (x, expression) + return stack, concat(quote, expression), dictionary @inscribe @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 @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 @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. - :: + ''' + 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 + ... 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 + ''' + (quote, (x, stack)) = S + stack = (quote, ((x, stack), stack)) + expression = (S_infra, (S_first, expression)) + return stack, expression, dictionary @inscribe @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 @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 @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 @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: @@ -1343,48 +1352,48 @@ def times(stack, expression, dictionary): @inscribe @FunctionWrapper def loop(stack, expression, dictionary): - ''' - Basic loop combinator. - :: + ''' + Basic loop combinator. + :: - ... True [Q] loop - ----------------------- - ... Q [Q] loop + ... True [Q] loop + ----------------------- + ... Q [Q] loop - ... False [Q] loop - ------------------------ - ... + ... False [Q] loop + ------------------------ + ... - ''' - quote, (flag, stack) = stack - if flag: - expression = concat(quote, (quote, (S_loop, expression))) - return stack, expression, dictionary + ''' + quote, (flag, stack) = stack + if flag: + expression = concat(quote, (quote, (S_loop, expression))) + return stack, expression, dictionary @inscribe @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), @@ -1393,46 +1402,46 @@ def cmp_(stack, expression, dictionary): for F in ( - #divmod_ = pm = __(n2, n1), __(n4, n3) + #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.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.xor), + BinaryBuiltinWrapper(operator.lshift), + BinaryBuiltinWrapper(operator.rshift), - BinaryBuiltinWrapper(operator.and_), - BinaryBuiltinWrapper(operator.or_), + 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), + 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(bool), + UnaryBuiltinWrapper(operator.not_), - UnaryBuiltinWrapper(abs), - UnaryBuiltinWrapper(operator.neg), - UnaryBuiltinWrapper(sqrt), + UnaryBuiltinWrapper(abs), + UnaryBuiltinWrapper(operator.neg), + UnaryBuiltinWrapper(sqrt), - UnaryBuiltinWrapper(floor), - UnaryBuiltinWrapper(round), - ): - inscribe(F) + UnaryBuiltinWrapper(floor), + UnaryBuiltinWrapper(round), + ): + inscribe(F) del F # Otherwise Sphinx autodoc will pick it up. for name, primitive in getmembers(genlib, isfunction): - inscribe(SimpleFunctionWrapper(primitive)) + inscribe(SimpleFunctionWrapper(primitive)) add_aliases(_dictionary, ALIASES) diff --git a/joy/parser.py b/joy/parser.py index e13eb9d..8bc038e 100644 --- a/joy/parser.py +++ b/joy/parser.py @@ -53,71 +53,71 @@ token_scanner = Scanner([ class Symbol(str): - '''A string class that represents Joy function names.''' - __repr__ = str.__str__ + '''A string class that represents Joy function names.''' + __repr__ = str.__str__ def text_to_expression(text): - '''Convert a string to a Joy expression. + '''Convert a string to a Joy expression. - When supplied with a string this function returns a Python datastructure - that represents the Joy datastructure described by the text expression. - Any unbalanced square brackets will raise a ParseError. + When supplied with a string this function returns a Python datastructure + that represents the Joy datastructure described by the text expression. + Any unbalanced square brackets will raise a ParseError. - :param str text: Text to convert. - :rtype: stack - :raises ParseError: if the parse fails. - ''' - return _parse(_tokenize(text)) + :param str text: Text to convert. + :rtype: stack + :raises ParseError: if the parse fails. + ''' + return _parse(_tokenize(text)) class ParseError(ValueError): - '''Raised when there is a error while parsing text.''' + '''Raised when there is a error while parsing text.''' def _tokenize(text): - '''Convert a text into a stream of tokens. + '''Convert a text into a stream of tokens. - Converts function names to Symbols. + Converts function names to Symbols. - Raise ParseError (with some of the failing text) if the scan fails. - ''' - tokens, rest = token_scanner.scan(text) - if rest: - raise ParseError( - 'Scan failed at position %i, %r' - % (len(text) - len(rest), rest[:10]) - ) - return tokens + Raise ParseError (with some of the failing text) if the scan fails. + ''' + tokens, rest = token_scanner.scan(text) + if rest: + raise ParseError( + 'Scan failed at position %i, %r' + % (len(text) - len(rest), rest[:10]) + ) + return tokens def _parse(tokens): - ''' - Return a stack/list expression of the tokens. - ''' - frame = [] - stack = [] - for tok in tokens: - if tok == '[': - stack.append(frame) - frame = [] - stack[-1].append(frame) - elif tok == ']': - try: - frame = stack.pop() - except IndexError: - raise ParseError('Extra closing bracket.') - frame[-1] = list_to_stack(frame[-1]) - elif tok == 'true': - frame.append(True) - elif tok == 'false': - frame.append(False) - else: - try: - thing = int(tok) - except ValueError: - thing = Symbol(tok) - frame.append(thing) - if stack: - raise ParseError('Unclosed bracket.') - return list_to_stack(frame) + ''' + Return a stack/list expression of the tokens. + ''' + frame = [] + stack = [] + for tok in tokens: + if tok == '[': + stack.append(frame) + frame = [] + stack[-1].append(frame) + elif tok == ']': + try: + frame = stack.pop() + except IndexError: + raise ParseError('Extra closing bracket.') + frame[-1] = list_to_stack(frame[-1]) + elif tok == 'true': + frame.append(True) + elif tok == 'false': + frame.append(False) + else: + try: + thing = int(tok) + except ValueError: + thing = Symbol(tok) + frame.append(thing) + if stack: + raise ParseError('Unclosed bracket.') + return list_to_stack(frame) diff --git a/joy/utils/pretty_print.py b/joy/utils/pretty_print.py index 3c6e8c5..b447591 100644 --- a/joy/utils/pretty_print.py +++ b/joy/utils/pretty_print.py @@ -46,79 +46,79 @@ from ..library import FunctionWrapper @FunctionWrapper def trace(stack, expression, dictionary): - '''Evaluate a Joy expression on a stack and print a trace. + '''Evaluate a Joy expression on a stack and print a trace. - This function is just like the `i` combinator but it also prints a - trace of the evaluation + This function is just like the `i` combinator but it also prints a + trace of the evaluation - :param stack stack: The stack. - :param stack expression: The expression to evaluate. - :param dict dictionary: A ``dict`` mapping names to Joy functions. - :rtype: (stack, (), dictionary) + :param stack stack: The stack. + :param stack expression: The expression to evaluate. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: (stack, (), dictionary) - ''' - tp = TracePrinter() - quote, stack = stack - try: - s, _, d = joy(stack, quote, dictionary, tp.viewer) - except: - tp.print_() - print('-' * 73) - raise - else: - tp.print_() - return s, expression, d + ''' + tp = TracePrinter() + quote, stack = stack + try: + s, _, d = joy(stack, quote, dictionary, tp.viewer) + except: + tp.print_() + print('-' * 73) + raise + else: + tp.print_() + return s, expression, d class TracePrinter(object): - ''' - This is what does the formatting. You instantiate it and pass the ``viewer()`` - method to the :py:func:`joy.joy.joy` function, then print it to see the - trace. - ''' + ''' + This is what does the formatting. You instantiate it and pass the ``viewer()`` + method to the :py:func:`joy.joy.joy` function, then print it to see the + trace. + ''' - def __init__(self): - self.history = [] + def __init__(self): + self.history = [] - def viewer(self, stack, expression): - ''' - Record the current stack and expression in the TracePrinter's history. - Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function. + def viewer(self, stack, expression): + ''' + Record the current stack and expression in the TracePrinter's history. + Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function. - :param stack quote: A stack. - :param stack expression: A stack. - ''' - self.history.append((stack, expression)) + :param stack quote: A stack. + :param stack expression: A stack. + ''' + self.history.append((stack, expression)) - def __str__(self): - return '\n'.join(self.go()) + def __str__(self): + return '\n'.join(self.go()) - def go(self): - ''' - Return a list of strings, one for each entry in the history, prefixed - with enough spaces to align all the interpreter dots. + def go(self): + ''' + Return a list of strings, one for each entry in the history, prefixed + with enough spaces to align all the interpreter dots. - This method is called internally by the ``__str__()`` method. + This method is called internally by the ``__str__()`` method. - :rtype: list(str) - ''' - max_stack_length = 0 - lines = [] - for stack, expression in self.history: - stack = stack_to_string(stack) - expression = expression_to_string(expression) - n = len(stack) - if n > max_stack_length: - max_stack_length = n - lines.append((n, '%s • %s' % (stack, expression))) - for i in range(len(lines)): # Prefix spaces to line up '•'s. - length, line = lines[i] - lines[i] = (' ' * (max_stack_length - length) + line) - return lines + :rtype: list(str) + ''' + max_stack_length = 0 + lines = [] + for stack, expression in self.history: + stack = stack_to_string(stack) + expression = expression_to_string(expression) + n = len(stack) + if n > max_stack_length: + max_stack_length = n + lines.append((n, '%s • %s' % (stack, expression))) + for i in range(len(lines)): # Prefix spaces to line up '•'s. + length, line = lines[i] + lines[i] = (' ' * (max_stack_length - length) + line) + return lines - def print_(self): - try: - print(self) - except: - print_exc() - print('Exception while printing viewer.') + def print_(self): + try: + print(self) + except: + print_exc() + print('Exception while printing viewer.') diff --git a/joy/utils/stack.py b/joy/utils/stack.py index cc00673..2f5a653 100644 --- a/joy/utils/stack.py +++ b/joy/utils/stack.py @@ -43,8 +43,8 @@ means we can directly "unpack" the expected arguments to a Joy function. For example:: - def dup((head, tail)): - return head, (head, tail) + def dup((head, tail)): + return head, (head, tail) We replace the argument "stack" by the expected structure of the stack, in this case "(head, tail)", and Python takes care of unpacking the @@ -56,9 +56,9 @@ Unfortunately, the Sphinx documentation generator, which is used to generate thi web page, doesn't handle tuples in the function parameters. And in Python 3, this syntax was removed entirely. Instead you would have to write:: - def dup(stack): - head, tail = stack - return head, (head, tail) + def dup(stack): + head, tail = stack + return head, (head, tail) We have two very simple functions, one to build up a stack from a Python @@ -73,59 +73,59 @@ printed left-to-right. These functions are written to support :doc:`../pretty`. def list_to_stack(el, stack=()): - '''Convert a Python list (or other sequence) to a Joy stack:: + '''Convert a Python list (or other sequence) to a Joy stack:: - [1, 2, 3] -> (1, (2, (3, ()))) + [1, 2, 3] -> (1, (2, (3, ()))) - :param list el: A Python list or other sequence (iterators and generators - won't work because ``reverse()`` is called on ``el``.) - :param stack stack: A stack, optional, defaults to the empty stack. - :rtype: stack + :param list el: A Python list or other sequence (iterators and generators + won't work because ``reverse()`` is called on ``el``.) + :param stack stack: A stack, optional, defaults to the empty stack. + :rtype: stack - ''' - for item in reversed(el): - stack = item, stack - return stack + ''' + for item in reversed(el): + stack = item, stack + return stack def iter_stack(stack): - '''Iterate through the items on the stack. + '''Iterate through the items on the stack. - :param stack stack: A stack. - :rtype: iterator - ''' - while stack: - item, stack = stack - yield item + :param stack stack: A stack. + :rtype: iterator + ''' + while stack: + item, stack = stack + yield item def stack_to_string(stack): - ''' - Return a "pretty print" string for a stack. + ''' + Return a "pretty print" string for a stack. - The items are written right-to-left:: + The items are written right-to-left:: - (top, (second, ...)) -> '... second top' + (top, (second, ...)) -> '... second top' - :param stack stack: A stack. - :rtype: str - ''' - f = lambda stack: reversed(list(iter_stack(stack))) - return _to_string(stack, f) + :param stack stack: A stack. + :rtype: str + ''' + f = lambda stack: reversed(list(iter_stack(stack))) + return _to_string(stack, f) def expression_to_string(expression): - ''' - Return a "pretty print" string for a expression. + ''' + Return a "pretty print" string for a expression. - The items are written left-to-right:: + The items are written left-to-right:: - (top, (second, ...)) -> 'top second ...' + (top, (second, ...)) -> 'top second ...' - :param stack expression: A stack. - :rtype: str - ''' - return _to_string(expression, iter_stack) + :param stack expression: A stack. + :rtype: str + ''' + return _to_string(expression, iter_stack) _JOY_BOOL_LITS = 'false', 'true' @@ -138,40 +138,40 @@ def _joy_repr(thing): def _to_string(stack, f): - if not isinstance(stack, tuple): return _joy_repr(stack) - if not stack: return '' # shortcut - return ' '.join(map(_s, f(stack))) + if not isinstance(stack, tuple): return _joy_repr(stack) + if not stack: return '' # shortcut + return ' '.join(map(_s, f(stack))) _s = lambda s: ( - '[%s]' % expression_to_string(s) + '[%s]' % expression_to_string(s) if isinstance(s, tuple) - else _joy_repr(s) - ) + else _joy_repr(s) + ) def concat(quote, expression): - '''Concatinate quote onto expression. + '''Concatinate quote onto expression. - In joy [1 2] [3 4] would become [1 2 3 4]. + In joy [1 2] [3 4] would become [1 2 3 4]. - :param stack quote: A stack. - :param stack expression: A stack. - :raises RuntimeError: if quote is larger than sys.getrecursionlimit(). - :rtype: stack - ''' - # This is the fastest implementation, but will trigger - # RuntimeError: maximum recursion depth exceeded - # on quotes longer than sys.getrecursionlimit(). + :param stack quote: A stack. + :param stack expression: A stack. + :raises RuntimeError: if quote is larger than sys.getrecursionlimit(). + :rtype: stack + ''' + # This is the fastest implementation, but will trigger + # RuntimeError: maximum recursion depth exceeded + # on quotes longer than sys.getrecursionlimit(). - return (quote[0], concat(quote[1], expression)) if quote else expression + return (quote[0], concat(quote[1], expression)) if quote else expression - # Original implementation. + # Original implementation. ## return list_to_stack(list(iter_stack(quote)), expression) - # In-lining is slightly faster (and won't break the - # recursion limit on long quotes.) + # In-lining is slightly faster (and won't break the + # recursion limit on long quotes.) ## temp = [] ## while quote: @@ -184,67 +184,67 @@ def concat(quote, expression): def dnd(stack, from_index, to_index): - ''' - Given a stack and two indices return a rearranged stack. - First remove the item at from_index and then insert it at to_index, - the second index is relative to the stack after removal of the item - at from_index. + ''' + Given a stack and two indices return a rearranged stack. + First remove the item at from_index and then insert it at to_index, + the second index is relative to the stack after removal of the item + at from_index. - This function reuses all of the items and as much of the stack as it - can. It's meant to be used by remote clients to support drag-n-drop - rearranging of the stack from e.g. the StackListbox. - ''' - assert 0 <= from_index - assert 0 <= to_index - if from_index == to_index: - return stack - head, n = [], from_index - while True: - item, stack = stack - n -= 1 - if n < 0: - break - head.append(item) - assert len(head) == from_index - # now we have two cases: - diff = from_index - to_index - if diff < 0: - # from < to - # so the destination index is still in the stack - while diff: - h, stack = stack - head.append(h) - diff += 1 - else: - # from > to - # so the destination is in the head list - while diff: - stack = head.pop(), stack - diff -= 1 - stack = item, stack - while head: - stack = head.pop(), stack - return stack + This function reuses all of the items and as much of the stack as it + can. It's meant to be used by remote clients to support drag-n-drop + rearranging of the stack from e.g. the StackListbox. + ''' + assert 0 <= from_index + assert 0 <= to_index + if from_index == to_index: + return stack + head, n = [], from_index + while True: + item, stack = stack + n -= 1 + if n < 0: + break + head.append(item) + assert len(head) == from_index + # now we have two cases: + diff = from_index - to_index + if diff < 0: + # from < to + # so the destination index is still in the stack + while diff: + h, stack = stack + head.append(h) + diff += 1 + else: + # from > to + # so the destination is in the head list + while diff: + stack = head.pop(), stack + diff -= 1 + stack = item, stack + while head: + stack = head.pop(), stack + return stack def pick(stack, n): - ''' - Return the nth item on the stack. + ''' + Return the nth item on the stack. - :param stack stack: A stack. - :param int n: An index into the stack. - :raises ValueError: if ``n`` is less than zero. - :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. - :rtype: whatever - ''' - if n < 0: - raise ValueError - while True: - try: - item, stack = stack - except ValueError: - raise IndexError - n -= 1 - if n < 0: - break - return item + :param stack stack: A stack. + :param int n: An index into the stack. + :raises ValueError: if ``n`` is less than zero. + :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. + :rtype: whatever + ''' + if n < 0: + raise ValueError + while True: + try: + item, stack = stack + except ValueError: + raise IndexError + n -= 1 + if n < 0: + break + return item