As we already saw in the previous articles in this series, F-Script can be used as a stand-alone application that dynamically loads your Objective-C classes and enables you to access them either interactively or using scripts. In this article, we'll explore the opposite possibility: including F-Script in your own applications.
All F-Script functionalities are available through components that can be integrated into applications. This enables you to:
Because F-Script and Cocoa share the same object model, integration is easy and advanced.
The F-Script runtime is integrated into an application via a certain number of Objective-C classes, which are provided with F-Script in the form of a framework (FScript.framework). The following table illustrates the main characteristics of these classes in terms of integration.
Name: |
|
Role: |
Each instance of this class represents a complete F-Script interpreter (workspace included). An application may instantiate any number of interpreters. |
Main Features: |
|
Name: |
|
Role: |
Represents the result of an execution of F-Script code by an FSInterpreter. |
Main Features: |
|
Name: |
|
Role: |
A graphical component, a subclass of NSView, which provides F-Script with a command line interface. An FSInterpreterView has its own interpreter and F-Script workspace and can thus be used directly without requiring any other configuration. |
Main Features: |
|
Name: |
|
Role: |
Enables access (from both Objective-C and F-Script) to various services of the F-Script interpreter. The runtime creates a System instance per interpreter and associates it with the identifier "sys" in the interpreter's workspace. |
Main Features: |
|
Name: |
|
Role: |
A block represents a script (in other words, a piece of F-Script code that can have arguments, local variables and bindings to other objects). |
Main Features: |
|
Name: |
|
Role: |
A category of NSString. |
Main Features: |
|
A program wishing to use this API should use FScript.framework and the following import directive:
#import <FScript/FScript.h>
In order to use the F-Script framework from your own applications you will likely need to put it into one of these standard locations for frameworks on Mac OS X:
~/Library/Frameworks
/Library/Frameworks
/Network/Library/Frameworks
/System/Library/Frameworks
You can also directly bundle the framework into your application. This way, you will be able to ship a self-contained solution.
|
The asBlock method offers the simplest means of creating and executing F-Script code from Objective-C. Invoked on an NSString containing the source code for an F-Script block, it generates and returns a Block instance that can then be executed.
We can pass it parameters and retrieve the product of the execution. This technique enables us to unify the usage of blocks as, once created, they are manipulated in the same way from both F-Script and Objective-C.
Here's how to execute a "hello world" program written in F-Script from Objective-C:
Create a string containing the F-Script code.
NSString *fscriptCode = @"[sys log:'hello world']";
Create a Block object from the string.
Block *myBlock = [fscriptCode asBlock];
Execute the block
[myBlock value];
It's possible to combine these instructions:
[[@"[sys log:'hello world']" asBlock] value]
Note: in Objective-C, brackets (i.e. "[" and "]") denote a message send, while in F-Script they denote a block.
|
Related Reading Building Cocoa Applications: A Step by Step Guide |
In the example, the character string containing the F-Script code is hard-coded, although it is possible to use a string dynamically built at runtime.
Let's now turn our attention to parameter passing between Objective-C and F-Script and retrieval of results. As we've already seen, with blocks everything is done in the same way as with F-Script, even though we are now manipulating them from Objective-C. In this example, we assume that we have an NSArray, called "airplanes," which is made up of objects from the Airplane class. This array will be passed-in a parameter to the F-Script code. The Airplane instances respond to the "capacity" method (which returns an int) and location method (which returns an NSString). In the array, we wish to select the planes that are currently in Chicago and whose capacity is greater than or equal to 200.
NSArray *airplanes;
...
NSArray *selectedAirplanes = [[@"[:a| a at:a location = 'CHICAGO' & (a capacity >= 200)]" asBlock] value:airplanes];
As F-Script blocks are objects, they can be referenced by other objects. In this example, a block will be the target of an NSButton. In addition, F-Script variables, inside the block code, may reference external objects. In this example, the F-Script code will reference an NSTextField.
Let's suppose that in an Objective-C program we have an NSButton and an NSTextField. We wish to use an F-Script block to display the current date and time in the text field when the button is pressed.
|
|
But how do you establish a link between an F-Script variable and the external text field? All that is required is the mother block technique, which is fairly frequent with F-Script. Our block is produced by another block, which is responsible for establishing the link with the NSTextField, which it will be given as an argument, using the fact that the blocks are closures (see the Smalltalk literature for more on this).
The following is the complete program for the example.
#import <Cocoa/Cocoa.h>
#import <FScript/FScript.h>
int main(int argc, const char *argv[])
{
NSApplication *NSApp = [NSApplication sharedApplication];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSButton *button = [[[NSButton alloc] initWithFrame:NSMakeRect(100,20,100,30)]
autorelease];
NSTextField *textField = [[[NSTextField alloc]
initWithFrame:NSMakeRect(50,100,200,20)] autorelease];
NSWindow *mainWindow = [[NSWindow alloc]
initWithContentRect:NSMakeRect(100,100,300,160)
styleMask:NSClosableWindowMask | NSTitledWindowMask
backing:NSBackingStoreBuffered defer:false];
Block *motherBlock = [[@"[:textField| [textField setStringValue:NSDate
now printString]]" asBlock] retain];
Block *printDate = [[motherBlock value:textField] retain];
[[mainWindow contentView] addSubview:button];
[[mainWindow contentView] addSubview:textField];
[button setBezelStyle:1];
[button setTarget:printDate];
[button setAction:@selector(value:)];
[mainWindow orderFront:nil];
[pool release];
[NSApp run];
return 0;
}
|
Previously in this series: Scripting Cocoa with F-Script -- F-Script by Philippe Mougin is an open-source project related to Mac OS X that has caught our eye. This lightweight object-oriented scripting layer provides interactive access to Cocoa frameworks and custom objects, and we present it here as part of our ongoing exploration into Cocoa-related tools. Browsing Cocoa with F-Script -- In his first article, Scripting Cocoa with F-Script, Philippe Mougin introduced O'Reilly readers to the joys of scripting their Cocoa projects. In this follow-up piece, he shows you a new tool, the object browser. |
In the above examples, the possibility of errors is not considered, as the F-Script code is fully known here, and we know that the syntax is correct and there is no risk of F-Script generating an error on execution. In more complex cases, errors may appear on two occasions:
For managing syntax errors we prefer the asBlock:onError: method to the asBlock method (see the F-Script guide). An error during execution will cause an exception to be thrown.
The FSInterpreter API, which is the subject of the next section, offers easier error management and is therefore recommended in this type of situation.
|
The technique described above is very easy to use and suitable for numerous situations. Nonetheless, certain cases require more control and additional functionalities.
In such cases we can look to the FSInterpreter class, which offers total control of the F-Script interpreter. Among other things, this class offers:
An FSInterpreter instance represents an F-Script interpreter associated with a workspace. It is easily created in Objective-C:
FSInterpreter *myInterpreter = [[FSInterpreter alloc] init];
An interpreter enables you to add variables in the associated workspace or to modify the value of existing variables:
[myInterpreter setObject:[NSDate date] forIdentifier:@"myDate"];
An interpreter enables you to consult the value of a variable in the workspace:
BOOL found;
id result = [myInterpreter objectForIdentifier:@"myDate" found:&found];
It's also possible to retrieve the list of names of variables defined in the workspace, in the form of an array of NSStrings:
NSArray *identifiers = [myInterpreter identifiers];
To execute F-Script code, pass a character string containing this code to the execute: method of the interpreter. You will get back an FSInterpreterResult object. The following example uses the F-Script "hello world" code:
FSInterpreterResult *result = [myInterpreter execute:@"sys log:'hello world'"];
The FSInterpreterResult object offers complete control over the execution result. The isSyntaxError, isExecutionError, and isOk methods return a Boolean, which enables us to know the status of the result. In the event of an error, the errorMessage and errorRange methods enable us to obtain the error message and relevant location in the F-Script source code. The inspectBlocksInCallStack method lets us open the graphical inspector of the blocks present in the callstack corresponding to the error. If execution does not throw an error, the result method enables us to get the result of the evaluation of the F-Script code.
To illustrate this API, here's a complete program, to be compiled in Project Builder like a Foundation Tool and launched from the Project Builder or a UNIX terminal window. It's a command line interface for F-Script: the program reads a command from its standard input, executes it, prints the result on its standard output and then starts again. Note that this is a non-graphical program (does not use the application kit). It therefore does not allow the F-Script graphic functionalities to be used.
#import <stdio.h>
#import <Foundation/Foundation.h>
#import <FScript/FScript.h>
int main (int argc, char **argv, char **env)
{
FSInterpreter *interpreter;
FSInterpreterResult *execResult;
char c_command[10000];
NSString *command;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
interpreter = [[FSInterpreter alloc] init]; // create the interpreter
[pool release];
while(1)
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
command = [NSString stringWithCString:fgets(c_command, 10000, stdin)];
execResult = [interpreter execute:command]; // execute the F-Script command
if ([execResult isOk]) // test status of the result
{
id result = [execResult result];
// print the result
if (result == nil) puts("nil");
else puts([[result printString] cString]);
if (![result isKindOfClass:[FSVoid class]]) putchar('\n');
}
else
{
// print an error message
puts([[NSString stringWithFormat:@"%@ ,
character %d\n",[execResult errorMessage],[execResult errorRange].location]
cString]);
}
[pool release];
}
return 0;
}
The FSInterpreterView class -- a subclass of NSView -- offers an interactive, command line interface-type F-Script graphical component. The component has its own F-Script interpreter and is ready to use. It may be instantiated and manipulated by program.
|
|
This object has methods that allow you to modify the font size, display a service message for the user, insert a command or retrieve the associated FSInterpreter object.
The following simple program opens a window containing an FSInterpreter View.
#import <Cocoa/Cocoa.h>
#import <FScript/FScript.h>
int main(int argc, const char *argv[])
{
NSApplication *NSApp = [NSApplication sharedApplication];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Create a window
NSWindow *mainWindow = [[NSWindow alloc]
initWithContentRect:NSMakeRect(100,100,500,400) styleMask:NSClosableWindowMask |
NSTitledWindowMask backing:NSBackingStoreBuffered defer:false];
// Create an FSInterpreterView
FSInterpreterView *fscriptView = [[[FSInterpreterView alloc]
initWithFrame:NSMakeRect(0,0,0,0)]
autorelease];
// Insert the FSInterpreterView inside the window
[mainWindow setContentView:fscriptView];
[fscriptView setFontSize:16]; // We want big fonts !
[mainWindow orderFront:nil];
[pool release];
[NSApp run];
return 0;
}
A FSInterpreterView may also be manipulated directly from Interface Builder using the FScriptPalette.palette.
|
|
The palette enables an FSInterpreterView to be placed in a graphical interface using a simple drag and drop movement.
It also offers another functionality--live mode--which enables F-Script to be used directly from Interface Builder, without being in test mode. In live mode, it is possible to set up connections between interface objects and F-Script objects (for example, set up a connection between a button and a block) and to save everything in the nib file. Using this feature it is possible to build a graphical application entirely from Interface Builder by associating F-Script code blocks with interface elements.
|
|
To use live mode, first you instantiate an F-Script interpreter. To do so, simply drag and drop from the F-Script palette to any other window. The instantiated FSInterpreterView object contains the F-Script interpreter, which enables operation in live mode. To activate this mode, double-click on the instance of FSInterpreterView. An active F-Script window appears from which it will be possible to configure interface objects (and their outlets) by using F-Script instructions directly. The FSInterpreterView is used for setting up connections between the F-Script interpreter and external objects (see Figure 4).
In this article we showed how Objective-C developers can embed F-Script into their applications. But what about existing, third-party, applications? Here comes F-Script Anywhere.
This is an amazing new tool, built by Nicholas Riley, which lets you dynamically embed F-Script at runtime into any Cocoa application and lets you take control of the objects inside the application and freely play with them. You can get F-Script Anywhere from Nicholas' Web site.
Also, don't forget to drop by the F-Script site to keep up on latest releases that you can download. You might also want to read the previous articles in this series, and take a look at their TalkBacks. Enjoy!
Philippe Mougin specializes in object-oriented technologies and enterprise systems. He is the creator of F-Script, an open-source object-oriented scripting language for Cocoa.
Return to the Mac DevCenter.
Copyright © 2009 O'Reilly Media, Inc.