A programming language that behaves like a calculator that we create.

The Pair Class

The pair class represents Scheme pairs and lists.
A list is a pair whose second element is either a list or nil.

class Pair:
	def __init__(self, first, second):
		self.first = first
		self.second = second # Second is either a well-formed list or nil for a Pair to be a well-formed list.

Scheme expression are represented as Scheme lists! (Data representation equals to the source code representation: Homoiconic)

Syntax

The calculator language has primitive expressions and call expressions.
These two things are like these two things in Scheme.

Semantics

The value of a calculator expression is defined recursively.

  • Primitive: A number evaluates to itself.
  • Call: A call expression evaluates to its argument values(operands which is evaluated by another recursive call) combined by an operator.
    • +: Sum of the arguments
    • *: Product of the arguments
    • -: If one argument, negate it. If more than one, subtract the rest from the first.
    • /: If one argument, invert it. If more than one, divide the rest from the first.

Evaluation

The eval Function

The eval function computes the value of an expression represented as a Scheme list, which is always a number.

It is a generic function that dispatches on the type of the expression (primitive or call).

Applying Built-in Operator

The apply function applies some operation to a (Scheme) list of argument values.
In calculator language, all operations are named by built-in operators: +, -, *, /

def calc_apply(operator, args):
    """Apply the named operator to a list of args.
    >>> calc_apply('+', as_scheme_list(1, 2, 3))
    6
    >>> calc_apply('-', as_scheme_list(10, 1, 2, 3))
    4
    >>> calc_apply('-', as_scheme_list(10))
    -10
    >>> calc_apply('*', nil)
    1
    >>> calc_apply('*', as_scheme_list(1, 2, 3, 4, 5))
    120
    >>> calc_apply('/', as_scheme_list(40, 5))
    8.0
    >>> calc_apply('/', as_scheme_list(10))
    0.1
    """
    if not isinstance(operator, str):
        raise TypeError(str(operator) +'is not a symbol')
    if operator == '+':
        return reduce(add, args, 0)
    elif operator == '-':
        if len(args) == 0:
            raise TypeError(operator +'requires at least 1 argument')
        elif len(args) == 1:
            return -args.first
        else:
            return reduce(sub, args.second, args.first)
    elif operator == '*':
        return reduce(mul, args, 1)
    elif operator == '/':
        if len(args) == 0:
            raise TypeError(operator +'requires at least 1 argument')
        elif len(args) == 1:
            return 1/args.first
        else:
            return reduce(truediv, args.second, args.first)
    else:
        raise TypeError(operator +'is an unknown operator')

Interactive Interpreter

The user interface for many programming language is an interactive interpreter.

Read-Eval-Print Loop

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
    
    A(Start):::startend --> B(Print a prompt.):::process
    B --> C(Read text input from the user.):::process
    C --> D(Parse the text input into an expression.):::process
    D --> E(Evaluate the expression.):::process
    E --> F{Whether there is any error?}:::decision
    F -->|Yes| G(Report those errors):::process
    G --> B(Print a prompt.):::process
    F -->|No| H(Print the value of the expression.):::process
    H --> B(Print a prompt.):::process    
def read_eval_print_loop():
    """Run a read-eval-print loop for Calculator."""
    while True:
        try:
            src = buffer_input()
            while src.more_on_line:
                expression = scheme_read(src)
                print(calc_eval(expression))
        except (SyntaxError, TypeError, ValueError, ZeroDivisionError) as err:
            print(type(err).__name__ + ':', err)
        except (KeyboardInterrupt, EOFError):  # <Control>-D, etc.
            print('Calculation completed.')
            return

Raising Exceptions

Exceptions are raised within lexical analysis, syntactic analysis, eval, and apply.

Handling Exceptions

An interactive interpreter should print information about each error.

A well-designed interactive interpreter should not halt completely on an error, so that the user has an opportunity to try again in the current environment.

How to accomplish this:
Put both parsing and evaluation within a try statement.
And put all of these within a while statement to go back and try again.