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 ProtoObject subclass: #Object instanceVariableNames: '' classVariableNames: 'DependentsFields' package: 'Kernel-Objects' .

Object subclass: #HelloWorld
  instanceVariableNames: ''
  classVariableNames: ''
  category: 'HelloWorld'.
  

As you might imagine, the method Class>>#subclass:instanceVariableNames:classVariableNames:category: subclass: aSubclassSymbol instanceVariableNames: instVarNameList classVariableNames: classVarNames category: aCategorySymbol "Added to allow for a simplified subclass creation experience. " ^ self subclass: aSubclassSymbol instanceVariableNames: instVarNameList classVariableNames: classVarNames poolDictionaries: '' package: aCategorySymbol is defined in Class ClassDescription subclass: #Class instanceVariableNames: 'subclasses name classPool sharedPools environment category' classVariableNames: '' package: 'Kernel-Classes' . (See Understanding Smalltalk classes and metaclasses, which explains that all metaclasses, such as Object ProtoObject subclass: #Object instanceVariableNames: '' classVariableNames: 'DependentsFields' package: 'Kernel-Objects' , inherit from Class ClassDescription subclass: #Class instanceVariableNames: 'subclasses name classPool sharedPools environment category' classVariableNames: '' package: 'Kernel-Classes' ).

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 Object subclass: #ShiftClassBuilder instanceVariableNames: 'buildEnvironment superclassName name layoutDefinition comment commentStamp superclass category newMetaclass newClass oldClass oldMetaclass builderEnhancer metaclassClass extensibleProperties changeComparers changes metaSuperclass superclassResolver inRemake' classVariableNames: 'BuilderEnhancer' package: 'Shift-ClassBuilder' . 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 Object subclass: #GtTour uses: TGtSlideShow instanceVariableNames: '' classVariableNames: '' package: 'GToolkit-Demo-MoldableDevelopment-Slideshows' 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 BrStencil subclass: #GtProtoLiveSlide instanceVariableNames: 'priority definingMethod notes' classVariableNames: '' package: 'GToolkit-Presenter' .