{Begin SubSec Macros} {Title Macros} {Text {Tag Macros} {index *PRIMARY* Macros} Macros provide an alternative way of specifying the action of a function. Whereas function definitions are evaluated with a "function call", which involves binding variables and other housekeeping tasks, macros are evaluated by {it translating} one Interlisp form into another, which is then evaluated. A litatom may have both a function definition and a macro definition. When a form is evaluated by the interpreter, if the {fn CAR} has a function definition, it is used (with a function call), otherwise if it has a macro definition, then that is used. However, when a form is compiled, the {fn CAR} is checked for a macro definition first, and only if there isn't one is the function definition compiled. This allows functions that behave differently when compiled and interpreted. For example, it is possible to define a function that, when interpreted, has a function definition that is slow and has a lot of error checks, for use when debugging a system. This function could also have a macro definition that defines a fast version of the function, which is used when the debugged system is compiled. {note it is not advised for users to have litatoms with both a function definition and a macro definition, since one can get into trouble with functions that act differently interpreted and compiled --- (from talk with lmm) mjs} Macro definitions are represented by lists that are stored on the property list of a litatom. Macros are often used for functions that should be compiled differently in different Interlisp implementations, and the exact property name a macro definition is stored under determines whether it should be used in a particular implementation. The global variable {var MACROPROPS}{index MACROPROPS Var} contains a list of all possible macro property names which should be saved by the {filecom MACROS} file package command. Typical macro property names are {index *PRIMARY* DMACRO Prop}{prop DMACRO} for Interlisp-D, {index *PRIMARY* 10MACRO Prop}{prop 10MACRO} for Interlisp-10, {index *PRIMARY* VAXMACRO Prop}{prop VAXMACRO} for Interlisp-VAX, {index *PRIMARY* JMACRO Prop}{prop JMACRO} for Interlisp-Jerico, and {index *PRIMARY* MACRO Prop}{prop MACRO} for "implementation independent" macros. The global variable {var COMPILERMACROPROPS}{index COMPILERMACROPROPS Var} is a list of macro property names. Interlisp determines whether a litatom has a macro definition by checking these property names, in order, and using the first non-{lisp NIL} property value as the macro definition. In Interlisp-D this list contains {prop DMACRO} and {prop MACRO} in that order so that {prop DMACRO}s will override the implementation-independent {prop MACRO} properties. In general, use a {prop DMACRO} property for macros that are to be used only in Interlisp-D, use {prop 10MACRO} for macros that are to be used only in Interlisp-10, and use {prop MACRO} for macros that are to affect both systems. {note BYTEMACRO means (OR DMACRO VAXMACRO), because they wanted to share some macros} {note ALTOMACRO is obsolete synonym for DMACRO} {note example of using DECLARE to define a macro removed at Larry's request --- mjs} Macro definitions can take the following forms: {Begin LabeledList} {Label {index *PRIMARY* LAMBDA (Macro Type)}{lisp (LAMBDA {ellipsis})}} {Label {index *PRIMARY* NLAMBDA (Macro Type)}{lisp (NLAMBDA {ellipsis})}} {Text A function can be made to compile open by giving it a macro definition of the form {lisp (LAMBDA {ellipsis})} or {lisp (NLAMBDA {ellipsis})}, e.g., {lisp (LAMBDA (X) (COND ((GREATERP X 0) X) (T (MINUS X))))} for {fn ABS}. The effect is as if the macro definition were written in place of the function wherever it appears in a function being compiled, i.e., it compiles as a lambda or nlambda expression. This saves the time necessary to call the function at the price of more compiled code generated in-line. } {Label {lisp (NIL {arg EXPRESSION})}} {Label {lisp ({arg LIST} {arg EXPRESSION})}} {Text "Substitution" macro.{index *PRIMARY* Substitution macros} Each argument in the form being evaluated or compiled is substituted for the corresponding atom in {arg LIST}, and the result of the substitution is used instead of the form. For example, if the macro definition of {fn ADD1} is {lisp ((X) (IPLUS X 1))}, then, {lisp (ADD1 (CAR Y))} is compiled as {lisp (IPLUS (CAR Y) 1)}. Note that {fn ABS} could be defined by the substitution macro {lisp ((X) (COND ((GREATERP X 0) X) (T (MINUS X))))}. In this case, however, {lisp (ABS (FOO X))} would compile as {lispcode (COND ((GREATERP (FOO X) 0) (FOO X)) (T (MINUS (FOO X))))} and {lisp (FOO X)} would be evaluated two times. (Code to evaluate {lisp (FOO X)} would be generated three times.) } {label {index *PRIMARY* OPENLAMBDA (Macro Type)}{lisp (OPENLAMBDA {arg ARGS} {arg BODY})}} {Text This is a cross between substitution and {lisp LAMBDA} macros. When the compiler processes an {lisp OPENLAMBDA}, it attempts to substitute the actual arguments for the formals wherever this preserves the frequency and order of evaluation that would have resulted from a {lisp LAMBDA} expression, and produces a {lisp LAMBDA} binding only for those that require it. Note: {lisp OPENLAMBDA} assumes that it can substitute literally the actual arguments for the formal arguments in the body of the macro if the actual is side-effect free or a constant. Thus, you should be careful to use names in {arg ARGS} which don't occur in {arg BODY} (except as variable references). For example, if {lisp FOO} has a macro definition of {lispcode (OPENLAMBDA (ENV) (FETCH (MY-RECORD-TYPE ENV) OF BAR))} then {lisp (FOO NIL)} will expand to {lispcode (FETCH (MY-RECORD-TYPE NIL) OF BAR)} {note any plans for OPEN-NLAMBDA, or is that pretty much like a subst macro??} {Begin Note} is there a good, correct example we can put here?? {End Note} } {label {index *PRIMARY* T (Macro Type)}{lisp T}} {Text When a macro definition is the atom {lisp T}, it means that the compiler should ignore the macro, and compile the function definition; this is a simple way of turning off other macros. For example, the user may have a function that runs in both Interlisp-D and Interlisp-10, but has a macro definition that should only be used when compiling in Interlisp-10. If the {prop MACRO} property has the macro specification, a {prop DMACRO} of {lisp T} will cause it to be ignored by the Interlisp-D compiler. Note that this {prop DMACRO} would not be necessary if the macro were specified by a {prop 10MACRO} instead of a {prop MACRO}. } {label {lisp (= . {arg OTHER-FUNCTION})}} {Text A simple way to tell the compiler to compile one function exactly as it would compile another. For example, when compiling in Interlisp-D, {fn FRPLACA}s are treated as {fn RPLACA}s. This is achieved by having {fn FRPLACA} have a {prop DMACRO} of {lisp (= . RPLACA)}. } {Label {index *PRIMARY* Computed macros}{lisp ({arg LITATOM} {arg EXPRESSION})}} {Text If a macro definition begins with a litatom other than those given above, this allows {it computation} of the Interlisp expression to be evaluated or compiled in place of the form. {arg LITATOM} is bound to the {fn CDR} of the calling form, {arg EXPRESSION} is evaluated, and the result of this evaluation is evaluated or compiled in place of the form. For example, {fn LIST} could be compiled using the computed macro: {lispcode [X (LIST 'CONS (CAR X) (AND (CDR X) (CONS 'LIST (CDR X]} This would cause {lisp (LIST X Y Z)} to compile as {lisp (CONS X (CONS Y (CONS Z NIL)))}. Note the recursion in the macro expansion. If the result of the evaluation is the litatom {index IGNOREMACRO Litatom}{atom IGNOREMACRO}, the macro is ignored and the compilation of the expression proceeds as if there were no macro definition. If the litatom in question is normally treated specially by the compiler ({fn CAR}, {fn CDR}, {fn COND}, {fn AND}, etc.), and also has a macro, if the macro expansion returns {atom IGNOREMACRO}, the litatom will still be treated specially. In Interlisp-10, if the result of the evaluation is the atom {atom INSTRUCTIONS},{index INSTRUCTIONS Litatom} no code will be generated by the compiler. It is then assumed the evaluation was done for effect and the necessary code, if any, has been added. This is a way of giving direct instructions to the compiler if you understand it. } {End LabeledList} {note there are a number of other macro forms which are used internally, and shouldn't be documented, such as MACRO = (APPLY* ...) MACRO = <atom>} Note: It is often useful, when constructing complex macro expressions, to use the {lisp BQUOTE} facility (see {PageRef Tag BQUOTE}). The following function is quite useful for debugging macro definitions: {FnDef {FnName EXPANDMACRO} {FnArgs EXP QUIETFLG {anonarg} {anonarg}} {Text Takes a form whose {fn CAR} has a macro definition and expands the form as it would be compiled. The result is prettyprinted, unless {arg QUIETFLG}={lisp T}, in which case the result is simply returned. }} {Begin SubSec DEFMACRO} {Title DEFMACRO} {Text {index *PRIMARY* Optional macro arguments} {index *PRIMARY* Keyword macro arguments} Macros defined with the function {fn DEFMACRO} are much like "computed" macros ({PageRef Term Computed macros}), in that they are defined with a form that is evaluated, and the result of the evaluation is used (evaluated or compiled) in place of the macro call. However, {fn DEFMACRO} macros support complex argument lists with optional arguments, default values, and keyword arguments. In addition, argument list destructuring is supported. {FnDef {Name DEFMACRO} {Args NAME ARGS FORM} {Type NLAMBDA NOSPREAD} {Text Defines {arg NAME} as a macro with the arguments {arg ARGS} and the definition form {arg FORM} ({arg NAME}, {arg ARGS}, and {arg FORM} are unevaluated). If an expression starting with {arg NAME} is evaluated or compiled, arguments are bound according to {arg ARGS}, {arg FORM} is evaluated, and the value of {arg FORM} is evaluated or compiled instead. The interpretation of {arg ARGS} is described below. Note: Unlike the function {fn DEFMACRO} in Common Lisp, this function currently does not remove any function definition for {arg NAME}. }} {arg ARGS} is a list that defines how the argument list passed to the macro {arg NAME} is interpreted. Specifically, {arg ARGS} defines a set of variables that are set to various arguments in the macro call (unevaluated), that {arg FORM} can reference to construct the macro form. In the simplest case, {arg ARGS} is a simple list of variable names that are set to the corresponding elements of the macro call (unevaluated). For example, given: {lispcode (DEFMACRO FOO (A B) (LIST 'PLUS A B B))} The macro call {lisp (FOO X (BAR Y Z))} will expand to {lisp (PLUS X (BAR Y Z) (BAR Y Z))}. The list {arg ARGS} can include any of a number of special "&-keywords" (beginning with the character "{lisp &}") that are used to set variables to particular items from the macro call form, as follows: {Begin LabeledList DEFMACRO keywords} {Label {indexX {Name OPTIONAL} {Type DEFMACRO keyword} {Text {lisp &OPTIONAL}} }{index *PRIMARY* &OPTIONAL (DEFMACRO keyword)}{lisp &OPTIONAL}} {Text Used to define optional arguments, possibly with default values. Each element on {arg ARGS} after {lisp &OPTIONAL} until the next &-keyword or the end of the list defines an optional argument, which can either be a litatom or a list, interpreted as follows: {arg VAR} If an optional argument is specified as a litatom, that variable is set to the corresponding element of the macro call (unevaluated). {lisp ({arg VAR} {arg DEFAULT})} If an optional argument is specified as a two element list, {arg VAR} is the variable to be set, and {arg DEFAULT} is a form that is evaluated and used as the default if there is no corresponding element in the macro call. {lisp ({arg VAR} {arg DEFAULT} {arg VARSETP})} If an optional argument is specified as a three element list, {arg VAR} and {arg DEFAULT} are the variable to be set and the default form, and {arg VARSETP} is a variable that is set to {lisp T} if the optional argument is given in the macro call, {lisp NIL} otherwise. This can be used to determine whether the argument was not given, or whether it was specified with the default value. For example, after {lispcode (DEFMACRO FOO (&OPTIONAL A (B 5) (C 6 CSET)) {arg FORM})} expanding the macro call {lisp (FOO)} would cause {arg FORM} to be evaluated with {lisp A} set to {lisp NIL}, {lisp B} set to {lisp 5}, {lisp C} set to {lisp 6}, and {lisp CSET} set to {lisp NIL}. {lisp (FOO 4 5 6)} would be the same, except that {lisp A} would be set to {lisp 4} and {lisp CSET} would be set to {lisp T}. } {Label {indexX {Name REST} {Type DEFMACRO keyword} {Text {lisp &REST}} }{index *PRIMARY* &REST (DEFMACRO keyword)}{lisp &REST}} {Label {indexX {Name BODY} {Type DEFMACRO keyword} {Text {lisp &BODY}} }{index *PRIMARY* &BODY (DEFMACRO keyword)}{lisp &BODY}} {Text Used to get a list of all additional arguments from the macro call. Either {lisp &REST} or {lisp &BODY} should be followed by a single litatom, which is set to a list of all arguments to the macro after the position of the &-keyword. For example, given {lispcode (DEFMACRO FOO (A B &REST C) {arg FORM})} expanding the macro call {lisp (FOO 1 2 3 4 5)} would cause {arg FORM} to be evaluated with {lisp A} set to {lisp 1}, {lisp B} set to {lisp 2}, and {lisp C} set to {lisp (3 4 5)}. Note: If the macro calling form contains keyword arguments (see {lisp &KEY} below) these are included in the {lisp &REST} list. } {Label {indexX {Name KEY} {Type DEFMACRO keyword} {Text {lisp &KEY}} }{index *PRIMARY* &KEY (DEFMACRO keyword)}{lisp &KEY}} {Text Used to define keyword arguments, that are specified in the macro call by including a "keyword" (a litatom starting with the character ":") followed by a value. Each element on {arg ARGS} after {lisp &KEY} until the next &-keyword or the end of the list defines a keyword argument, which can either be a litatom or a list, interpreted as follows: {arg VAR} {lisp ({arg VAR})} {lisp (({arg KEYWORD} {arg VAR}))} If a keyword argument is specified by a single litatom {arg VAR}, or a one-element list containing {arg VAR}, it is set to the value of a keyword argument, where the keyword used is created by adding the character ":" to the front of {arg VAR}. If a keyword argument is specified by a single-element list containing a two-element list, {arg KEYWORD} is interpreted as the keyword (which should start with the letter ":"), and {arg VAR} is the variable to set. {lisp ({arg VAR} {arg DEFAULT})} {lisp (({arg KEYWORD} {arg VAR}) {arg DEFAULT})} {lisp ({arg VAR} {arg DEFAULT} {arg VARSETP})} {lisp (({arg KEYWORD} {arg VAR}) {arg DEFAULT} {arg VARSETP})} If a keyword argument is specified by a two or three-element list, the first element of the list specifies the keyword and variable to set as above. Similar to {lisp &OPTIONAL} (above), the second element {arg DEFAULT} is a form that is evaluated and used as the default if there is no corresponding element in the macro call, and the third element {arg VARSETP} is a variable that is set to {lisp T} if the optional argument is given in the macro call, {lisp NIL} otherwise. For example, the form {lispcode (DEFMACRO FOO (&KEY A (B 5 BSET) ((:BAR C) 6 CSET)) {arg FORM})} Defines a macro with keys {lisp :A}, {lisp :B} (defaulting to 5), and {lisp :BAR}. Expanding the macro call {lisp (FOO :BAR 2 :A 1)} would cause {arg FORM} to be evaluated with {lisp A} set to {lisp 1}, {lisp B} set to {lisp 5}, {lisp BSET} set to {lisp NIL}, {lisp C} set to 2, and {lisp CSET} set to {lisp T}. } {Label {indexX {Name ALLOW-OTHER-KEYS} {Type DEFMACRO keyword} {Text {lisp &ALLOW-OTHER-KEYS}} }{index *PRIMARY* &ALLOW-OTHER-KEYS (DEFMACRO keyword)}{lisp &ALLOW-OTHER-KEYS}} {Text It is an error for any keywords to be suplied in a macro call that are not defined as keywords in the macro argument list, unless either the &-keyword {lisp &ALLOW-OTHER-KEYS} appears in {arg ARGS}, or the keyword {lisp :ALLOW-OTHER-KEYS} (with a non-{lisp NIL} value) appears in the macro call. } {Label {indexX {Name AUX} {Type DEFMACRO keyword} {Text {lisp &AUX}} }{index *PRIMARY* &AUX (DEFMACRO keyword)}{lisp &AUX}} {Text Used to bind and initialize auxiliary varables, using a syntax similar to {fn PROG} ({PageRef Fn PROG}). Any elements after {lisp &AUX} should be either litatoms or lists, interpreted as follows: {arg VAR} Single litatoms are interpreted as auxiliary variables that are initially bound to {lisp NIL}. {lisp ({arg VAR} {arg EXP})} If an auxiliary variable is specified as a two element list, {arg VAR} is a variable initially bound to the result of evaluating the form {arg EXP}. For example, given {lispcode (DEFMACRO FOO (A B &AUX C (D 5)) {arg FORM})} {lisp C} will be bound to {lisp NIL} and {lisp D} to {lisp 5} when {arg FORM} is evaluated. } {Label {indexX {Name WHOLE} {Type DEFMACRO keyword} {Text {lisp &WHOLE}} }{index *PRIMARY* &WHOLE (DEFMACRO keyword)}{lisp &WHOLE}} {Text Used to get the whole macro calling form. Should be the first element of {arg ARGS}, and should be followed by a single litatom, which is set to the entire macro calling form. Other &-keywords or arguments can follow. For example, given {lispcode (DEFMACRO FOO (&WHOLE X A B) {arg FORM})} Expanding the macro call {lisp (FOO 1 2)} would cause {arg FORM} to be evaluated with {lisp X} set to {lisp (FOO 1 2)}, {lisp A} set to {lisp 1}, and {lisp B} set to {lisp 2}. } {End LabeledList DEFMACRO keywords} {index *PRIMARY* Destructuring argument lists} {fn DEFMACRO} macros also support argument list "destructuring," a facility for accessing the structure of individual arguments to a macro. Any place in an argument list where a litatom is expected, an argument list (in the form described above) can appear instead. Such an embedded argument list is used to match the corresponding parts of that particular argument, which should be a list structure in the same form. In the simplest case, where the embedded argument list does not include &-keywords, this provides a simple way of picking apart list structures passed as arguments to a macro. For example, given {lispcode (DEFMACRO FOO (A (B (C . D)) E) {arg FORM})} Expanding the macro call {lisp (FOO 1 (2 (3 4 5)) 6)} would cause {arg FORM} to be evaluated with with {lisp A} set to {lisp 1}, {lisp B} set to {lisp 2}, {lisp C} set to {lisp 3}, {lisp D} set to {lisp (4 5)}, and {lisp E} set to {lisp 6}. Note that the embedded argument list {lisp (B (C . D))} has an embedded argument list {lisp (C . D)}. Also notice that if an argument list ends in a dotted pair, that the final litatom matches the rest of the arguments in the macro call. An embedded argument list can also include &-keywords, for interpreting parts of embedded list structures as if they appeared in a top-level macro call. For example, given {lispcode (DEFMACRO FOO (A (B &OPTIONAL (C 6)) D) {arg FORM})} Expanding the macro call {lisp (FOO 1 (2) 3)} would cause {arg FORM} to be evaluated with with {lisp A} set to {lisp 1}, {lisp B} set to {lisp 2}, {lisp C} set to {lisp 6} (because of the default value), and {lisp D} set to {lisp 3}. Warning: Embedded argument lists can only appear in positions in an argument list where a list is otherwise not accepted. In the above example, it would not be possible to specify an embedded argument list after the {lisp &OPTIONAL} keyword, because it would be interpreted as an optional argument specification (with variable name, default value, set variable). However, it would be possible to specify an embedded argument list as the first element of an optional argument specification list, as so: {lispcode (DEFMACRO FOO (A (B &OPTIONAL ((X (Y) Z) '(1 (2) 3))) D) {arg FORM})} In this case, {lisp X}, {lisp Y}, and {lisp Z} default to {lisp 1}, {lisp 2}, and {lisp 3}, respectively. Note that the "default" value has to be an appropriate list structure. Also, in this case either the whole structure {lisp (X (Y) Z)} can be supplied, or it can be defaulted (i.e. is not possible to specify {lisp X} while letting {lisp Y} default). }{End SubSec DEFMACRO} {Begin SubSec Interpreting Macros} {Title Interpreting Macros} {Text When the interpreter encounters a form {fn CAR} of which is an undefined function, it tries interpreting it as a macro. If {fn CAR} of the form has a macro definition, the macro is expanded, and the result of this expansion is evaluated in place of the original form. {fn CLISPTRAN} ({PageRef Fn CLISPTRAN}) is used to save the result of this expansion so that the expansion only has to be done once. On subsequent occasions, the translation (expansion) is retrieved from {var CLISPARRAY} the same as for other CLISP constructs. Note: Because of the way that the evaluator processes macros, if you have a macro on {lisp FOO}, then typing {lisp (FOO 'A 'B)} will work, but {lisp FOO(A B)} will not work.{note this should be fixed sometime} Sometimes, macros contain calls to functions that assume that the macro is being compiled. The variable {var SHOULDCOMPILEMACROATOMS}{index SHOULDCOMPILEMACROATOMS Var} is a list of functions that should be compiled to work correctly (initially {lisp (OPCODES)} in Interlisp-D, {lisp (ASSEMBLE LOC)} in Interlisp-10). {var UNSAFEMACROATOMS}{index UNSAFEMACROATOMS Var} is a list of functions which effect the operation of the compiler, so such macro forms shouldn't even be expanded except by the compiler (initially {lisp NIL} in Interlisp-D, {lisp (C2EXP STORIN CEXP COMP)} in Interlisp-10). If the interpreter encounters a macro containing calls to functions on these two lists, instead of the macro being expanded, a dummy function is created with the form as its definition, and the dummy function is then compiled. A form consisting of a call to this dummy function with no arguments is then evaluated in place of the original form, and {fn CLISPTRAN} is used to save the translation as described above. There are some situations for which this procedure is not amenable, e.g. a {fn GO} inside the form which is being compiled will cause the compiler to give an {lisp UNDEFINED TAG}{index UNDEFINED TAG Error} error message because it is not compiling the entire function, just a part of it. {Begin Note} Interpreter macro expansion is controlled by (MUSTCOMPILEMACROP MACRO SAFEFLG) Returns NIL if the macro body "macro" is OK to compile. Actually works by scanning the body of macro for instances of atoms on UNSAFEMACROATOMS and SHOULDCOMPILEMACROATOMS. These variables are in turn set up by COMPILEMODE. Interpreted calls to atoms with only macro definitions will automatically compile the instance of the macro expansion if the macro contains an instance of SHOULDCOMPILEMACROATOMS. {End Note} }{End SubSec Interpreting Macros} }{End SubSec Macros}