clearing out cruft
This commit is contained in:
parent
a2cf184301
commit
f7315291a9
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
class NotAListError(Exception): pass
|
||||
class NotAnIntError(Exception): pass
|
||||
class StackUnderflowError(Exception): pass
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue