Compiling and performing code
We show several ways to programmatically create classes or methods, or evaluate code.
You can compile and evaluate a valid Smalltalk string like this:
Smalltalk compiler evaluate: '3 + 4'.
This can be useful if you need to construct and evaluate Smalltalk expressions from data (that you trust).
In Smalltalk, you can classically create a new class programmatically by sending a suitable message to the superclass of the class to be created. Here, for example, we create HellowWorld as a subclass of Object
.
Object subclass: #HelloWorld instanceVariableNames: '' classVariableNames: '' category: 'HelloWorld'.
As you might imagine, the method Class>>#subclass:instanceVariableNames:classVariableNames:category:
is defined in Class
. (See Understanding Smalltalk classes and metaclasses, which explains that all metaclasses, such as Object
, inherit from Class
).
If you browse the method above, you will see that it has a large number of subclass:* siblings, with various paramters for creating subclasses.
Class methods select: [:m | m selector beginsWith: 'subclass:'].
Caveat: Actually, in Pharo the heavy lifting of creating classes is now done by a dedicated
builder
class, ShiftClassBuilder
. Just browse any of the subclass:* methods and follow the delegation chain till you find where the work is actually done.
Once you have a class, you can add methods by asking the class to compile the source code of the new method:
(Smalltalk at: #HelloWorld) compile: 'hello ^ ''hello'''.
This is the same as:
#HelloWorld asClass compile: 'hello ^ ''hello'''.
We can then inspect the newly compiled method:
#HelloWorld asClass >> #hello.
Which is the same as:
#HelloWorld asClass methodDict at: #hello.
If the classor method to perform are only known at run time, there are two ways to evaluate a method programmatically. You can either send #perform: to the object, or you can send #valueWithReceiver:arguments: to the method.
Here we send perform: twice, first to create a new instance of Hello World and then to send then instance #hello.
((Smalltalk at: #HelloWorld) perform: #new) perform: #hello.
Without reflection this is the same as: HelloWorld new hello.
As you might guess, there are multiple variants of perform:* that take different numbers of arguments.
3 perform: #+ with: 4.
3 perform: #+ withArguments: {4}.
Note that with perform:, the method to be evaluated will still be dynamically looked up in the superclass chain.
You can also directly execute a method, explicitly passing in the receiver and any arguments. Here we look up the hello method we compiled earlier in the HelloWorld class. Then we directly execute the method (
i.e.,
without any further lookup) with a Hello World instance as the receover and an empty argument array:
method := #HelloWorld asClass>>#hello. method valueWithReceiver: #HelloWorld asClass new arguments: #().
And here is 3+4 evaluated explicitly:
SmallInteger >> #+ valueWithReceiver: 3 arguments: #(4).
Here we query an instance of the GtTour
slideshow for its <gtSlide> methods, select one at random, and evaluate it.
aSlideshow := GtTour new.
(aSlideshow class methods
select: [ :m | m hasPragmaNamed: 'gtSlide' ]) atRandom
valueWithReceiver: aSlideshow
arguments: {GtProtoLiveSlide new}.
Of course we have to know that a slide can be instantiated by passing it an instance of GtProtoLiveSlide
.