Programming Language for Old Timers

by David A. Moon
February 2006 .. September 2008

Comments and criticisms to dave underscore moon atsign alum dot mit dot edu.

Previous page   Table of Contents   Next page

Control Statements

The standard control statements in PLOT, with their syntax, are as follows:

defmacro do ?:body => ...

Evaluate a sequence of expressions in order. The last expression supplies the result returned by the do statement. The preceding expressions are executed for effect.

All statements that have bodies evaluate their bodies in this same way.

defmacro block ?:block => ...

Similar to do but defines a local scope.

All statements that contain embedded blocks evaluate those blocks in this same way.

defmacro cleanup ?cleanup ?:body => ...

Evaluates the body expressions the same as do, but whenever control leaves the cleanup statement, whether normally or abnormally, the cleanup expression is executed for effect. The cleanup expression is enclosed in its own block so any definitions in it are not visible outside.

An abnormal exit can be due to a non-local transfer of control performed by calling an exit function established at an outer level.

catch ?:name ?:body => ...

This defines the name to be an exit function which when called returns immediately from the catch statement without executing the rest of the body. Any intervening function calls are terminated. The argument to the exit function becomes the result of the catch statement. The exit function cannot be called once control has left the catch statement. The entire catch statement is enclosed in a block.

defmacro if ?test [then] ?then is block
            [ ^= else ?else is block ] => ...

Conditional. Evaluates the test; if its value is not false executes the then block and returns its value as the result of the if statement. The else block is not evaluated. If the test is false, the else block is executed and its value is returned, or if there is no else block the result is false.


        if x < 10
          g(x - 1)
        else if x < 100
          f(x, x * 2)
          f(x / 1000)

Any definition in a test expression is defined in the enclosing block, so its scope includes the entire conditional statement including the test and both the bodies, and also extends outside the if statement. It is considered poor style to access such a definition outside the if statement. Each then or else clause is itself a block.


        if def temp = f(x)

Note that by PLOT newline consistency rules you can write

if f(x) then g(x) else e(x)
and you can write
if f(x) then g(x)
else e(x)
and you can write
if f(x)
else e(x)
but you cannot write
if f(x) then g(x)
else e(x)
A multi-line body must start and end on a new line. This applies to all statements containing bodies or blocks, not just if.

defmacro case ?object { ^ ?value \=> ?:block }*
                      [ ^ default: ?default is block ] => ...

Case dispatch. Evaluate the object expression once and save the value. Then evaluate the value expressions one by one. The first value that is = to the object executes the corresponding block and returns its value as the result of the case statement. The remaining values are not evaluated. If no value matches, the default block is executed and its value is returned, or if there is no default block the result is false.


        case color
          #red    => f("magenta")
          #blue   => f("cyan")
          #green  => f("jade")
          selected-color => f(0)
          default: f(1)

defmacro with-slots ?object ?:block => ...

Slot access abbreviation. Evaluate the object expression once and save the value. For each slot in the compile-time class of the object, locally define a macro whose name is the slot and whose expansion is object.slotname. Execute the block in the scope of those macros and return its value as the result of the with-slots statement. The slot macros are only visible in the body block.


        with-slots point
          (x - x1) ^ 2 + (y - y1) ^2
is the same as
        (point.x - x1) ^ 2 + (point.y - y1) ^2
assuming that the compile-time type of point is a class that has slots named x and y.

Example of early exit:

        catch yield
          for x in sequence
            if f(x) yield(x + 1)

Example of cleanup:

          cleanup close-gate()

All of these statements use the syntactic types body and block. The syntax of body and implementation of its parser are:

defparser body { ^ ?expr is expression-or-lambda }+ =>
  ;; Return the P-expression
  if length(expr) > 1
  else expr[0]

The syntactic type expression-or-lambda is the same as expression except that it also allows a curried function.

The syntactic type block encloses a body in a block thus locally scoping any definitions in the body. The implementation looks like:

defun parse-block(token-stream, indentation, error?)
  with-source-location token-stream
      def scope = get-local-compiler-scope()

      ;; Parse the body
      def bod = parse-body(token-stream, indentation, error?)

      ;; If nothing was parsed, return false (error? must be false)
      ;; Otherwise return the body, wrapped in a block if necessary
      if not bod false
      else if empty?(scope) bod
      else if bod.class eq $collation
        scopation@compiler(scope, bod.body...)
      else scopation@compiler(scope, bod)
Note that it not sufficient just to enclose the body in a scopation, the scope must be set up before parsing any definitions that may be in the body.

For similar reasons, catch cannot be parsed with a simple pattern. The exit function must be in scope while the body is parsed.

defmacro catch ?:name =>        ; body is parsed later
  with-source-location tokens
      def scope = get-local-compiler-scope()

      ;; Define the exit function in the scope
        constant-definition(scope, exit, exit-function(name)))

      ;; Parse the body
      def body = parse-body(tokens, indentation, true)

      ;; Wrap the body in an exit and a scope
      scopation@compiler(scope, exitation@compiler(scope, exit, body))

Previous page   Table of Contents   Next page