MacDevCenter    
 Published on MacDevCenter (http://www.macdevcenter.com/)
 http://www.macdevcenter.com/pub/a/mac/2002/07/12/embed_fscript.html
 See this if you're having trouble printing code examples


Embedding F-Script into Cocoa Applications

by Philippe Mougin
07/12/2002

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 Main Classes in the F-Script Integration API

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:

FSInterpreter

Role:

Each instance of this class represents a complete F-Script interpreter (workspace included). An application may instantiate any number of interpreters.

Main Features:

  • Executes the F-Script code contained in an NSString.
  • Searches for the object associated with a certain identifier in the workspace.
  • Assigns an object to an identifier in the workspace.
  • Lets you know the identifiers defined in the workspace.

Name:

FSInterpreterResult

Role:

Represents the result of an execution of F-Script code by an FSInterpreter.

Main Features:

  • Lets you know the execution status of the F-Script code, i.e. syntax error, execution error or success.
  • In the event of an error, lets you retrieve information on this error: human readable description, location in the code, block call-stacks.
  • If successful, lets you get the result of the execution.

Name:

FSInterpreterView

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:

  • Can be integrated into a Cocoa view hierarchy.
  • Manages user interaction in command line mode. Various functions: copy/paste, command history, choice of fonts, graphical signaling of errors, etc.
  • Lets you insert an F-Script command programmatically.
  • Lets you transmit a message to the user.
  • Lets you retrieve the associated interpreter (an FSInterpreter instance).

Name:

System

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:

  • Can build a block object in the associated workspace from an NSString containing F-Script code.
  • Lets you open a graphical F-Script object browser.
  • Lets you save/reload a workspace.
  • Obtains the list of all the variables defined in the associated workspace.

Name:

Block

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:

  • Executes itself (throwing an exception in the event of an error).
  • Manages a graphical interface for code editing.

Name:

FSNSString

Role:

A category of NSString.

Main Features:

  • Builds a block from F-Script code represented by the NSString object.

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.

Using the asBlock Method

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.

Example 1: Hello World

Here's how to execute a "hello world" program written in F-Script from Objective-C:

  1. Create a string containing the F-Script code.

    NSString *fscriptCode = @"[sys log:'hello world']";

  2. Create a Block object from the string.

    Block *myBlock = [fscriptCode asBlock];

  3. 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.

Building Cocoa Applications: A Step by Step Guide

Related Reading

Building Cocoa Applications: A Step by Step Guide
By Simson Garfinkel, Michael Mahoney

Table of Contents
Index
Sample Chapter

Read Online--Safari
Search this book on Safari:
 

Code Fragments only

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.

Example 2: passing parameters and retrieving the result

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];

Example 3: object graph

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.

Screen shot.
The mini-application we'll build.

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;
}

Error Management

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.

Using the FSInterpreter Class

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;      
}

FSInterpreterView Component and Palette for Interface Builder

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.

Screenshot.
An FSInterpreterView.

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.

Screen shot.
The F-Script palette for Interface Builder.

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.

Screen shot.
F-Script live mode in Interface Builder: connecting F-Script to a button.

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).

Final Thoughts

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 © 2007 O'Reilly Media, Inc.