Thun/joy/utils/stack.py

173 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright © 2014, 2015, 2017 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/>.
#
'''
When talking about Joy we use the terms "stack", "list", "sequence",
"quote" 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.
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.
For example::
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
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.)
.. _cons list: https://en.wikipedia.org/wiki/Cons#Lists
'''
##We have two very simple functions to build up a stack from a Python
##iterable and also to iterate through a stack and yield its items
##one-by-one in order, and two functions to generate string representations
##of stacks::
##
## list_to_stack()
##
## iter_stack()
##
## expression_to_string() (prints left-to-right)
##
## stack_to_string() (prints right-to-left)
##
##
##A word about the stack data structure.
def list_to_stack(el, stack=()):
'''Convert a Python list (or other sequence) to a Joy stack::
[1, 2, 3] -> (1, (2, (3, ())))
'''
for item in reversed(el):
stack = item, stack
return stack
def iter_stack(stack):
'''Iterate through the items on the stack.'''
while stack:
item, stack = stack
yield item
def stack_to_string(stack):
'''
Return a "pretty print" string for a stack.
The items are written right-to-left::
(top, (second, ...)) -> '... second top'
'''
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.
The items are written left-to-right::
(top, (second, ...)) -> 'top second ...'
'''
return _to_string(expression, iter_stack)
def _to_string(stack, f):
if isinstance(stack, long): return str(stack).rstrip('L')
if not isinstance(stack, tuple): return repr(stack)
if not stack: return '' # shortcut
return ' '.join(map(_s, f(stack)))
_s = lambda s: (
'[%s]' % expression_to_string(s) if isinstance(s, tuple)
else str(s).rstrip('L') if isinstance(s, long)
else repr(s)
)
def pushback(quote, expression):
'''Concatinate quote onto expression.
In joy [1 2] [3 4] would become [1 2 3 4].
'''
# 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.)
## temp = []
## while quote:
## item, quote = quote
## temp.append(item)
## for item in reversed(temp):
## expression = item, expression
## return expression
# This is the fastest, but will trigger
# RuntimeError: maximum recursion depth exceeded
# on quotes longer than sys.getrecursionlimit().
return (quote[0], pushback(quote[1], expression)) if quote else expression
def pick(s, n):
'''
Find the nth item on the stack. (Pick with zero is the same as "dup".)
'''
if n < 0:
raise ValueError
while True:
try:
item, s = s
except ValueError:
raise IndexError
n -= 1
if n < 0:
break
return item