Fred Rivard
Object Technology International Inc.
Ottawa - Ontario
fred_rivard@oti.com
Reflection'96 - San Franscico - CA
Edited by Gregor Kiczales
postcript version (152190 octets) here
![]()
Most of smalltalk reflective aspects are used to implement NeoClasstalk
(NeoClasstalk web page).
![]()
1 Introduction
2 Reflective aspects survey
![]()
2.1 Meta-Operations
![]()
2.2 Structure
![]()
2.3 Semantics
![]()
2.4 Message Sending
![]()
2.5 Control State
3 Reflective Extension: Addition of Pre/Post Conditions
![]()
3.1 Model Extension
![]()
3.2 Environment Extension
![]()
3.3 Compiler Extension
![]()
3.4 Benchmarks
4 Conclusion
References
A.1 Code
![]()
SMALLTALK derives its success largely
from being not only a language but also an operating
system and a development environment as well as producing applications which
are extremely portable on multiple
platforms.
The most important
aspect about the language is that, in the LISP tradition, it
is almost entirely written in
itself. This property makes it an open system that is easily
extendable. The implementation of SMALLTALK [Par94b]
itself is structured as
an object-oriented program, expressed in
SMALLTALK and organized around meta-level objects representing the
classes, methods, lexical closures, processes, compilers, and even the
stack frames.
SMALLTALK belongs to the field
of languages that deals with reflection.
`` Reflection is the ability of a program to manipulate as data something representing the state of the program during its own execution. There are two aspects of such manipulation : introspection and intercession. Introspection is the ability of a program to observe and therefore reason about its own state. Intercession is the ability of a program to modify its own execution state or alter its own interpretation or meaning. Both aspects require a mechanism for encoding execution state as data; providing such an encoding is called reification'' [DBW93].
Even if the precise point at which a language with reflective facilities becomes a reflective language is not well defined (and is an interesting issue that merits examination by the reflective community as a whole), SMALLTALK has one of the most complete sets of reflective facilities of any language in widespread use. Although SMALLTALK is not fully reflective due to the pragmatic reason of efficiency [GR83], its reflective facilities can provide much of the power of full reflection [FJ89]. This characteristic is responsible for most of its advantages over other industrial object-oriented languages, such as C++ and ADA95.
What probably accounts for a large part of the success of the early LISP interpreters and their different derived dialects, is the great ease with which one can describe and build programs in terms of simple objects such as lists. Taking the trivial example of the addition of two numbers, the program can be described as (cons '+ '(1 2)) Thus, one can consider programs as regular data and may use them as such. Futhermore, the program can reason about itself. The idea follows that a program could see itself as data, and thus modify itself.
Although SMALLTALK seems to be a little bit more complicated than LISP at first glance, it has kept LISP's approach towards code, regarding and manipulating it as regular data. Taking the creation of simple objects such as points as an illustration, the external representation of a point matches exactly the program that creates it.
1@2 represents a point where the x value is 1 and the y value is 2. Moreover, the execution of this representation, viewed as an expression, returns exactly the point object 1@2. The internal representation can also be accessed. An object may have a textual representation of its internal state using the message storeString, which returns a sequence of characters that is an expression whose evaluation creates an object similar to itself. Thus (1@2) storeString returns the string 'Point x: 1 y: 2'. Explicitly calling the regular evaluator using Compiler evaluate: '...aString...', the evaluation of this next string returns true:
(1@2) = (Compiler evaluate: ((1@2) storeString ))Classes, which are complex objects, also have a textual representation.true
ArithmeticValue subclass: #Point instanceVariableNames: 'x y ' classVariableNames: '' poolDictionaries: '' category: 'Graphics-Geometry'The above text matches the definition of the Point class, which can be obtained by sending the definition method to the reified object that represents the Point class. Thus the evaluation of a class definition returns an object (a class) that returns exactly the same string when asked for its definition.
The SMALLTALK code is stored in what is called a method, which corresponds (approximately) to a named LISP lambda-expression. As for classes, a textual representation may be obtained just by sending introspective messages. [:x | x+1] is equivalent to the (lambda (x) (+ x 1)) LISP expression. It is represented by an object from which one can ask for its external textual representation. In order to get their external textual representation, methods and lexical closures, denoted under the vocable block, use their internal representation, which mainly comprises bytecodes, as well as a decompiler (which is reified, too). A special tool ( CompiledCodeInspector) makes the access to this source representation very user friendly, using the mouse and a click on a field.
Therefore, following the LISP tradition, a SMALLTALK program may reason about itself regarding and manipulating the different objects that represent it (textually or internally).
``First, the basic elements of the programming language - classes, methods and generic functions - are made accessible as objects. Because these objects represent fragments of a program, they are given the special name of metaobjects. Second, individual decisions about the behavior of the language are encoded in a protocol operating on these metaobjects - a metaobject protocol. Third, for each kind of metaobject, a default class is created, which lays down the behavior of the default language in the form of methods in the protocol.'' [KdRB91]
Ordinary objects are used to model the real world. Meta-objects describe these ordinary objects. As a consequence, meta-objects mostly describe SMALLTALK entities. We quote non-exhaustively major meta-object classes (classified by subject):
This paper is divided in two parts: the first part is a survey of the reflective capabilities of the language, and the second is an illustrative example of those capabilities. After having presented meta-operations and their use, we focus on the most important reflective subjects: structure, behavior, semantic and control state. We describe the involved meta-objects and their classes. We quote significant applications using such objects. As an illustration of reflective manipulations, we introduce pre/post conditions in SMALLTALK, dealing with (small) extensions of the model, the compiler and the development environment. We conclude with the current propensity of SMALLTALK to include more and more reflection in recent releases, which we consider as a sign of adaptability to new software engineering challenges.
Rather then going through a complete enumeration of all the reflective facilities of SMALLTALK, we concentrate on the most important ones:
Meta-operations are operations that provide information about an object as opposed to information directly contained by the object. [...] They permit things to be done that are not normally possible (page 195 of [LP90]).
Major meta-operations are defined in the root of the inheritance tree, the class Object as methods for:
Introspection is the essence of reflection, and so the first applications using structural reflective facilities are tools used to introspect the SMALLTALK system: the Inspector class and its subclasses.
An inspector enables the user to look at the structure of an object, and to modify its instance variable values, using Object>>#instVarAt:( put:) methods. The inspector uses the inspected object class ( Object>>#class) to get its instance variable names ( Behavior>>#allInstVarNames) and the index of the instance variables. Notice that these methods allow the programmer to break the encapsulation of an object, and this must only be used in pertinent contexts.
(3@4) xA hierarchy of inspectors is available, allowing specialized inspection on particular objects, such as collections, dictionaries, etc.3 (3@4) instVarAt: 1
3 (3@4) instVarAt: 1 put: 5
5@4 (3@4) class instSize
2 (3@4) class allInstVarNames
('x' 'y')
Inspector ChangeSetInspector CompiledCodeInspector ContextInspector DictionaryInspector SequenceableCollectionInspector OrderedCollectionInspector
Structural reflection implies the ability of the language to provide a complete reification both of the program currently being executed as well as of its abstract data type[DM95]. SMALLTALK as a unified language only manipulates objects. Each object is an instance of a class that describes both the behavior and the structure of its instances. A class named Object defines the basic behavior of every object of the system, such as accessing the class of an object.
Classes as regular objects are described by other (regular)
classes called metaclasses
. A
metaclass has a single instance (except
metaclasses involved in the kernel of SMALLTALK). It establishes a couple
class/metaclass schema. Inheritance on metaclasses follows the one
at the class level (cf Figure 1), defining the SMALLTALK
metaclass composition rule.
This schema is known as the
SMALLTALK-80 schema, and states how metaclasses are composed. It may
induce class hierarchy conflicts [Gra89], but for everyday
development, the pragmatic SMALLTALK choice suits most needs.
Metaclass display is the concatenation of the global name of its sole instance (a class),
and the class string. As an example, the metaclass of the class
Object is the Object class metaclass.
The behavior of classes and metaclasses are described by two (meta)classes respectively named Class and Metaclass. In order for classes to behave as classes, Object class inherits from Class. In particular the new method, enabling object creation, is accessible. This property is often given as the definition of a class. All metaclasses are instances of Metaclass, and in particular the Metaclass class is also an instance of Metaclass, stopping de facto an instantiation of infinite regression. Two abstract classes named Behavior and ClassDescription regroup the common behavior between metaclasses and classes (for example new is defined on Behavior).
Finally the class/metaclass kernel of SMALLTALK is self-described with only five classes:

Figure 1: SMALLTALK class/metaclass kernel
The SMALLTALK-80 kernel has pragmatic origins,
resulting from several years of intensive development
using simpler models that chronologically were SMALLTALK-72 [KG76] and SMALLTALK-76 [Ing78]. In order to keep an ``easy to use'' model,
a tool named ClassBuilder hides the apparent complexity
of the kernel from the end-user. A class creation
(and its associated metaclass creation) is fully managed by
the tool, which is called by the class creation
protocol
.
It
also automatically manages class redefinition, guaranteeing system
consistency in terms of object structures and preventing name
conflicts, especially instance variable name conflicts.
When a class
definition changes, existing instances must be structurally modified
in order to match the definition of their new class. Instead of modifying
an existing object, the ClassBuilder creates a new one with the
correct structure (i.e., from the new class that replaces the old one). It then fills this new object
with the values of the old one. The
ClassBuilder uses the become: primitive (cf 2.1.1) to
proceed with the strutural modifications, by replacing
the old objects
with the new ones throughout the entire system.
Methods are held by classes in an instance variable methodDict, whose value is an instance of the MethodDictionary class. It enables access to the SMALLTALK code. It also allows methods to be dynamically added at runtime ( ClassDescription>>compile:classified:). The ClassOrganizer class provides an organization of methods according to their purpose in protocols and every class holds such an organization in the instance variable organization. Classes themselves are grouped into categories according to their purpose. Smalltalk organization represents the organization of classes. It is an instance of the SystemOrganizer class which is a subclass of the ClassOrganizer class.
An ordinary use of the self-expressed kernel is to extend it in order to match new application domains. Our next pre/post conditions example (cf 3) is such an extension. As another typical example, CLASSTALK [Coi90] proposes an experimental platform (an extension of SMALLTALK) to study explicit metaclass programming. But even in the language, reification is of great benefit allowing introspection using dedicated tools: Browser. It manipulates classes and metaclasses as regular objects. Thus, it can investigate their definitions ClassDefinition>>#definition and their inheritance links, following the reified superclass/subclasses instance variables.
The Browser organizes the user external interface according to the information held by the different reified organizations :
The reification of classes allows the language to provide essential efficient utilities such as implementors (look into all classes for methods matching a given name), senders (look into all methods for the ones performing a given sending message) and messages (look for implementors of a message present in a given method).
Point selectorsIdentitySet( #x #y #transpose ...) Point compiledMethodAt: #+
Point>>+ Point findSelector: #class
#( Object Object>>class) Point superclass
ArithmeticValue Point compilerClass
Compiler
One of the salient features of SMALLTALK is the fully reified compilation process. Since any compiler implicitly gives the semantics of the language it compiles, and because SMALLTALK has in itself, as regular objects, its own compiler, the SMALLTALK semantics is fully controllable. Therefore one may extend the current language semantics providing new compile-time features by extending/modifying current compilers.
This approach must be compared to the one of compile-time MOP [LKRR92], which breaks the compilation process into small independent fully redesignable pieces. SMALLTALK compilation uses the existing SMALLTALK code for its own needs, and is designed as a regular OO program which is causally connected to the language. Thus, using current OO technology, one can extend the current compilation process. Next we describe what can be considered as the first compile-time MOP. But the heavy interaction between what is part of the compiler and what is not sometimes makes the use of this compile-time MOP difficult. Therefore the authors of [HJ95] proposes a more parametrized compiler. This big interconnection between the compilation phase and the SMALLTALK language as a whole is demonstrated by the next small example, which discusses the order of argument evaluation of a message send. The compilation process uses the regular do: method from the SequenceableCollection class, allowing the treatment of each element of a collection in a left to right order. Therefore, it defines a left to right semantics for the argument evaluation order. In that, the SequenceableCollection class can be seen as a part of the compilation process because it defines the semantics of the argument evaluation order. Notice that the array that is used to hold the arguments of a message at compile time is therefore a meta-object(cf 1.2) but other arrays would not necessarily be meta-objects.
ProgramNode MethodNode ParameterNode StatementNode ReturnNode ValueNode ArithmeticLoopNode AssignmentNode CascadeNode ConditionalNode LeafNode BlockNode LiteralNode VariableNode LoopNode SequenceNode SimpleMessageNode MessageNodeThe MessageNode class represents message sending. It implements a tiny macro expansion mechanism at code generation time. The MacroSelectors dictionary holds selectors that need expansion
All of these classes are part of the compilation process. In order to introduce new semantics into SMALLTALK, one can extend these classes and the associated process that compiles code. We next describe what steps this compilation process follows:
SmalltalkCompiler>>translate:aStream noPattern:... "<1>... parsing..." methodNode := class parserClass new parse: aStream builder: Prog ramNodeBuilder new ... "<2>... code generation..." codeStream := self newCodeStream. methodNode emitEffect: codeStream. method := codeStream makeMethod: methodNode. ^method
Extending the proposed semantics by intervening in the two phases of compilation allows new semantics to be implemented that suit the domain of the application to be modeled as well as possible. The open ended compiler allows modification of itself in order to get improvements needed to face new user requirements, such as a new breakpoint mechanism [HJ95]. The introduction of new methods into the language can be easily performed by subclassing MessageNode, in order to propose new message sending semantics. The code generation of this new node will be different, inserting its own semantics. In our experience there are five major methods that are frequently used to add new semantics:
(i) extension of the parser (ii) extension of the node construction (iii) modification of the obtained parse tree (iv) extension of the code generation phase (v) extension of the compilation environment
Our next pre/post conditions introduction (cf 3) uses a modification of the parse tree (iii). As another example, we provide an efficient implementation of asynchronous message sending for ACTALK [Bri89] (cf 2.4.2), dealing with node construction extension (ii) [Riv95].
Within ACTALK, the user has two message send semantics at his disposal: the regular SMALLTALK one, and an asynchronous one. An asynchronous message send is syntactically declared
using the 'a.' prefix
.
anActor a.message
The distinction between the two semantics can be made by a syntactic analysis. Thus,
the idea is to intercept the messageNode creation made by aNodeBuilder
( mewMessageReceiver:selector:arguments:).
We introduce a new class,
ActalkProgramNodeBuilder, subclassing the regular
ProgramNodeBuilder. When the new nodeBuilder creates a messageNode,
it analyzes the selector of the message. If it starts with the
'a.' prefix, then the ActalkProgramNodeBuilder returns aMessageNode of
which the selector is the one that queues (at runtime) the asynchronous message
into the received messages queue of
the actor ( addMessage:arguments:).
Thus, for the ' anActor a.message'
expression, the builder returns the next messageNode
:
aMessageNode selector : #addMessage:arguments: receiver : anActor arguments: #( message, #() )Notice that this transformation can be assimilated to a macro-expansion of all 'a.' prefixed message sends.
More generally,
used in association with the kernel extension, compilation reflection allows
one to build new languages [RC94]. It allows SMALLTALK
to execute
source code whose semantics is different from the default one. A
large industrial example is given by OBJECT5 [Sie94]
.
It is a strongly typed hybrid language based both on the
actor and class paradigms, dedicated to Programmable-Logical-Controllers.
Although it has 3
different message sending semantics (2 are asynchronous), it is entirely
executed in SMALLTALK, without an OBJECT5 interpreter being
written. This eliminates an always penalizing software stratum. Types have been
introduced extending the class/metaclass kernel ( TypedClass subclass
of Class) in order to provide typed
information (method signature, instance variable types, ...). New
syntactical nodes have been introduced, and new compilers, too. Finally the SMALLTALK VM executes this new language as it used to execute regular SMALLTALK. Contrary to the (latent) reproach of the lack of efficiency
of reflective systems, here reflection brought an outstanding
gain of efficiency.
The unique control structure of SMALLTALK is message sending. It is composed of two phases:
2 zorkAn explicit message send may be called using the perform: primitive2 doesNotUnderstand: aMessage aMessage selector
#zork aMessage arguments
#()
-regular message send: 5 factorialAccesses to overwritten behavior are qualified by sending a message to the pseudo variable super. The lookup semantics of such a message is slightly different from the default lookup, since it starts from the from the superclass of the class which implements the method that executes the super. As a matter of fact, the class from whose superclass the lookup starts is accessible within the compiledMethod variable part120 -explicit message send using a symbol: 5 perform: #factorial
120 -application of a CompiledMethod: (Integer>>#factorial) valueWithReceiver: 5 arguments: #()
120
To sum up lookup, SMALLTALK provides two different entry points:
Everything is expressed in terms of sending messages.
There is no need for special keywords or special forms, as in
BASIC, ADA'95 or C++, etc.
As an example, a class declaration is made by sending the
subclass:instanceVariableNames:classVariableNames:-
poolDictionary:category:
message with correct arguments. Browsers use
this facility (cf 2.2.2).
An evaluation is expressed in terms of a default method. Then it is mostly evaluated (using #valueWithReceiver:arguments:) with nil as the default receiver. The result is either discarded (doIt action), inspected through the sending of the inspect message (inspectIt action), or pretty-printed through the sending of the message printString (printIt action).
The management of the lookup failure allows the building of a catch-up mechanism by specialization of the doesNotUnderstand: method, as in the encapsulator paradigm [Pas86], and in the implementation of asynchronous messages for ACTALK [Bri89]. In particular, #valueWithReceiver:arguments: and #perform: methods can be used. More generally, #valueWithReceiver:arguments: enables one to dispense with the use of the default lookup and to implement (in cooperation with the Compiler) new lookup algorithms, such as multiple inheritance. This last approach is an efficient alternative to the use of the doesNotUnderstand:method (cf 2.3.2).
As an example of the use of the doesNotUnderstand method, we
describe the implementation of lazy evaluation in
SMALLTALK
.
aLazyObject := [ ... aBlock ...] lazyValue.
A lazy object represents an execution that may not be required. It does
not start execution until at least one message has been
received. aLazyObject is used as the regular object that would have resulted
from the evaluation of the code inside the block ( [ ... aBlock
...]). Thus it receives messages, such as color if it represents a
Car.
nil subclass: #Lazy instanceVariableNames: 'result done args ' classVariableNames: '' poolDictionaries: '' category: 'Kernel-Processes'As the Lazy class is a subclass of nil, every message send causes the invocation of the doesNotUnderstand method.
Lazy doesNotUnderstand: aMessage done ifFalse:[ result:= result valueWithArguments: args. done := true]. ^result perform: aMessage selector withArguments: aMessage argumentsWhen it receives its first message, the lazy object forces the evaluation of the block. Therefore it computes the real object, which was previously in a lazy state (i.e., uncomputed). It is buffered for other message sends. An explicit message send, using perform:withArguments:, allows the regular execution scheme to continue.
A classical use of super is the initialization of newly-created objects. When adding a subclass, both new and inherited initializations must be carried out. Thus, the initialize method of the subclass usually looks like:
Subclass>>initializesuper initialize. self localInitialization
The SMALLTALK system is based on reified processes, and more generally on the objects needed to build a multiprocess system. Processes manage time scheduling ( timingPriority), event inputs such as keyboard/mouse ( lowIOPriority), and regular user evaluations ( userBackgroundPriority, userSchedulingPriority, userInterruptPriority).
The BlockClosure class represents lexical closures. It freezes a piece of code (along with its environment) so that it may be evaluated later on. Blocks can have temporaries and arguments. The general syntactic form is [:arg1 ... :argN | | tmp1 ... tmpM | expr1 ... exprP]. Block evaluation is provided by primitives named value, value:, valueWithArguments: depending of the number of arguments. SMALLTALK uses lots of blocks, as in the SequenceableCollection>>do: method for example:
do: aBlock "Evaluate aBlock with each of the receiver's elements as the argument." 1 to: self size do:[:i | aBlock value: (self at: i)]Process creation is based on blocks; the body of a process is the body of the block. The BlockClosure>>fork method creates a process. As blocks may share an environment, independent processes uses this facility to share common objects. A process may be suspended, resumed or killed (using respectively suspend, resume or terminate methods). The interruptWith: method forces the process that receives it to interrupt whatever it is doing and to evaluate the received block, passed as the argument. The ProcessorScheduler>>yield method is a tiny but good illustrative example
yield ``Give other Processes at the current priority a chance to run.'' | semaphore | semaphore := Semaphore new. [semaphore signal] fork. semaphore waitThe currently running process (the one that executes this code) creates a new semaphore. It proceeds to the creation of a new process ( [...] fork) that is pushed into the list of the processes that may run (at the same priority). The current running process then suspends itself while it executes the wait primitive. The VM then takes the next available process and makes it run. The small created process, which shares the semaphore with the previously running process, will run in its turn. Its only action before dying is to unblock the previously running process using the signal primitive on the common semaphore.
The most remarkable reflective facility of SMALLTALK is the reification of any process runtime stack, through a chain of linked stack frames, called contexts [Par94a]. The pseudo-variable thisContext returns the present context of the currently running process. It is an instance of the MethodContext class, or the BlockContext class.
A context mainly knows (Figure 3):

Figure 3: Two elements of the executive stack. The top-most MethodContext represents thisContext.
Therefore, a first application of this execution control is the implementation of the exception handler mechanism into SMALLTALK, which modifies the ``regular'' execution scheme. The Exception class reifies objects which manipulate the executive stack in order to handle errors ( return, reject, restart). Exceptions are raised through the stack, and are caught by handlers defined by the handle:do: message, in order to take appropriate actions on errors. This implementation may itself be extended or replaced in order to propose an alternative to the error handling system of SMALLTALK [Don90].
A second very important application of the reification of the runtime stack is the Debugger tool (see Figure 3), which can:
Applications are not stable during both development and coding phases. Therefore it is essential to provide mechanisms in order to check both the properties of and the assumptions made on methods. Pre/post conditions are devoted to this role. A number of languages, following Flavors [Moo86], implement before/after methods (SOM [DFM94], CLOS,... ). One of their uses can be the implementation of pre/post conditions on methods. But because before/after methods rely on a complex composition mechanism and because they are assigned to a selector (name of methods) instead of the methods themselves (regular objects in SMALLTALK (cf 2.3.1)), we use another implementation. It better suits their roles as described by: ``The pre-condition expresses the properties that must be checked when the method is called. The post-condition certifies those properties that the method guarantees when it returns.'' [Mey90]. When the development is over and the software is about to be released, correct method use makes pre/post conditions no longer useful. They should be removed in order to provide software clean from any development topics. This is how we use pre/post conditions. Our goal is to provide pre/post conditions in SMALLTALK that respect the dynamic and convivial tradition of the language. Specifications are summarized as follows:

Figure 4: The currently selected class (NodeItem) has its conditions activeness set (cf "(c)"). The associated conditions codes is executed at runtime. The figure also shows the menu (conditions) that permits the change from active to non-active conditions (and vice versa)
Next we present the convention used to write conditions (one or both conditions may be omitted):
selector " comment" | temporaries |This syntactic representation offers several advantages:[..blockPreCondition..] preCondition. [..blockPostCondition..] postCondition. expr1 .... exprN
We next describe our solution based on the introduction of a subclass of
Metaclass
.
Considering that the behavior related to conditions activity is both on the class and its metaclass, and that it should not interfere with the inheritance, we put the activity notion on Metaclass, and on a newly created subclass named MetaclassWithControl. This new metaclass manages behavior according to development topics such as pre/post conditions. The compilerClass method (cf 2.3.1) returns the class whose instances (a compiler) are used to compile the methods of a given class. Thus the default compilerClass method is conceptually raised one meta level from that of Behavior to that of Metaclass and MetaclassWithControl (cf SMALLTALK kernel 2.2.1).

Figure 5: The metaclass class changes its class dynamically.
This solution has many advantages:
Our choice of syntactic convention allows the method context to be accessible from condition codes. From an interface point of view, the user looks at its method and associated condition sources at the same time. Practical experience shows the advantage of this convivial representation. It is combined with an immediate view of the activity of the class conditions: when a class has active conditions, the name of the class is suffixed by the (c) string (cf Figure 4).
As we have extended the model in order to add a new metaclass description to deal with development topics, browsers should also take into account this new description. Standard SMALLTALK browsers, as global introspection class tools, assume that class semantics are fixed. Thus, in order to take new class semantics into account, we modifiy the class interface by adding a cooperation between classes and browsers [RM93]: a browser does not simply ask for the name of the class, but for its browsingName. With this message, a class fully controls what a browser shows. MetaclassWithControl>>browsingName adds the ' (c)' string suffix to the name ( classOnControlString method).
Having designed the structural part of the model and shown its implication in terms of interface extension, we now need to extend the compilation in order to manage the needed codes for active pre/post conditions.
Our solution is based on manipulation of the parse tree, which is generated by the SMALLTALK parser. We need:
selector `` comment'' | temporaries | [[..blockPreCondition..] value ifFalse:[ParserWithControl preConditionSignal raiseRequest]. expr1 .... exprN ] valueNowOrOnUnwindDo:[ [..blockPostCondition..] value ifTrue:[ParserWithControl postConditionSignal raiseRequest]]As we need a new compiler when pre/post conditions are active, the CompilerWithControl class is introduced as a subclass of the standard Compiler class. We subclass the Parser class with ParserWithControl class, which is associated with the new CompilerWithControl class through a redefinition of its preferredParserClass method (cf 2.3.1). We next describe the steps that produce a method and its conditions:
The major goal of this extension is to provide code free from any tests when pre/post conditions are not active. Thus, if not active, conditions do not affect the runtime performance at all. When active their code is executed according to the code wrapped around the conditions, which of course takes time.
We make two significant benchmarks on the compilation process:
We have described the current reflective facilities of SMALLTALK. We have presented the most important current aspects: meta-operations, the class/metaclass model, semantics control through the reified compiler, message sending and behavioral representation through the reification of the runtime stack processes. We have fully described an example of reflective use with the introduction of pre/post conditions into SMALLTALK.
As it evolves, SMALLTALK tends to become more and more reflective. In particular we can quote the reification of the dependent link ( DependencyTransformer class), and the definition of a parser generator ( ParserCompiler class), written in itself. REFLECTION is the heart of SMALLTALK. It gives the language its great expressive power. Because the language possesses the ability to naturally adapt itself to new application domains, it may be considered as a truly perennial language.
I wish to thank all the reviewers for their comments. Thanks to Pierre Cointe who helped me in the organization of the paper. Special thanks to Jacques Malenfant who spent time on the elaboration of the final version of the paper.
![]()
The full
development can be loaded using ftp at ftp.emn.fr under
/pub/rivard/Smalltalk/visualworks2.0/prepost.st.
(We provide a version for
visualworks1.0 in /pub/rivard/Smalltalk/visualworks1.0/prepost.st.)
for the full source of the newMessageReceiver:selec
tor:arguments:
method of the ActalkProgramNodeBuilder class.