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
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