Chapter 10
Macros
Rewrite Rule Examples
The following definitions of all of the built-in macros are provided as examples. This section is not intended to be a tutorial on how to write macros, just a collection of demonstrations of some of the tricks.
The built-in macros cannot really be implemented this way; for example if
and case cannot really both be implemented by expanding to the other. Certain
built-in macros cannot be implemented with rewrite rules or necessarily rewrite into
implementation-dependent code; in these cases the right-hand sides are shown
as id.
Statement Macros
Begin
define macro begin
{ begin ?:body end } => { ?body }
end;
Block
define macro block
{ block () ?ebody end }
=> { ?ebody }
{ block (?:name) ?ebody end }
=> { with-exit(method(?name) ?ebody end) }
// Left-recursive so leftmost clause is innermost
ebody:
{ ... exception (?type:expression, ?eoptions) ?:body }
=> { with-handler(method() ... end,
method(ignore) ?body end,
?type, ?eoptions) }
{ ... exception (?:name :: ?type:expression, ?eoptions) ?:body }
=> { with-handler(method() ... end,
method(?name) ?body end,
?type, ?eoptions) }
{ ?abody cleanup ?cleanup:body}
=> { with-cleanup(method() ?abody end, method () ?cleanup end) }
{ ?abody }
=> { ?abody }
abody:
{ ?main:body }
=> { ?main }
{ ?main:body afterwards ?after:body }
=> { with-afterwards(method() ?main end, method () ?after end) }
eoptions:
{ #rest ?options:expression,
#key ?test:expression = always(#t),
?init-arguments:expression = #() }
=> { ?options }
end;
Case
define macro case
{ case ?:case-body end } => { ?case-body }
case-body:
{ } => { #f }
{ otherwise ?:body } => { ?body }
{ ?test:expression => ?:body; ... } => { if (?test) ?body
else ... end if }
end;
For
// This macro has three auxiliary macros, whose definitions follow
define macro for
{ for (?header) ?fbody end } => { for-aux ?fbody, ?header end }
// pass main body and finally body as two expressions
fbody:
{ ?main:body } => { ?main, #f }
{ ?main:body finally ?val:body } => { ?main, ?val }
// convert iteration clauses to property list via for-clause macro
header:
{ ?v:variable in ?c:expression, ... }
=> { for-clause(?v in ?c) ... }
{ ?v:variable = ?e1:expression then ?e2:expression, ... }
=> { for-clause(?v = ?e1 then ?e2) ... }
{ ?v:variable from ?e1:expression ?to, ... }
=> { for-clause(?v from ?e1 ?to) ... }
{ } => { }
{ #key ?while:expression } => { for-clause(~?while stop) }
{ #key ?until:expression } => { for-clause(?until stop) }
// parse the various forms of numeric iteration clause
to:
{ to ?limit:expression by ?step:expression }
=> { hard ?limit ?step }
{ to ?limit:expression } => { easy ?limit 1 > }
{ above ?limit:expression ?by } => { easy ?limit ?by <= }
{ below ?limit:expression ?by } => { easy ?limit ?by >= }
{ ?by } => { loop ?by }
by:
{ } => { 1 }
{ by ?step:expression } => { ?step }
end;
// Auxiliary macro to make the property list for an iteration clause.
// Each iteration clause is a separate call to this macro so the
// hygiene rules will keep the temporary variables for each clause
// distinct.
// The properties are:
// init0: - constituents for start of body, outside the loop
// var1: - a variable to bind on each iteration
// init1: - initial value for that variable
// next1: - value for that variable on iterations after the first
// stop1: - test expression, stop if true, after binding var1's
// var2: - a variable to bind on each iteration, after stop1 tests
// next2: - value for that variable on every iteration
// stop2: - test expression, stop if true, after binding var2's
define macro for-clause
// while:/until: clause
{ for-clause(?e:expression stop) }
=> { , stop2: ?e }
// Explicit step clause
{ for-clause(?v:variable = ?e1:expression then ?e2:expression) }
=> { , var1: ?v, init1: ?e1, next1: ?e2 }
// Collection clause
{ for-clause(?v:variable in ?c:expression) }
=> { , init0: [ let collection = ?c;
let (initial-state, limit,
next-state, finished-state?,
current-key, current-element)
= forward-iteration-protocol(collection); ]
, var1: state, init1: initial-state
, next1: next-state(collection, state)
, stop1: finished-state?(collection, state, limit)
, var2: ?v, next2: current-element(collection, state) }
// Numeric clause (three cases depending on ?to right-hand side)
{ for-clause(?v:name :: ?t:expression from ?e1:expression
loop ?by:expression) }
=> { , init0: [ let init = ?e1;
let by = ?by; ]
, var1: ?v :: ?t, init1: init, next1: ?v + by }
{ for-clause(?v:name :: ?t:expression from ?e1:expression
easy ?limit:expression ?by:expression ?test:token) }
=> { , init0: [ let init = ?e1;
let limit = ?limit;
let by = ?by; ]
, var1: ?v :: ?t, init1: init, next1: ?v + by
, stop1: ?v ?test limit }
{ for-clause(?v:name :: ?t:expression from ?e1:expression
hard ?limit:expression ?by:expression) }
=> { , init0: [ let init = ?e1;
let limit = ?limit;
let by = ?by; ]
, var1: ?v :: ?t, init1: init, next1: ?v + by
, stop1: if (by >= 0) ?v > limit else ?v < limit end if }
end;
// Auxiliary macro to expand multiple for-clause macros and
// concatenate their expansions into a single property list.
define macro for-aux
{ for-aux ?main:expression, ?value:expression, ?clauses:* end }
=> { for-aux2 ?main, ?value ?clauses end }
clauses:
{ } => { }
{ ?clause:macro ... } => { ?clause ... }
end;
// Auxiliary macro to assemble collected stuff into a loop.
// Tricky points:
// loop iterates by tail-calling itself.
// return puts the finally clause into the correct lexical scope.
// ??init0 needs an auxiliary rule set to strip off the shielding
// brackets that make it possible to stash local declarations in
// a property list.
// ??var2 and ??next2 need a default because let doesn't allow
// an empty variable list.
// ??stop1 and ??stop2 need a default because if () is invalid.
define macro for-aux2
{ for-aux2 ?main:expression, ?value:expression,
#key ??init0:*, ??var1:variable,
??init1:expression, ??next1:expression,
??stop1:expression = #f,
??var2:variable = x, ??next2:expression = 0,
??stop2:expression = #f
end }
=> { ??init0 ...
local method loop(??var1, ...)
let return = method() ?value end method;
if (??stop1 | ...) return()
else let (??var2, ...) = values(??next2, ...);
if(??stop2 | ...) return()
else ?main; loop(??next1, ...)
end if;
end if;
end method;
loop(??init1, ...) }
// strip off brackets used only for grouping
init0:
{ [ ?stuff:* ] } => { ?stuff }
end;
If
define macro if
{ if (?test:expression) ?:body ?elses end }
=> { case ?test => ?body;
otherwise ?elses end }
elses:
{ } => { #f }
{ else ?:body } => { ?body }
{ elseif (?test:expression) ?:body ... }
=> { case ?test => ?body;
otherwise ... end }
end;
Method
define macro method
{ method (?parameters:*) => (?results:*) ; ?:body end } => id
{ method (?parameters:*) => (?results:*) ?:body end } => id
{ method (?parameters:*) => ?result:variable ; ?:body end } => id
{ method (?parameters:*) ; ?:body end } => id
{ method (?parameters:*) ?:body end } => id
end;
Select
define macro select
{ select (?what) ?:case-body end } => { ?what; ?case-body }
what:
{ ?object:expression by ?compare:expression }
=> { let object = ?object;
let compare = ?compare }
{ ?object:expression } => { let object = ?object;
let compare = \== }
case-body:
{ }
=> { error("select error, %= doesn't match any key", object) }
{ otherwise ?:body } => { ?body }
{ ?keys => ?:body; ... } => { if (?keys) ?body
else ... end if }
keys:
{ ?key:expression } => { compare(object, ?key) }
{ (?keys2) } => { ?keys2 }
{ ?keys2 } => { ?keys2 }
keys2:
{ ?key:expression } => { compare(object, ?key) }
{ ?key:expression, ... } => { compare(object, ?key) | ... }
end;
Unless
define macro unless
{ unless (?test:expression) ?:body end }
=> { if (~ ?test) ?body end }
end;
Until
define macro until
{ until (?test:expression) ?:body end }
=> { local method loop ()
if (~ ?test)
?body;
loop()
end if;
end method;
loop() }
end;
While
define macro while
{ while (?test:expression) ?:body end }
=> { local method loop ()
if (?test)
?body;
loop()
end if;
end method;
loop() }
end;
Definition Macros
Define Class
define macro class-definer
{ define ?mods:* class ?:name (?supers) ?slots end } => id
supers:
{ } => id
{ ?super:expression, ... } => id
slots:
{ } => id
{ inherited slot ?:name, #rest ?options:*; ... } => id
{ inherited slot ?:name = ?init:expression,
#rest ?options:*; ... } => id
{ ?mods:* slot ?:name, #rest ?options:*; ... } => id
{ ?mods:* slot ?:name = ?init:expression,
#rest ?options:*; ... } => id
{ ?mods:* slot ?:name :: ?type:expression,
#rest ?options:*; ... } => id
{ ?mods:* slot ?:name :: ?type:expression = ?init:expression,
#rest ?options:*; ... } => id
{ required keyword ?key:expression,
#rest ?options:*; ... } => id
{ required keyword ?key:expression ?equals:token ?init:expression,
#rest ?options:*; ... } => id
{ keyword ?key:expression, #rest ?options:*; ... } => id
{ keyword ?key:expression ?equals:token ?init:expression,
#rest ?options:*; ... } => id
end;
Define Constant
define macro constant-definer
{ define ?modifiers:* constant
?:name :: ?type:expression = ?init:expression } => id
{ define ?modifiers:* constant
(?variables:*) ?equals:token ?init:expression } => id
end;
Define Domain
define macro domain-definer
{ define sealed domain ?:name ( ?types ) } => id
types:
{ } => { }
{ ?type:expression, ... } => { ?type, ... }
end;
Define Generic
define macro generic-definer
{ define ?mods:* generic ?:name ?rest:* } => id
rest:
{ ( ?parameters:* ), #key } => id
{ ( ?parameters:* ) => ?:variable, #key } => id
{ ( ?parameters:* ) => (?variables:*), #key } => id
end;
Define Library
define macro library-definer
{ define library ?:name ?items end } => id
items:
{ } => id
{ use ?:name, #rest ?options:*; ... } => id
{ export ?names; ... } => id
names:
{ ?:name } => id
{ ?:name, ... } => id
end;
Define Method
define macro method-definer
{ define ?mods:* method ?:name ?rest end } => id
rest:
{ (?parameters:*) => (?results:*) ; ?:body } => id
{ (?parameters:*) => (?results:*) ?:body } => id
{ (?parameters:*) => ?result:variable ; ?:body } => id
{ (?parameters:*) ; ?:body } => id
{ (?parameters:*) ?:body } => id
end;
Define Module
define macro module-definer
{ define module ?:name ?items end } => id
items:
{ } => id
{ use ?:name, #rest ?options:*; ... } => id
{ export ?names; ... } => id
{ create ?names; ... } => id
names:
{ ?:name } => id
{ ?:name, ... } => id
end;
Define Variable
define macro variable-definer
{ define ?modifiers:* variable
?:name :: ?type:expression = ?init:expression } => id
{ define ?modifiers:* variable
(?variables:*) ?equals:token ?init:expression } => id
end;
Operator Function Macros
&
define macro \&
{ \&(?first:expression, ?second:expression) }
=> { if (?first) ?second else #f end }
end;
|
define macro \|
{ \|(?first:expression, ?second:expression) }
=> { let temp = ?first;
if (temp) temp else ?second end }
end;
:=
define macro \:=
{ \:=(?place:macro, ?value:expression) } => id
{ \:=(?place:expression, ?value:expression) } => id
end;
Additional Examples
The following macros are not built-in, but are simply supplied as examples. Each is shown as a definition followed by a sample call.
Test and Test-setter
define macro test
{ test(?object:expression) }
=> { frame-slot-getter(?object, #"test") }
end macro;
define macro test-setter
{ test-setter(?value:expression, ?object:expression) }
=> { frame-slot-setter(?value, ?object, #"test") }
end macro;
test(foo.bar) := foo.baz;
Transform!
define macro transform!
// base case
{ transform!(?xform:expression) } => { ?xform }
// the main recursive rule
{ transform!(?xform:expression, ?x:expression, ?y:expression,
?more:*) }
=> { let xform = ?xform;
let (nx, ny) = transform(xform, ?x, ?y);
?x := nx; ?y := ny;
transform!(xform, ?more) }
end macro;
transform!(w.transformation, xvar, yvar, w.pos.x, w.pos.y);
Formatting-table
define macro formatting-table
{ formatting-table (?:expression,
#rest ?options:expression,
#key ?x-spacing:expression = 0,
?y-spacing:expression = 0)
?:body end }
=> { do-formatting-table(?expression, method() ?body end,
?options) }
end macro;
formatting-table (stream, x-spacing: 10, y-spacing: 12)
foobar(stream)
end;
With-input-context
define macro with-input-context
{ with-input-context (?context-type:expression,
#key ?override:expression = #f)
?bbody end }
=> { do-with-input-context(?context-type, ?bbody,
override: ?override) }
bbody:
{ ?:body ?clauses } => { list(?clauses), method() ?body end }
clauses:
{ } => { }
{ on (?:name :: ?spec:expression, ?type:variable) ?:body ... }
=> { pair(?spec, method (?name :: ?spec, ?type) ?body end),
... }
end macro;
with-input-context (context-type, override: #t)
// the body that reads from the user
read-command-or-form (stream);
// the clauses that dispatch on the type
on (object :: <command>, type) execute-command (object);
on (object :: <form>, type) evaluate-form (object, type);
end;
Define Command
define macro command-definer
{ define command ?:name (?arguments:*) (#rest ?options:expression)
?:body end }
=> { define-command-1 ?name (?arguments) ?body end;
define-command-2 ?name (?arguments) (?options) end }
end macro;
// define the method that implements a command
// throws away the "stuff" in each argument used by the command parser
define macro define-command-1
{ define-command-1 ?:name (?arguments) ?:body end }
=> { define method ?name (?arguments) ?body end }
// map over ?arguments, reducing each to a parameter-list entry
// but when we get to the first argument that has a default, put
// in #key and switch to the key-arguments loop
arguments:
{ } => { }
{ ?:variable = ?default:expression ?stuff:*, ?key-arguments }
=> { #key ?variable = ?default, ?key-arguments }
{ ?argument, ... } => { ?argument, ... }
// map over keyword arguments the same way, each must
// have a default
key-arguments:
{ } => { }
{ ?key-argument, ... } => { ?key-argument, ... }
// reduce one required argument spec to a parameter-list entry
argument:
{ ?:variable ?stuff:* } => { ?variable }
// reduce one keyword argument spec to a parameter-list entry
key-argument:
{ ?:variable = ?default:expression ?stuff:* }
=> { ?variable = ?default }
end macro;
// generate the datum that describes a command and install it
define macro define-command-2
{ define-command-2 ?:name (?arguments) (#rest ?options:*) end }
=> { install-command(?name, list(?arguments), ?options) }
// map over ?arguments, reducing each to a data structure
arguments:
{ } => { }
{ ?argument, ... } => { ?argument, ... }
// reduce one argument specification to a data structure
argument:
{ ?:name :: ?type:expression = ?default:expression ?details }
=> { make(<argument-info>, name: ?"name", type: ?type,
default: ?default, ?details) }
{ ?:name :: ?type:expression ?details }
=> { make(<argument-info>, name: ?"name", type: ?type, ?details) }
// translate argument specification to <argument-info> init keywords
details:
{ } => { }
{ ?key:name ?value:expression ... } => { ?#"key" ?value, ... }
end macro;
define command com-show-home-directory
(directory :: <type> provide-default #t,
before :: <time> = #() prompt "date",
after :: <time> = #() prompt "date")
// Options
(command-table: directories,
name: "Show Home Directory")
body()
end command com-show-home-directory;
Get-resource
// The idea is that in this application each library has its own
// variable named $library, which is accessible to modules in that
// library. Get-resource gets a resource associated with the library
// containing the call to it. Get-resource-from-library is a function.
// The get-resource macro is a device to make programs more concise.
define macro get-resource
{ get-resource(?type:expression, ?id:expression) }
=> { get-resource-from-library(?=$library, ?type, ?id) }
end macro;
show-icon(get-resource(ResType("ICON"), 1044));
Completing-from-suggestions
// The completing-from-suggestions macro defines a lexically visible
// helper function called "suggest," which is only meaningful inside
// of calls to the completer. The "suggest" function is passed as an
// argument to the method passed to complete-input; alternatively it
// could have been defined in a local declaration wrapped around the
// method.
define macro completing-from-suggestions
{ completing-from-suggestions (?stream:expression,
#rest ?options:expression)
?:body end }
=>{ complete-input(?stream,
method (?=suggest) ?body end,
?options) }
end macro;
completing-from-suggestions (stream, partial-completers: #(' ', '-'))
for (command in commands)
suggest (command, command-name (command))
end for;
end completing-from-suggestions;
Define Jump-instruction
define macro jump-instruction-definer
{ define jump-instruction ?:name ?options:* end }
=> { register-instruction("j" ## ?#"name",
make(<instruction>,
debug-name: "j" ## ?"name",
?options)) }
end macro;
define jump-instruction eq cr-bit: 2, commutative?: #t end;