Harden up the types.

This commit is contained in:
Simon Forman 2022-09-07 19:39:00 -07:00
parent d7b445fdd4
commit 7594fb887f
1 changed files with 119 additions and 87 deletions

View File

@ -64,6 +64,31 @@ class UnknownSymbolError(KeyError):
pass pass
def isnt_int(i):
'''
Raise NotAnIntError if i isn't an integer.
(Booleans are not integers in Joy.)
'''
if not isinstance(i, int) or isinstance(i, bool):
raise NotAnIntError(f'Not an integer: {_s(i)}')
def isnt_bool(b):
'''
Raise NotABoolError if b isn't a Boolean.
'''
if not isinstance(b, bool):
raise NotABoolError(f'Not a Boolean value: {_s(b)}')
def isnt_stack(el):
'''
Raise NotAListError if el isn't a stack/quote/list.
'''
if not isinstance(el, tuple):
raise NotAListError(f'Not a list {_s(el)}')
''' '''
@ -118,8 +143,8 @@ the fact that they are not Symbol objects.
A crude grammar:: A crude grammar::
joy = <term>* joy := <term>*
term = <integer> | 'true' | 'false' | '[' <joy> ']' | <symbol> term := <integer> | 'true' | 'false' | '[' <joy> ']' | <symbol>
A Joy expression is a sequence of zero or more terms. A term is a A Joy expression is a sequence of zero or more terms. A term is a
literal value (integer, Boolean, or quoted Joy expression) or a function symbol. literal value (integer, Boolean, or quoted Joy expression) or a function symbol.
@ -129,7 +154,7 @@ around square brackets.
''' '''
JOY_BOOL_LITERALS = _T, _F = 'false', 'true' JOY_BOOL_LITERALS = _F, _T = 'false', 'true'
BRACKETS = r'\[|\]' # Left or right square bracket. BRACKETS = r'\[|\]' # Left or right square bracket.
@ -266,27 +291,16 @@ Putting some numbers onto a stack::
Python has very nice "tuple packing and unpacking" in its syntax which Python has very nice "tuple packing and unpacking" in its syntax which
means we can directly "unpack" the expected arguments to a Joy function. means we can directly "unpack" the expected arguments to a Joy function.
We assign the argument stack to the expected structure of the stack and
For example:: Python takes care of unpacking the
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 incoming tuple and assigning values to the names. (Note that Python
syntax doesn't require parentheses around tuples used in expressions syntax doesn't require parentheses around tuples used in expressions
where they would be redundant.) 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): 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
list and another to iterate through a stack and yield its items 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 one-by-one in order. There are also two functions to generate string representations
@ -350,8 +364,8 @@ def concat(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.)
if not isinstance(quote, tuple): isnt_stack(quote)
raise NotAListError(f'Not a list {_s(quote)}') isnt_stack(expression)
temp = [] temp = []
while quote: while quote:
item, quote = quote item, quote = quote
@ -361,6 +375,31 @@ def concat(quote, expression):
return expression return expression
def get_n_items(n, stack):
'''
Return items and remainder of stack.
Raise StackUnderflowError if there are fewer than n items on the stack.
'''
assert n > 0, repr(n)
temp = []
while n > 0:
n -= 1
try:
item, stack = stack
except ValueError:
raise StackUnderflowError('Not enough values on stack.') from None
temp.append(item)
temp.append(stack)
return tuple(temp)
def reversed_stack(stack):
'''
Return list_reverseiterator object for a stack.
'''
return reversed(list(iter_stack(stack)))
''' '''
@ -382,8 +421,7 @@ def stack_to_string(stack):
:param stack stack: A stack. :param stack stack: A stack.
:rtype: str :rtype: str
''' '''
f = lambda stack: reversed(list(iter_stack(stack))) return _to_string(stack, reversed_stack)
return _to_string(stack, f)
def expression_to_string(expression): def expression_to_string(expression):
@ -416,11 +454,12 @@ def _to_string(stack, f):
return ' '.join(map(_s, f(stack))) return ' '.join(map(_s, f(stack)))
_s = lambda s: ( def _s(s):
'[%s]' % expression_to_string(s) return (
if isinstance(s, tuple) '[%s]' % expression_to_string(s)
else _joy_repr(s) if isinstance(s, tuple)
) else _joy_repr(s)
)
''' '''
@ -500,8 +539,8 @@ def interp(stack=(), dictionary=None):
print('Unknown:', sym) print('Unknown:', sym)
except StackUnderflowError as e: except StackUnderflowError as e:
print(e) # 'Not enough values on stack.' print(e) # 'Not enough values on stack.'
except NotAnIntError: except NotAnIntError as e:
print('Not an integer.') print(e)
except NotAListError as e: except NotAListError as e:
print(e) print(e)
except: except:
@ -547,10 +586,10 @@ def SimpleFunctionWrapper(f):
''' '''
@wraps(f) @wraps(f)
def inner(stack, expr, dictionary): def SimpleFunctionWrapper_inner(stack, expr, dictionary):
return f(stack), expr, dictionary return f(stack), expr, dictionary
return inner return SimpleFunctionWrapper_inner
@inscribe @inscribe
@ -617,13 +656,10 @@ def branch(stack, expr, dictionary):
T T
''' '''
(then, (else_, (flag, stack))) = stack then, else_, flag, stack = get_n_items(3, stack)
if not isinstance(flag, bool): isnt_bool(flag)
raise NotABoolError(f'Not a Boolean value: {_s(flag)}') isnt_stack(else_)
if not isinstance(else_, tuple): isnt_stack(then)
raise NotAListError(f'Not a list {_s(else_)}')
if not isinstance(then, tuple):
raise NotAListError(f'Not a list {_s(then)}')
do = then if flag else else_ do = then if flag else else_
return stack, concat(do, expr), dictionary return stack, concat(do, expr), dictionary
@ -641,7 +677,7 @@ def dip(stack, expr, dictionary):
... Q x ... Q x
''' '''
quote, (x, stack) = stack quote, x, stack = get_n_items(2, stack)
return stack, concat(quote, (x, expr)), dictionary return stack, concat(quote, (x, expr)), dictionary
@ -657,7 +693,7 @@ def i(stack, expr, dictionary):
Q Q
''' '''
quote, stack = stack quote, stack = get_n_items(1, stack)
return stack, concat(quote, expr), dictionary return stack, concat(quote, expr), dictionary
@ -679,7 +715,9 @@ def loop(stack, expr, dictionary):
... ...
''' '''
quote, (flag, stack) = stack quote, flag, stack = get_n_items(2, stack)
isnt_bool(flag)
isnt_stack(quote)
if flag: if flag:
expr = concat(quote, (quote, (LOOP, expr))) expr = concat(quote, (quote, (LOOP, expr)))
return stack, expr, dictionary return stack, expr, dictionary
@ -723,7 +761,7 @@ def concat_(stack):
[a b c d e f] [a b c d e f]
''' '''
(tos, (second, stack)) = stack tos, second, stack = get_n_items(2, stack)
return concat(second, tos), stack return concat(second, tos), stack
@ -742,7 +780,8 @@ def cons(stack):
( https://en.wikipedia.org/wiki/Cons#Lists ). ( https://en.wikipedia.org/wiki/Cons#Lists ).
Its inverse operation is uncons. Its inverse operation is uncons.
''' '''
s0, (a1, stack) = stack s0, a1, stack = get_n_items(2, stack)
isnt_stack(s0)
return ((a1, s0), stack) return ((a1, s0), stack)
@ -758,8 +797,8 @@ def dup(stack):
a a a a
''' '''
(a1, s23) = stack a1, stack = get_n_items(1, stack)
return (a1, (a1, s23)) return a1, (a1, stack)
@inscribe @inscribe
@ -773,8 +812,10 @@ def first(stack):
a a
''' '''
((a1, s1), s23) = stack s0, stack = get_n_items(1, stack)
return (a1, s23) isnt_stack(s0)
a1, _ = get_n_items(1, s0)
return a1, stack
@inscribe @inscribe
@ -787,7 +828,7 @@ def pop(stack):
----------- -----------
''' '''
(_, s23) = stack _, s23 = get_n_items(1, stack)
return s23 return s23
@ -802,8 +843,10 @@ def rest(stack):
[b c] [b c]
''' '''
(_, s1), stack = stack s0, stack = get_n_items(1, stack)
return (s1, stack) isnt_stack(s0)
_, s1 = get_n_items(1, s0)
return s1, stack
@inscribe @inscribe
@ -832,8 +875,9 @@ def swaack(stack):
6 5 4 [3 2 1] 6 5 4 [3 2 1]
''' '''
(s1, s0) = stack s1, s0 = get_n_items(1, stack)
return (s0, s1) isnt_stack(s1)
return s0, s1
@inscribe @inscribe
@ -847,8 +891,8 @@ def swap(stack):
b a b a
''' '''
(a2, (a1, s23)) = stack a2, a1, stack = get_n_items(2, stack)
return (a1, (a2, s23)) return (a1, (a2, stack))
def BinaryLogicWrapper(f): def BinaryLogicWrapper(f):
@ -857,17 +901,14 @@ def BinaryLogicWrapper(f):
''' '''
@wraps(f) @wraps(f)
def inner(stack, expression, dictionary): def BinaryLogicWrapper_inner(stack, expression, dictionary):
try: a, b, stack = get_n_items(2, stack)
(a, (b, stack)) = stack isnt_bool(a)
except ValueError: isnt_bool(b)
raise StackUnderflowError('Not enough values on stack.')
if not isinstance(a, bool) or not isinstance(b, bool):
raise NotABoolError
result = f(b, a) result = f(b, a)
return (result, stack), expression, dictionary return (result, stack), expression, dictionary
return inner return BinaryLogicWrapper_inner
def BinaryMathWrapper(func): def BinaryMathWrapper(func):
@ -876,22 +917,14 @@ def BinaryMathWrapper(func):
''' '''
@wraps(func) @wraps(func)
def inner(stack, expression, dictionary): def BinaryMathWrapper_inner(stack, expression, dictionary):
try: a, b, stack = get_n_items(2, stack)
(a, (b, stack)) = stack isnt_int(a)
except ValueError: isnt_int(b)
raise StackUnderflowError('Not enough values on stack.')
if (
not isinstance(a, int)
or not isinstance(b, int)
or isinstance(a, bool)
or isinstance(b, bool)
):
raise NotAnIntError
result = func(b, a) result = func(b, a)
return (result, stack), expression, dictionary return (result, stack), expression, dictionary
return inner return BinaryMathWrapper_inner
def UnaryLogicWrapper(f): def UnaryLogicWrapper(f):
@ -900,14 +933,13 @@ def UnaryLogicWrapper(f):
''' '''
@wraps(f) @wraps(f)
def inner(stack, expression, dictionary): def UnaryLogicWrapper_inner(stack, expression, dictionary):
(a, stack) = stack a, stack = get_n_items(1, stack)
if not isinstance(a, bool): isnt_bool(a)
raise NotABoolError
result = f(a) result = f(a)
return (result, stack), expression, dictionary return (result, stack), expression, dictionary
return inner return UnaryLogicWrapper_inner
def UnaryMathWrapper(f): def UnaryMathWrapper(f):
@ -916,14 +948,13 @@ def UnaryMathWrapper(f):
''' '''
@wraps(f) @wraps(f)
def inner(stack, expression, dictionary): def UnaryMathWrapper_inner(stack, expression, dictionary):
(a, stack) = stack a, stack = get_n_items(1, stack)
if not isinstance(b, int) or isinstance(a, bool): isnt_int(a)
raise NotAnIntError
result = f(a) result = f(a)
return (result, stack), expression, dictionary return (result, stack), expression, dictionary
return inner return UnaryMathWrapper_inner
def UnaryWrapper(f): def UnaryWrapper(f):
@ -932,12 +963,12 @@ def UnaryWrapper(f):
''' '''
@wraps(f) @wraps(f)
def inner(stack, expression, dictionary): def UnaryWrapper_inner(stack, expression, dictionary):
(a, stack) = stack a, stack = get_n_items(1, stack)
result = f(a) result = f(a)
return (result, stack), expression, dictionary return (result, stack), expression, dictionary
return inner return UnaryWrapper_inner
for F in ( for F in (
@ -1144,7 +1175,7 @@ third rest second
tuck dup swapd tuck dup swapd
unary nullary popd unary nullary popd
uncons [first] [rest] cleave uncons [first] [rest] cleave
unit [] cons unit [] cons
unquoted [i] dip unquoted [i] dip
unswons uncons swap unswons uncons swap
while swap nulco dupdipd concat loop while swap nulco dupdipd concat loop
@ -1168,6 +1199,7 @@ _map2 [infrst] cons dipd roll< swons
if __name__ == '__main__': if __name__ == '__main__':
import sys import sys
J = interp if '-q' in sys.argv else repl J = interp if '-q' in sys.argv else repl
dictionary = initialize() dictionary = initialize()
Def.load_definitions(DEFS.splitlines(), dictionary) Def.load_definitions(DEFS.splitlines(), dictionary)