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)
if '-q' in sys.argv:
j = interp
j = interp
else:
j = repl
print('''\
j = repl
print('''\
Thun - Copyright © 2017 Simon Forman
This program comes with ABSOLUTELY NO WARRANTY; for details type "warranty".
This is free software, and you are welcome to redistribute it

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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