Previous page Table of Contents Next page
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
defun response(threat)
if threat = #severe
run-in-circles()
scream-and-shout()
else if 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
defun 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
defun fib(x) 1
defun 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
defun inclist(lst is list, optional: incr = 1)
map(fun(x) x + incr, lst)
defun inclist(lst is list, optional: incr = 1)
map(_ + incr, lst)
defun inclist(lst is list, optional: incr = 1)
for x in lst collect x + incr
;; The simplest iteration macro
defmacro while ?test ?:body =>
`block
defun loop()
if ?test
?body
loop()
loop()`
;; 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 ?:body =>
`block
def start = ?from
def limit = ?to
defun loop(?var)
if ?var <= limit
?body
loop(?var + 1)
loop(start)`
;; 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
defun loop(i)
if i <= limit
print(f(i))
loop(i + 1)
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, indentation, true)
match!(#from, tokens)
def from = parse-expression(tokens, indentation + 1, true)
match!(#to, tokens)
def to = parse-expression(tokens, indentation + 1, true)
def body = parse-body(tokens, indentation + 1, true)
;; 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, #false@PLOT)
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))
;; 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`, `?=indentation`, _, _, _),
err, pattern, block)
`def ?name = macro@compiler(fun ?name(?=tokens is token-stream,
?=indentation is integer)
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 and an indentation as its arguments, defines macro-context to be a new macro context, and runs the expander. The definitions of tokens, indentation, 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
defun range(x is invocation) code(x.arguments[0]) .. code(x.arguments[1])
defun range(x is anything) code(x) .. code(x)
defun code(x is integer) x
defun 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@compiler(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