|
Allegro CL version 11.0 |
An environment in Common Lisp is a Lisp object that contains, in some fashion, a set of bindings and also information about operators, variables, symbols, and so on. This information can be useful in various ways, particularly during evaluation, compilation and macro expansion. The macroexpand operator takes an optional environment argument, whose value is either nil
or an environment object.
The actual nature of an environment object is implementation dependent. In standard Common Lisp, there is no way for programmers to access or modify environment objects. In the proposed Common Lisp standard specified by Guy Steele et. al. in the second edition of Common Lisp: the Language, an interface to environment objects was proposed (in Section 8.5, pp 207-214), but the ANSI J13 committee decided not to include this in the ANS.
Allegro CL now has implemented much of the CLtL-2 environments proposal, with some differences that we describe. We recommend that users read that section of CLtL-2, although this document is complete in itself.
Environments can be thought of as being specified in CLtL-2, Section 8.5, pp 207-214, with a number of differences. In the following points, we describe the differences between our implementation and the CLtL-2 specification.
augmentable-environment-kind
slot: :evaluation
, :interpreter
, :compiler
(previously :compilation
), :compilation
(previously :compilation-walking
), and :macros-only
. These kinds are described in detail in Kinds of environments. Note that :compilation
assists in distinguishing between the compilation process, which wants to expand macros, and the walking process, which does not. :macros-only
allows the creation of an environment which is appropriate for a macrolet lexical closure.:special-operator
), one call with a :compiler
or :compilation
environment (which might return :compiler-macro) and one with a :evaluation
environment in order to see if there is a real functional definition (:function
or :macro
).All interface functions are exported from the system
package.
CLtL-2 called for three namespaces: variable, function, and declaration. Allegro CL implements two additional ones: block and tag. These have block-information and tag-information accessors, respectively, and corresponding keyword arguments to augment-environment.
The second return value for all *-information functions except for declaration-information has been moved to the fourth value (since it is seldom used) and instead the second value returned is usually a locative cons (a cons whose car and/or cdr may be used as a mutable value). This locative cons is used to provide the local binding of the value (for :evaluation environments) or local compiler structure information (for the two compilation environments). To reduce consing at critical points in the environments implementation, there are a few exceptions where the second value is not a cons representing a locative:
When the first return value from variable-information is :constant, then the second value returned is an actual value, and not a value housed within a locative. This ensures that the value is not changed by replacing it in the (non-existent) locative.
When the first return value from variable-information is :special
, then the second value is nil
, because the value is dynamic and can be best accessed via symbol-value.
When the first return value from function-information is :special-operator
, then the second value is nil
, because the value of a special operator is opaque to the programmer (companion-macros are mandated by the spec in order for a non-compiler to "see" into special-operators, and so a functional value doesn't make sense).
When the first return value from function-information is either :function
or :macro
, and the definition is in fact dynamic and thus accessible via fdefinition or macro-function, respectively, then the second returned value is nil
, unless the third argument to function-information is non-nil
, indicating that consing definitions and declarations is ok.
All of variable-information, function-information, and declaration-information accept a null environment argument, which may mean to look in the global environment. The global environment is the environment of the running lisp, possibly shadowed by the *compilation-unit-environment*.
augment-environment allows some very limited augmentation of the global environment (either the null lexical environment or compile-file-environments) when only one name-to-value mapping is being added, or when only declarations are being added. Rules for this augmentation are not solid yet, and in certain cases may require use of the :reuse
argument described below.
Both variable-information and function-information may receive an optional third argument, a boolean which requests a building of all declarations out of previous declarations of the same name, including the global environment. This argument being non-nil
also results in a non-nil
second return value (the definition housed in a locative), although these return values are likely to be consed on the fly. This switch is added so that the interpreter, which almost never looks at declarations, doesn't need to cons as much for no good reason.
function-information receives an optional fourth argument controlling information returned for special-operators. See Kinds of environments or the description of function-information for more details.
function-information might return as its first value :special-operator
, not :special-form
.
augment-environment accepts the additional keywords special-operator, block, tag, compiler-macro, constant, and flavor-iv (i.e. flavor instance variable), which contribute to the function, block, tag, compiler-macro, variable, and variable namespaces, respectively.
augment-environmenta accepts a single name for each keyword except :declare
, in order to reduce consing. In this case, the locative (if present) is sought in the :locative
argument (see below)
augment-environment accepts the additional keyword argument reuse. It is not necessary to create a new augmentable-environment object whenever adding to the environment, but it is only useful to cons a new environment object when entering a new contour (both in the compiler and in an interpreter). When reuse is nil
(the default) a new environment object is consed, i.e. the environment object returned is not eq to the one given. But when reuse is non-nil
, then the old environment object is returned, and any additions to the environment are added at the same level, as if they had been all added in the same call to augment-environment which created this environment object.
augment-environment accepts the additional keyword argument locative. Usually, the locative is nil
or a cons cell, and can be used efficiently when only one name is being added to the environment. When a :constant is being augmented, the locative argument is the actual value of the constant. The locative argument becomes the value which is returned as the second value from the *-information functions. For augmentation with many names at a time, a locative can be specified for each name, where instead of a list of names for each keyword, the list may be an alist, each element of which specifies the name in the car and the locative in the cdr. The car of a non-nil
locative cons is always mutable, unless it represents a :constant value. See also the function system:constant-value.
define-declaration is implemented, but the lambda-list called for in its definition in CLtL-2 is ambiguous as to whether it applies to some kind of syntax for the declaration specifiers, or the arglist of the two-argument function this macro is supposed to define. Therefore the lambda-list is chosen to specify the syntax of the declaration, and the arglist for functionality of the declaration is given either implicitly or via a lambda form. The lambda-list for define-declaration is different from that specified in CLtL-2, with additional required arguments prop and kind.
The functions that are described as being defined by the macro are in fact implemented, and work as specified, except that it may return as its first value one of :function
, :variable
, :both
(meaning :function
and :variable
), or :declare
.
ensure-portable-walking-environment: this function returns an environment suitable for portable code walkers to use entirely within the ANSI Specification of Common Lisp.
The declaration alist as described in CLtL2 is optimized to save a cons cell at the expense of being human readable. Thus, a declaration added to variable foo which describes the variable as a dynamic-extent simple-vector
of type t
might have the CLtL2-style variable-information return ((type simple-array t (*)) (dynamic-extent . t))
which is confusing. We have chosen not to try to save this extra cons cell, and as a result the same declaration would appear as ((type (simple-array t (*)) (dynamic-extent t)))
which thus appears in exactly the same format as is required by declare syntax.
The kind slot of an augmentable-environment object determines what behaviors the accessors have on it. Usually, accessors will return similar information, but for several reasons including performance and the fact that namespaces are not pure mappings, the kind does play a part, along with optional arguments, in returning different levels of information.
An environment object can be "modified" to become a different kind simply by performing a null augmentation on the original environment and by storing into the kind slot:
(setq e2 (sys:augment-environment e1))
(setf (sys::augmentable-environment-kind e2) :evaluation)
which results in an evaluation environment in e2, which has exactly the same information as e1 (which might be any kind of environment, but which in practice is probably a :compiler
environment).
The environment kinds are:
:interpreter
: for a lisp with an interpreter, such as Allegro CL, an interpreter environment can form the basis and implementation for the interpreter. Accesses on it will tend to generate no declaration information (with the special-case of the special declaration), and global values will be left up to the caller to retrieve, rather than to cons up a locative for each call. The by-words for an interpreter environment are speed and cons-free operation.
:compiler
: a compiler environment is the normal environment which the compiler uses to establish contours and contexts for the compilation process. The by-word for a compilation environment is as much information as possible.
A compiler environment might also have a global component, suitable for storing definitions temporarily during a compile-file If so, it is generally stored into *compilation-unit-environment*, and any definitions it contains will shadow any global definitions stored directly in the lisp. When the *compilation-unit-environment* is removed, then the shadowing is stopped, and the original definitions appear again.
:evaluation
: an evaluation environment is a compilation environment posing as an interpreter environment. It has many of the same characteristics of a compilation environment, and in fact is created with the augment-environment/setf technique described above.
:compilation
: a compilation environment is also similar to a compiler environment, except that macros and compiler-macros can recognize one and macroexpand differently. Note: it is a goal to eventually remove this kind of environment; the distinction should not be as useful as it currently is.
:macros-only
: this environment kind serves a special-purpose when making a lexical-closure for a macrolet. Because macrolet makes macro-functions in its own lexical environment, but because referencing a local variable within this environment is undefined, it is necessary that only macro definitions be copied when the lexical-closure is created.
If one considers a namespace to be a one-to-one mapping of a name to a binding, then the function namespace is not a pure namespace in Common Lisp; consider that a name can simultaneously name both a special-operator and either a macro or a compiler-macro, or it can name a macro or function and a compiler-macro simultaneously. Of course, any lexical definition created for that name (such as an flet, labels, or macrolet) will shadow all of these potential combinations, but if no such shadowing occurs, there is a necessity for function-information to be able to make the distinctions between the various combinations of definition that are possible.
If the fourth argument (the special-operators argument) to function-information is true, and if the name is a non-shadowed special-operator, then :special-operator
is returned, even if it has a macro or a compiler-macro definition as well.
If the argument is nil
, then for a special-operator which also has a compiler-macro, :compiler-macro
is returned only for :compiler
environments (otherwise :special-operator
is returned), and for a special-operator which also has a macro definition, :macro
is returned only for :interpreter
and :evaluation
environments (otherwise :special-operator
is returned).
We do not define what occurs if a special-operator has both a macro and a compiler-macro definition, because Allegro CL has none of these situations. There should be a normalized behavior for such a situation.
If a name defines a compiler-macro as well as either a macro or a function, then which is returned depends on the environment kind: a :compiler
environment will cause the :compiler-macro
to be returned, and any other environment will result in the :function
or :macro
being returned.
The following functions and variables are defined in our environments implementation:
*compile-file-environment*
)The optimize qualities (safety, space, speed, debug) are no longer repesented by variables in the excl package (e.g. excl::.speed.
) Also, the compilation-speed quality is added to the set, because it is specified by ANS. These 5 qualities are now accessed by (sys:declaration-information 'optimize <env>)
which returns an alist which will always include at least one of each of these qualities. This return value is constant -- never modify its contents. Also, there may be more than one entry for a quality; the first one encountered is the correct value for the specified environment.
The optimize declaration is defined by
(sys:define-declaration optimize (declaration env)
.optimize.
:declare
<body>)
This means, for debugging purposes, that sys::.optimize.
is used as the holder of the global optimization quality list.
The declaration-information function will return this list, possibly shadowed by local lexical optimize declarations, depending on the environment object passed to it.
cl-user(1): (sys:declaration-information 'optimize nil)
((safety 1) (space 1) (speed 1) (compilation-speed 1) (debug 2))
cl-user(2):
cl-user(1): (setq e1 (sys::make-augmentable-environment-boa :compilation))
#<Augmentable compilation environment @ #x71b0941a>
cl-user(2): (setq e2 (sys:augment-environment e1 :declare '((optimize speed (safety 0)))))
#<Augmentable compilation environment 1 @ #x71b0992a>
cl-user(3): (sys:declaration-information 'optimize e1)
((safety 1) (space 1) (speed 1) (compilation-speed 1) (debug 2))
cl-user(4): (sys:declaration-information 'optimize e2)
((speed 3) (safety 0) (safety 1) (space 1) (speed 1)
(compilation-speed 1) (debug 2))
cl-user(5):
This is an example of a mutually exclusive declaration; one can declare the same symbol binding both inline and notinline, but not at the same time. Therefore, these two declarations are combined into a single declaration class called inline.
The entire definition for these two declarations is
(sys:define-declaration (inline inline notinline) (declaration env)
.inline.
:function)
The first inline in the namespace is the class, and each of the two further names in the namespace are the declaration instances. The kind of declaration is :function, and the body is null and thus takes on the default action.
Thus, function information for a function name that has been declared either inline or notinline will include as its third returned value an entry of the form (inline nil
(but this might usually be left off).
cl-user(1): (setq e1 (sys::make-augmentable-environment-boa :compilation))
#<Augmentable compilation environment @ #x71b0941a>
cl-user(2): (setq e2 (sys:augment-environment e1 :function 'foo
:locative (list #'(lambda (x) (1+ x)))
:declare '((notinline foo))))
#<Augmentable compilation environment 1 @ #x71b09c62>
cl-user(3): (setq e3 (sys:augment-environment e2 :declare '((inline foo))))
#<Augmentable compilation environment 1 1 @ #x71b09eba>
cl-user(4): (sys:function-information 'foo e1)
nil
cl-user(5): (sys:function-information 'foo e2)
:function
(#<Interpreted Function (unnamed) @ #x71b09c32>)
((inline notinline))
t
cl-user(6): (sys:function-information 'foo e3)
:function
(#<Interpreted Function (unnamed) @ #x71b09c32>)
((inline inline) (inline notinline))
t
cl-user(7):
cl-user(1): (sys:function-information 'bar nil)
nil
cl-user(2): (sys:augment-environment nil :declare '((notinline bar)) :reuse t)
nil
cl-user(3): (sys:function-information 'bar nil)
nil
cl-user(4): (sys:function-information 'bar nil t)
:free
(nil)
((inline notinline))
cl-user(5): (setq e1 (sys::make-augmentable-environment-boa :compilation))
#<Augmentable compilation environment @ #x71b09bd2>
cl-user(6): (setq e2 (sys:augment-environment e1 :declare '((inline bar))))
#<Augmentable compilation environment 1 @ #x71b09fba>
cl-user(7): (sys:function-information 'bar e1)
nil
cl-user(8): (sys:function-information 'bar e1 t)
:free
(nil)
((inline notinline))
cl-user(9): (sys:function-information 'bar e2 t)
:free
(nil)
((inline notinline) (inline inline) (inline notinline))
nil
cl-user(10):
Like inline and notinline, these declarations are mutually exclusive. excl::ignore-if-unused is the archaic form for ignorable. The declaration class is called ignore, and will have a value of ignore, ignorable, or excl::ignore-if-unused (though the archaic form only shows up in a compilerless lisp, where it is never used anyway).
The initial definition of these declarations is:
(sys:define-declaration (ignore ignore ignorable excl::ignore-if-unused)
(declaration env)
.ignore.
:variable)
However, when the compiler is loaded, it overwrites this definiton with one which will
It doesn't make sense to ignore a variable which hasn't been bound, but no error is generated when such a situation occurs.
cl-user(1): (setq e1 (sys::make-augmentable-environment-boa :compilation))
#<Augmentable compilation environment @ #x71b0941a>
cl-user(2): (setq e2 (sys:augment-environment e1 :variable 'foo
:locative (list (comp::make-varrec-boa 'foo))
:declare '((ignore foo))))
#<Augmentable compilation environment 1 @ #x71b09ab2>
cl-user(3): (multiple-value-list (sys:variable-information 'foo e2))
(:lexical
(#<compiler::varrec foo DynamicExtent: maybe Used: ignore
unknown-type>)
((ignore ignore)) t)
cl-user(4): (comp::varrec-used (caadr *))
ignore
cl-user(5):
Example of maintenance of definitions in compile-file-environment, as opposed to the global-environment:
Note that the two definitions of foo given here are maintained in different locations; one in the global environment, and one in the lexical environment:
cl-user(1): (fboundp 'foo)
nil
cl-user(2): (sys:augment-environment nil :function 'foo
:locative (list (compile nil (lambda (x y)
(+ x y)))))
nil
cl-user(3): (sys:function-information 'foo)
:function
nil
nil
cl-user(4): (fdefinition 'foo)
#<Function (:anonymous-lambda 9) @ #x71b0ae42>
cl-user(5): (funcall * 10 20)
30
cl-user(6): (setq e1 (sys:make-compile-file-environment))
#<Augmentable compilation environment @ #x71b0e7b2>
cl-user(7): (setq e2 (sys:augment-environment e1 :function 'foo
:locative (list (compile nil (lambda (x y)
(- x y))))))
#<Augmentable compilation environment 1 @ #x71b11d62>
cl-user(8): (sys:function-information 'foo)
:function
nil
nil
cl-user(9): (fdefinition 'foo)
#<Function (:anonymous-lambda 9) @ #x71b0ae42>
cl-user(10): (sys:function-information 'foo e2)
:function
(#<Function (:anonymous-lambda 10) @ #x71b10bda>)
nil
t
cl-user(11): (multiple-value-bind (ignore locative)
(sys:function-information 'foo e2)
(funcall (car locative) 10 20))
-10
cl-user(12):
Copyright (c) Franz Inc. Lafayette, CA., USA. All rights reserved.
|
Allegro CL version 11.0 |