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