Overview
Macros are created by use of the
Defmacro keyword followed by the
Name,
Lambda List of parameters, and the
Body of the macro. The
Body of the macro may consist of numerous expressions that require evaluating. When
Defmacro is used, the compiler calls the
Macro Expansion code which parses out the
Lambda List from the expression and sends it to the
Macro Parser. In the
Macro Parser the list is separated into "bindings" that contain the pertinent information for each variable/expression in the
Lambda List. All of the "bindings" are grouped into a larger list which is then passed to the
Macro Letification code where the individual variables are bound to a symbol which links their place/keyword to the init-form in the
Lambda List.
Once completed, the bound
Lambda List is then put into the overall Lambda expression that is used when the macro is called. This is where the
Body of the macro gets connected with the bound variable symbols that were used in the
Defmacro statement. The bound
Lambda List and the bound
Body are put together into a Let* statement. This entire Let* is then used by the compiler, whenever someone initiates the macro, to build the code that will be used during run-time.
Implementation
Details of implementation
All code for these functions was written in Lisp and placed into the
Compiler section of
Core Functions. The first part written was the
Macro Parser which was based on the rewritten
Function Parser and reuses many of the existing
Function Parser functions to limit duplication of code. The functions for
required,
optional,
rest, and
keyword parameters had to be rewritten to account for the extra parameters
whole,
environment, and
body. Since
environment can appear at any point in the argument list after
whole,
whole and each of the original functions had to be modified to check for
environment when it completed all the arguments in its section.
Whole, if used, could only appear at the beginning - before
required - and body is the same as rest so these changes were easy.
The
Defmacro form is passed into the outer wrapper
create-macro-expansion where the
Lambda List is removed and handed to
parse-macro-lambda-list. Each argument type is checked in order -
whole,
environment,
required,
environment,
optional,
environment,
body/rest,
environment, and
keywords. Each argument type has a binding created that includes the variable name, scope, usage, type, init-form, and allocation. Of these - only the variable name, usage, and init-form are currently of interest to the letification code. Each binding is added to the list of bindings until there are no more parameters to be bound.
When the
Macro Parser returns the list of bindings, they are passed to
letify-macro-lambda-list where two things occur: 1) each succesive parameter is converted to a symbol and bound to its init-form and 2) the unbound symbols are added to a list that is then bound to the macro name and body in the Let* application. Each successive function only handles parameters of its type and when done, or if there are no parameters of that type, passes the remaining list of bindings to the next function. The final return is an anonymous Lambda that the compiler uses to create the form when a user calls the macro. Because the macro parameters are part of a symbolized list, a counter is used to iterate through them on each succesive call up to
body/rest.
Body/rest makes a list of all remaining parameters and if
keywords are included the list will be verified as a property list before the first
keyword is processed at run-time.
Discussions
Links to Blog issues
Status:
Release Level:
Open bug count:
Test Suites
Links to JUnit results
--
JerryBoetje - 12 Jul 2003