clearing out cruft

This commit is contained in:
Simon Forman 2022-09-11 11:44:48 -07:00
parent a2cf184301
commit f7315291a9
6 changed files with 0 additions and 937 deletions

View File

@ -1,150 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014, 2015, 2017, 2018 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 module implements an interpreter for a dialect of Joy that
attempts to stay very close to the spirit of Joy but does not precisely
match the behaviour of the original version(s) written in C.
'''
from builtins import input
from traceback import print_exc
from joy.parser import text_to_expression, ParseError, Symbol
from joy.utils.stack import stack_to_string
from joy.utils.errors import (
NotAListError,
NotAnIntError,
StackUnderflowError,
)
class UnknownSymbolError(KeyError): pass
def joy(stack, expression, dictionary, viewer=None):
'''
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.
: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:
if viewer: viewer(stack, expression)
term, expression = expression
if isinstance(term, Symbol):
if term not in dictionary:
raise UnknownSymbolError(term)
func = dictionary[term]
stack, expression, dictionary = func(stack, expression, dictionary)
else:
stack = term, stack
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.
: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)
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:
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 StackUnderflowError as e:
print(e) # 'Not enough values on stack.'
except NotAnIntError:
print('Not an integer.')
except NotAListError as e:
print(e)
except:
print_exc()
print(stack_to_string(stack))
except:
print_exc()
return stack

View File

@ -1,236 +1,12 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014-2020 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 module contains the Joy function infrastructure and a library of
functions. Its main export is a Python function initialize() that
returns a dictionary of Joy functions suitable for use with the joy()
function.
'''
from pkg_resources import resource_stream
from io import TextIOWrapper
from inspect import getdoc, getmembers, isfunction
from functools import wraps
from itertools import count
import operator, math
from . import __name__ as _joy_package_name
from .parser import text_to_expression, Symbol
from .utils import generated_library as genlib
from .utils.errors import (
NotAListError,
NotAnIntError,
StackUnderflowError,
)
from .utils.stack import (
concat,
expression_to_string,
iter_stack,
list_to_stack,
pick,
)
def default_defs(dictionary):
def_stream = TextIOWrapper(
resource_stream(_joy_package_name, 'defs.txt'),
encoding='UTF_8',
)
Def.load_definitions(def_stream, dictionary)
HELP_TEMPLATE = '''\
==== Help on %s ====
%s
---- end ( %s )
'''
# 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] = function
return function
def initialize():
'''Return a dictionary of Joy functions for use with joy().'''
return _dictionary.copy()
ALIASES = (
('add', ['+']),
('and', ['&']),
('bool', ['truthy']),
('mul', ['*']),
('floordiv', ['/floor', '//', '/', 'div']),
('mod', ['%', 'rem', 'remainder', 'modulus']),
('eq', ['=']),
('ge', ['>=']),
('getitem', ['pick', 'at']),
('gt', ['>']),
('le', ['<=']),
('lshift', ['<<']),
('lt', ['<']),
('ne', ['<>', '!=']),
('rshift', ['>>']),
('sub', ['-']),
('xor', ['^']),
('succ', ['++']),
('pred', ['--']),
('rolldown', ['roll<']),
('rollup', ['roll>']),
('eh', ['?']),
('id', [u'']),
)
def add_aliases(D, A):
'''
Given a dict and a iterable of (name, [alias, ...]) pairs, create
additional entries in the dict mapping each alias to the named function
if it's in the dict. Aliases for functions not in the dict are ignored.
'''
for name, aliases in A:
try:
F = D[name]
except KeyError:
continue
for alias in aliases:
D[alias] = F
def FunctionWrapper(f):
'''Set name attribute.'''
if not f.__doc__:
raise ValueError('Function %s must have doc string.' % f.__name__)
f.name = f.__name__.rstrip('_') # Don't shadow builtins.
return f
def SimpleFunctionWrapper(f):
'''
Wrap functions that take and return just a stack.
'''
@FunctionWrapper
@wraps(f)
def inner(stack, expression, dictionary):
return f(stack), expression, dictionary
return inner
def BinaryMathWrapper(f):
'''
Wrap functions that take two numbers and return a single result.
'''
@FunctionWrapper
@wraps(f)
def inner(stack, expression, dictionary):
try:
(a, (b, stack)) = stack
except ValueError:
raise StackUnderflowError('Not enough values on stack.')
if ( not isinstance(a, int)
or not isinstance(b, int)
# bool is int in Python.
or isinstance(a, bool)
or isinstance(b, bool)
):
raise NotAnIntError
result = f(b, a)
return (result, stack), expression, dictionary
return inner
def BinaryLogicWrapper(f):
'''
Wrap functions that take two numbers and return a single result.
'''
@FunctionWrapper
@wraps(f)
def inner(stack, expression, dictionary):
try:
(a, (b, stack)) = stack
except ValueError:
raise StackUnderflowError('Not enough values on stack.')
## if (not isinstance(a, bool)
## or not isinstance(b, bool)
## ):
## raise NotABoolError
result = f(b, a)
return (result, stack), expression, dictionary
return inner
def UnaryBuiltinWrapper(f):
'''
Wrap functions that take one argument and return a single result.
'''
@FunctionWrapper
@wraps(f)
def inner(stack, expression, dictionary):
(a, stack) = stack
result = f(a)
return (result, stack), expression, dictionary
return inner
class Def(object):
'''
Definitions created by inscribe.
'''
def __init__(self, name, body):
self.name = name
self.body = body
self._body = tuple(iter_stack(body))
self.__doc__ = expression_to_string(body)
self._compiled = None
def __call__(self, stack, expression, dictionary):
if self._compiled:
return self._compiled(stack, expression, dictionary) # pylint: disable=E1102
expression = list_to_stack(self._body, expression)
return stack, expression, dictionary
@classmethod
def load_definitions(class_, stream, dictionary):
for line in stream:
if line.lstrip().startswith('#'):
continue
name, body = text_to_expression(line)
if name not in dictionary:
inscribe(class_(name, body), dictionary)
## inscribe(class_(name, body), dictionary)
#
# Functions
#
@inscribe
@FunctionWrapper
def inscribe_(stack, expression, dictionary):
@ -247,16 +23,6 @@ def inscribe_(stack, expression, dictionary):
return stack, expression, dictionary
# @inscribe
# @SimpleFunctionWrapper
# def infer_(stack):
# '''Attempt to infer the stack effect of a Joy expression.'''
# E, stack = stack
# effects = infer_expression(E)
# e = list_to_stack([(fi, (fo, ())) for fi, fo in effects])
# return e, stack
@inscribe
@SimpleFunctionWrapper
def getitem(stack):
@ -462,21 +228,6 @@ def sort_(S):
return list_to_stack(sorted(iter_stack(tos))), stack
@inscribe
@SimpleFunctionWrapper
def clear(stack):
'''Clear everything from the stack.
::
clear == stack [pop stack] loop
... clear
---------------
'''
return ()
@inscribe
@SimpleFunctionWrapper
def disenstacken(stack):
@ -503,22 +254,6 @@ def reverse(S):
return res, stack
@inscribe
@SimpleFunctionWrapper
def concat_(S):
'''
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)) = S
return concat(second, tos), stack
@inscribe
@SimpleFunctionWrapper
def shunt(stack):
@ -607,26 +342,6 @@ def divmod_(S):
return r, (q, stack)
def sqrt(a):
'''
Return the square root of the number a.
Negative numbers return complex roots.
'''
try:
r = math.sqrt(a)
except ValueError:
assert a < 0, repr(a)
r = math.sqrt(-a) * 1j
return r
#def execute(S):
# (text, stack) = S
# if isinstance(text, str):
# return run(text, stack)
# return stack
@inscribe
@SimpleFunctionWrapper
def id_(stack):
@ -634,31 +349,6 @@ def id_(stack):
return stack
@inscribe
@SimpleFunctionWrapper
def void(stack):
'''True if the form on TOS is void otherwise False.'''
form, stack = stack
return _void(form), stack
def _void(form):
return any(not _void(i) for i in iter_stack(form))
## transpose
## sign
## take
@inscribe
@FunctionWrapper
def words(stack, expression, dictionary):
'''Print all the words in alphabetical order.'''
print(' '.join(sorted(dictionary)))
return stack, expression, dictionary
@inscribe
@FunctionWrapper
@ -693,35 +383,6 @@ def warranty(stack, expression, dictionary):
return stack, expression, dictionary
# def simple_manual(stack):
# '''
# Print words and help for each word.
# '''
# for name, f in sorted(FUNCTIONS.items()):
# d = getdoc(f)
# boxline = '+%s+' % ('-' * (len(name) + 2))
# print('\n'.join((
# boxline,
# '| %s |' % (name,),
# boxline,
# d if d else ' ...',
# '',
# '--' * 40,
# '',
# )))
# return stack
@inscribe
@FunctionWrapper
def help_(S, expression, dictionary):
'''Accepts a quoted symbol on the top of the stack and prints its docs.'''
((symbol, _), stack) = S
word = dictionary[symbol]
print(HELP_TEMPLATE % (symbol, getdoc(word), symbol))
return stack, expression, dictionary
#
# § Combinators
#
@ -1347,48 +1008,8 @@ def cmp_(stack, expression, dictionary):
# FunctionWrapper(while_),
for F in (
#divmod_ = pm = __(n2, n1), __(n4, n3)
BinaryMathWrapper(operator.eq),
BinaryMathWrapper(operator.ge),
BinaryMathWrapper(operator.gt),
BinaryMathWrapper(operator.le),
BinaryMathWrapper(operator.lt),
BinaryMathWrapper(operator.ne),
BinaryMathWrapper(operator.xor),
BinaryMathWrapper(operator.lshift),
BinaryMathWrapper(operator.rshift),
BinaryLogicWrapper(operator.and_),
BinaryLogicWrapper(operator.or_),
BinaryMathWrapper(operator.add),
BinaryMathWrapper(operator.floordiv),
BinaryMathWrapper(operator.mod),
BinaryMathWrapper(operator.mul),
BinaryMathWrapper(operator.pow),
BinaryMathWrapper(operator.sub),
## BinaryMathWrapper(operator.truediv),
UnaryBuiltinWrapper(bool),
UnaryBuiltinWrapper(operator.not_),
UnaryBuiltinWrapper(abs),
UnaryBuiltinWrapper(operator.neg),
UnaryBuiltinWrapper(sqrt),
UnaryBuiltinWrapper(floor),
UnaryBuiltinWrapper(round),
):
inscribe(F)
del F # Otherwise Sphinx autodoc will pick it up.
for name, primitive in getmembers(genlib, isfunction):
inscribe(SimpleFunctionWrapper(primitive))
add_aliases(_dictionary, ALIASES)

View File

@ -1,131 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright © 2014, 2015, 2016, 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/>.
#
'''
This module exports a single function for converting text to a joy
expression as well as a single Symbol class and a single 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 | '[' joy ']' | symbol
A Joy expression is a sequence of zero or more terms. A term is a
literal value (integer or 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.
'''
from re import Scanner
from joy.utils.stack import list_to_stack
from joy.utils.snippets import (
pat as SNIPPETS,
from_string,
Snippet,
)
BRACKETS = r'\[|\]' # Left or right square bracket.
BLANKS = r'\s+' # One-or-more blankspace.
WORDS = (
'[' # Character class
'^' # not a
'[' # left square bracket nor a
'\]' # right square bracket (escaped so it doesn't close the character class)
'\s' # nor blankspace
']+' # end character class, one-or-more.
)
token_scanner = Scanner([
(SNIPPETS, lambda _, token: from_string(token)),
(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))
class ParseError(ValueError):
'''Raised when there is a error while parsing 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 == 'true': frame.append(True)
elif tok == 'false': frame.append(False)
elif isinstance(tok, Snippet): frame.append(tok)
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

@ -1,5 +0,0 @@
class NotAListError(Exception): pass
class NotAnIntError(Exception): pass
class StackUnderflowError(Exception): pass

View File

@ -1,272 +0,0 @@
# -*- 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", "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.
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.)
Unfortunately, the Sphinx documentation generator, which is used to generate 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::
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
'''
from .errors import NotAListError
from .snippets import Snippet, to_string as snip_to_string
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 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
'''
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 ...'
:param stack expression: A stack.
:rtype: str
'''
return _to_string(expression, iter_stack)
JOY_BOOL_LITERALS = 'false', 'true'
def _joy_repr(thing):
if isinstance(thing, bool): return JOY_BOOL_LITERALS[thing]
if isinstance(thing, Snippet): return snip_to_string(thing)
return repr(thing)
def _to_string(stack, f):
if not isinstance(stack, tuple): return _joy_repr(stack)
if not stack: return '' # shortcut
if isinstance(stack, Snippet): return snip_to_string(stack)
return ' '.join(map(_s, f(stack)))
_s = lambda s: (
'[%s]' % expression_to_string(s)
if isinstance(s, tuple)
and not isinstance(s, Snippet)
# Is it worth making a non-tuple class for Snippet?
# Doing this check on each tuple seems a bit much.
else _joy_repr(s)
)
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 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.)
if not isinstance(quote, tuple):
raise NotAListError('Not a list.')
temp = []
while quote:
item, quote = quote
temp.append(item)
for item in reversed(temp):
expression = item, expression
return 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.
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.
: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