2 # -*- coding: utf-8 -*-
4 # Copyright © 2022 - 2023 Simon Forman
6 # This file is part of Thun
8 # Thun is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # Thun is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with Thun. If not see <http://www.gnu.org/licenses/>.
22 ████████╗██╗ ██╗██╗ ██╗███╗ ██╗
23 ╚══██╔══╝██║ ██║██║ ██║████╗ ██║
24 ██║ ███████║██║ ██║██╔██╗ ██║
25 ██║ ██╔══██║██║ ██║██║╚██╗██║
26 ██║ ██║ ██║╚██████╔╝██║ ╚████║
27 ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
29 This script implements an interpreter for a dialect of Joy.
31 Joy is a programming language created by Manfred von Thun that is easy to
32 use and understand and has many other nice properties. This Python
33 package implements an interpreter for a dialect of Joy that attempts to
34 stay very close to the spirit of Joy but does not precisely match the
35 behaviour of the original version(s) written in C. The main difference
36 between Thun and the originals, other than being written in Python, is
37 that it works by the “Continuation-Passing Style”.
39 Here is an example of Joy code:
47 [[ !-] [[++]] [[--]] ifte dip]
48 [[pop !-] [--] [++] ifte ]
51 This function accepts two integers on the stack and increments or
52 decrements one of them such that the new pair of numbers is the next
53 coordinate pair in a square spiral (like the kind used to construct an
56 from functools import wraps
57 from inspect import getdoc
58 from sys import stderr
59 from traceback import print_exc
68 ██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██████╗ ███████╗████████╗███████╗██████╗
69 ██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗
70 ██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██████╔╝█████╗ ██║ █████╗ ██████╔╝
71 ██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔═══╝ ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██╗
72 ██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║ ██║███████╗ ██║ ███████╗██║ ██║
73 ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
75 The joy() interpreter function is extrememly simple. It accepts a stack,
76 an expression, and a dictionary, and it iterates through the expression
77 putting values onto the stack and delegating execution to functions which
78 it looks up in the dictionary.
83 def joy(stack, expression, dictionary):
85 Evaluate a Joy expression on a stack.
87 This function iterates through a sequence of terms.
88 Literals are put onto the stack and Symbols are
89 looked up in the dictionary and the functions they
92 :param stack stack: The stack.
93 :param stack expression: The expression to evaluate.
94 :param dict dictionary: A ``dict`` mapping names to Joy functions.
96 :rtype: (stack, dictionary)
99 expr = push_quote(expression) # We keep a stack-of-stacks, see below.
102 # f'{stack_to_string(stack)} • {expr_to_string(expr)}'
104 term, expr = next_term(expr)
105 if isinstance(term, Symbol):
107 func = dictionary[term]
109 raise UnknownSymbolError(term) from None
110 stack, expr, dictionary = func(stack, expr, dictionary)
113 return stack, dictionary
116 class UnknownSymbolError(KeyError):
121 ███████╗████████╗ █████╗ ██████╗██╗ ██╗
122 ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
123 ███████╗ ██║ ███████║██║ █████╔╝
124 ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
125 ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
126 ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
128 When talking about Joy we use the terms "stack", "quote", "sequence",
129 "list", and others to mean the same thing: a simple linear datatype that
130 permits certain operations such as iterating and pushing and popping
131 values from (at least) one end.
133 In describing Joy I have used the term quotation to describe all of the
134 above, because I needed a word to describe the arguments to combinators
135 which fulfill the same role in Joy as lambda abstractions (with
136 variables) fulfill in the more familiar functional languages. I use the
137 term list for those quotations whose members are what I call literals:
138 numbers, characters, truth values, sets, strings and other quotations.
139 All these I call literals because their occurrence in code results in
140 them being pushed onto the stack. But I also call [London Paris] a list.
141 So, [dup *] is a quotation but not a list.
143 `"A Conversation with Manfred von Thun" w/ Stevan Apter <http://archive.vector.org.uk/art10000350>`_
145 There is no "Stack" Python class, instead we use the `cons list`_, a
146 venerable two-tuple recursive sequence datastructure, where the empty
147 tuple ``()`` is the empty stack and ``(head, rest)`` gives the recursive
148 form of a stack with one or more items on it::
150 stack := () | (item, stack)
152 Putting some numbers onto a stack::
158 [3 2 1] (3, (2, (1, ())))
161 Python has very nice "tuple packing and unpacking" in its syntax which
162 means we can directly "unpack" the expected arguments to a Joy function.
163 We assign the argument stack to the expected structure of the stack and
164 Python takes care of unpacking the incoming tuple and assigning values to
165 the names. (Note that Python syntax doesn't require parentheses around
166 tuples used in expressions where they would be redundant.)
170 return head, (head, tail)
173 .. _cons list: https://en.wikipedia.org/wiki/Cons#Lists
178 class StackUnderflowError(Exception):
182 def list_to_stack(el, stack=()):
184 Convert a Python list (or other sequence) to a Joy stack::
186 [1, 2, 3] -> (1, (2, (3, ())))
188 :param list el: A Python list or other sequence (iterators and generators
189 won't work because ``reversed()`` is called on ``el``.)
190 :param stack stack: A stack, optional, defaults to the empty stack. This
191 allows for concatinating Python lists (or other sequence objects)
192 onto an existing Joy stack.
196 for item in reversed(el):
201 def iter_stack(stack):
203 Iterate through the items on the stack.
205 :param stack stack: A stack.
213 def concat(quote, expression):
215 Concatinate quote onto expression.
217 In joy [1 2] [3 4] would become [1 2 3 4].
219 :param stack quote: A stack.
220 :param stack expression: A stack.
224 isnt_stack(expression)
225 return list_to_stack(list(iter_stack(quote)), expression)
227 ## return (quote[0], concat(quote[1], expression)) if quote else expression
228 # :raises RuntimeError: if quote is larger than sys.getrecursionlimit().
229 # This is faster implementation but it would trigger
230 # RuntimeError: maximum recursion depth exceeded
231 # on quotes longer than sys.getrecursionlimit().
234 def get_n_items(n, stack):
236 Return n items and remainder of stack.
237 Raise StackUnderflowError if there are fewer than n items on the stack.
239 assert n > 0, repr(n)
246 raise StackUnderflowError(
247 'Not enough values on stack.'
254 def reversed_stack(stack):
256 Return list_reverseiterator object for a stack.
258 return reversed(list(iter_stack(stack)))
262 ███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗██╗ ██████╗ ███╗ ██╗
263 ██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██║██╔═══██╗████╗ ██║
264 █████╗ ╚███╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗██║██║ ██║██╔██╗ ██║
265 ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║██║██║ ██║██║╚██╗██║
266 ███████╗██╔╝ ██╗██║ ██║ ██║███████╗███████║███████║██║╚██████╔╝██║ ╚████║
267 ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
270 As elegant as it is to model the expression as a stack, it's not very
271 efficient, as concatenating definitions and other quoted programs to
272 the expression is a common and expensive operation.
274 Instead, let's keep a stack of sub-expressions, reading from them
275 one-by-one, and prepending new sub-expressions to the stack rather than
280 def push_quote(quote, expression=()):
282 Put the quoted program onto the stack-of-stacks.
284 return (quote, expression) if quote else expression
287 def next_term(expression):
289 Return the next term from the expression and the new expression.
290 Raises ValueError if called on an empty expression.
292 (item, quote), expression = expression
293 return item, push_quote(quote, expression)
297 ██████╗ █████╗ ██████╗ ███████╗███████╗██████╗
298 ██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗
299 ██████╔╝███████║██████╔╝███████╗█████╗ ██████╔╝
300 ██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗
301 ██║ ██║ ██║██║ ██║███████║███████╗██║ ██║
302 ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
305 There is a single function for converting text to joy expressions
306 as well as a Symbol class and an Exception type. The Symbol
307 string class is used by the interpreter to recognize literals by
308 the fact that they are not Symbol objects.
313 term := <integer> | 'true' | 'false' | '[' <joy> ']' | <symbol>
315 A Joy expression is a sequence of zero or more terms. A term is a
316 literal value (integer, Boolean, or quoted Joy expression) or a function symbol.
317 Function symbols are sequences of non-blanks and cannot contain square
318 brackets. Terms must be separated by blanks, which can be omitted
319 around square brackets.
323 JOY_BOOL_LITERALS = _F, _T = 'false', 'true'
326 class ParseError(ValueError):
328 Raised when there is a error while parsing text.
334 A string class that represents Joy function names.
337 __repr__ = str.__str__
340 def text_to_expression(text):
342 Convert a string to a Joy expression.
344 When supplied with a string this function returns a Python datastructure
345 that represents the Joy datastructure described by the text expression.
346 Any unbalanced square brackets will raise a ParseError.
348 :param str text: Text to convert.
350 :raises ParseError: if the parse fails.
355 for tok in text.replace('[', ' [ ').replace(']', ' ] ').split():
363 thing = list_to_stack(frame)
367 raise ParseError('Extra closing bracket.') from None
381 raise ParseError('Unclosed bracket.')
383 return list_to_stack(frame)
387 ██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗
388 ██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗
389 ██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝
390 ██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗
391 ██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║
392 ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
397 def stack_to_string(stack):
399 Return a "pretty print" string for a stack.
401 The items are written right-to-left::
403 (top, (second, ...)) -> '... second top'
405 :param stack stack: A stack.
408 return _stack_to_string(stack, reversed_stack)
411 def expression_to_string(expression):
413 Return a "pretty print" string for a expression.
414 (For historical reasons this function works on a single quote
415 not a stack-of-stacks.)
417 The items are written left-to-right::
419 (top, (second, ...)) -> 'top second ...'
421 :param stack expression: A stack.
424 return _stack_to_string(expression, iter_stack)
427 def expr_to_string(expr):
429 Return a "pretty print" string for a stack-of-stacks expression.
431 return ' '.join(map(expression_to_string, iter_stack(expr)))
434 def _stack_to_string(stack, iterator):
436 if not stack: # shortcut
438 return ' '.join(map(_s, iterator(stack)))
443 '[%s]' % expression_to_string(thing)
444 if isinstance(thing, tuple)
445 else JOY_BOOL_LITERALS[thing]
446 if isinstance(thing, bool)
452 ██████╗ ███████╗██████╗ ██╗
453 ██╔══██╗██╔════╝██╔══██╗██║
454 ██████╔╝█████╗ ██████╔╝██║
455 ██╔══██╗██╔══╝ ██╔═══╝ ██║
456 ██║ ██║███████╗██║ ███████╗
457 ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝
459 Read-Evaluate-Print Loop
464 def hack_error_message(exception):
466 Some of the Python exception messages (such as when you attempt to
467 shift a number by a negative amount of bits) are used as Joy error
468 messages. They should start with a capital letter and end with a
469 period. This function takes care of that.
471 message = str(exception)
472 if message[0].islower():
473 message = message[0].swapcase() + message[1:]
474 if '.' != message[-1]:
476 print(message, file=stderr)
479 def repl(stack=(), dictionary=None):
481 Read-Evaluate-Print Loop
483 Accept input and run it on the stack, loop.
485 :param stack stack: The stack.
486 :param dict dictionary: A ``dict`` mapping names to Joy functions.
490 if dictionary is None:
495 text = input('joy? ')
496 except (EOFError, KeyboardInterrupt):
499 stack, dictionary = run(text, stack, dictionary)
500 except UnknownSymbolError as sym:
501 print('Unknown:', sym, file=stderr)
502 except SystemExit as e:
503 raise SystemExit from e
504 except Exception as e:
505 hack_error_message(e)
506 print(stack_to_string(stack))
507 except SystemExit as e:
508 raise SystemExit from e
515 def run(text, stack, dictionary):
517 Return the stack resulting from running the Joy code text on the stack.
519 :param str text: Joy code.
520 :param stack stack: The stack.
521 :param dict dictionary: A ``dict`` mapping names to Joy functions.
522 :rtype: (stack, dictionary)
525 return joy(stack, text_to_expression(text), dictionary)
528 def interp(stack=(), dictionary=None):
530 Simple REPL with no extra output, suitable for use in scripts.
532 if dictionary is None:
538 except (EOFError, KeyboardInterrupt):
541 stack, dictionary = run(text, stack, dictionary)
542 except UnknownSymbolError as sym:
543 print('Unknown:', sym, file=stderr)
550 print(e, file=stderr)
551 except SystemExit as e:
552 raise SystemExit from e
553 except Exception as e:
554 hack_error_message(e)
555 print(stack_to_string(stack))
556 except SystemExit as e:
557 raise SystemExit from e
564 ██████╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██████╗ ██╗ ██╗
565 ██╔══██╗██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝
566 ██║ ██║██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████║██████╔╝ ╚████╔╝
567 ██║ ██║██║██║ ██║ ██║██║ ██║██║╚██╗██║██╔══██║██╔══██╗ ╚██╔╝
568 ██████╔╝██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║██║ ██║██║ ██║ ██║
569 ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
574 # This is the main dict we're building.
578 def inscribe(function, d=_dictionary):
580 A decorator to inscribe functions into the default dictionary.
582 name = function.__name__
583 if name.endswith('_'):
591 Return a dictionary of Joy functions for use with joy().
593 return _dictionary.copy()
596 def SimpleFunctionWrapper(f):
598 Wrap functions that take and return just a stack.
602 def SimpleFunctionWrapper_inner(stack, expr, dictionary):
603 return f(stack), expr, dictionary
605 return SimpleFunctionWrapper_inner
609 def words(stack, expression, dictionary):
611 Put a list of all the words in alphabetical order onto the stack.
614 for name in reversed(sorted(dictionary)):
615 if name.startswith('_'):
617 w = (Symbol(name), ()), w
618 return (w, stack), expression, dictionary
632 def help_(stack, expression, dictionary):
634 Accepts a quoted symbol on the top of the stack and prints its docs.
636 ((symbol, _), stack) = stack
637 word = dictionary[symbol]
638 print(HELP_TEMPLATE % (symbol, getdoc(word), symbol))
639 return stack, expression, dictionary
643 ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
644 ██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
645 ██║ ██║ ██║██╔████╔██║██████╔╝██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝███████╗
646 ██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
647 ╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
648 ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
654 def branch(stack, expr, dictionary):
656 Use a Boolean value to select one of two quoted programs to run.
660 branch == roll< choice i
665 --------------------------
669 -------------------------
673 then, else_, flag, stack = get_n_items(3, stack)
677 expr = push_quote((then if flag else else_), expr)
678 return stack, expr, dictionary
682 def dip(stack, expr, dictionary):
684 The dip combinator expects a quoted program on the stack and below it
685 some item, it hoists the item into the expression and runs the program
686 on the rest of the stack.
694 quote, x, stack = get_n_items(2, stack)
696 expr = push_quote((x, ()), expr)
697 expr = push_quote(quote, expr)
698 return stack, expr, dictionary
702 def i(stack, expr, dictionary):
704 The i combinator expects a quoted program on the stack and unpacks it
705 onto the pending expression for evaluation.
713 quote, stack = get_n_items(1, stack)
715 return stack, push_quote(quote, expr), dictionary
718 S_loop = Symbol('loop')
722 def loop(stack, expr, dictionary):
724 Basic loop combinator.
728 -----------------------
732 ------------------------
736 quote, stack = get_n_items(1, stack)
738 flag, stack = get_n_items(1, stack)
741 expr = push_quote((quote, (S_loop, ())), expr)
742 expr = push_quote(quote, expr)
743 return stack, expr, dictionary
747 def quit(stack, expr, dictionary):
749 Stop the interpreter.
755 ██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
756 ██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║ ██║██╔═══██╗██╔══██╗██╔══██╗██╔════╝
757 ██║ ██║ ██║██████╔╝█████╗ ██║ █╗ ██║██║ ██║██████╔╝██║ ██║███████╗
758 ██║ ██║ ██║██╔══██╗██╔══╝ ██║███╗██║██║ ██║██╔══██╗██║ ██║╚════██║
759 ╚██████╗╚██████╔╝██║ ██║███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██████╔╝███████║
760 ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝
766 @SimpleFunctionWrapper
769 Clear everything from the stack.
772 clear == stack [pop stack] loop
782 @SimpleFunctionWrapper
785 Concatinate the two lists on the top of the stack.
788 [a b c] [d e f] concat
789 ----------------------------
793 tos, second, stack = get_n_items(2, stack)
794 return concat(second, tos), stack
798 @SimpleFunctionWrapper
801 Given an item and a list, append the item to the list to make a new list.
808 Cons is a venerable old function from Lisp
809 ( https://en.wikipedia.org/wiki/Cons#Lists ).
810 Its inverse operation is uncons.
812 s0, stack = get_n_items(1, stack)
814 a1, stack = get_n_items(1, stack)
815 return ((a1, s0), stack)
819 @SimpleFunctionWrapper
822 "Dup"licate the top item on the stack.
830 a1, stack = get_n_items(1, stack)
831 return a1, (a1, stack)
835 @SimpleFunctionWrapper
838 Replace a list with its first item.
845 s0, stack = get_n_items(1, stack)
847 a1, _ = get_n_items(1, s0)
852 @SimpleFunctionWrapper
855 Pop the top item from the stack and discard it.
864 raise StackUnderflowError('Cannot pop empty stack.') from None
869 @SimpleFunctionWrapper
872 Replace a list with its tail.
879 s0, stack = get_n_items(1, stack)
884 raise StackUnderflowError(
885 'Cannot take rest of empty list.'
891 @SimpleFunctionWrapper
894 Put the stack onto the stack.
897 ---------------------------
898 ... c b a [a b c ...]
905 @SimpleFunctionWrapper
908 Swap stack. Take a list from the top of the stack, replace the stack
909 with the list, and put the old stack onto it.
912 --------------------------
916 s1, s0 = get_n_items(1, stack)
922 @SimpleFunctionWrapper
925 Swap the top two items on the stack.
932 a2, a1, stack = get_n_items(2, stack)
933 return (a1, (a2, stack))
936 def BinaryLogicWrapper(f, name=None):
938 Wrap functions that take two numbers and return a single result.
942 def BinaryLogicWrapper_inner(stack, expression, dictionary):
943 a, b, stack = get_n_items(2, stack)
947 return (result, stack), expression, dictionary
950 BinaryLogicWrapper_inner.__name__ = name
952 return BinaryLogicWrapper_inner
955 def BinaryMathWrapper(func):
957 Wrap functions that take two numbers and return a single result.
961 def BinaryMathWrapper_inner(stack, expression, dictionary):
962 a, b, stack = get_n_items(2, stack)
966 return (result, stack), expression, dictionary
968 return BinaryMathWrapper_inner
971 def UnaryLogicWrapper(f):
973 Wrap functions that take one argument and return a single result.
977 def UnaryLogicWrapper_inner(stack, expression, dictionary):
978 a, stack = get_n_items(1, stack)
981 return (result, stack), expression, dictionary
983 return UnaryLogicWrapper_inner
986 def UnaryMathWrapper(f):
988 Wrap functions that take one argument and return a single result.
992 def UnaryMathWrapper_inner(stack, expression, dictionary):
993 a, stack = get_n_items(1, stack)
996 return (result, stack), expression, dictionary
998 return UnaryMathWrapper_inner
1001 def UnaryWrapper(f):
1003 Wrap functions that take one argument and return a single result.
1007 def UnaryWrapper_inner(stack, expression, dictionary):
1008 a, stack = get_n_items(1, stack)
1010 return (result, stack), expression, dictionary
1012 return UnaryWrapper_inner
1016 ## ██████╗ ██████╗ ███╗ ███╗██████╗ █████╗ ██████╗ ██╗███████╗██╗ ██████╗ ███╗ ██╗
1017 ##██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║██╔════╝██║██╔═══██╗████╗ ██║
1018 ##██║ ██║ ██║██╔████╔██║██████╔╝███████║██████╔╝██║███████╗██║██║ ██║██╔██╗ ██║
1019 ##██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██╔══██║██╔══██╗██║╚════██║██║██║ ██║██║╚██╗██║
1020 ##╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║██║ ██║██║███████║██║╚██████╔╝██║ ╚████║
1021 ## ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
1022 BinaryMathWrapper(operator.eq),
1023 BinaryMathWrapper(operator.ge),
1024 BinaryMathWrapper(operator.gt),
1025 BinaryMathWrapper(operator.le),
1026 BinaryMathWrapper(operator.lt),
1027 BinaryMathWrapper(operator.ne),
1028 ##██╗ ██████╗ ██████╗ ██╗ ██████╗
1029 ##██║ ██╔═══██╗██╔════╝ ██║██╔════╝
1030 ##██║ ██║ ██║██║ ███╗██║██║
1031 ##██║ ██║ ██║██║ ██║██║██║
1032 ##███████╗╚██████╔╝╚██████╔╝██║╚██████╗
1033 ##╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝
1034 UnaryWrapper(bool), # Convert any value to Boolean.
1035 # (The only polymorphic function.)
1036 BinaryLogicWrapper(operator.xor, name='_\\/_'),
1037 BinaryLogicWrapper(operator.and_, name='/\\'),
1038 BinaryLogicWrapper(operator.or_, name='\\/'),
1039 UnaryLogicWrapper(operator.not_),
1040 ##███╗ ███╗ █████╗ ████████╗██╗ ██╗
1041 ##████╗ ████║██╔══██╗╚══██╔══╝██║ ██║
1042 ##██╔████╔██║███████║ ██║ ███████║
1043 ##██║╚██╔╝██║██╔══██║ ██║ ██╔══██║
1044 ##██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║
1045 ##╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
1046 BinaryMathWrapper(operator.lshift),
1047 BinaryMathWrapper(operator.rshift),
1048 BinaryMathWrapper(operator.add),
1049 BinaryMathWrapper(operator.floordiv),
1050 BinaryMathWrapper(operator.mod),
1051 BinaryMathWrapper(operator.mul),
1052 BinaryMathWrapper(operator.pow),
1053 BinaryMathWrapper(operator.sub),
1054 UnaryMathWrapper(abs),
1055 UnaryMathWrapper(operator.neg),
1060 for alias, name in (
1075 _dictionary[alias] = _dictionary[name]
1081 ██████╗ ███████╗███████╗██╗███╗ ██╗██╗████████╗██╗ ██████╗ ███╗ ██╗███████╗
1082 ██╔══██╗██╔════╝██╔════╝██║████╗ ██║██║╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝
1083 ██║ ██║█████╗ █████╗ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗
1084 ██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║
1085 ██████╔╝███████╗██║ ██║██║ ╚████║██║ ██║ ██║╚██████╔╝██║ ╚████║███████║
1086 ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
1093 Definitions are given by equations:
1095 name foo bar baz ...
1097 When a definition symbol is evaluated its body expression is put onto
1098 the pending expression.
1101 # tribar = '\u2261' # '≡'
1103 def __init__(self, name, body):
1104 self.__doc__ = f'{name} {expression_to_string(body)}'
1105 self.__name__ = name
1108 def __call__(self, stack, expr, dictionary):
1109 return stack, push_quote(self.body, expr), dictionary
1112 def load_definitions(class_, stream, dictionary):
1114 Given an iterable of lines (strings) and a dictionary put
1115 definitions into the dictionary.
1118 name, body = text_to_expression(line)
1119 if name not in dictionary:
1121 if name.endswith('_'):
1123 # See, I want to define some Python functions and use inscribe()
1124 # as a decorator and get the Joy symbol from the name of the
1125 # Python function. But some Joy names are the same as some
1126 # Python names, so to differentiate them I decided on a convention
1127 # of putting an underscore after the Python function name and
1128 # stripping it off in inscribe(). But now that there's a definition
1129 # that ends with an underscore ('_\/_' logical Boolean xor) it's
1130 # getting stripped off (to make '_\/'.) So, rather than deal with
1131 # all that in a reasonable way, I'm just going to hack it here and
1132 # add an extra underscore for inscribe() to pick off.
1133 # As I say, it's a filthy hack, but it works, and it took less time
1134 # to write than this note explaining it. :)
1135 inscribe(class_(name, body), dictionary)
1139 ████████╗██╗ ██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗███████╗
1140 ╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝
1141 ██║ ╚████╔╝ ██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ███████╗
1142 ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ╚════██║
1143 ██║ ██║ ██║ ███████╗ ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████║
1144 ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝
1147 Simple guard functions, for type inference see the Prolog versions.
1151 class NotAListError(Exception):
1153 Raised when a stack is expected but not received.
1157 class NotAnIntError(Exception):
1161 class NotABoolError(Exception):
1167 Raise NotAnIntError if i isn't an integer.
1168 (Booleans are not integers in Joy.)
1170 if not isinstance(i, int) or isinstance(i, bool):
1171 raise NotAnIntError('Not an integer.')
1177 Raise NotABoolError if b isn't a Boolean.
1179 if not isinstance(b, bool):
1180 raise NotABoolError('Not a Boolean value.')
1186 Raise NotAListError if el isn't a stack/quote/list.
1188 if not isinstance(el, tuple):
1189 raise NotAListError('Not a list.')
1193 # Put these into the dictionary so users can, uh, use them.
1194 # Not as decorators because we want to use the unwrapped
1195 # functions in our python code.
1196 inscribe(UnaryWrapper(isnt_int))
1197 inscribe(UnaryWrapper(isnt_bool))
1198 inscribe(UnaryWrapper(isnt_stack))
1202 ███████╗██╗ ██╗████████╗██████╗ █████╗
1203 ██╔════╝╚██╗██╔╝╚══██╔══╝██╔══██╗██╔══██╗
1204 █████╗ ╚███╔╝ ██║ ██████╔╝███████║
1205 ██╔══╝ ██╔██╗ ██║ ██╔══██╗██╔══██║
1206 ███████╗██╔╝ ██╗ ██║ ██║ ██║██║ ██║
1207 ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝
1212 def trace(stack, expr, dictionary):
1213 '''Evaluate a Joy expression on a stack and print a trace.
1215 This function is just like the `i` combinator but it also prints a
1216 trace of the evaluation
1218 :param stack stack: The stack.
1219 :param stack expression: The expression to evaluate.
1220 :param dict dictionary: A ``dict`` mapping names to Joy functions.
1221 :rtype: (stack, (), dictionary)
1224 quote, stack = get_n_items(1, stack)
1227 append = history.append
1228 local_expr = push_quote(quote)
1231 if len(history) > 1000:
1233 append((stack, local_expr))
1234 term, local_expr = next_term(local_expr)
1235 if isinstance(term, Symbol):
1237 func = dictionary[term]
1239 print(trace_to_string(history))
1240 raise UnknownSymbolError(term) from None
1241 stack, local_expr, dictionary = func(stack, local_expr, dictionary)
1245 print(trace_to_string(history))
1247 append((stack, local_expr))
1248 print(trace_to_string(history))
1249 return stack, expr, dictionary
1252 def trace_to_string(history):
1253 max_stack_length = 0
1255 for stack, expression in history:
1256 stack = stack_to_string(stack)
1257 expression = expr_to_string(expression)
1259 max_stack_length = max(max_stack_length, length)
1260 lines.append((length, stack, expression))
1262 # Prefix spaces to line up '•'s.
1263 (' ' * (max_stack_length - length) + f'{stack} • {expression}')
1264 for i, (length, stack, expression) in enumerate(lines)
1268 S_swaack = Symbol('swaack')
1269 S_genrec = Symbol('genrec')
1270 S_ifte = Symbol('ifte')
1271 S_infra = Symbol('infra')
1272 S_first = Symbol('first')
1273 S_primrec = Symbol('primrec')
1274 S_choice = Symbol('choice')
1276 S_cond = Symbol('cond')
1277 S_step = Symbol('step')
1278 S_times = Symbol('times')
1280 _ifte_ = (S_infra, (S_first, (S_choice, (S_i, ()))))
1283 def dnd(stack, from_index, to_index):
1285 Given a stack and two indices return a rearranged stack.
1286 First remove the item at from_index and then insert it at to_index,
1287 the second index is relative to the stack after removal of the item
1290 This function reuses all of the items and as much of the stack as it
1291 can. It's meant to be used by remote clients to support drag-n-drop
1292 rearranging of the stack from e.g. the StackListbox.
1294 assert 0 <= from_index
1295 assert 0 <= to_index
1296 if from_index == to_index:
1298 head, n = [], from_index
1305 assert len(head) == from_index
1306 # now we have two cases:
1307 diff = from_index - to_index
1310 # so the destination index is still in the stack
1317 # so the destination is in the head list
1319 stack = head.pop(), stack
1323 stack = head.pop(), stack
1329 Return the nth item on the stack.
1331 :param stack stack: A stack.
1332 :param int n: An index into the stack.
1333 :raises ValueError: if ``n`` is less than zero.
1334 :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
1351 def inscribe_(stack, expression, dictionary):
1353 Create a new Joy function definition in the Joy dictionary. A
1354 definition is given as a quote with a name followed by a Joy
1355 expression. for example:
1357 [sqr dup mul] inscribe
1360 (name, body), stack = stack
1361 inscribe(Def(name, body), dictionary)
1362 return stack, expression, dictionary
1366 @SimpleFunctionWrapper
1371 getitem == drop first
1373 Expects an integer and a quote on the stack and returns the item at the
1374 nth position in the quote counting from 0.
1378 -------------------------
1382 n, (Q, stack) = stack
1383 return pick(Q, n), stack
1387 @SimpleFunctionWrapper
1392 drop == [rest] times
1394 Expects an integer and a quote on the stack and returns the quote with
1395 n items removed off the top.
1399 ----------------------
1403 n, (Q, stack) = stack
1408 raise StackUnderflowError
1414 @SimpleFunctionWrapper
1417 Expects an integer and a quote on the stack and returns the quote with
1418 just the top n items in reverse order (because that's easier and you can
1419 use reverse if needed.)
1423 ----------------------
1427 n, (Q, stack) = stack
1433 raise StackUnderflowError
1440 def gcd2(stack, expression, dictionary):
1441 '''Compiled GCD function.'''
1442 (v1, (v2, stack)) = stack
1447 (v1, (v2, stack)) = (v3, (v1, stack))
1448 return (v2, stack), expression, dictionary
1452 @SimpleFunctionWrapper
1455 Use a Boolean value to select one of two items.
1459 ----------------------
1464 ---------------------
1468 (if_, (then, (else_, stack))) = stack
1469 assert isinstance(if_, bool), repr(if_)
1470 return then if if_ else else_, stack
1474 @SimpleFunctionWrapper
1477 Use a Boolean value to select one of two items from a sequence.
1481 ------------------------
1486 -----------------------
1489 The sequence can contain more than two items but not fewer.
1490 Currently Python semantics are used to evaluate the "truthiness" of the
1491 Boolean value (so empty string, zero, etc. are counted as false, etc.)
1493 (flag, (choices, stack)) = stack
1494 (else_, (then, _)) = choices
1495 return then if flag else else_, stack
1499 @SimpleFunctionWrapper
1501 '''Given a list find the maximum.'''
1503 return max(iter_stack(tos)), stack
1507 @SimpleFunctionWrapper
1509 '''Given a list find the minimum.'''
1511 return min(iter_stack(tos)), stack
1515 @SimpleFunctionWrapper
1518 Given a quoted sequence of numbers return the sum.
1521 sum == 0 swap [+] step
1525 return sum(iter_stack(tos)), stack
1529 @SimpleFunctionWrapper
1532 Expects an item on the stack and a quote under it and removes that item
1533 from the the quote. The item is only removed once. If the list is
1534 empty or the item isn't in the list then the list is unchanged.
1538 ------------------------
1542 (item, (quote, stack)) = S
1543 return _remove(item, quote), stack
1546 def _remove(item, quote):
1547 try: head, tail = quote
1548 except ValueError: return quote
1549 return tail if head == item else (head, _remove(item, tail))
1553 @SimpleFunctionWrapper
1555 '''Given a list remove duplicate items.'''
1557 I = list(iter_stack(tos))
1558 return list_to_stack(sorted(set(I), key=I.index)), stack
1562 @SimpleFunctionWrapper
1564 '''Given a list return it sorted.'''
1566 return list_to_stack(sorted(iter_stack(tos))), stack
1570 @SimpleFunctionWrapper
1571 def disenstacken(stack):
1573 The disenstacken operator expects a list on top of the stack and makes that
1574 the stack discarding the rest of the stack.
1580 @SimpleFunctionWrapper
1583 Reverse the list on the top of the stack.
1586 reverse == [] swap shunt
1590 for term in iter_stack(tos):
1596 @SimpleFunctionWrapper
1599 Like concat but reverses the top list into the second.
1602 shunt == [swons] step == reverse swap concat
1604 [a b c] [d e f] shunt
1605 ---------------------------
1609 (tos, (second, stack)) = stack
1612 second = term, second
1613 return second, stack
1617 @SimpleFunctionWrapper
1620 Replace the two lists on the top of the stack with a list of the pairs
1621 from each list. The smallest list sets the length of the result list.
1623 (tos, (second, stack)) = S
1626 for a, b in zip(iter_stack(tos), iter_stack(second))
1628 return list_to_stack(accumulator), stack
1632 @SimpleFunctionWrapper
1634 '''Increment TOS.'''
1636 return tos + 1, stack
1640 @SimpleFunctionWrapper
1642 '''Decrement TOS.'''
1644 return tos - 1, stack
1648 @SimpleFunctionWrapper
1659 a, (b, stack) = stack
1660 p, m, = b + a, b - a
1661 return m, (p, stack)
1665 @SimpleFunctionWrapper
1668 Similarly to pm ("Plus or minus") this function computes
1673 ---------------------
1675 ---------------------
1678 Where: q * b + r == a
1683 return r, (q, stack)
1687 def sharing(stack, expression, dictionary):
1688 '''Print redistribution information.'''
1689 print("You may convey verbatim copies of the Program's source code as"
1690 ' you receive it, in any medium, provided that you conspicuously'
1691 ' and appropriately publish on each copy an appropriate copyright'
1692 ' notice; keep intact all notices stating that this License and'
1693 ' any non-permissive terms added in accord with section 7 apply'
1694 ' to the code; keep intact all notices of the absence of any'
1695 ' warranty; and give all recipients a copy of this License along'
1696 ' with the Program.'
1697 ' You should have received a copy of the GNU General Public License'
1698 ' along with Thun. If not see <http://www.gnu.org/licenses/>.')
1699 return stack, expression, dictionary
1703 def warranty(stack, expression, dictionary):
1704 '''Print warranty information.'''
1705 print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
1706 ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
1707 ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
1708 ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
1709 ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
1710 ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
1711 ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
1712 ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
1713 ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
1714 return stack, expression, dictionary
1718 def x(stack, expr, dictionary):
1724 ... [Q] x = ... [Q] dup i
1725 ... [Q] x = ... [Q] [Q] i
1726 ... [Q] x = ... [Q] Q
1731 return stack, push_quote(quote, expr), dictionary
1735 def b(stack, expr, dictionary):
1741 ... [P] [Q] b == ... [P] i [Q] i
1742 ... [P] [Q] b == ... P Q
1745 q, (p, (stack)) = stack
1748 expr = push_quote(q, expr)
1749 expr = push_quote(p, expr)
1750 return stack, expr, dictionary
1754 def ii(stack, expr, dictionary):
1763 quote, (a, stack) = stack
1765 expr = push_quote((a, quote), expr)
1766 expr = push_quote(quote, expr)
1767 return stack, expr, dictionary
1771 def dupdip(stack, expr, dictionary):
1775 [F] dupdip == dup [F] dip
1783 quote, stack = stack
1786 expr = push_quote((a, ()), expr)
1787 expr = push_quote(quote, expr)
1788 return stack, expr, dictionary
1792 def infra(stack, expr, dictionary):
1794 Accept a quoted program and a list on the stack and run the program
1795 with the list as its stack. Does not affect the rest of the stack.
1798 ... [a b c] [Q] . infra
1799 -----------------------------
1800 c b a . Q [...] swaack
1803 quote, aggregate, stack = get_n_items(2, stack)
1805 isnt_stack(aggregate)
1806 expr = push_quote((stack, (S_swaack, ())), expr)
1807 expr = push_quote(quote, expr)
1808 return aggregate, expr, dictionary
1812 def genrec(stack, expr, dictionary):
1814 General Recursion Combinator.
1817 [if] [then] [rec1] [rec2] genrec
1818 ---------------------------------------------------------------------
1819 [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
1821 From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
1822 "The genrec combinator takes four program parameters in addition to
1823 whatever data parameters it needs. Fourth from the top is an if-part,
1824 followed by a then-part. If the if-part yields true, then the then-part
1825 is executed and the combinator terminates. The other two parameters are
1826 the rec1-part and the rec2-part. If the if-part yields false, the
1827 rec1-part is executed. Following that the four program parameters and
1828 the combinator are again pushed onto the stack bundled up in a quoted
1829 form. Then the rec2-part is executed, where it will find the bundled
1830 form. Typically it will then execute the bundled form, either with i or
1831 with app2, or some other combinator."
1833 The way to design one of these is to fix your base case [then] and the
1834 test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
1835 a quotation of the whole function.
1837 For example, given a (general recursive) function 'F':
1840 F == [I] [T] [R1] [R2] genrec
1842 If the [I] if-part fails you must derive R1 and R2 from:
1847 Just set the stack arguments in front, and figure out what R1 and R2
1848 have to do to apply the quoted [F] in the proper way. In effect, the
1849 genrec combinator turns into an ifte combinator with a quoted copy of
1850 the original definition in the else-part:
1853 F == [I] [T] [R1] [R2] genrec
1854 == [I] [T] [R1 [F] R2] ifte
1856 Primitive recursive functions are those where R2 == i.
1859 P == [I] [T] [R] tailrec
1860 == [I] [T] [R [P] i] ifte
1861 == [I] [T] [R P] ifte
1864 rec2, rec1, then, if_, stack = get_n_items(4, stack)
1869 F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
1870 else_ = concat(rec1, (F, rec2))
1871 stack = (else_, (then, (if_, stack)))
1872 expr = push_quote((S_ifte, ()), expr)
1873 return stack, expr, dictionary
1877 def map_(stack, expr, dictionary):
1879 Run the quoted program on TOS on the items in the list under it, push a
1880 new list with the results in place of the program and original list.
1882 quote, aggregate, stack = get_n_items(2, stack)
1884 isnt_stack(aggregate)
1886 return (aggregate, stack), expr, dictionary
1888 for term in iter_stack(aggregate):
1890 batch = (s, (quote, (S_infra, (S_first, batch))))
1891 stack = (batch, ((), stack))
1892 expr = push_quote((S_infra, ()), expr)
1893 return stack, expr, dictionary
1897 def primrec(stack, expr, dictionary):
1899 From the "Overview of the language JOY":
1901 > The primrec combinator expects two quoted programs in addition to a
1902 data parameter. For an integer data parameter it works like this: If
1903 the data parameter is zero, then the first quotation has to produce
1904 the value to be returned. If the data parameter is positive then the
1905 second has to combine the data parameter with the result of applying
1906 the function to its predecessor.::
1910 > Then primrec tests whether the top element on the stack (initially
1911 the 5) is equal to zero. If it is, it pops it off and executes one of
1912 the quotations, the [1] which leaves 1 on the stack as the result.
1913 Otherwise it pushes a decremented copy of the top element and
1914 recurses. On the way back from the recursion it uses the other
1915 quotation, [*], to multiply what is now a factorial on top of the
1916 stack by the second element on the stack.::
1918 n [Base] [Recur] primrec
1920 0 [Base] [Recur] primrec
1921 ------------------------------
1924 n [Base] [Recur] primrec
1925 ------------------------------------------ n > 0
1926 n (n-1) [Base] [Recur] primrec Recur
1929 recur, base, n, stack = get_n_items(3, stack)
1933 expr = push_quote(base, expr)
1935 expr = push_quote(recur, expr)
1936 expr = push_quote((S_primrec, ()), expr)
1937 stack = recur, (base, (n - 1, (n, stack)))
1938 return stack, expr, dictionary
1942 def ifte(stack, expr, dictionary):
1944 If-Then-Else Combinator
1947 ... [if] [then] [else] ifte
1948 -------------------------------------------------------
1949 ... [else] [then] [...] [if] infra first choice i
1952 Has the effect of grabbing a copy of the stack on which to run the
1953 if-part using infra.
1955 else_, then, if_, stack = get_n_items(3, stack)
1956 expr = push_quote(_ifte_, expr)
1957 stack = (if_, (stack, (then, (else_, stack))))
1958 return stack, expr, dictionary
1962 def cond(stack, expr, dictionary):
1964 This combinator works like a case statement. It expects a single quote
1965 on the stack that must contain zero or more condition quotes and a
1966 default quote. Each condition clause should contain a quoted predicate
1967 followed by the function expression to run if that predicate returns
1968 true. If no predicates return true the default function runs.
1970 It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1976 [[[IF] THEN] [D]] cond
1977 ---------------------------- (with single condition, same as ifte)
1978 [IF] [THEN] [D] ifte
1981 [[[IF] THEN] ...] cond
1982 ----------------------------------- (multiple conditions)
1983 [IF] [THEN] [[...] cond] ifte
1986 The middle case isn't actually implemented. It's implied by the
1987 base case and the "multiple conditions" case.
1989 conditions, stack = get_n_items(1, stack)
1990 isnt_stack(conditions)
1992 raise StackUnderflowError('cond without default clause')
1994 condition_clause, conditions = conditions
1995 isnt_stack(condition_clause)
1997 if not conditions: # This is the default clause, run it.
1998 expr = push_quote(condition_clause, expr)
2000 if_, then = get_n_items(1, condition_clause)
2002 else_ = (conditions, (S_cond, ()))
2003 stack = (else_, (then, (if_, stack)))
2004 expr = push_quote((S_ifte, ()), expr)
2006 return stack, expr, dictionary
2010 def dipd(stack, expr, dictionary):
2012 The dipd combinator is like dip but expects two items.
2015 ----------------------
2019 quote, x, y, stack = get_n_items(3, stack)
2021 expr = push_quote((y, (x, ())), expr)
2022 expr = push_quote(quote, expr)
2023 return stack, expr, dictionary
2027 def dipdd(stack, expr, dictionary):
2029 The dipdd combinator is like dip but expects three items.
2032 -------------------------
2036 quote, x, y, z, stack = get_n_items(3, stack)
2038 expr = push_quote((z, (y, (x, ()))), expr)
2039 expr = push_quote(quote, expr)
2040 return stack, expr, dictionary
2044 def cmp_(stack, expr, dictionary):
2046 cmp takes two values and three quoted programs on the stack and runs
2047 one of the three depending on the results of comparing the two values:
2051 ------------------------- a > b
2055 ------------------------- a = b
2059 ------------------------- a < b
2062 L, E, G, b, a, stack = get_n_items(5, stack)
2068 expr = push_quote(G if a > b else L if a < b else E, expr)
2069 return stack, expr, dictionary
2073 def step(stack, expr, dictionary):
2075 Run a quoted program on each item in a sequence.
2079 ---------------------
2084 ----------------------
2088 ... [a b c] [Q] step
2089 ----------------------------
2090 ... a Q [b c] [Q] step
2092 The step combinator executes the quotation on each member of the list
2093 on top of the stack.
2095 quote, aggregate, stack = get_n_items(2, stack)
2097 isnt_stack(aggregate)
2099 return stack, expr, dictionary
2101 head, tail = aggregate
2104 expr = push_quote((tail, (quote, (S_step, ()))), expr)
2105 expr = push_quote(quote, expr)
2106 return stack, expr, dictionary
2110 def times(stack, expr, dictionary):
2112 times == [-- dip] cons [swap] infra [0 >] swap while pop
2116 --------------------- w/ n <= 0
2121 ---------------------
2126 --------------------------- w/ n > 1
2127 ... Q (n-1) [Q] times
2130 # times == [-- dip] cons [swap] infra [0 >] swap while pop
2131 quote, n, stack = get_n_items(2, stack)
2135 return stack, expr, dictionary
2138 expr = push_quote((n, (quote, (S_times, ()))), expr)
2139 expr = push_quote(quote, expr)
2140 return stack, expr, dictionary
2144 @SimpleFunctionWrapper
2145 def _Tree_add_Ee(stack):
2149 ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1])
2152 (a1, (a2, (a3, ((a4, (a5, s1)), s2)))) = stack
2153 return ((a2, (a3, s1)), s2)
2157 @SimpleFunctionWrapper
2158 def _Tree_delete_R0(stack):
2162 ([a2 ...1] a1 -- [a2 ...1] a2 a1 a1)
2165 (a1, ((a2, s1), s2)) = stack
2166 return (a1, (a1, (a2, ((a2, s1), s2))))
2170 @SimpleFunctionWrapper
2171 def _Tree_delete_clear_stuff(stack):
2175 (a3 a2 [a1 ...1] -- [...1])
2178 ((a1, s1), (a2, (a3, s2))) = stack
2183 @SimpleFunctionWrapper
2184 def _Tree_get_E(stack):
2188 ([a3 a4 ...1] a2 a1 -- a4)
2191 (a1, (a2, ((a3, (a4, s1)), s2))) = stack
2196 @SimpleFunctionWrapper
2201 (a2 a1 [...1] -- [a2 a1 ...1])
2204 (s1, (a1, (a2, s2))) = stack
2205 return ((a2, (a1, s1)), s2)
2212 ## (a1 [...0] -- [a1 ...0])
2215 ## try: s0, stack = stack
2216 ## except ValueError: raise StackUnderflowError('Not enough values on stack.')
2217 ## if not isinstance(s0, tuple): raise NotAListError('Not a list.')
2218 ## try: a1, s23 = stack
2219 ## except ValueError: raise StackUnderflowError('Not enough values on stack.')
2220 ## return ((a1, s0), s23)
2230 ## (a1, s23) = stack
2231 ## return (a1, (a1, s23))
2235 @SimpleFunctionWrapper
2243 (a1, (a2, s23)) = stack
2244 return (a1, (a2, (a2, s23)))
2248 @SimpleFunctionWrapper
2253 (a3 a2 a1 -- a3 a3 a2 a1)
2256 (a1, (a2, (a3, s23))) = stack
2257 return (a1, (a2, (a3, (a3, s23))))
2264 ## ([a1 ...1] -- a1)
2267 ## ((a1, s1), s23) = stack
2272 @SimpleFunctionWrapper
2273 def first_two(stack):
2277 ([a1 a2 ...1] -- a1 a2)
2280 ((a1, (a2, s1)), s2) = stack
2281 return (a2, (a1, s2))
2285 @SimpleFunctionWrapper
2290 ([a1 a2 a3 a4 ...1] -- a4)
2293 ((a1, (a2, (a3, (a4, s1)))), s2) = stack
2298 @SimpleFunctionWrapper
2306 (a1, (a2, s23)) = stack
2307 return (a2, (a1, (a2, s23)))
2318 ## (a1, s23) = stack
2319 ## except ValueError:
2320 ## raise StackUnderflowError('Cannot pop empty stack.')
2325 @SimpleFunctionWrapper
2333 (a1, (a2, s23)) = stack
2338 @SimpleFunctionWrapper
2346 (a1, (a2, (a3, s23))) = stack
2347 return (a1, (a2, s23))
2351 @SimpleFunctionWrapper
2359 (a1, (a2, s23)) = stack
2364 @SimpleFunctionWrapper
2372 (a1, (a2, (a3, s23))) = stack
2377 @SimpleFunctionWrapper
2382 (a4 a3 a2 a1 -- a2 a1)
2385 (a1, (a2, (a3, (a4, s23)))) = stack
2386 return (a1, (a2, s23))
2393 ## ([a1 ...0] -- [...0])
2397 ## s0, stack = stack
2398 ## except ValueError:
2399 ## raise StackUnderflowError
2400 ## if not isinstance(s0, tuple):
2401 ## raise NotAListError('Not a list.')
2404 ## except ValueError:
2405 ## raise StackUnderflowError('Cannot take rest of empty list.')
2406 ## return (s1, stack)
2410 @SimpleFunctionWrapper
2411 def rolldown(stack):
2415 (a1 a2 a3 -- a2 a3 a1)
2418 (a3, (a2, (a1, s23))) = stack
2419 return (a1, (a3, (a2, s23)))
2423 @SimpleFunctionWrapper
2428 (a1 a2 a3 -- a3 a1 a2)
2431 (a3, (a2, (a1, s23))) = stack
2432 return (a2, (a1, (a3, s23)))
2436 @SimpleFunctionWrapper
2441 ([a1 a2 ...1] -- [...1])
2444 ((a1, (a2, s1)), s2) = stack
2449 @SimpleFunctionWrapper
2454 ([a1 a2 ...1] -- a2)
2457 ((a1, (a2, s1)), s2) = stack
2462 @SimpleFunctionWrapper
2475 @SimpleFunctionWrapper
2476 def stuncons(stack):
2480 (... a1 -- ... a1 a1 [...])
2484 return (s1, (a1, (a1, s1)))
2488 @SimpleFunctionWrapper
2489 def stununcons(stack):
2493 (... a2 a1 -- ... a2 a1 a1 a2 [...])
2496 (a1, (a2, s1)) = stack
2497 return (s1, (a2, (a1, (a1, (a2, s1)))))
2500 ##def swaack(stack):
2504 ## ([...1] -- [...0])
2509 ## except ValueError:
2510 ## raise StackUnderflowError('Not enough values on stack.')
2511 ## if not isinstance(s1, tuple):
2512 ## raise NotAListError('Not a list.')
2524 ## (a2, (a1, s23)) = stack
2525 ## except ValueError:
2526 ## raise StackUnderflowError('Not enough values on stack.')
2527 ## return (a1, (a2, s23))
2531 @SimpleFunctionWrapper
2536 ([...1] a1 -- [a1 ...1])
2539 (a1, (s1, s2)) = stack
2540 return ((a1, s1), s2)
2544 @SimpleFunctionWrapper
2549 ([a1 a2 a3 ...1] -- a3)
2552 ((a1, (a2, (a3, s1))), s2) = stack
2557 @SimpleFunctionWrapper
2565 (a1, (a2, s23)) = stack
2566 return (a1, (a2, (a1, s23)))
2570 @SimpleFunctionWrapper
2575 ([a1 ...0] -- a1 [...0])
2578 ((a1, s0), s23) = stack
2579 return (s0, (a1, s23))
2583 @SimpleFunctionWrapper
2592 return ((a1, ()), s23)
2596 @SimpleFunctionWrapper
2601 ([a1 ...1] -- [...1] a1)
2604 ((a1, s1), s2) = stack
2605 return (a1, (s1, s2))
2608 def default_defs(dictionary):
2609 Def.load_definitions(DEFS, dictionary)
2612 if __name__ == '__main__':
2615 J = interp if '-q' in sys.argv else repl
2616 dictionary = initialize()
2617 default_defs(dictionary)
2619 stack = J(dictionary=dictionary)
2622 ## jcode = "5 10 [>][++][*]ifte"
2623 ## jcode = '1 2 [[+]] cond'
2624 ## jcode = '1 2 [[[>] -] [[<] +] [*]] cond'
2625 ## jcode = '2 1 [[[>] -] [[<] +] [*]] cond'
2626 ## jcode = '3 3 [[[>] -] [[<] +] [*]] cond'
2627 ## jcode = '3 dup [dup mul] times'
2628 ## jcode = '0 [1 2 3] [add] step'
2629 ## stack, _ = run(jcode, (), dictionary)
2630 ##print(stack_to_string(stack))