ORCA Scripting Ref
ORCA Scripting Ref
Overview
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:
• The syntax is, as much as possible, the same as C
• A procedural programming paradigm, with facilities for structured programming
• A small set (around 15) of reserved keywords
• Function parameters are passed by value
• Recursion is supported
ORCAScript also has the following specific properties:
• Variables are untyped and treated internally as NSDecimalNumber objects.
• Variables are global in scope within each function
• Arrays are allowed
• Access to objects in an ORCA configuration is supported using Obj-C syntax
• 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.
• 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:
• With the exception of ORCA configuration object access, there are no pointers
• No structures, enums, or typedefs
• Strings are allowed only in print, logfile, strcat functions, and as arguments to Obj-C methods
• In some cases, misspellings of certain keywords (i.e. break) will not be caught by the parser
• 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.
• 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).
• You can not call methods that return stuctures.
• Arrays should be OK as arguments and return values.
• 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.
• 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:
-(void) postCouchDBRecordToHistory:(NSDictionary*)aRecord;
to insert records into the history database.