Boom, A Little Language for Arithmetic

Motivation

This document defines an extended version of the Boom language we created for Homework 9, which was motivated by a common programming task. The best way to appreciate how languages are processed is to write language processors. The best way to appreciate how to implement a language is to implement one.

To write more complex expressions in Boom, it would be convenient to be able to break computations into parts and even to create new kinds of operators. This requires the ability to name an expression that computes a particular value and use the name as a value in a larger expression.

This version of Boom includes named numbers as expressions. They will be useful for writing programs that compute more ambitious numbers using Boom's operators.

Extensions to the language definition from Homework 9 are marked in this document as new. This includes additions to the BNF description and new sections in the description of semantics.

Language Grammar

BNF Description

The initial version of the language consists of exact numbers and simple arithmetic expressions. Here is the BNF description of the language:

      <exp> ::= <number>
              | <varref>                         ; new
              | ( <unary-op> <exp> )
              | ( <exp> <binary-op> <exp> )
              | ( let <var> = <exp> in <exp> )   ; new

 <unary-op> ::= - | sq
<binary-op> ::= + | - | * | / | %
              | @ | ^ | <<

<number> can be any exact number, positive or negative, including integers and rationals. The semantics of the values and operators are defined below.

The symbols let, =, and in are keywords in the language.

Example Expressions

These are all legal Boom expressions:

2                 (2 + 4)
20                (4 * (10 ^ 3))
(sq 16)           (2 * (4 / (sq 6)))
(- (2 + 4))       ((2 * 14) + (4 << 6))
(8 << 3)          ((8 % 3) + ((- 4) * (6 @ 10)))

These are now also legal Boom expressions: — new

z                 (let z = (x * y) in (- z))
square            (let square = (x * x) in
                    (let half = (square / 2) in
                      (half + (4 * x))))

As the grammar indicates, expressions nest arbitrarily deeply.

Semantics

Unary Expressions

A unary operator takes an expression that evaluates to a number as its operand and returns a new number.
  • (- operand) returns the negation of its operand.
    (- (1 + 2)) equals -3.
  • (sq operand) returns the square of its operand.
    (sq (1 + 2)) equals 9.

Binary Expressions

The binary operators take two numbers as their operands.
  • The first three — +, -, and * — mean what you expect them to mean from past experience: addition, subtraction, and multiplication.
  • (exp1 / exp2) computes integer division.
    (17 / 4) equals 4.
  • (exp1 % exp2) computes the remainder from integer division.
    (17 % 4) equals 1.
  • (exp1 @ exp2) computes the average of its operands.
    (8 % 4) equals 6, and (17 % 4) equals 10.
  • (m ^ n) raises m to the power of n.
    (2 ^ 4) equals 16, and (10 ^ 3) equals 1000.
  • (exp << n) shifts exp to the left by n places, padding with 0s.
    (8 << 3) equals 8000.

Primitive Values — new

The initial environment of a Boom program contains three primitives:
  • zero, bound to the value 0
  • two, bound to the value 2
  • ten, bound to the value 10

Blocks with Local Variables — new

A variable reference is meaningful only within the body of the let/in expression that declares the variable. It is not meaningful in the value being assigned to the variable.

A reference to a variable that has not been defined either in the initial environment or in a containing let/in expression is an error.

A let/in expression behaves as follows:

  • First, the value of the variable is evaluated in the current environment.
  • Then, a new environment is created by extending the current environment with the new variable/value pair.
  • Finally, the body of the expression is evaluated in the new environment.

... just as local variables work in Racket.

Syntactic Sugar

All of the features defined above are part of the core of the Boom language except:

In Boom, syntactic abstractions are preprocessed away before expressions are evaluated.

The remaining operators, both unary and boolean, are part of the core of the language. The behavior of the core operators is implemented directly in the Boom interpreter.