Compiling and performing code
TL;DR
We show several ways to programmatically create classes or methods, or evaluate code.
Evaluating expressions
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).
Defining classes programmatically
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.
Compiling methods
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.
Performing methods
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
.