# -*- 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