ORCA Scripting Ref

 
 

Overview

ORCAScript is a small interpreted programming language that can be used to automate some ORCA run-time tasks. Among its design goals were that it could be interpreted using a relatively simple lex/yacc based code, provide a C-like language that would be easy to learn, and provide access to most of the objects in an ORCA configuration. ORCAScripts are created and edited using the ScriptTask object. Scripts are automatically entered into the list of tasks managed by the Task Master and can be executed either from there of from the ScriptTask object. Note that ORCA also has a FilterScript language that is meant for processing data streams.


Scripting examples can be found in the RunControl, Plotting, and Manual Plotter sections of this manual.


Characteristics

As an C-like language, ORCAScript has the following characteristics:

  1. The syntax is, as much as possible, the same as C

  2. A procedural programming paradigm, with facilities for structured programming

  3. A small set (around 15) of reserved keywords

  4. Function parameters are passed by value

  5. Recursion is supported


ORCAScript also has the following specific properties:

  1. Variables are untyped and treated internally as NSDecimalNumber objects.

  2. Variables are global in scope within each function

  3. Arrays are allowed

  4. Access to objects in an ORCA configuration is supported using Obj-C syntax

  5. If you have a pointer to an object, then the methods of that object can be called. This includes, for example, being able to use the NSString methods on string variables.

  6. The common C math functions are supported, i.e.: pow, sqrt, ceil, floor, round, cos, sin, tan, acos, asin, atan, abs, exp, log, log10, fmod.


Limitations:

  1. With the exception of ORCA configuration object access, there are no pointers

  2. No structures, enums, or typedefs

  3. Strings are allowed only in print, logfile, strcat functions, and as arguments to Obj-C methods

  4. In some cases, misspellings of certain keywords (i.e. break) will not be caught by the parser

  5. Some limits apply to the methods that can be called on Cocoa foundation objects, for example, the NSString formatting methods can not be called because they use a variable argument list.


Script Task Object


The ScriptTask object is a run-time environment for creating and executing tasks written in OCRAScript. Double-clicking on the icon will bring up the Script IDE.


Once scripts are written and debugged, they can be chained together to do even more complex tasks. Here is a example of a chain of four tasks. However, note that the third task in the chain has the 'break chain' option selected. In this case only the first three tasks will run. The return value from each finished task will be passed into the next task in the chain as a function argument.

 

Running Task

This will run when the first task is done

This task will run, but will NOT execute the next task in the chain

This will NOT automatically run because the chain is broken

All scripts are entered into the list of tasks in the TaskMaster and can be executed from there as well.


Language Reference


Introduction


With some limitations, ORCAScript is essentially a sub-set of the 'C' language. A couple of new keywords and some Obj-C syntax are introduced to facilitate interaction with the ORCA run-time.

An introduction, here is the ubiquitous 'hello world' example in ORCAScript:


1 function main(a)

2 {

3     print ("hello world -- I got passed a = ", a);

4     a++;

5     return a;

6 }


Looks a lot like 'C' doesn't it? Some notes on the example:

Line 1. The main function definition. All scripts must have a main function. Other functions may either precede or follow the main function. Note that the variable 'a' is not typed. The only variables that need to be defined are array variables. Uninitialized variables are set to zero the first time they are accessed.


Line 3. ORCAScript's output function is print. The bracketing '(' ')' s are optional. This function just outputs its argument list, which is a comma separated list of strings, variables, and constants.


Line 5. When the main function returns the next script in the chain is started. The return value of the main function will be passed into the next script in the chain. If no scripts follow, the return value will be ignored.


Importing Scripts

You can import scripts from external files using the #import command.


1 #import “~/myDirOfScripts/greatScript”

2 function main()

3 {

4     a = greatScriptMethod();

5     print “The returned value was: “,a;

6 }


The full path name must be used. Do not put a main() function in any of the files that are to be imported. If duplicate method names are found, the last one processed will be the one used. You can import multiple files.


Variables


Variables in ORCAScript are easy-- just use them. Variables are untyped and initialized to zero by default. In general, things should work as you would expect; if you add a float and an integer, the result will be a float. The resulting precision will follow the use of NSDecimalNumber objects. Check with Apple's Cocoa documentation if you have questions in this area.


Global Variables


You can define a group of variables that are global to all functions:


global a,b,c=6,e;


The global keyword can only appear in the main function and should be the first line(s) in the main function. It is legal to appear later in the script, but any variables that are used before the global definition will be moved into the global symbol table.


Arrays


There are two ways to define arrays.

The first way is similar to the ways are defined in ‘C’. In this case arrays are defined using the keyword array and must be given with a max array size. The syntax is:


array anArray[10];


You can initialize arrays when they are defined like so:


array anArray[3] = {5,6,7};


The second way is to use the @[..] syntax which defines the initial length and also loads the array with some initial values:


anArray = @[5,6,7];


Either way, once defined the array can be accessed with same syntax as 'C'. The print command will print out the entire array if just the array name is given. For example:


array anArray[4];

anArray[2] = 3.2;

anArray[3] = 1;

print "The array = ",anArray;


will print out the following to the status log:


The array = (0,0,3.2,1)


Note that the array in this example contained a mix of integer and float values and it works as expected. Stepping beyond the bounds of an array will cause an exception that will cause the script to exit prematurely.


There is one major limitation in the use of arrays -- some operators can not be used. In particular, you can not use any of the operators that assign a value back into the array, i.e. +=, -=, *=, ++, etc... And unfortunately, those constructs will parse correctly--you'll just get a run-time exception when they are executed.


Predefined Constants


There are some predefined constants. The values should be obvious:


true, false, TRUE, FALSE

yes, no, YES, NO

nil, NUL


Functions


Functions take the form:


function name ( [optional comma separated list of arguments])


ORCAScript follows C in that you must define a main function. Other functions may be defined anywhere in the script, either before or after the main function--it doesn't matter. Calling a function is just in C. Here's an example:


function main()

{

    a = valuePlusOne(3);

    print a;

}


function valuePlusOne(x)

{

    return x+1;

}


That example will obviously print out 4.


Since functions can be directly or indirectly recursive, care must be taken to ensure program correctness to prevent infinite recursion. Just like in C and other languages, this can not be checked for and would cause a crash of the entire ORCA environment.


Loops


All of the 'C' language loop constructs are allowed.


do {

    <statements>

}while(<condition>);


while(<condition>) {

    <statements>

};


for(i=0;i<10;i++){

    <statements>

}


If you are iterating over an array or a dictionary, it is possible to use the Obj-C syntax:


for(anItem in anArray){

    <statements>

}

in the case of a dictionary you will be looping over the dictionary’s keys rather than the values:


for(aKey in aDictionary){

    <statements>

}


The break and continue statements work same as in 'C'.

The exit statement causes the script to exit completely and return 0;


Flow Control and Branching


All of the 'C' language flow control constructs are allowed.


if(<condition>){

    <statements>

}

else {

    <statements>

}


switch(<variable>){

case <val>:

    <statements>

break;


case <val1>  <=< <val2>:  // if switch variable is in range of val1 and val2 inclusive

   <statements>

break;


...

...

default:

<statements>

break;

}


Special Switch Case Statements


There are some unique conditional case statements, which let you streamline a group of checks. Here are some examples:


switch(x){


    case < 2: //check if x < 2

        <statements>

    break;


   case <= 6: //check if x <= 6

        <statements>

    break;


  case 7 <=< 15: //check if 7 <= x <=15 inclusive

        <statements>

    break;


case 20 << 30: //check if 20 < x < 30

        <statements>

    break;


  case > 30: //check if x > 30

        <statements>

    break;


   case >= 40: //check if x >= 40

        <statements>

    break;

}


Note that the case statements are executed in order.


Exception Handling


It is possible that calls to Cocoa or Obj-C objects will fail with an exception. An example is stepping past the bounds of an NSArray. Normally such an exception would cause ORCAScript to exit, but if you need to try to continue or fix the problem that caused the exception you can use an exception handler. The syntax is:


try {

    <statements>

}

catch (<variable>) {

    <statements>

}

finally {

    <statements>

}


The variable in the catch block contains a string with the reason the exception was thrown.


The finally block is optional and will be executed whether or not an exception was generated in the try block.


If you need the script to exit after catching an exception you will have to call exit, otherwise the script will continue to run.


You can throw an exception by making an NSException object and raising it:


anException = nsexception(“Name”,”Reason”,@{“userInfo”,”more Info”});

[anException raise];


or throwing it:


throw(anException);


Exceptions that are raised or thrown will be caught by an exception handler if one exists.

If a handler does not exist, the script will exit.


Printing and Logging


Printing is a cross between 'C' and 'C++'. A list of arguments are just printing in the same order as defined. The syntax is:


print [(] <list of variables and or strings> [)];


Note that the bracketing parentheses are optional. Arrays may part of the list of variables to be printed and will be printed as a parentheses bracketed list.

Examples:


for(i=0;i<6;i++){

    print "The variable is: ",i;

}


Will print out:


The variable is: 0

The variable is: 1

The variable is: 2

The variable is: 3

The variable is: 4

The variable is: 5


Printing functions


There are some functions to help format printed output. They are:


hex(<var>);


The hex() function takes a single argument and returns a hex value in string format.


string(<list of variables and strings>);

strcat(<list of variables and strings>);


The string() function takes a comma separated list of arguments and format the result into a string delimited by spaces.

The strcat() function takes a comma separated list of arguments and format the result into a string without spaces.


Strings can also be concatenated with ‘+’.


Examples:

print string("The hex value of 255 is ",hex(255)); //outputs "The hex value of 255 is 0xff"

print "The hex value of 255 is ",hex(255);            //outputs "The hex value of 255 is 0xff"

print hex(255);                                                      //outputs 0xff

print strcat(“a”,1);                                                 //outputs “a1”

print “Hello” + “ “ + “World”;                                 //prints “Hello World”


Logging to Output Files


logfile [(] <list of variables and or strings that make a path name> [)];


Examples:


logfile "~/MyOutputLog",10 ; //the output file is now "MyOutputLog10" in your home dir.

print "HELLO"; //outputs to both the status log and to MyOutputLog10 file.


Note that output files are never deleted. Output is appended to the end of existing files. If you wish to start a new empty file with the same name, you must manually delete the old file.


File I/O


There is only very limited file I/O, but it can be used for simple tasks. The functions are:


stringFromFile(<path>);          //returns the content of a file as a string

writeLine(<path>,<string>);    //appends the string to the file and adds a newline character

deleteFile(<path>);                //deletes the file


The following example writes “Hello World” to a file in the users home folder, reads the contents of the file back into a string, then deletes the file.


function main() {

  s = @"hello world";

  path = @"~/TestFile.txt";

  writeLine(path,s);


  contents = stringFromFile(path);

  print contents;


  deleteFile(path);

}


String manipulation can be done using the NSString functions.


Supported Math functions


The supported math functions include:

pow(), sqrt(), ceil(), floor(), round(), cos(), acos()

sin(), asin(), tan(), atan(), abs(), exp(), log(), log10(), fmod()


Integration With ORCA


It is simple to interact with many of the objects in an ORCA configuration. There basically two steps:


1) Get an object reference to an ORCA object

2) Send a message to that object


To get an object reference, use the find() function, the syntax is :


objRef = find(<ORCA Object Class Name>); or

objRef = find(<ORCA Object Class Name>,<slot or tag #>); or

objRef = find(<ORCA Object Class Name>,<crate #>,<slot or tag #>);


If the object is NOT a card object and tag # is left off, the first object found will be used.

If the object IS a card object, the crate number will default to 1 and the only the first crate will be searched for objects. For cards, you must supply at least the slot number. For CAMAC and IPE cards, use the station number.


Note: you can drag an object from the configuration window into the script editor to get the proper identification string can should be used in the find command.


Once you have the objRef you can use regular ObjC syntax to send a message to the object.


Here's an example:


1 rc = find(ORRunMode);

2 if([rc isRunning])print "Yes--Running";

3 else print "No-- NOT Running";


Line 1: Finds the Run control object in the configuration.

Lines 2: Sends a 'isRunning' message to the run control object, which returns a bool.

Lines 2,3: Print out the result.


1 shaper = find(ORShaperModel,2);

2 [shaper setThreshold: chan withValue:aValue];

3 print "The Theshold value for channel ", chan, "is ",[shaper threshold:chan];


Line 1: Finds the Shaper card object in slot 2. (crate # is defaulted to 1)

Line 2: Sets the threshold for one of the channels.

Line 3: Reads back and prints out the threshold.


Note: A couple of things to take note about talking when talking to ORCA objects.

  1. You can not call methods that take structures (i.e. NSRange structures) as arguments. The exceptions are the NSPoint and NSRect structures. Create a NSPoint struct using the point(x,y) fuction and the NSRect struct with rect(x,y,w,h).

  2. You can not call methods that return stuctures.

  3. Arrays should be OK as arguments and return values.

  4. For the most part, you can only access Objects in the configuration. With few exceptions other objects, such as the main document can not be reached.

  5. It is OK to pass the object pointer to functions and it is recommended that you do so, as finding objects is a relatively expensive operation and it is much more efficient to only find them once.


Here’s another example to start a run, wait until the run is in progress then do some unspecified action:


function main()

{

rc = find(ORRunModel);

if(rc){

[rc startRun];

waituntil([rc isRunning]);


//do something


[rc stopRun];

}

else print "couldn’t find run control";

}


Special ObjC functions


point(x,y) //makes a NSPoint struct

rect(x,y,w,h) //makes a NSRect struct

range(loc,len) //makes a NSRange struct

pointx(<point structure>) //returns x value

pointy(<point structure>) //returns y value

rectx(<rect structure>) //returns x value

recty(<rect structure>) //returns y value

recth(<rect structure>) //returns h value

rectw(<rect structure>) //returns w value

rangeloc(<range structure>) //returns location value

rangelen(<range structure>) //returns length value


Other Special Functions


There are some special functions:


waituntil(<condition>); //waits until the condition is true, always returns true

waituntil(<condition>,<#secs>); //waits until the condition is true OR for # seconds.

//returns true is <condition> is met, false if timed-out.

sleep(<duration>); //yields the script thread in 0.01 time slices for the duration.

yield(<duration>); //like sleep, but yields for 1 full second per time slice. Uses less cpu as a result.

postalarm(<alarmName>,<alarmSeverity> [,<alarmHelp>]);

clearalarm(<alarmName>);

nsdictionary(); //creates an NSDictionary object

nsdictionary(<key1>,<value1>,...); //creates an NSDictionary object from a list of keys and values

@{<key1>:<value1>,...} //creates an NSDictionary object from a list of keys and values


nsarray(); //creates an NSArray object

nsarray(<value1>,...); //creates an NSArray object from a list of values

@[<value1>,...] //creates an NSArray object from a list of values


nsexception(<name>,<reason>,<userInfo dictionary>); //creates an NSException object


nsfilemanager(); //creates an NSFileManager object

seedRandom(); //seeds a random number generator

random(<min>,<max>); //returns a random number between <min> and <max>

sort(<array or dictionary>); //returns the array or a dictionary’s keys sorted in ascending order

sortRev(<array or dictionary>); //returns the array or a dictionary’s keys sorted in descending order


Note: The waituntil() function is only useful if the condition can change. This means you should only call it where the condition is an ORCA object call. An example:


1 rc = find(ORRunModel);

2 waituntil([rc isRunning]);

3 if([rc isRunning]) print ("Yep, we are running");


Line 1: Finds the Run Control object.

Line 2: Execution will stop here until a run is in progress.

Line 3: When the run starts execution will continue here.


Date and Time Functions


There are some date and time functions built into the main base class that can be accessed by getting a reference to any object in the configuration. An example:


function main()

{

  s = find(ORRunModel,1);

  print [s month],"/",[s day],"/",[s year]," ",[s hour],":",[s minute],":",[s second];

}


The output looks something like this:


032610 08:48:20 [OrcaScript] The date is: 3 / 26 / 2010   8 : 48 : 20


Mail Functions


It is possible to send eMail using a script. An example:


function main()

{

  i = 10;

  s = find(ORScriptTaskModel,1);

  content = "Hi\nThis was sent from an ORCA script\nMark\n\n";

  content = strcat(content,"The Value is:",i,"\nTimes 5:",i*5,"\nTimes 10:",i*10);

  [s sendMailTo:"mhowe@physics.unc.edu" cc:nil subject:"script mail test" content:content];

}


You have to cascade multiple calls to strcat to build up complex content. Note -- a recent addition to ORCAScript allows the use of ‘+’ to concatenate strings also.


Dialog Functions


There are a couple of ways to interact with the user from a script. The keywords are confirm, request, and show. The syntax of these commands are:


result = confirm(<string>);                        //return true if user confirms, false if user cancels

result = confirm(<string>,<#secs>);         //return false after # seconds if the user fails respond

result = request(<var1, var2, .... varN);    //opens dialog with textfields for the variables.

                                                                //Changes are ignored if the user selects cancel.

ptr = show (<var1, var2, .... varN);          //opens a dialog displaying the values.

                                                                //Returns a pointer of type NSWindowController).


To update the show dialog do:               [ptr refresh];

To set the title of the show dialog:          [[ptr window] setTitle:”New Title”];


Persistent Data


It is possible to have data that is persistent across ORCA restarts by using functions in the scripting object. As long as ORCA is saved, the data that is stored using those functions will be available from the configuration file from the scripting object that saved the data. As an example:


function main() {

  s = find(ORScriptTaskModel,1);

  [s setStoredObject:666 forKey:"test"];

  print "got: ",[s storedObjectWithKey:"test"];

}



Temporary Globally Available Data


It is possible to have data that is accessible by other ORCA objects by using functions in the scripting object. As an example:


function main() {

  s = find(ORScriptTaskModel,1);

  [s setTemporaryObject:666 forKey:"test"];

  print "got: ",[s temporaryObjectWithKey:"test"];

}


This can be used to cache data that can be accessed from other machines running ORCA.


Self Inspection


There are a few special variable names for accessing internal scripting parameters:


_func_  //variable holding the function name

_symbols_//variable holding the local symbol table

_globals_//variable holding the global symbol table



Notifications


Note that this is an advanced topic. Be careful, as you can reek havoc with these calls.


It is possible for a  script to react to notifications. The caveat is that the script must have run at least once first in order to be registered for a particular notification -OR- another script must have run that does the registration for it.


The registration is done with the following calls on the script object:


- (void) stopOnNotificationName:(NSString*)aName fromObject:(id)anObject;

- (void) runOnNotificationName:(NSString*)aName fromObject:(id)anObject;

- (void) cancelNotificationName:(NSString*)aName;


Note that if the anObject is nil, then all notifications with aName will be caught. In general things will behave like Cocoa NSNotifications.


Example:


function main() {

self = find(ORScriptTaskModel,1);    //find ourself

other = find(ORScriptTaskModel,2); //some object that we will listen to for a notification

[self runOnNotificationName:"test" fromObject: other]; //our future startup notification

[self stopOnNotificationName:"stop" fromObject: other]; //our stop notification

while(1){

            //do something

}

}


Scripts can also post a notification (and possibly fake the sender):


- (void) postNotificationName:(NSString*)aName;

- (void) postNotificationName:(NSString*)aName fromObject:(id)anObject;

- (void) postNotificationName:(NSString*)aName fromObject:(id)anObject userInfo:(id)userInfo;


Example:


function main() {

self = find(ORScriptTaskModel,13);        //find ourself

other = find(ORScriptTaskModel,1);     //some object that we will listen to for a notification

[other runOnNotificationName:"start" fromObject: self]; //some object that will listen for start

[other stopOnNotificationName:"stop" fromObject: self]; //some object that will listen for stop


       [self postNotificationName:"start" fromObject:self]; //post a notification that will start other object

yield(4);

[self postNotificationName:"stop" fromObject:self]; //post a notification that will stop other object

}


Notice in the above example, the script is registering another object and then posting notifications.


Couch Database


Using methods in the scriptIDE object you can insert records into a couch database.

For example the following script (with the name ‘ThresholdFinder’):


function main() {

s = find(ORScriptTaskModel,1);

started = time();

sleep(5);

ended = time();

aRecord = @{"started":string(started),"ended":string(ended)};

[s postCouchDBRecordToHistory:aRecord];

}


Will result in the following record being added to your machine’s history database.


{

   "_id": "91e883669ce843be4138331be100dad1",

   "_rev": "1-1a0c0714f8314e5b20c5fc73e900b3d9",

   "title": "Scripts",

   "time": 1484146651,

   "scriptName": "ThresholdFinder",

   "ended": 1484146651.5913758,

   "started": 1484146646.142457,

   "timestamp": "2017/01/11 14:57:31"

}


You can also use the method:


  1. -(void) postCouchDBRecordToHistory:(NSDictionary*)aRecord;


to insert records into the history database.