Introduction

Thanks to Objective-C, Cocoa is built upon a highly dynamic, reflexive and open object model. Among other things, this makes it possible to build visual object graph editors, run-time monitoring tools, object browsers and numerous other programs making use of the capabilities of the Cocoa run-time. One important application of Cocoa's openness is its integration with scripting languages. This is very useful because it allows for interactive use and easy gluing of Cocoa components.

In this article I want to give you a taste of Cocoa scripting, and to show you the level of integration that you can expect. We will use F-Script, an open source scripting language for Cocoa, to build a little graphical application. Using F-Script, we will directly program against the Application Kit, which is the Cocoa object framework for graphical user interfaces. Note that one can build this kind of application using Interface Builder, but in this article we will use the Application Kit programmatically, in order to illustrate Cocoa scripting.

Note: This article is an updated version of Scripting Cocoa with F-Script, which is published on the O'Reilly MacDevcenter here.

Yet another currency converter

Our application is a "currency converter" similar to the one found in Apple tutorials.

The currency converter we will build using F-Script and Cocoa.

F-Script

F-Script is a pure object-oriented scripting language with Smalltalk-like syntax and concepts. You can interact with F-Script through the FScript.app application, which gives you an interactive shell into which you can type instructions.

Some pointers on the F-Script syntax:

Instructions are separated by a dot.

:=
denotes an assignment.

Strings use simple quotes:
'A string'
.

Comments use double quotes:
"A comment"

(125<>513 extent:383<>175) denotes a rectangle. It is equivalent to NSMakeRect(125, 513, 383, 175) in Objective-C.

The currency converter script: first version

The following script can be entered into the F-Script shell using a simple copy/paste. You can also enter the intructions step by step: you will then see the currency converter interface appearing bit by bit. Of course, you can change the values of arguments used in the script or omit some instructions in order to see what happens. You can even interact with the objects after you have built the currency converter.

"F-SCRIPT CURRENCY CONVERTER (VERSION 1)""Instantiate and configure a window"window := NSWindowallocinitWithContentRect:(125<>513extent:383<>175)
styleMask:NSTitledWindowMask+NSClosableWindowMask+NSMiniaturizableWindowMaskbacking:NSBackingStoreBuffereddefer:NO.
"Put the window onscreen"windoworderFront:nil.
"Give a title to the window"windowsetTitle:'Currency Converter'.
"Instantiate a form object"form := NSFormallocinitWithFrame:(15<>70extent:348<>85).
"Put the form into the window"windowcontentViewaddSubview:form.
"Configure the form"formaddEntry:'Exchange Rate per $1'.
formaddEntry:'Dollars to Convert'.
formaddEntry:'Amount in Other Currency'.
formsetInterlineSpacing:9.
formsetAutosizesCells:YES.
"Instantiate a decorative line and put it in the window"line := NSBoxallocinitWithFrame:(15<>59extent:353<>2).
windowcontentViewaddSubview:line.
"Instantiate a button, put it in the window and configure it"button := NSButtonallocinitWithFrame:(247<>15extent:90<>30).
windowcontentViewaddSubview:button.
buttonsetBezelStyle:NSRoundedBezelStyle.
buttonsetTitle:'Convert'.
buttonsetKeyEquivalent:'\r'.
"Create the script that will compute the currency conversion"conversionScript := [(formcellAtIndex:2) setStringValue:(formcellAtIndex:0) floatValue* (formcellAtIndex:1) floatValue].
"Make the script the target of the form.
The script will be evaluated when the user presses Return"formsetTarget:conversionScript.
formsetAction:#value.
"Make the script the target of the button.
The script will be evaluated when the user presses the button"buttonsetTarget:conversionScript.
buttonsetAction:#value.

We note several interesting things straight away:

The syntax for sending messages to objects is similar to the Objective-C syntax, except that we don't use
[
and
]
to mark the beginning and end of a message send. This similarity doesn't come as a surprise because Objective-C syntax for message sending is borrowed from Smalltalk.

We directly reference the Cocoa classes in the code:
NSWindow,
NSForm,
NSButton, and
NSBox
are Cocoa classes.

We directly reference Cocoa constants: constants such as
NSTitledWindowMask,
NSClosableWindowMask, or
NSRoundedBezelStyle
are symbolic names defined by Cocoa.

We directly use the Cocoa API. Methods such as
alloc,
initWithFrame:,
setTitle:, and so on are part of the Cocoa frameworks.

We can pass F-Script objects as arguments to Cocoa methods. Alternatively, we can get Cocoa objects from these calls and further use them from F-Script. As you may know, providing this kind of integration often requires the use of somewhat delicate bridging techniques. With F-Script, things are much simpler in this respect because there is nothing to bridge: The F-Script object model is the Cocoa object model.

The actual currency conversion is computed by an object created using the following instruction:

The
[...]
notation creates an object of class Block which represents a block of code that can be executed later (Block is an Objective-C class provided by the F-Script framework). In our block, we simply get the values of the fields in the user interface objects, perform the computation (simply involves multiplication) and put the result in a UI element.

Further on in the code, our block object becomes the target of the form and button objects. Thus, it is evaluated when the user hits Return or clicks on the button.

The currency converter script: second version

Below is the second version of the currency converter. This script is shorter because we use some neat F-Script features that we avoided in the first version for simplicity's sake. We also removed some comments (the code speaks for itself) and reorganized the program a little bit: the interface is now entirely constructed and configured before being put on screen.

One of the new things we use in this version is the ";" notation for cascading messages. This notation enables us to send several messages to a single receiver without having to re-specify the receiver each time.

Another interesting thing is the instruction:

formaddEntry:@{'Exchange Rate per $1', 'Dollars to Convert', 'Amount in Other Currency'}.

One of the innovative features of F-Script is that it allows us to manipulate entire groups of objects at once, even with methods that have not been specifically designed to support objects collections (actually, F-Script provides a full object query language, directly usable on Cocoa objects). This is the case in this instruction, where we add a whole list of entries to the form at once. We use the "messaging pattern" notation (denoted by "@") that allows us to specify potentially complex groups of message sends. A messaging pattern generally involves single or multiple collections of objects: in our example, we use an array of string objects, denoted by "{" and "}". At run-time, the instruction will trigger the generation of these three message sends:

formaddEntry:'Exchange Rate per $1'

formaddEntry:'Dollars to Convert'

formaddEntry:'Amount in Other Currency'

The same pattern is also used in the following instruction, where we put a whole set of views into the window at once:

windowcontentViewaddSubview:@{form, button, line}.

In these examples, we use a relatively simple pattern. F-Script provides a syntax that makes it possible to express more complex messaging patterns.
All the specific concepts of F-Script, like messaging patterns, can be used when scripting Cocoa.

Our script is designed for use with Objective-C automatic garbage collection. If we run F-Script in reference counting mode we should add a few additional calls to ensure that the various Cocoa objects we create will be destroyed when no longer in use. Specifically, we should add autorelease message sends to the expression that create the form, the expression that create the button and the expression that create the line. So, for instance, the expression that create the form becomes:

In reference counting mode, when we assign an object to a script's global or local variable, F-Script automatically retains it, releasing it when we re-assign another object to the variable. Thus, in this particular script, we don't need to add any explicit retain invocations.

The currency converter script: third version

As it stands, our script is not very modular: it's just a set of instructions, using global variables, that we paste into the F-Script console in order to execute them. The following version introduces modularity:

Our script will become itself an object.

It will now use local variables instead of global variables, in order to not pollute the top-level environment.

In this version, we put the instructions between [ and ] in order to make the F-Script interpreter generates a block object. We also declare, on the first line of the script, the argument and the local variables. Finally, we give the name "converter" to the block object that represents our script.
A block can take an arbitrary number of arguments and can have an arbitrary number of local variables, as well as having access to variables of its enclosing environment.

We can now invoke our script by sending it a "value" message, without forgetting to provide the required argument. For instance:

convertervalue:'My Great Converter'

Each time we invoke it, a new, fully functional currency converter is created and displayed on screen.

Since our script is now a block object, we can manipulate it like any other object: put it in a collection, pass it as an argument to methods, archive it on disk etc. There are also several facilities specifically related to blocks, including a graphical code editor. We can open it by sending an inspect message to our block.

converterinspect

Finally, since F-Script comes in the form of a framework ready to be embedded into any Cocoa application, it is easy to place our script in a standard Mac OS X executable.

Final thoughts

We have seen what scripting Cocoa with F-Script involves. Clearly, many subjects have not been tackled in this article, such as Cocoa exception handling, Interface Builder integration, usage of custom Objective-C classes or mapping of non-object types, but you can expect a very high level of integration on these aspects.