Switch back to spaces for indentation.

For better or worse, Python 3 won.  No need to be shitty about it, eh?
This commit is contained in:
Simon Forman 2021-04-09 16:16:34 -07:00
parent 6fc77a9a4a
commit 65b2b4a7e3
6 changed files with 1088 additions and 1074 deletions

View File

@ -26,10 +26,10 @@ from .utils.pretty_print import trace
inscribe(trace) inscribe(trace)
if '-q' in sys.argv: if '-q' in sys.argv:
j = interp j = interp
else: else:
j = repl j = repl
print('''\ print('''\
Thun - Copyright © 2017 Simon Forman Thun - Copyright © 2017 Simon Forman
This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty". This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty".
This is free software, and you are welcome to redistribute it This is free software, and you are welcome to redistribute it

View File

@ -27,113 +27,118 @@ from builtins import input
from traceback import print_exc from traceback import print_exc
from .parser import text_to_expression, ParseError, Symbol from .parser import text_to_expression, ParseError, Symbol
from .utils.stack import stack_to_string from .utils.stack import stack_to_string
from .library import NotAnIntError, StackUnderflowError
class UnknownSymbolError(KeyError): pass class UnknownSymbolError(KeyError): pass
def joy(stack, expression, dictionary, viewer=None): 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 This function iterates through a sequence of terms which are either
literals (strings, numbers, sequences of terms) or function symbols. literals (strings, numbers, sequences of terms) or function symbols.
Literals are put onto the stack and functions are looked up in the Literals are put onto the stack and functions are looked up in the
dictionary and executed. dictionary and executed.
The viewer is a function that is called with the stack and expression The viewer is a function that is called with the stack and expression
on every iteration, its return value is ignored. on every iteration, its return value is ignored.
:param stack stack: The stack. :param stack stack: The stack.
:param stack expression: The expression to evaluate. :param stack expression: The expression to evaluate.
:param dict dictionary: A ``dict`` mapping names to Joy functions. :param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function. :param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary) :rtype: (stack, (), dictionary)
''' '''
while expression: while expression:
if viewer: viewer(stack, expression) if viewer: viewer(stack, expression)
term, expression = expression term, expression = expression
if isinstance(term, Symbol): if isinstance(term, Symbol):
try: try:
term = dictionary[term] term = dictionary[term]
except KeyError: except KeyError:
raise UnknownSymbolError(term) raise UnknownSymbolError(term)
stack, expression, dictionary = term(stack, expression, dictionary) stack, expression, dictionary = term(stack, expression, dictionary)
else: else:
stack = term, stack stack = term, stack
if viewer: viewer(stack, expression) if viewer: viewer(stack, expression)
return stack, expression, dictionary return stack, expression, dictionary
def run(text, stack, dictionary, viewer=None): 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 str text: Joy code.
:param stack stack: The stack. :param stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions. :param dict dictionary: A ``dict`` mapping names to Joy functions.
:param function viewer: Optional viewer function. :param function viewer: Optional viewer function.
:rtype: (stack, (), dictionary) :rtype: (stack, (), dictionary)
''' '''
expression = text_to_expression(text) expression = text_to_expression(text)
return joy(stack, expression, dictionary, viewer) return joy(stack, expression, dictionary, viewer)
def repl(stack=(), dictionary=None): 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 stack stack: The stack.
:param dict dictionary: A ``dict`` mapping names to Joy functions. :param dict dictionary: A ``dict`` mapping names to Joy functions.
:rtype: stack :rtype: stack
''' '''
if dictionary is None: if dictionary is None:
dictionary = {} dictionary = {}
try: try:
while True: while True:
print() print()
print(stack_to_string(stack), '<-top') print(stack_to_string(stack), '<-top')
print() print()
try: try:
text = input('joy? ') text = input('joy? ')
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
break break
try: try:
stack, _, dictionary = run(text, stack, dictionary) stack, _, dictionary = run(text, stack, dictionary)
except: except:
print_exc() print_exc()
except: except:
print_exc() print_exc()
print() print()
return stack return stack
def interp(stack=(), dictionary=None): def interp(stack=(), dictionary=None):
''' '''
Simple REPL with no extra output, suitable for use in scripts. Simple REPL with no extra output, suitable for use in scripts.
''' '''
if dictionary is None: if dictionary is None:
dictionary = {} dictionary = {}
try: try:
while True: while True:
try: try:
text = input() text = input()
except (EOFError, KeyboardInterrupt): except (EOFError, KeyboardInterrupt):
break break
try: try:
stack, _, dictionary = run(text, stack, dictionary) stack, _, dictionary = run(text, stack, dictionary)
except UnknownSymbolError as sym: except UnknownSymbolError as sym:
print('Unknown:', sym) print('Unknown:', sym)
except: except StackUnderflowError:
print_exc() print('Not enough values on stack.')
print(stack_to_string(stack)) except NotAnIntError:
except: print('Not an integer.')
print_exc() except:
return stack print_exc()
print(stack_to_string(stack))
except:
print_exc()
return stack

File diff suppressed because it is too large Load Diff

View File

@ -53,71 +53,71 @@ token_scanner = Scanner([
class Symbol(str): class Symbol(str):
'''A string class that represents Joy function names.''' '''A string class that represents Joy function names.'''
__repr__ = str.__str__ __repr__ = str.__str__
def text_to_expression(text): 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 When supplied with a string this function returns a Python datastructure
that represents the Joy datastructure described by the text expression. that represents the Joy datastructure described by the text expression.
Any unbalanced square brackets will raise a ParseError. Any unbalanced square brackets will raise a ParseError.
:param str text: Text to convert. :param str text: Text to convert.
:rtype: stack :rtype: stack
:raises ParseError: if the parse fails. :raises ParseError: if the parse fails.
''' '''
return _parse(_tokenize(text)) return _parse(_tokenize(text))
class ParseError(ValueError): class ParseError(ValueError):
'''Raised when there is a error while parsing text.''' '''Raised when there is a error while parsing text.'''
def _tokenize(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. Raise ParseError (with some of the failing text) if the scan fails.
''' '''
tokens, rest = token_scanner.scan(text) tokens, rest = token_scanner.scan(text)
if rest: if rest:
raise ParseError( raise ParseError(
'Scan failed at position %i, %r' 'Scan failed at position %i, %r'
% (len(text) - len(rest), rest[:10]) % (len(text) - len(rest), rest[:10])
) )
return tokens return tokens
def _parse(tokens): def _parse(tokens):
''' '''
Return a stack/list expression of the tokens. Return a stack/list expression of the tokens.
''' '''
frame = [] frame = []
stack = [] stack = []
for tok in tokens: for tok in tokens:
if tok == '[': if tok == '[':
stack.append(frame) stack.append(frame)
frame = [] frame = []
stack[-1].append(frame) stack[-1].append(frame)
elif tok == ']': elif tok == ']':
try: try:
frame = stack.pop() frame = stack.pop()
except IndexError: except IndexError:
raise ParseError('Extra closing bracket.') raise ParseError('Extra closing bracket.')
frame[-1] = list_to_stack(frame[-1]) frame[-1] = list_to_stack(frame[-1])
elif tok == 'true': elif tok == 'true':
frame.append(True) frame.append(True)
elif tok == 'false': elif tok == 'false':
frame.append(False) frame.append(False)
else: else:
try: try:
thing = int(tok) thing = int(tok)
except ValueError: except ValueError:
thing = Symbol(tok) thing = Symbol(tok)
frame.append(thing) frame.append(thing)
if stack: if stack:
raise ParseError('Unclosed bracket.') raise ParseError('Unclosed bracket.')
return list_to_stack(frame) return list_to_stack(frame)

View File

@ -46,79 +46,79 @@ from ..library import FunctionWrapper
@FunctionWrapper @FunctionWrapper
def trace(stack, expression, dictionary): 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 This function is just like the `i` combinator but it also prints a
trace of the evaluation trace of the evaluation
:param stack stack: The stack. :param stack stack: The stack.
:param stack expression: The expression to evaluate. :param stack expression: The expression to evaluate.
:param dict dictionary: A ``dict`` mapping names to Joy functions. :param dict dictionary: A ``dict`` mapping names to Joy functions.
:rtype: (stack, (), dictionary) :rtype: (stack, (), dictionary)
''' '''
tp = TracePrinter() tp = TracePrinter()
quote, stack = stack quote, stack = stack
try: try:
s, _, d = joy(stack, quote, dictionary, tp.viewer) s, _, d = joy(stack, quote, dictionary, tp.viewer)
except: except:
tp.print_() tp.print_()
print('-' * 73) print('-' * 73)
raise raise
else: else:
tp.print_() tp.print_()
return s, expression, d return s, expression, d
class TracePrinter(object): class TracePrinter(object):
''' '''
This is what does the formatting. You instantiate it and pass the ``viewer()`` 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 method to the :py:func:`joy.joy.joy` function, then print it to see the
trace. trace.
''' '''
def __init__(self): def __init__(self):
self.history = [] self.history = []
def viewer(self, stack, expression): def viewer(self, stack, expression):
''' '''
Record the current stack and expression in the TracePrinter's history. 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. Pass this method as the ``viewer`` argument to the :py:func:`joy.joy.joy` function.
:param stack quote: A stack. :param stack quote: A stack.
:param stack expression: A stack. :param stack expression: A stack.
''' '''
self.history.append((stack, expression)) self.history.append((stack, expression))
def __str__(self): def __str__(self):
return '\n'.join(self.go()) return '\n'.join(self.go())
def go(self): def go(self):
''' '''
Return a list of strings, one for each entry in the history, prefixed Return a list of strings, one for each entry in the history, prefixed
with enough spaces to align all the interpreter dots. 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) :rtype: list(str)
''' '''
max_stack_length = 0 max_stack_length = 0
lines = [] lines = []
for stack, expression in self.history: for stack, expression in self.history:
stack = stack_to_string(stack) stack = stack_to_string(stack)
expression = expression_to_string(expression) expression = expression_to_string(expression)
n = len(stack) n = len(stack)
if n > max_stack_length: if n > max_stack_length:
max_stack_length = n max_stack_length = n
lines.append((n, '%s%s' % (stack, expression))) lines.append((n, '%s%s' % (stack, expression)))
for i in range(len(lines)): # Prefix spaces to line up '•'s. for i in range(len(lines)): # Prefix spaces to line up '•'s.
length, line = lines[i] length, line = lines[i]
lines[i] = (' ' * (max_stack_length - length) + line) lines[i] = (' ' * (max_stack_length - length) + line)
return lines return lines
def print_(self): def print_(self):
try: try:
print(self) print(self)
except: except:
print_exc() print_exc()
print('Exception while printing viewer.') print('Exception while printing viewer.')

View File

@ -43,8 +43,8 @@ means we can directly "unpack" the expected arguments to a Joy function.
For example:: For example::
def dup((head, tail)): def dup((head, tail)):
return head, (head, tail) return head, (head, tail)
We replace the argument "stack" by the expected structure of the stack, We replace the argument "stack" by the expected structure of the stack,
in this case "(head, tail)", and Python takes care of unpacking the 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 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:: syntax was removed entirely. Instead you would have to write::
def dup(stack): def dup(stack):
head, tail = stack head, tail = stack
return head, (head, tail) return head, (head, tail)
We have two very simple functions, one to build up a stack from a Python 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=()): 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 :param list el: A Python list or other sequence (iterators and generators
won't work because ``reverse()`` is called on ``el``.) won't work because ``reverse()`` is called on ``el``.)
:param stack stack: A stack, optional, defaults to the empty stack. :param stack stack: A stack, optional, defaults to the empty stack.
:rtype: stack :rtype: stack
''' '''
for item in reversed(el): for item in reversed(el):
stack = item, stack stack = item, stack
return stack return stack
def iter_stack(stack): def iter_stack(stack):
'''Iterate through the items on the stack. '''Iterate through the items on the stack.
:param stack stack: A stack. :param stack stack: A stack.
:rtype: iterator :rtype: iterator
''' '''
while stack: while stack:
item, stack = stack item, stack = stack
yield item yield item
def stack_to_string(stack): 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. :param stack stack: A stack.
:rtype: str :rtype: str
''' '''
f = lambda stack: reversed(list(iter_stack(stack))) f = lambda stack: reversed(list(iter_stack(stack)))
return _to_string(stack, f) return _to_string(stack, f)
def expression_to_string(expression): 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. :param stack expression: A stack.
:rtype: str :rtype: str
''' '''
return _to_string(expression, iter_stack) return _to_string(expression, iter_stack)
_JOY_BOOL_LITS = 'false', 'true' _JOY_BOOL_LITS = 'false', 'true'
@ -138,40 +138,40 @@ def _joy_repr(thing):
def _to_string(stack, f): def _to_string(stack, f):
if not isinstance(stack, tuple): return _joy_repr(stack) if not isinstance(stack, tuple): return _joy_repr(stack)
if not stack: return '' # shortcut if not stack: return '' # shortcut
return ' '.join(map(_s, f(stack))) return ' '.join(map(_s, f(stack)))
_s = lambda s: ( _s = lambda s: (
'[%s]' % expression_to_string(s) '[%s]' % expression_to_string(s)
if isinstance(s, tuple) if isinstance(s, tuple)
else _joy_repr(s) else _joy_repr(s)
) )
def concat(quote, expression): 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 quote: A stack.
:param stack expression: A stack. :param stack expression: A stack.
:raises RuntimeError: if quote is larger than sys.getrecursionlimit(). :raises RuntimeError: if quote is larger than sys.getrecursionlimit().
:rtype: stack :rtype: stack
''' '''
# This is the fastest implementation, but will trigger # This is the fastest implementation, but will trigger
# RuntimeError: maximum recursion depth exceeded # RuntimeError: maximum recursion depth exceeded
# on quotes longer than sys.getrecursionlimit(). # 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) ## return list_to_stack(list(iter_stack(quote)), expression)
# In-lining is slightly faster (and won't break the # In-lining is slightly faster (and won't break the
# recursion limit on long quotes.) # recursion limit on long quotes.)
## temp = [] ## temp = []
## while quote: ## while quote:
@ -184,67 +184,67 @@ def concat(quote, expression):
def dnd(stack, from_index, to_index): def dnd(stack, from_index, to_index):
''' '''
Given a stack and two indices return a rearranged stack. Given a stack and two indices return a rearranged stack.
First remove the item at from_index and then insert it at to_index, 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 the second index is relative to the stack after removal of the item
at from_index. at from_index.
This function reuses all of the items and as much of the stack as it 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 can. It's meant to be used by remote clients to support drag-n-drop
rearranging of the stack from e.g. the StackListbox. rearranging of the stack from e.g. the StackListbox.
''' '''
assert 0 <= from_index assert 0 <= from_index
assert 0 <= to_index assert 0 <= to_index
if from_index == to_index: if from_index == to_index:
return stack return stack
head, n = [], from_index head, n = [], from_index
while True: while True:
item, stack = stack item, stack = stack
n -= 1 n -= 1
if n < 0: if n < 0:
break break
head.append(item) head.append(item)
assert len(head) == from_index assert len(head) == from_index
# now we have two cases: # now we have two cases:
diff = from_index - to_index diff = from_index - to_index
if diff < 0: if diff < 0:
# from < to # from < to
# so the destination index is still in the stack # so the destination index is still in the stack
while diff: while diff:
h, stack = stack h, stack = stack
head.append(h) head.append(h)
diff += 1 diff += 1
else: else:
# from > to # from > to
# so the destination is in the head list # so the destination is in the head list
while diff: while diff:
stack = head.pop(), stack stack = head.pop(), stack
diff -= 1 diff -= 1
stack = item, stack stack = item, stack
while head: while head:
stack = head.pop(), stack stack = head.pop(), stack
return stack return stack
def pick(stack, n): def pick(stack, n):
''' '''
Return the nth item on the stack. Return the nth item on the stack.
:param stack stack: A stack. :param stack stack: A stack.
:param int n: An index into the stack. :param int n: An index into the stack.
:raises ValueError: if ``n`` is less than zero. :raises ValueError: if ``n`` is less than zero.
:raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``.
:rtype: whatever :rtype: whatever
''' '''
if n < 0: if n < 0:
raise ValueError raise ValueError
while True: while True:
try: try:
item, stack = stack item, stack = stack
except ValueError: except ValueError:
raise IndexError raise IndexError
n -= 1 n -= 1
if n < 0: if n < 0:
break break
return item return item