OSDN Git Service

Type-guard the ops.
authorSimon Forman <sforman@hushmail.com>
Wed, 7 Sep 2022 21:18:55 +0000 (14:18 -0700)
committerSimon Forman <sforman@hushmail.com>
Wed, 7 Sep 2022 21:18:55 +0000 (14:18 -0700)
implementations/Python/simplejoy.py

index 9c2679c..c29996c 100755 (executable)
@@ -44,6 +44,10 @@ class NotAnIntError(Exception):
     pass
 
 
+class NotABoolError(Exception):
+    pass
+
+
 class StackUnderflowError(Exception):
     pass
 
@@ -142,13 +146,16 @@ token_scanner = Scanner(
 
 
 class Symbol(str):
-    '''A string class that represents Joy function names.'''
+    '''
+    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.
@@ -162,7 +169,9 @@ def text_to_expression(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):
@@ -804,21 +813,80 @@ def swap(stack):
     return (a1, (a2, s23))
 
 
-def BinaryFunc(f):
+def BinaryLogicWrapper(f):
     '''
-    Wrap functions that take two arguments and return a single result.
+    Wrap functions that take two numbers and return a single result.
     '''
-
     @wraps(f)
     def inner(stack, expression, dictionary):
-        (a, (b, stack)) = stack
+        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 BinaryMathWrapper(func):
+    '''
+    Wrap functions that take two numbers and return a single result.
+    '''
+    @wraps(func)
+    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)
+            or     isinstance(a, bool)
+            or     isinstance(b, bool)
+            ):
+            raise NotAnIntError
+        result = func(b, a)
+        return (result, stack), expression, dictionary
     return inner
 
 
-def UnaryBuiltinWrapper(f):
+def UnaryLogicWrapper(f):
+    '''
+    Wrap functions that take one argument and return a single result.
+    '''
+
+    @wraps(f)
+    def inner(stack, expression, dictionary):
+        (a, stack) = stack
+        if not isinstance(a, bool):
+            raise NotABoolError
+        result = f(a)
+        return (result, stack), expression, dictionary
+
+    return inner
+
+
+def UnaryMathWrapper(f):
+    '''
+    Wrap functions that take one argument and return a single result.
+    '''
+
+    @wraps(f)
+    def inner(stack, expression, dictionary):
+        (a, stack) = stack
+        if (not isinstance(b, int)
+            or isinstance(a, bool)):
+            raise NotAnIntError
+        result = f(a)
+        return (result, stack), expression, dictionary
+
+    return inner
+
+
+def UnaryWrapper(f):
     '''
     Wrap functions that take one argument and return a single result.
     '''
@@ -839,39 +907,40 @@ for F in (
     ##██║     ██║   ██║██║╚██╔╝██║██╔═══╝ ██╔══██║██╔══██╗██║╚════██║██║██║   ██║██║╚██╗██║
     ##╚██████╗╚██████╔╝██║ ╚═╝ ██║██║     ██║  ██║██║  ██║██║███████║██║╚██████╔╝██║ ╚████║
     ## ╚═════╝ ╚═════╝ ╚═╝     ╚═╝╚═╝     ╚═╝  ╚═╝╚═╝  ╚═╝╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝
-    BinaryFunc(operator.eq),
-    BinaryFunc(operator.ge),
-    BinaryFunc(operator.gt),
-    BinaryFunc(operator.le),
-    BinaryFunc(operator.lt),
-    BinaryFunc(operator.ne),
+    BinaryMathWrapper(operator.eq),
+    BinaryMathWrapper(operator.ge),
+    BinaryMathWrapper(operator.gt),
+    BinaryMathWrapper(operator.le),
+    BinaryMathWrapper(operator.lt),
+    BinaryMathWrapper(operator.ne),
     ##██╗      ██████╗  ██████╗ ██╗ ██████╗
     ##██║     ██╔═══██╗██╔════╝ ██║██╔════╝
     ##██║     ██║   ██║██║  ███╗██║██║
     ##██║     ██║   ██║██║   ██║██║██║
     ##███████╗╚██████╔╝╚██████╔╝██║╚██████╗
     ##╚══════╝ ╚═════╝  ╚═════╝ ╚═╝ ╚═════╝
-    BinaryFunc(operator.xor),
-    BinaryFunc(operator.and_),
-    BinaryFunc(operator.or_),
-    UnaryBuiltinWrapper(operator.not_),
+    UnaryWrapper(bool),  # Convert any value to Boolean.
+    # (The only polymorphic function.)
+    BinaryLogicWrapper(operator.xor),
+    BinaryLogicWrapper(operator.and_),
+    BinaryLogicWrapper(operator.or_),
+    UnaryLogicWrapper(operator.not_),
     ##███╗   ███╗ █████╗ ████████╗██╗  ██╗
     ##████╗ ████║██╔══██╗╚══██╔══╝██║  ██║
     ##██╔████╔██║███████║   ██║   ███████║
     ##██║╚██╔╝██║██╔══██║   ██║   ██╔══██║
     ##██║ ╚═╝ ██║██║  ██║   ██║   ██║  ██║
     ##╚═╝     ╚═╝╚═╝  ╚═╝   ╚═╝   ╚═╝  ╚═╝
-    BinaryFunc(operator.lshift),
-    BinaryFunc(operator.rshift),
-    BinaryFunc(operator.add),
-    BinaryFunc(operator.floordiv),
-    BinaryFunc(operator.mod),
-    BinaryFunc(operator.mul),
-    BinaryFunc(operator.pow),
-    BinaryFunc(operator.sub),
-    UnaryBuiltinWrapper(abs),
-    UnaryBuiltinWrapper(bool),
-    UnaryBuiltinWrapper(operator.neg),
+    BinaryMathWrapper(operator.lshift),
+    BinaryMathWrapper(operator.rshift),
+    BinaryMathWrapper(operator.add),
+    BinaryMathWrapper(operator.floordiv),
+    BinaryMathWrapper(operator.mod),
+    BinaryMathWrapper(operator.mul),
+    BinaryMathWrapper(operator.pow),
+    BinaryMathWrapper(operator.sub),
+    UnaryMathWrapper(abs),
+    UnaryMathWrapper(operator.neg),
 ):
     inscribe(F)
 
@@ -887,8 +956,16 @@ for F in (
 
 
 class Def(object):
+    '''
+    Definitions are given by equations:
+
+        name ≡ foo bar baz ...
+
+    When a definition symbol is evaluated its body expression is put onto
+    the pending expression.
+    '''
 
-    tribar = '\u2261'  # 
+    tribar = '\u2261'  # '≡'
 
     def __init__(self, name, body):
         self.__doc__ = f'{name} {self.tribar} {expression_to_string(body)}'
@@ -901,6 +978,10 @@ class Def(object):
 
     @classmethod
     def load_definitions(class_, stream, dictionary):
+        '''
+        Given an iterable of lines (strings) and a dictionary put any
+        definitions (lines with '≡' in them) into the dictionary.
+        '''
         for line in stream:
             if class_.tribar not in line:
                 continue