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


Introductory Examples

To fully understand these examples you will need to read and understand the entirety of this document. However, perusing the examples can give you some sense of the "flavor" of the PLOT language.

;; A silly toy program
def response(threat)
  if threat = #severe
    run-in-circles()
    scream-and-shout()
  elseif threat = #moderate
    print("Danger Will Robinson")
  else
    sleep(1)

;; A simple class definition with two slots
defclass cons (a, d) is collection
  car := a
  cdr := d

Iteration:

;; Fibonacci, the classic recursive function
def fib(x)
  if x > 2
    fib(x - 1) + fib(x - 2)
  else 1

;; Another way to write Fibonacci, letting function dispatch
;; take care of the conditional
def fib(x) 1
def fib(x is integer-above(2)) fib(x - 1) + fib(x - 2)

;; Three ways to increment the elements of a list and produce a new list

def inclist(lst is list, optional: incr = 1)
  map([x => x + incr], lst)

def inclist(lst is list, optional: incr = 1)
  map(_ + incr, lst)

def inclist(lst is list, optional: incr = 1)
  for x in lst collect x + incr

;; The simplest iteration macro
defmacro while ?test ?:loop-body =>
  def body    = loop-body.body
  def result  = loop-body.result or `false`
  def cleanup = loop-body.cleanup
  def exit    = loop-body.exit
  def cleanup-clause = cleanup and `cleanup: ?cleanup`
  def exit-clause    = exit and `exit: ?exit`
  `block
     ?exit-clause
     def loop()
       if ?test
         ?body
         loop()
       else
         ?result
     loop()
    ?cleanup-clause`

;; A simple iteration macro so we can do things like
;; for i from 0 to 10 print(f(i))
defmacro for ?var is name from ?from to ?to ?:loop-body =>
  def body    = loop-body.body
  def result  = loop-body.result or `false`
  def cleanup = loop-body.cleanup
  def exit    = loop-body.exit
  def cleanup-clause = cleanup and `cleanup: ?cleanup`
  def exit-clause    = exit and `exit: ?exit`
  `block
     ?exit-clause
     def start = ?from
     def limit = ?to
     def loop(?var)
       if ?var <= limit
         ?body
         loop(?var + 1)
       else ?result
     loop(start)
    ?cleanup-clause`

;; The macro expansion of for i from 0 to 10 print(f(i)) is
;; as follows, ignoring hygienic scoping:
block
  def start = 0
  def limit = 10
  def loop(i)
    if i <= limit
      print(f(i))
      loop(i + 1)
    else false
  loop(start)

Obviously in order to work this depends on tail recursion removal so the recursive function loop does not blow out the stack, and depends on hygienic macros so the local definitions of start, limit, and loop do not pollute the namespace of the body block. If the body had been print(limit(i)), that limit would not be the same limit that is defined just before the loop.

Here is how to define the same macro without using patterns and templates, instead parsing and constructing the object-oriented representation of source code directly:

defmacro for =>
  ;; The for statement will be inside a block
  ;; I have cheated a little and used the same block for the whole statement
  ;; and for the loop method, since I know adding a nested block for the
  ;; method would not make any difference
  with-new-compiler-scope

    ;; Parse the simple for statement
    def var       = parse-name(tokens, true)
    match!(#from, tokens)
    def from      = parse-expression(tokens, true)
    match!(#to, tokens)
    def to        = parse-expression(tokens, true)
    def loop-body = parse-loop-body(tokens, true)
    def body      = loop-body.body
    def result    = loop-body.result or `false`
    def cleanup   = loop-body.cleanup
    def exit      = loop-body.exit

    ;; Create names that will not be visible to the caller
    def start-var = name("start", macro-context)
    def limit-var = name("limit", macro-context)
    def loop-var  = name("loop",  macro-context)

    ;; Construct the loop function
    def params = parameter-list@compiler(list(var), list(#$anything@PLOT),  // required
                                         false, false, false,               // optional
                                         false, false,                      // rest
                                         false, false, false,               // key
                                         false, false)                      // result
    def test = invocation@compiler(#<=@PLOT, var, limit-var)
    def recurse = invocation@compiler(loop-var, invocation@compiler(#+@PLOT, var, 1))
    def body2 = collation@compiler(body, recurse)
    def loop-body = conditional@compiler(test, body2, result)
    def loop = functation@compiler(loop-var, get-local-compiler-scope(),
                                   params, loop-body)

    ;; Construct the main part of the expansion
    def scope = get-local-compiler-scope()
    def expansion := scopation@compiler(
      scope,
      variable-definition@compiler(scope, start-var, $anything, from),
      variable-definition@compiler(scope, limit-var, $anything, to),
      method-definition@compiler(scope, loop-var, loop),
      invocation@compiler(loop-var, start-var))

    ;; Wrap the cleanup clause around it
    if cleanup
      expansion := sanitation(expansion, cleanup)

    ;; Wrap the exit clause around it
    if exit
      expansion := exitation(scope, exit, body)

    ;; Return the macro expansion
    expansion

Any language that can't handle macro-defining macros is babyish. Here is PLOT's predefined macro-defining macro:

;; Define a macro, using pattern matching to specify the parsing
defmacro defmacro ?:name { ^ ?:pattern \=> ?:block }+ =>
  def possibilities = collect-pattern-starts(pattern)
  def err = `wrong-token-error(?=tokens, ?possibilities)`
  def expander = reduce-right(translate-pattern(`?=tokens`, _, _, _),
                              err, pattern, block)
  `def ?name = macro@compiler([name: ?name,
                                 ?=tokens is token-stream =>
                               def ?=macro-context = unique-macro-context()
                               ?expander ])`

What does this mean? Just this: The syntax of defmacro is a name (possibly quoted if it's an operator) followed by one or more occurrences of "pattern => block". Each occurrence can start on a new line, but all occurrences must be indented the same, as indicated by the ^ in the pattern. If none of the patterns match, call wrong-token-error with the token-stream (for source location context) and an error message constructed from the patterns that describes what tokens were allowed. The body of the macro expander is constructed by translating each pattern into code that parses the tokens coming from the token-stream and if the pattern matches, defines the pattern variables as appropriate portions of the parse tree, executes the body block, and returns the result of the body as the expansion of the macro. If the pattern does not match, try the next "pattern => block" rule. If no more rules, call wrong-token-error. Having constructed the expander, define the name of the macro to be a macro object encapsulating a function that takes a token-stream as its argument, defines macro-context to be a new macro context, and runs the expander. The definitions of tokens and macro-context are made visible to the expander rather than hygienic. Fairly simple as macro-defining macros go.

Suppose you were writing some kind of parser and wanted to use a bit vector indexed by character code to classify incoming characters. Further suppose that, quite reasonably, you wanted to construct the bit vectors at compile time rather than at run time or during program initialization. Here is one way you could do that with a macro:

defmacro def-character-class
         ?:name = { ?expr }+ [ size: ?size] =>
  ;; Functions to convert input to character code ranges
  def range(x is invocation) code(x.arguments[0]) : code(x.arguments[1])
  def range(x is anything) code(x) : code(x)
  def code(x is integer) x
  def code(x is character) x.code
  ;; Build the bit vector at compile time
  def bits = bit-vector#(size or 256)
  for x in expr
    for code in range(x)
      bits[code] := 1
  ;; Define name to be that constant bit vector
  def constant = quotation(bits)
  `def ?name = ?constant`

;; Example uses of the macro

def-character-class whitespace = ' ' '\t' 10 13

def-character-class letters = 'A' : 'Z'    ; Majuscules
                              'a' : 'z'    ; miniscules

That code is a bit sloppy, for example it assumes that any invocation must be an invocation of the range construction operator : however that is probably how much care a typical application programmer would take.


Previous page   Table of Contents   Next page