1256 lines
40 KiB
Python
Executable File
1256 lines
40 KiB
Python
Executable File
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# Copyright © 2022 Simon Forman
|
|
#
|
|
# This file is part of Thun
|
|
#
|
|
# Thun is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# Thun is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with Thun. If not see <http://www.gnu.org/licenses/>.
|
|
#
|
|
'''
|
|
████████╗██╗ ██╗██╗ ██╗███╗ ██╗
|
|
╚══██╔══╝██║ ██║██║ ██║████╗ ██║
|
|
██║ ███████║██║ ██║██╔██╗ ██║
|
|
██║ ██╔══██║██║ ██║██║╚██╗██║
|
|
██║ ██║ ██║╚██████╔╝██║ ╚████║
|
|
╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
|
|
This script implements an interpreter for a dialect of Joy.
|
|
|
|
'''
|
|
from functools import wraps
|
|
from inspect import getdoc
|
|
from re import Scanner
|
|
from traceback import print_exc
|
|
import operator
|
|
|
|
|
|
class NotAListError(Exception):
|
|
'''
|
|
Raised when a stack is expected but not received.
|
|
'''
|
|
|
|
|
|
class NotAnIntError(Exception):
|
|
pass
|
|
|
|
|
|
class NotABoolError(Exception):
|
|
pass
|
|
|
|
|
|
class ParseError(ValueError):
|
|
'''
|
|
Raised when there is a error while parsing text.
|
|
'''
|
|
|
|
|
|
class StackUnderflowError(Exception):
|
|
pass
|
|
|
|
|
|
class UnknownSymbolError(KeyError):
|
|
pass
|
|
|
|
|
|
def isnt_int(i):
|
|
'''
|
|
Raise NotAnIntError if i isn't an integer.
|
|
(Booleans are not integers in Joy.)
|
|
'''
|
|
if not isinstance(i, int) or isinstance(i, bool):
|
|
raise NotAnIntError(f'Not an integer: {_s(i)}')
|
|
|
|
|
|
def isnt_bool(b):
|
|
'''
|
|
Raise NotABoolError if b isn't a Boolean.
|
|
'''
|
|
if not isinstance(b, bool):
|
|
raise NotABoolError(f'Not a Boolean value: {_s(b)}')
|
|
|
|
|
|
def isnt_stack(el):
|
|
'''
|
|
Raise NotAListError if el isn't a stack/quote/list.
|
|
'''
|
|
if not isinstance(el, tuple):
|
|
raise NotAListError(f'Not a list: {_s(el)}')
|
|
|
|
|
|
'''
|
|
██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██████╗ ███████╗████████╗███████╗██████╗
|
|
██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗
|
|
██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██████╔╝█████╗ ██║ █████╗ ██████╔╝
|
|
██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔═══╝ ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██╗
|
|
██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║ ██║███████╗ ██║ ███████╗██║ ██║
|
|
╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
'''
|
|
|
|
|
|
def joy(stack, expr, dictionary):
|
|
'''
|
|
Evaluate a Joy expression on a stack.
|
|
|
|
This function iterates through a sequence of terms.
|
|
Literals are put onto the stack and Symbols are
|
|
looked up in the dictionary and the functions they
|
|
denote are executed.
|
|
|
|
: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)
|
|
|
|
'''
|
|
while expr:
|
|
term, expr = expr
|
|
if isinstance(term, Symbol):
|
|
try:
|
|
func = dictionary[term]
|
|
except KeyError:
|
|
raise UnknownSymbolError(term) from None
|
|
stack, expr, dictionary = func(stack, expr, dictionary)
|
|
else:
|
|
stack = term, stack
|
|
return stack, expr, dictionary
|
|
|
|
|
|
'''
|
|
██████╗ █████╗ ██████╗ ███████╗███████╗██████╗
|
|
██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗
|
|
██████╔╝███████║██████╔╝███████╗█████╗ ██████╔╝
|
|
██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗
|
|
██║ ██║ ██║██║ ██║███████║███████╗██║ ██║
|
|
╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝
|
|
|
|
There is a single function for converting text to joy expressions
|
|
as well as a Symbol class and an Exception type. The Symbol
|
|
string class is used by the interpreter to recognize literals by
|
|
the fact that they are not Symbol objects.
|
|
|
|
A crude grammar::
|
|
|
|
joy := <term>*
|
|
term := <integer> | 'true' | 'false' | '[' <joy> ']' | <symbol>
|
|
|
|
A Joy expression is a sequence of zero or more terms. A term is a
|
|
literal value (integer, Boolean, or quoted Joy expression) or a function symbol.
|
|
Function symbols are sequences of non-blanks and cannot contain square
|
|
brackets. Terms must be separated by blanks, which can be omitted
|
|
around square brackets.
|
|
'''
|
|
|
|
|
|
JOY_BOOL_LITERALS = _F, _T = 'false', 'true'
|
|
|
|
|
|
BRACKETS = r'\[|\]' # Left or right square bracket.
|
|
BLANKS = r'\s+' # One-or-more blankspace.
|
|
WORDS = (
|
|
'[' # Character class
|
|
'^' # not a
|
|
'[' # left square bracket nor a
|
|
r'\]' # right square bracket (escaped so it doesn't close the character class)
|
|
r'\s' # nor blankspace
|
|
']+' # end character class, one-or-more.
|
|
)
|
|
|
|
|
|
token_scanner = Scanner(
|
|
[
|
|
(BRACKETS, lambda _, token: token),
|
|
(BLANKS, None),
|
|
(WORDS, lambda _, token: token),
|
|
]
|
|
)
|
|
|
|
|
|
class Symbol(str):
|
|
'''
|
|
A string class that represents Joy function names.
|
|
'''
|
|
|
|
__repr__ = str.__str__
|
|
|
|
|
|
def text_to_expression(text):
|
|
'''
|
|
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.
|
|
|
|
:param str text: Text to convert.
|
|
:rtype: stack
|
|
:raises ParseError: if the parse fails.
|
|
'''
|
|
return _parse(_tokenize(text))
|
|
|
|
|
|
def _tokenize(text):
|
|
'''Convert a text into a stream of tokens.
|
|
|
|
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
|
|
|
|
|
|
def _parse(tokens):
|
|
'''
|
|
Return a stack/list expression of the tokens.
|
|
'''
|
|
frame = []
|
|
stack = []
|
|
for tok in tokens:
|
|
if tok == '[':
|
|
stack.append(frame)
|
|
frame = []
|
|
elif tok == ']':
|
|
v = frame
|
|
try:
|
|
frame = stack.pop()
|
|
except IndexError:
|
|
raise ParseError('Extra closing bracket.') from None
|
|
frame.append(list_to_stack(v))
|
|
elif tok == _T:
|
|
frame.append(True)
|
|
elif tok == _F:
|
|
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)
|
|
|
|
|
|
r'''
|
|
███████╗████████╗ █████╗ ██████╗██╗ ██╗
|
|
██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
|
|
███████╗ ██║ ███████║██║ █████╔╝
|
|
╚════██║ ██║ ██╔══██║██║ ██╔═██╗
|
|
███████║ ██║ ██║ ██║╚██████╗██║ ██╗
|
|
╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
|
|
|
|
When talking about Joy we use the terms "stack", "quote", "sequence",
|
|
"list", and others to mean the same thing: a simple linear datatype that
|
|
permits certain operations such as iterating and pushing and popping
|
|
values from (at least) one end.
|
|
|
|
In describing Joy I have used the term quotation to describe all of the
|
|
above, because I needed a word to describe the arguments to combinators
|
|
which fulfill the same role in Joy as lambda abstractions (with
|
|
variables) fulfill in the more familiar functional languages. I use the
|
|
term list for those quotations whose members are what I call literals:
|
|
numbers, characters, truth values, sets, strings and other quotations.
|
|
All these I call literals because their occurrence in code results in
|
|
them being pushed onto the stack. But I also call [London Paris] a list.
|
|
So, [dup \*] is a quotation but not a list.
|
|
|
|
`"A Conversation with Manfred von Thun" w/ Stevan Apter <http://archive.vector.org.uk/art10000350>`_
|
|
|
|
There is no "Stack" Python class, instead we use the `cons list`_, a
|
|
venerable two-tuple recursive sequence datastructure, where the
|
|
empty tuple ``()`` is the empty stack and ``(head, rest)`` gives the
|
|
recursive form of a stack with one or more items on it::
|
|
|
|
stack := () | (item, stack)
|
|
|
|
Putting some numbers onto a stack::
|
|
|
|
()
|
|
(1, ())
|
|
(2, (1, ()))
|
|
(3, (2, (1, ())))
|
|
...
|
|
|
|
Python has very nice "tuple packing and unpacking" in its syntax which
|
|
means we can directly "unpack" the expected arguments to a Joy function.
|
|
We assign the argument stack to the expected structure of the stack and
|
|
Python takes care of unpacking the
|
|
incoming tuple and assigning values to the names. (Note that Python
|
|
syntax doesn't require parentheses around tuples used in expressions
|
|
where they would be redundant.)
|
|
|
|
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
|
|
list and another to iterate through a stack and yield its items
|
|
one-by-one in order. There are also two functions to generate string representations
|
|
of stacks. They only differ in that one prints the terms in stack from left-to-right while the other prints from right-to-left. In both functions *internal stacks* are
|
|
printed left-to-right. These functions are written to support :doc:`../pretty`.
|
|
|
|
.. _cons list: https://en.wikipedia.org/wiki/Cons#Lists
|
|
|
|
'''
|
|
|
|
|
|
def list_to_stack(el, stack=()):
|
|
'''
|
|
Convert a Python list (or other sequence) to a Joy stack::
|
|
|
|
[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. This
|
|
allows for concatinating Python lists (or other sequence objects)
|
|
onto an existing Joy stack.
|
|
:rtype: stack
|
|
|
|
'''
|
|
for item in reversed(el):
|
|
stack = item, stack
|
|
return stack
|
|
|
|
|
|
def iter_stack(stack):
|
|
'''
|
|
Iterate through the items on the stack.
|
|
|
|
:param stack stack: A stack.
|
|
:rtype: iterator
|
|
'''
|
|
while stack:
|
|
item, stack = stack
|
|
yield item
|
|
|
|
|
|
def concat(quote, expression):
|
|
'''
|
|
Concatinate quote onto expression.
|
|
|
|
In joy [1 2] [3 4] would become [1 2 3 4].
|
|
|
|
:param stack quote: A stack.
|
|
:param stack expression: A stack.
|
|
:rtype: stack
|
|
'''
|
|
# This (below) is the fastest implementation, but will trigger
|
|
# RuntimeError: maximum recursion depth exceeded
|
|
# on quotes longer than sys.getrecursionlimit().
|
|
# :raises RuntimeError: if quote is larger than sys.getrecursionlimit().
|
|
|
|
## return (quote[0], concat(quote[1], expression)) if quote else expression
|
|
|
|
# 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.)
|
|
|
|
isnt_stack(quote)
|
|
isnt_stack(expression)
|
|
temp = []
|
|
while quote:
|
|
item, quote = quote
|
|
temp.append(item)
|
|
for item in reversed(temp):
|
|
expression = item, expression
|
|
return expression
|
|
|
|
|
|
def get_n_items(n, stack):
|
|
'''
|
|
Return items and remainder of stack.
|
|
Raise StackUnderflowError if there are fewer than n items on the stack.
|
|
'''
|
|
assert n > 0, repr(n)
|
|
temp = []
|
|
while n > 0:
|
|
n -= 1
|
|
try:
|
|
item, stack = stack
|
|
except ValueError:
|
|
raise StackUnderflowError(
|
|
'Not enough values on stack.'
|
|
) from None
|
|
temp.append(item)
|
|
temp.append(stack)
|
|
return tuple(temp)
|
|
|
|
|
|
def reversed_stack(stack):
|
|
'''
|
|
Return list_reverseiterator object for a stack.
|
|
'''
|
|
return reversed(list(iter_stack(stack)))
|
|
|
|
|
|
'''
|
|
██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗
|
|
██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗
|
|
██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝
|
|
██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗
|
|
██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║
|
|
╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
'''
|
|
|
|
|
|
def stack_to_string(stack):
|
|
'''
|
|
Return a "pretty print" string for a stack.
|
|
|
|
The items are written right-to-left::
|
|
|
|
(top, (second, ...)) -> '... second top'
|
|
|
|
:param stack stack: A stack.
|
|
:rtype: str
|
|
'''
|
|
return _to_string(stack, reversed_stack)
|
|
|
|
|
|
def expression_to_string(expression):
|
|
'''
|
|
Return a "pretty print" string for a expression.
|
|
|
|
The items are written left-to-right::
|
|
|
|
(top, (second, ...)) -> 'top second ...'
|
|
|
|
:param stack expression: A stack.
|
|
:rtype: str
|
|
'''
|
|
return _to_string(expression, iter_stack)
|
|
|
|
|
|
def _joy_repr(thing):
|
|
return (
|
|
JOY_BOOL_LITERALS[thing]
|
|
if isinstance(thing, bool)
|
|
else 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)))
|
|
|
|
|
|
def _s(s):
|
|
return (
|
|
'[%s]' % expression_to_string(s)
|
|
if isinstance(s, tuple)
|
|
else _joy_repr(s)
|
|
)
|
|
|
|
|
|
'''
|
|
██████╗ ███████╗██████╗ ██╗
|
|
██╔══██╗██╔════╝██╔══██╗██║
|
|
██████╔╝█████╗ ██████╔╝██║
|
|
██╔══██╗██╔══╝ ██╔═══╝ ██║
|
|
██║ ██║███████╗██║ ███████╗
|
|
╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝
|
|
|
|
Read-Evaluate-Print Loop
|
|
|
|
'''
|
|
|
|
|
|
def repl(stack=(), dictionary=None):
|
|
'''
|
|
Read-Evaluate-Print 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
|
|
|
|
'''
|
|
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 SystemExit as e:
|
|
raise SystemExit from e
|
|
except:
|
|
print_exc()
|
|
except SystemExit as e:
|
|
raise SystemExit from e
|
|
except:
|
|
print_exc()
|
|
print()
|
|
return stack
|
|
|
|
|
|
def run(text, stack, dictionary):
|
|
'''
|
|
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.
|
|
:rtype: (stack, (), dictionary)
|
|
|
|
'''
|
|
expr = text_to_expression(text)
|
|
return joy(stack, expr, dictionary)
|
|
|
|
|
|
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 StackUnderflowError as e:
|
|
print(e) # 'Not enough values on stack.'
|
|
except NotAnIntError as e:
|
|
print(e)
|
|
except NotAListError as e:
|
|
print(e)
|
|
except SystemExit as e:
|
|
raise SystemExit from e
|
|
except:
|
|
print_exc()
|
|
print(stack_to_string(stack))
|
|
except SystemExit as e:
|
|
raise SystemExit from e
|
|
except:
|
|
print_exc()
|
|
return stack
|
|
|
|
|
|
'''
|
|
██████╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██████╗ ██╗ ██╗
|
|
██╔══██╗██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝
|
|
██║ ██║██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████║██████╔╝ ╚████╔╝
|
|
██║ ██║██║██║ ██║ ██║██║ ██║██║╚██╗██║██╔══██║██╔══██╗ ╚██╔╝
|
|
██████╔╝██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║██║ ██║██║ ██║ ██║
|
|
╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝
|
|
'''
|
|
|
|
|
|
# This is the main dict we're building.
|
|
_dictionary = {}
|
|
|
|
|
|
def inscribe(function, d=_dictionary):
|
|
'''
|
|
A decorator to inscribe functions into the default dictionary.
|
|
'''
|
|
d[function.__name__.rstrip('_')] = function
|
|
return function
|
|
|
|
|
|
def initialize():
|
|
'''
|
|
Return a dictionary of Joy functions for use with joy().
|
|
'''
|
|
return _dictionary.copy()
|
|
|
|
|
|
def SimpleFunctionWrapper(f):
|
|
'''
|
|
Wrap functions that take and return just a stack.
|
|
'''
|
|
|
|
@wraps(f)
|
|
def SimpleFunctionWrapper_inner(stack, expr, dictionary):
|
|
return f(stack), expr, dictionary
|
|
|
|
return SimpleFunctionWrapper_inner
|
|
|
|
|
|
@inscribe
|
|
def words(stack, expression, dictionary):
|
|
'''
|
|
Put a list of all the words in alphabetical order onto the stack.
|
|
'''
|
|
w = ()
|
|
for name in reversed(sorted(dictionary)):
|
|
if name.startswith('_'):
|
|
continue
|
|
w = (Symbol(name), ()), w
|
|
return (w, stack), expression, dictionary
|
|
|
|
|
|
HELP_TEMPLATE = '''\
|
|
|
|
==== Help on %s ====
|
|
|
|
%s
|
|
|
|
---- end ( %s )
|
|
'''
|
|
|
|
|
|
@inscribe
|
|
def help_(stack, expression, dictionary):
|
|
'''
|
|
Accepts a quoted symbol on the top of the stack and prints its docs.
|
|
'''
|
|
((symbol, _), stack) = stack
|
|
word = dictionary[symbol]
|
|
print(HELP_TEMPLATE % (symbol, getdoc(word), symbol))
|
|
return stack, expression, dictionary
|
|
|
|
|
|
'''
|
|
██████╗ ██████╗ ███╗ ███╗██████╗ ██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗
|
|
██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝
|
|
██║ ██║ ██║██╔████╔██║██████╔╝██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝███████╗
|
|
██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║
|
|
╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║
|
|
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
|
'''
|
|
|
|
|
|
@inscribe
|
|
def branch(stack, expr, dictionary):
|
|
'''
|
|
Use a Boolean value to select one of two quoted programs to run.
|
|
|
|
::
|
|
|
|
branch == roll< choice i
|
|
|
|
::
|
|
|
|
False [F] [T] branch
|
|
--------------------------
|
|
F
|
|
|
|
True [F] [T] branch
|
|
-------------------------
|
|
T
|
|
|
|
'''
|
|
then, else_, flag, stack = get_n_items(3, stack)
|
|
isnt_bool(flag)
|
|
isnt_stack(else_)
|
|
isnt_stack(then)
|
|
do = then if flag else else_
|
|
return stack, concat(do, expr), dictionary
|
|
|
|
|
|
@inscribe
|
|
def dip(stack, expr, 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.
|
|
::
|
|
|
|
... x [Q] dip
|
|
-------------------
|
|
... Q x
|
|
|
|
'''
|
|
quote, x, stack = get_n_items(2, stack)
|
|
return stack, concat(quote, (x, expr)), dictionary
|
|
|
|
|
|
@inscribe
|
|
def i(stack, expr, dictionary):
|
|
'''
|
|
The i combinator expects a quoted program on the stack and unpacks it
|
|
onto the pending expression for evaluation.
|
|
::
|
|
|
|
[Q] i
|
|
-----------
|
|
Q
|
|
|
|
'''
|
|
quote, stack = get_n_items(1, stack)
|
|
return stack, concat(quote, expr), dictionary
|
|
|
|
|
|
LOOP = Symbol('loop')
|
|
|
|
|
|
@inscribe
|
|
def loop(stack, expr, dictionary):
|
|
'''
|
|
Basic loop combinator.
|
|
::
|
|
|
|
... True [Q] loop
|
|
-----------------------
|
|
... Q [Q] loop
|
|
|
|
... False [Q] loop
|
|
------------------------
|
|
...
|
|
|
|
'''
|
|
quote, flag, stack = get_n_items(2, stack)
|
|
isnt_bool(flag)
|
|
isnt_stack(quote)
|
|
if flag:
|
|
expr = concat(quote, (quote, (LOOP, expr)))
|
|
return stack, expr, dictionary
|
|
|
|
|
|
@inscribe
|
|
def halt(stack, expr, dictionary):
|
|
'''
|
|
Put the pending expression onto the stack and halt.
|
|
'''
|
|
return (expr, stack), (), dictionary
|
|
|
|
|
|
@inscribe
|
|
def quit(stack, expr, dictionary):
|
|
'''
|
|
Stop the interpreter.
|
|
'''
|
|
raise SystemExit
|
|
|
|
|
|
'''
|
|
██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗
|
|
██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║ ██║██╔═══██╗██╔══██╗██╔══██╗██╔════╝
|
|
██║ ██║ ██║██████╔╝█████╗ ██║ █╗ ██║██║ ██║██████╔╝██║ ██║███████╗
|
|
██║ ██║ ██║██╔══██╗██╔══╝ ██║███╗██║██║ ██║██╔══██╗██║ ██║╚════██║
|
|
╚██████╗╚██████╔╝██║ ██║███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██████╔╝███████║
|
|
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝
|
|
'''
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def clear(stack):
|
|
'''
|
|
Clear everything from the stack.
|
|
::
|
|
|
|
clear == stack [pop stack] loop
|
|
|
|
... clear
|
|
---------------
|
|
|
|
'''
|
|
return ()
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def concat_(stack):
|
|
'''
|
|
Concatinate the two lists on the top of the stack.
|
|
::
|
|
|
|
[a b c] [d e f] concat
|
|
----------------------------
|
|
[a b c d e f]
|
|
|
|
'''
|
|
tos, second, stack = get_n_items(2, stack)
|
|
return concat(second, tos), stack
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def cons(stack):
|
|
'''
|
|
Given an item and a list, append the item to the list to make a new list.
|
|
::
|
|
|
|
a [...] cons
|
|
------------------
|
|
[a ...]
|
|
|
|
Cons is a venerable old function from Lisp
|
|
( https://en.wikipedia.org/wiki/Cons#Lists ).
|
|
Its inverse operation is uncons.
|
|
'''
|
|
s0, a1, stack = get_n_items(2, stack)
|
|
isnt_stack(s0)
|
|
return ((a1, s0), stack)
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def dup(stack):
|
|
'''
|
|
"Dup"licate the top item on the stack.
|
|
::
|
|
|
|
a dup
|
|
-----------
|
|
a a
|
|
|
|
'''
|
|
a1, stack = get_n_items(1, stack)
|
|
return a1, (a1, stack)
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def first(stack):
|
|
'''
|
|
Replace a list with its first item.
|
|
|
|
[a ...]
|
|
--------------
|
|
a
|
|
|
|
'''
|
|
s0, stack = get_n_items(1, stack)
|
|
isnt_stack(s0)
|
|
a1, _ = get_n_items(1, s0)
|
|
return a1, stack
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def pop(stack):
|
|
'''
|
|
Pop the top item from the stack and discard it.
|
|
|
|
a pop
|
|
-----------
|
|
|
|
'''
|
|
try:
|
|
_, stack = stack
|
|
except ValueError:
|
|
raise StackUnderflowError('Cannot pop empty stack.') from None
|
|
return stack
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def rest(stack):
|
|
'''
|
|
Replace a list with its tail.
|
|
|
|
[a b c] rest
|
|
------------------
|
|
[b c]
|
|
|
|
'''
|
|
s0, stack = get_n_items(1, stack)
|
|
isnt_stack(s0)
|
|
try:
|
|
_, s1 = s0
|
|
except ValueError:
|
|
raise StackUnderflowError(
|
|
'Cannot take rest of empty list.'
|
|
) from None
|
|
return s1, stack
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def stack(stack):
|
|
'''
|
|
Put the stack onto the stack.
|
|
|
|
... c b a stack
|
|
---------------------------
|
|
... c b a [a b c ...]
|
|
|
|
'''
|
|
return stack, stack
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def swaack(stack):
|
|
'''
|
|
Swap stack. Take a list from the top of the stack, replace the stack
|
|
with the list, and put the old stack onto it.
|
|
|
|
1 2 3 [4 5 6] swaack
|
|
--------------------------
|
|
6 5 4 [3 2 1]
|
|
|
|
'''
|
|
s1, s0 = get_n_items(1, stack)
|
|
isnt_stack(s1)
|
|
return s0, s1
|
|
|
|
|
|
@inscribe
|
|
@SimpleFunctionWrapper
|
|
def swap(stack):
|
|
'''
|
|
Swap the top two items on the stack.
|
|
|
|
a b swap
|
|
--------------
|
|
b a
|
|
|
|
'''
|
|
a2, a1, stack = get_n_items(2, stack)
|
|
return (a1, (a2, stack))
|
|
|
|
|
|
def BinaryLogicWrapper(f):
|
|
'''
|
|
Wrap functions that take two numbers and return a single result.
|
|
'''
|
|
|
|
@wraps(f)
|
|
def BinaryLogicWrapper_inner(stack, expression, dictionary):
|
|
a, b, stack = get_n_items(2, stack)
|
|
isnt_bool(a)
|
|
isnt_bool(b)
|
|
result = f(b, a)
|
|
return (result, stack), expression, dictionary
|
|
|
|
return BinaryLogicWrapper_inner
|
|
|
|
|
|
def BinaryMathWrapper(func):
|
|
'''
|
|
Wrap functions that take two numbers and return a single result.
|
|
'''
|
|
|
|
@wraps(func)
|
|
def BinaryMathWrapper_inner(stack, expression, dictionary):
|
|
a, b, stack = get_n_items(2, stack)
|
|
isnt_int(a)
|
|
isnt_int(b)
|
|
result = func(b, a)
|
|
return (result, stack), expression, dictionary
|
|
|
|
return BinaryMathWrapper_inner
|
|
|
|
|
|
def UnaryLogicWrapper(f):
|
|
'''
|
|
Wrap functions that take one argument and return a single result.
|
|
'''
|
|
|
|
@wraps(f)
|
|
def UnaryLogicWrapper_inner(stack, expression, dictionary):
|
|
a, stack = get_n_items(1, stack)
|
|
isnt_bool(a)
|
|
result = f(a)
|
|
return (result, stack), expression, dictionary
|
|
|
|
return UnaryLogicWrapper_inner
|
|
|
|
|
|
def UnaryMathWrapper(f):
|
|
'''
|
|
Wrap functions that take one argument and return a single result.
|
|
'''
|
|
|
|
@wraps(f)
|
|
def UnaryMathWrapper_inner(stack, expression, dictionary):
|
|
a, stack = get_n_items(1, stack)
|
|
isnt_int(a)
|
|
result = f(a)
|
|
return (result, stack), expression, dictionary
|
|
|
|
return UnaryMathWrapper_inner
|
|
|
|
|
|
def UnaryWrapper(f):
|
|
'''
|
|
Wrap functions that take one argument and return a single result.
|
|
'''
|
|
|
|
@wraps(f)
|
|
def UnaryWrapper_inner(stack, expression, dictionary):
|
|
a, stack = get_n_items(1, stack)
|
|
result = f(a)
|
|
return (result, stack), expression, dictionary
|
|
|
|
return UnaryWrapper_inner
|
|
|
|
|
|
for F in (
|
|
## ██████╗ ██████╗ ███╗ ███╗██████╗ █████╗ ██████╗ ██╗███████╗██╗ ██████╗ ███╗ ██╗
|
|
##██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║██╔════╝██║██╔═══██╗████╗ ██║
|
|
##██║ ██║ ██║██╔████╔██║██████╔╝███████║██████╔╝██║███████╗██║██║ ██║██╔██╗ ██║
|
|
##██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██╔══██║██╔══██╗██║╚════██║██║██║ ██║██║╚██╗██║
|
|
##╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║██║ ██║██║███████║██║╚██████╔╝██║ ╚████║
|
|
## ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝
|
|
BinaryMathWrapper(operator.eq),
|
|
BinaryMathWrapper(operator.ge),
|
|
BinaryMathWrapper(operator.gt),
|
|
BinaryMathWrapper(operator.le),
|
|
BinaryMathWrapper(operator.lt),
|
|
BinaryMathWrapper(operator.ne),
|
|
##██╗ ██████╗ ██████╗ ██╗ ██████╗
|
|
##██║ ██╔═══██╗██╔════╝ ██║██╔════╝
|
|
##██║ ██║ ██║██║ ███╗██║██║
|
|
##██║ ██║ ██║██║ ██║██║██║
|
|
##███████╗╚██████╔╝╚██████╔╝██║╚██████╗
|
|
##╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝
|
|
UnaryWrapper(bool), # Convert any value to Boolean.
|
|
# (The only polymorphic function.)
|
|
BinaryLogicWrapper(operator.xor),
|
|
BinaryLogicWrapper(operator.and_),
|
|
BinaryLogicWrapper(operator.or_),
|
|
UnaryLogicWrapper(operator.not_),
|
|
##███╗ ███╗ █████╗ ████████╗██╗ ██╗
|
|
##████╗ ████║██╔══██╗╚══██╔══╝██║ ██║
|
|
##██╔████╔██║███████║ ██║ ███████║
|
|
##██║╚██╔╝██║██╔══██║ ██║ ██╔══██║
|
|
##██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║
|
|
##╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
BinaryMathWrapper(operator.lshift),
|
|
BinaryMathWrapper(operator.rshift),
|
|
BinaryMathWrapper(operator.add),
|
|
BinaryMathWrapper(operator.floordiv),
|
|
BinaryMathWrapper(operator.mod),
|
|
BinaryMathWrapper(operator.mul),
|
|
BinaryMathWrapper(operator.pow),
|
|
BinaryMathWrapper(operator.sub),
|
|
UnaryMathWrapper(abs),
|
|
UnaryMathWrapper(operator.neg),
|
|
):
|
|
inscribe(F)
|
|
|
|
|
|
'''
|
|
██████╗ ███████╗███████╗██╗███╗ ██╗██╗████████╗██╗ ██████╗ ███╗ ██╗███████╗
|
|
██╔══██╗██╔════╝██╔════╝██║████╗ ██║██║╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝
|
|
██║ ██║█████╗ █████╗ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗
|
|
██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║
|
|
██████╔╝███████╗██║ ██║██║ ╚████║██║ ██║ ██║╚██████╔╝██║ ╚████║███████║
|
|
╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝
|
|
'''
|
|
|
|
|
|
class Def(object):
|
|
'''
|
|
Definitions are given by equations:
|
|
|
|
name ≡ foo bar baz ...
|
|
|
|
When a definition symbol is evaluated its body expression is put onto
|
|
the pending expression.
|
|
'''
|
|
|
|
# tribar = '\u2261' # '≡'
|
|
|
|
def __init__(self, name, body):
|
|
self.__doc__ = f'{name} ≡ {expression_to_string(body)}'
|
|
self.__name__ = name
|
|
self.body = tuple(iter_stack(body))
|
|
|
|
def __call__(self, stack, expr, dictionary):
|
|
expr = list_to_stack(self.body, expr)
|
|
return stack, expr, dictionary
|
|
|
|
@classmethod
|
|
def load_definitions(class_, stream, dictionary):
|
|
'''
|
|
Given an iterable of lines (strings) and a dictionary put any
|
|
definitions (lines with '≡' in them) into the dictionary.
|
|
'''
|
|
for line in stream:
|
|
if '≡' not in line:
|
|
continue
|
|
name, body = text_to_expression(line.replace('≡', ''))
|
|
if name not in dictionary:
|
|
inscribe(class_(name, body), dictionary)
|
|
|
|
|
|
DEFS = '''\
|
|
|
|
Start with increment and decrement:
|
|
|
|
-- ≡ 1 -
|
|
++ ≡ 1 +
|
|
|
|
= ≡ eq
|
|
+ ≡ add
|
|
> ≡ gt
|
|
< ≡ lt
|
|
>= ≡ ge
|
|
<= ≡ le
|
|
!= ≡ ne
|
|
<> ≡ ne
|
|
|
|
% ≡ mod
|
|
+ ≡ add
|
|
- ≡ sub
|
|
* ≡ mul
|
|
/ ≡ floordiv
|
|
div ≡ floordiv
|
|
|
|
|
|
|
|
? ≡ dup bool
|
|
&& ≡ nulco [nullary [false]] dip branch
|
|
|| ≡ nulco [nullary] dip [true] branch
|
|
!- ≡ 0 >=
|
|
<{} ≡ [] swap
|
|
<<{} ≡ [] rollup
|
|
abs ≡ dup 0 < [] [neg] branch
|
|
anamorphism ≡ [pop []] swap [dip swons] genrec
|
|
app1 ≡ grba infrst
|
|
app2 ≡ [grba swap grba swap] dip [infrst] cons ii
|
|
app3 ≡ 3 appN
|
|
appN ≡ [grabN] codi map disenstacken
|
|
at ≡ drop first
|
|
average ≡ [sum] [size] cleave /
|
|
b ≡ [i] dip i
|
|
binary ≡ unary popd
|
|
ccccons ≡ ccons ccons
|
|
ccons ≡ cons cons
|
|
clear ≡ [] swaack pop
|
|
cleave ≡ fork popdd
|
|
clop ≡ cleave popdd
|
|
cmp ≡ [[>] swap] dipd [ifte] ccons [=] swons ifte
|
|
codi ≡ cons dip
|
|
codireco ≡ codi reco
|
|
dinfrirst ≡ dip infrst
|
|
dipd ≡ [dip] codi
|
|
disenstacken ≡ ? [uncons ?] loop pop
|
|
down_to_zero ≡ [0 >] [dup --] while
|
|
drop ≡ [rest] times
|
|
dupd ≡ [dup] dip
|
|
dupdd ≡ [dup] dipd
|
|
dupdip ≡ dupd dip
|
|
dupdipd ≡ dup dipd
|
|
enstacken ≡ stack [clear] dip
|
|
first ≡ uncons pop
|
|
flatten ≡ <{} [concat] step
|
|
fork ≡ [i] app2
|
|
fourth ≡ rest third
|
|
gcd ≡ true [tuck mod dup 0 >] loop pop
|
|
genrec ≡ [[genrec] ccccons] nullary swons concat ifte
|
|
grabN ≡ <{} [cons] times
|
|
grba ≡ [stack popd] dip
|
|
hypot [sqr] ii + sqrt
|
|
ifte ≡ [nullary] dipd swap branch
|
|
ii ≡ [dip] dupdip i
|
|
infra ≡ swons swaack [i] dip swaack
|
|
infrst ≡ infra first
|
|
make_generator ≡ [codireco] ccons
|
|
manual ≡ [] words [help] step pop
|
|
neg ≡ 0 swap -
|
|
not ≡ [true] [false] branch
|
|
nulco ≡ [nullary] cons
|
|
nullary ≡ [stack] dinfrirst
|
|
of ≡ swap at
|
|
pam ≡ [i] map
|
|
pm ≡ [+] [-] clop
|
|
popd ≡ [pop] dip
|
|
popdd ≡ [pop] dipd
|
|
popop ≡ pop pop
|
|
popopop ≡ pop popop
|
|
popopd ≡ [popop] dip
|
|
popopdd ≡ [popop] dipd
|
|
product ≡ 1 swap [*] step
|
|
quoted ≡ [unit] dip
|
|
range ≡ [0 <=] [1 - dup] anamorphism
|
|
range_to_zero ≡ unit [down_to_zero] infra
|
|
reco ≡ rest cons
|
|
rest ≡ uncons popd
|
|
reverse ≡ <{} shunt
|
|
roll> ≡ swap swapd
|
|
roll< ≡ swapd swap
|
|
rollup ≡ roll>
|
|
rolldown roll<
|
|
rrest ≡ rest rest
|
|
run ≡ <{} infra
|
|
second ≡ rest first
|
|
shift ≡ uncons [swons] dip
|
|
shunt ≡ [swons] step
|
|
size ≡ [pop ++] step_zero
|
|
spiral_next ≡ [[[abs] ii <=] [[<>] [pop !-] ||] &&] [[!-] [[++]] [[--]] ifte dip] [[pop !-] [--] [++] ifte] ifte
|
|
split_at ≡ [drop] [take] clop
|
|
split_list ≡ [take reverse] [drop] clop
|
|
sqr ≡ dup *
|
|
stackd ≡ [stack] dip
|
|
step_zero ≡ 0 roll> step
|
|
stuncons ≡ stack uncons
|
|
sum ≡ [+] step_zero
|
|
swapd ≡ [swap] dip
|
|
swons ≡ swap cons
|
|
swoncat ≡ swap concat
|
|
sqr ≡ dup mul
|
|
tailrec ≡ [i] genrec
|
|
take ≡ <<{} [shift] times pop
|
|
ternary ≡ binary popd
|
|
third ≡ rest second
|
|
tuck ≡ dup swapd
|
|
unary ≡ nullary popd
|
|
uncons ≡ [first] [rest] cleave
|
|
unit ≡ [] cons
|
|
unquoted ≡ [i] dip
|
|
unswons ≡ uncons swap
|
|
while ≡ swap nulco dupdipd concat loop
|
|
x ≡ dup i
|
|
step ≡ [_step0] x
|
|
_step0 ≡ _step1 [popopop] [_stept] branch
|
|
_step1 ≡ [?] dipd roll<
|
|
_stept ≡ [uncons] dipd [dupdipd] dip x
|
|
times ≡ [_times0] x
|
|
_times0 ≡ _times1 [popopop] [_timest] branch
|
|
_times1 ≡ [dup 0 >] dipd roll<
|
|
_timest ≡ [[--] dip dupdipd] dip x
|
|
map ≡ [_map0] cons [[] [_map?] [_mape]] dip tailrec
|
|
_map? ≡ pop bool not
|
|
_mape ≡ popd reverse
|
|
_map0 ≡ [_map1] dipd _map2
|
|
_map1 ≡ stackd shift
|
|
_map2 ≡ [infrst] cons dipd roll< swons
|
|
'''
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
|
|
J = interp if '-q' in sys.argv else repl
|
|
dictionary = initialize()
|
|
Def.load_definitions(DEFS.splitlines(), dictionary)
|
|
try:
|
|
stack = J(dictionary=dictionary)
|
|
except SystemExit:
|
|
pass
|