OSDN Git Service

Working on bug #15
[joypy/Thun.git] / joy / joy.py
index 430a2ab..ad597a7 100644 (file)
 # -*- 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 .parser import text_to_expression, ParseError, Symbol
+from .utils.stack import stack_to_string
+from .utils.errors import (
+    NotAListError,
+    NotAnIntError,
+    StackUnderflowError,
+    )
 
-A dialect of Joy in Python.
-
-
-Joy is a programming language created by Manfred von Thun that is easy to
-use and understand and has many other nice properties.  This Python script
-is 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.  A Tkinter GUI is provided as well.
-
-
-    Copyright © 2014, 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/>.
-
-
-§ joy()
-
-The basic joy() function is quite straightforward.  It iterates through a
-sequence of terms which are either literals (strings, numbers, sequences)
-or functions.  Literals are put onto the stack and functions are
-executed.
 
-Every Joy function is an unary mapping from stacks to stacks.  Even
-literals are considered to be functions that accept a stack and return a
-new stack with the literal value on top.
+class UnknownSymbolError(KeyError): pass
 
-Exports:
 
-  joy(stack, expression, dictionary, viewer=None)
+def joy(stack, expression, dictionary, viewer=None):
+    '''Evaluate a Joy expression on a stack.
 
-  run(text, stack, dictionary, viewer=None)
+  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.
 
-  repl(stack=(), dictionary=())
-
-'''
-from __future__ import print_function
-try:
-  input = raw_input
-except NameError:
-  pass
-from traceback import print_exc, format_exc
-from .parser import text_to_expression, ParseError, Symbol
-from .utils.stack import stack_to_string
-from .utils.pretty_print import TracePrinter
+    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)
 
-def joy(stack, expression, dictionary, viewer=None):
-  '''
-  Evaluate the Joy expression on the stack.
+    '''
+    while expression:
 
-  :param quote stack: The stack.
-  :param quote expression: The expression to evaluate.
-  :param dict dictionary: A `dict` mapping names to Joy functions.
-  :param function viewer: Optional viewer function.
+        if viewer: viewer(stack, expression)
 
-  '''
-  while expression:
+        term, expression = expression
+        if isinstance(term, Symbol):
+            try:
+                term = dictionary[term]
+            except KeyError:
+                raise UnknownSymbolError(term)
+            stack, expression, dictionary = term(stack, expression, dictionary)
+        else:
+            stack = term, stack
 
     if viewer: viewer(stack, expression)
+    return stack, expression, dictionary
 
-    term, expression = expression
-    if isinstance(term, Symbol):
-      term = dictionary[term]
-      stack, expression, dictionary = term(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)
 
-def run(text, stack, dictionary, viewer=None):
-  '''
-  Return the stack resulting from running the Joy code text on the stack.
-  '''
-  expression = text_to_expression(text)
-  return joy(stack, expression, dictionary, viewer)
+    '''
+    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.
-  '''
-  if dictionary is None:
-    dictionary = {}
-  try:
-    while True:
-      print()
-      print(stack_to_string(stack), '<-top')
-      print()
-      try:
-        text = input('joy? ')
-      except (EOFError, KeyboardInterrupt):
-        break
-      viewer = TracePrinter()
-      try:
-        stack, _, dictionary = run(text, stack, dictionary, viewer.viewer)
-      except:
-        exc = format_exc() # Capture the exception.
-        viewer.print_() # Print the Joy trace.
-        print('-' * 73)
-        print(exc) # Print the original exception.
-      else:
-        viewer.print_()
-  except:
-    print_exc()
-  print()
-  return stack
+    '''
+    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)  # 'Not a list.'
+            except:
+                print_exc()
+            print(stack_to_string(stack))
+    except:
+        print_exc()
+    return stack