11.3.5 Application

Charme supports two kinds of primitives: natural numbers and primitive procedures. If the expression is a number, it is a string of digits. The is_number procedure evaluates to True if and only if its input is a number:

def is_primitive(expr):
    return is_number(expr) or is_primitive_procedure(expr)

def is_number(expr):
    return isinstance(expr, str) and expr.isdigit()

Here, we use the built-in function isinstance to check if expr is of type str. The and expression in Python evaluates similarly to the Scheme and special form: the left operand is evaluated first; if it evaluates to a false value, the value of the and expression is that false value. If it evaluates to a true value, the right operand is evaluated, and the value of the and expression is the value of its right operand. This evaluation rule means it is safe to use expr.isdigit() in the right operand, since it is only evaluated if the left operand evaluated to a true value, which means expr is a string.

Primitive procedures are defined using Python procedures. We define the procedure is_primitive_procedure using callable, a procedure that returns true only for callable objects such as procedures and methods:

def is_primitive_procedure(expr):
    return callable(expr)

The evaluation rule for a primitive is identical to the Scheme rule:

Charme Evaluation Rule 1: Primitives. A primitive expression evaluates to its pre-defined value.

We need to implement the pre-defined values in our Charme interpreter.

To evaluate a number primitive, we need to convert the string representation to a number of type int. The int(s) constructor takes a string as its input and outputs the corresponding integer:

def eval_primitive(expr):
    if is_number(expr): return int(expr)
    else: return expr

The else clause means that all other primitives (in Charme, this is only primitive procedures and Boolean constants) self-evaluate: the value of evaluating a primitive is itself.

For the primitive procedures, we need to define Python procedures that implement the primitive procedure. For example, here is the primitive_plus procedure that is associated with the + primitive procedure:

def primitive_plus (operands):
    if (len(operands) == 0): return 0
    else: return operands[0] + primitive_plus (operands[1:])

The input is a list of operands. Since a procedure is applied only after all subexpressions are evaluated, there is no need to evaluate the operands: they are already the evaluated values. For numbers, the values are Python integers, so we can use the Python + operator to add them. To provide the same behavior as the Scheme primitive + procedure, we define our Charme primitive + procedure to evaluate to 0 when there are no operands, and otherwise to recursively add all of the operand values.

The other primitive procedures are defined similarly:

def primitive_times (operands):
    if (len(operands) == 0): return 1
    else: return operands[0] * primitive_times (operands[1:])
def primitive_minus (operands):
    if (len(operands) == 1): return -1 * operands[0]
    elif len(operands) == 2: return operands[0] - operands[1]
       eval_error('- expects 1 or 2 operands, given %s: %s'
                          % (len(operands), str(operands)))
def primitive_equals (operands):
    check_operands (operands, 2, '=')
    return operands[0] == operands[1]
def primitive_lessthan (operands):
    check_operands (operands, 2, '<')
    return operands[0] < operands[1]

The check_operands procedure reports an error if a primitive procedure is applied to the wrong number of operands:

def check_operands(operands, num, prim):
    if (len(operands) != num):
       eval_error('Primitive %s expected %s operands, given %s: %s'
                            % (prim, num, len(operands), str(operands)))