A Closer Look at ObjectScript
This page gives an overview of the ObjectScript language and provides useful background information for those who want to program in ObjectScript or need to understand code that other people have written.
Both methods and routines can be written in ObjectScript, but most modern code is written using methods. Methods are contained within classes, which allow you to group like methods together, automatically generate documentation in the Class Reference, as well as use the object-oriented capabilities of InterSystems IRIS.
This does not mean routines are not important. Many useful system utilities are written as routines, and routines are generated when you compile a class.
A Sample Class
The following shows an sample class named User.DemoClass that contains methods written in ObjectScript. This example gives us an opportunity to see some common ObjectScript commands, operators, and functions, and to see how code is organized within a method.
Class User.DemoClass
{
/// Generate a random number.
/// This method can be called from outside the class.
ClassMethod Random() [ Language = objectscript ]
{
set rand=$RANDOM(10)+1 ; rand is an integer in the range 1-10
write "Your random number: "_rand
set name=..GetNumberName(rand)
write !, "Name of this number: "_name
}
/// Input a number.
/// This method can be called from outside the class.
ClassMethod Input() [ Language = objectscript ]
{
read "Enter a number from 1 to 10: ", input
set name=..GetNumberName(input)
write !, "Name of this number: "_name
}
/// Given an number, return the name.
/// This method can be called only from within this class.
ClassMethod GetNumberName(number As %Integer) As %Integer [ Language = objectscript, Private ]
{
set name=$CASE(number,1:"one",2:"two",3:"three",
4:"four",5:"five",6:"six",7:"seven",8:"eight",
9:"nine",10:"ten",:"other")
quit name
}
/// Write some interesting values.
/// This method can be called from outside the class.
ClassMethod Interesting() [ Language = objectscript ]
{
write "Today's date: "_$ZDATE($HOROLOG,3)
write !,"Your installed version: "_$ZVERSION
write !,"Your username: "_$USERNAME
write !,"Your security roles: "_$ROLES
}
}
Note the following highlights:
-
The Random() and Input() methods invoke the GetNumberName() method, which is private to this class and cannot be called from outside the class.
-
WRITE, QUIT, SET, and READ are commands. The language includes other commands to remove variables, commands to control program flow, commands to control I/O devices, commands to manage transactions (possibly nested), and so on.
The names of commands are not case-sensitive, although they are shown in running text in all upper case by convention.
-
The sample includes two operators. The plus sign (+) performs addition, and the underscore (_) performs string concatenation.
ObjectScript provides the usual operators and some special operators not seen in other languages.
-
$RANDOM, $CASE, and $ZDATE are functions.
The language provides functions for string operations, conversions of many kinds, formatting operations, mathematical operations, and others.
-
$HOROLOG, $ZVERSION, $USERNAME, and $ROLES are system variables (called special variables in InterSystems IRIS). Most special variables contain values for aspects of the InterSystems IRIS operating environment, the current processing state, and so on.
-
ObjectScript supports comment lines, block comments, and comments at the end of statements.
We can execute the methods of this class in the Terminal, as a demonstration. In these examples, TESTNAMESPACE> is the prompt shown in the Terminal. The text after the prompt on the same line is the entered command. The lines after that show the values that the system writes to the Terminal in response.
TESTNAMESPACE>do ##class(User.DemoClass).Input()
Enter a number from 1 to 10: 7
Name of this number: seven
TESTNAMESPACE>do ##class(User.DemoClass).Interesting()
Today's date: 2021-07-15
Your installed version: IRIS for Windows (x86-64) 2019.3 (Build 310U) Mon Oct 21 2019 13:48:58 EDT
Your username: SuperUser
Your security roles: %All
TESTNAMESPACE>
A Sample Routine
The following shows an sample routine named demoroutine that is written in ObjectScript. It contains procedures that do the exact same thing as the methods shown in the sample class in the previous section.
; this is demoroutine
write "Use one of the following entry points:"
write !,"random"
write !,"input"
write !,"interesting"
quit
//this procedure can be called from outside the routine
random() public {
set rand=$RANDOM(10)+1 ; rand is an integer in the range 1-10
write "Your random number: "_rand
set name=$$getnumbername(rand)
write !, "Name of this number: "_name
}
//this procedure can be called from outside the routine
input() public {
read "Enter a number from 1 to 10: ", input
set name=$$getnumbername(input)
write !, "Name of this number: "_name
}
//this procedure can be called only from within this routine
getnumbername(number) {
set name=$CASE(number,1:"one",2:"two",3:"three",
4:"four",5:"five",6:"six",7:"seven",8:"eight",
9:"nine",10:"ten",:"other")
quit name
}
/* write some interesting values
this procedure can be called from outside the routine
*/
interesting() public {
write "Today's date: "_$ZDATE($HOROLOG,3)
write !,"Your installed version: "_$ZVERSION
write !,"Your username: "_$USERNAME
write !,"Your security roles: "_$ROLES
}
Note the following highlights:
-
The only identifiers that actually start with a caret (^) are the names of globals; these are discussed later in this page. However, in running text and in code comments, it is customary to refer to a routine as if its name started with a caret, because you use the caret when you invoke the routine (as shown later in this page). For example, the routine demoroutine is usually called ^demoroutine.
-
The routine name does not have to be included within the routine. However, many programmers include the routine name as a comment at the start of the routine or as the first label in the routine.
-
The routine has multiple labels: random, input, getnumbername, and interesting.
Labels are used to indicate the starting point for procedures (as in this example), functions, and subroutines. You can also use them as a destination for certain commands.
Labels are common in routines, but you can also use them within methods.
Labels are also called entry points or tags.
-
The random and input subroutines invoke the getnumbername subroutine, which is private to the routine.
We can execute parts of this routine in the Terminal, as a demonstration. First, the following shows a Terminal session, in which we run the routine itself.
TESTNAMESPACE>do ^demoroutine
Use one of the following entry points:
random
input
TESTNAMESPACE>
When we run the routine, we just get help information, as you can see. It is not required to write your routines in this way, but it is common. Note that the routine includes a QUIT before the first label, to ensure that when a user invokes the routine, processing is halted before that label. This practice is also not required, but is also common.
Next, the following shows how a couple of the subroutines behave:
TESTNAMESPACE>do input^demoroutine
Enter a number from 1 to 10: 7
Name of this number: seven
TESTNAMESPACE>do interesting^demoroutine
Today's date: 2018-02-06
Your installed version: IRIS for Windows (x86-64) 2018.1 (Build 513U) Fri Jan 26 2018 18:35:11 EST
Your username: _SYSTEM
Your security roles: %All
TESTNAMESPACE>
A method can contain the same statements, labels, and comments as routines do. That is, all the information here about the contents of a routine also applies to the contents of a method.
Variables
In ObjectScript, there are two kinds of variables, as categorized by how they hold data:
-
Local variables, which hold data in memory.
Local variables can have public or private scope.
-
Global variables, which hold data in a database. These are also called globals. All interactions with a global affect the database immediately. For example, when you set the value of a global, that change immediately affects what is stored; there is no separate step for storing values. Similarly, when you remove a global, the data is immediately removed from the database.
There are special kinds of globals not discussed here; see Caret (^) in What’s That?.
Variable Names
The names of variables follow these rules:
-
For most local variables, the first character is a letter, and the rest of the characters are letters or numbers. Valid names include myvar and i
-
For most global variables, the first character is always a caret (^). The rest of the characters are letters, numbers, or periods. Valid names include ^myvar and ^my.var
InterSystems IRIS also supports a special kind of variable known as a percent variable; these are less common. The name of a percent variable starts with a percent character (%). Percent variables are special in that they are always public; that is they are visible to all code within a process. This includes all methods and all procedures within the calling stack.
When you define percent variables, use the following rules:
-
For a local percent variable, start the name with %Z or %z. Other names are reserved for system use.
-
For a global percent variable, start the name with ^%Z or ^%z. Other names are reserved for system use.
For details on percent variables and variable scope, see Variable Availability and Scope; also see Callable User-defined Code Modules).
For further details on names and for variations, see Syntax Rules. Or see Rules and Guidelines for Identifiers.
Variable Types
Variables in ObjectScript are weakly, dynamically typed. They are dynamically typed because you do not have to declare the type for a variable, and variables can take any legal value — that is, any legal literal value or any legal ObjectScript expression. They are weakly typed because usage determines how they are evaluated.
A legal literal value in ObjectScript has one of the following forms:
-
Numeric. Examples: 100, 17.89, and 1e3
-
Quoted string, which is a set of characters contained within a matched set of quotation marks ("). For example: "my string"
To include a double quote character within a string literal, precede it with another double quote character. For example: "This string has ""quotes"" in it."
Depending on the context, a string can be treated as a number and vice versa. Similarly, in some contexts, a value may be interpreted as a boolean (true or false) value; anything that evaluates to zero is treated as false; anything else is treated as true.
When you create classes, you can specify types for properties, for arguments to methods, and so on. The InterSystems IRIS class mechanisms enforce these types as you would expect.
Variable Length
The length of a value of a variable must be less than the string length limit.
Variable Existence
You usually define a variable with the SET command. As noted earlier, when you define a global variable, that immediately affects the database.
A global variable becomes undefined only when you kill it (which means to remove it via the KILL command). This also immediately affects the database.
A local variable can become undefined in one of three ways:
-
It is killed.
-
The process (in which it was defined) ends.
-
It goes out of scope within that process.
To determine whether a variable is defined, you use the $DATA function. For example, the following shows a Terminal session that uses this function:
TESTNAMESPACE>write $DATA(x)
0
TESTNAMESPACE>set x=5
TESTNAMESPACE>write $DATA(x)
1
In the first step, we use $DATA to see if a variable is defined. The system displays 0, which means that the variable is not defined. Then we set the variable equal to 5 and try again. Now the function returns 1.
In this example and in previous examples, you may have noticed that it is not necessary to declare the variable in any way. The SET command is all that you need.
If you attempt to access an undefined variable, you get the <UNDEFINED> error. For example:
TESTNAMESPACE>WRITE testvar
WRITE testvar
^
<UNDEFINED> *testvar
Variable Availability and Scope
ObjectScript supports the following program flow, which is similar (in most ways) to what other programming languages support:
-
A user invokes a method, perhaps from a user interface.
-
The method executes some statements and then invokes another method.
-
The method defines local variables A, B, and C.
Variables A, B, and C are in scope within this method. They are private to this method.
The method also defines the global variable ^D.
-
The second method ends, and control returns to the first method.
-
The first method resumes execution. This method cannot use variables A, B, and C, which are no longer defined. It can use ^D, because that variable was immediately saved to the database.
The preceding program flow is quite common. InterSystems IRIS provides other options, however, of which you should be aware.
Summary of Variable Scope
Several factors control whether a variable is available outside of the method that defines it. Before discussing those, it is necessary to point out the following environmental details:
-
An InterSystems IRIS instance includes multiple namespaces, including multiple system namespaces and probably multiple namespaces that you define.
A namespace is the environment in which any code runs. Namespaces are discussed later in more detail.
-
You can run multiple processes simultaneously in a namespace. In a typical application, many processes are running at the same time.
The following table summarizes where variables are available:
Variable availability, broken out by kind of variable... | Outside of method that defines it (but in the same process) | In other processes in the same namespace | In other namespaces within same InterSystems IRIS instance |
---|---|---|---|
Local variable, private scope* | No | No | No |
Local variable, public scope | Yes | No | No |
Local percent variable | Yes | No | No |
Global variable (not percent) | Yes | Yes | Not unless global mappings permit this† |
Global percent variable | Yes | Yes | Yes |
*By default, variables defined in a method are private to the method, as noted before. Also, in a method, you can declare variables as public variables, although this practice is not preferred. See PublicList.
†Each namespace has default databases for specific purposes and can have mappings that give access to additional databases. Consequently, a global variable can be available to multiple namespaces, even if it is not a global percent variable. See Namespaces and Databases.
Multidimensional Arrays
In ObjectScript, any variable can be an InterSystems IRIS multidimensional array (also called an array). A multidimensional array is generally intended to hold a set of values that are related in some way. ObjectScript provides commands and functions that provide convenient and fast access to the values.
You may or may not work directly with multidimensional arrays, depending on the system classes that you use and your own preferences. InterSystems IRIS provides a class-based alternative to use when you want a container for sets of related values; see Collection Classes.
Basics
A multidimensional array consists of any number of nodes, defined by subscripts. The following example sets several nodes of an array and then prints the contents of the array:
set myarray(1)="value A"
set myarray(2)="value B"
set myarray(3)="value C"
zwrite myarray
This example shows a typical array. Notes:
-
This array has one subscript. In this case, the subscripts are the integers 1, 2, and 3.
-
There is no need to declare the structure of the array ahead of time.
-
myarray is the name of the array itself.
-
ObjectScript provides commands and functions that can act on an entire array or on specific nodes. For example:
kill myarray
You can also kill a specific node and its child nodes.
-
The following variation sets several subscripts of a global array named ^myglobal; that is, these values are written to disk:
set ^myglobal(1)="value A" set ^myglobal(2)="value B" set ^myglobal(3)="value C"
-
There is a limit to the possible length of a global reference. This limit affects the length of the global name and the length and number of any subscripts. If you exceed the limit, you get a <SUBSCRIPT> error. See Maximum Length of a Global Reference.
-
The length of a value of a node must be less than the string length limit.
A multidimensional array has one reserved memory location for each defined node and no more than that. For a global, all the disk space that it uses is dynamically allocated.
Structure Variations
The preceding examples show a common form of array. Note the following possible variations:
-
You can have any number of subscripts. For example:
Set myarray(1,1,1)="grandchild of value A"
-
A subscript can be a string. The following is valid:
set myarray("notes to self","2 Dec 2010")="hello world"
Use Notes
For those who are learning ObjectScript, a common mistake is to confuse globals and arrays. It is important to remember that any variable is either local or global, and may or may not have subscripts. The following table shows the possibilities:
Kind of Variable | Example and Notes |
---|---|
Local variable without subscripts | Set MyVar=10
Variables like this are quite common. The majority of the variables you see might be like this. |
Local variable with subscripts |
Set MyVar(1)="alpha" Set MyVar(2)="beta" Set MyVar(3)="gamma" A local array like this is useful when you want to pass a set of related values. |
Global variable without subscripts | Set ^MyVar="saved note"
In practice, globals usually have subscripts. |
Global variable with subscripts | Set ^MyVar($USERNAME,"Preference 1")=42 |
Passing Variables by Value or by Reference
When you invoke a method, you can pass values of variables to that method either by value or by reference. In most cases, these variables are local variables with no subscripts, so this section discusses those first.
As with other programming languages, InterSystems IRIS has a memory location that contains the value of each local variable. The name of the variable acts as the address to the memory location.
When you pass a local variable with no subscripts to a method, you pass the variable by value. This means that the system makes a copy of the value, so that the original value is not affected. To pass the memory address instead, place a period immediately before the name of the variable in the argument list.
To demonstrate this, consider the following method in a class called Test.Parameters:
ClassMethod Square(input As %Integer) As %Integer
{
set answer=input*input
set input=input + 10
return answer
}
Suppose that you define a variable and pass it by value to this method:
TESTNAMESPACE>set myVariable = 5
TESTNAMESPACE>write ##class(Test.Parameters).Square(myVariable)
25
TESTNAMESPACE>write myVariable
5
In contrast, suppose that you pass the variable by reference:
TESTNAMESPACE>set myVariable = 5
TESTNAMESPACE>write ##class(Test.Parameters).Square(.myVariable)
25
TESTNAMESPACE>write myVariable
15
Consider the following method, which writes the contents of the argument it receives:
ClassMethod WriteContents(input As %String)
{
zwrite input
}
Now, suppose you have an array with three nodes:
TESTNAMESPACE>zwrite myArray
myArray="Hello"
myArray(1)="My"
myArray(2)="Friend"
If you pass the array to the method by value, you are only passing the top-level node:
TESTNAMESPACE>do ##class(Test.Parameters).WriteContents(myArray)
input="Hello"
If you pass the array to the method by reference, you are passing the entire array:
TESTNAMESPACE>do ##class(Test.Parameters).WriteContents(.myArray)
input="Hello"
input(1)="My"
input(2)="Friend"
You can pass the value of a single node of a global to a method:
TESTNAMESPACE>zwrite ^myGlobal
^myGlobal="Start"
^myGlobal(1)="Your"
^myGlobal(2)="Engines"
TESTNAMESPACE>do ##class(Test.Parameters).WriteContents(^myGlobal)
input="Start"
Trying to pass a global to a method by reference results in a syntax error:
TESTNAMESPACE>do ##class(Test.Parameters).WriteContents(.^myGlobal)
^
<SYNTAX>
The following table summarizes all the possibilities:
Kind of Variable | Passing by Value | Passing by Reference |
---|---|---|
Local variable with no subscripts | The standard way in which these variables are passed | Allowed |
Local with subscripts (array) | Passes the value of a single node | The standard way in which these variables are passed |
Global variable with or without subscripts | Passes the value of a single node | Cannot be passed this way (data for a global is not in memory) |
Object Reference (OREF)* | The standard way in which these variables are passed | Allowed |
* If you have a variable representing an object, you refer to the object by means of an object reference (OREF). When you pass an OREF as an argument, you typically pass it by value. However, since an OREF is a pointer to the object, you are effectively passing the object by reference. Changing the value of a property of the object inside the method changes the actual object, not a copy of the object. Passing an OREF by reference is allowed and can be used if you want to change the OREF to point to a different object. This is not a common usage. See Objects for more information on objects and object references.
Operators
This section provides an overview of the operators in ObjectScript; some are familiar, and others are not.
Operator precedence in ObjectScript is strictly left-to-right; within an expression, operations are performed in the order in which they appear. You can use explicit parentheses within an expression to force certain operations to be carried out ahead of others.
Typically you use parentheses even where you do not strictly need them. It is useful to other programmers (and to yourself at a later date) to do this because it makes the intent of your code clearer.
Familiar Operators
ObjectScript provides the following operators for common activities:
-
Mathematical operators: addition (+), subtraction (-), division (/), multiplication (*), integer division (\), modulus (#), and exponentiation (**)
-
Unary operators: positive (+) and negative (-)
-
String concatenation operator (_)
-
Logical comparison operators: equals (=), greater than (>), greater than or equal to (>=), less than (<), less than or equal to (<=)
-
Logical complement operator (')
You can use this immediately before any logical value as well as immediately before a logical comparison operator.
-
Operators to combine logical values: AND (&&), OR (||)
Note that ObjectScript also supports an older, less efficient form of each of these: & is a form of the && operator, and ! is a form of the || operator. You might see these older forms in existing code.
Unfamiliar Operators
ObjectScript also includes operators that have no equivalent in some languages. The most important ones are as follows:
-
The pattern match operator (?) tests whether the characters in its left operand use the pattern in its right operand. You can specify the number of times the pattern is to occur, specify alternative patterns, specify pattern nesting, and so on.
For example, the following writes the value 1 (true) if a string (testthis) is formatted as a U.S. Social Security Number and otherwise writes 0.
Set testthis="333-99-0000" Write testthis ?3N1"-"2N1"-"4N
This is a valuable tool for ensuring the validity of input data, and you can use it within the definition of class properties.
-
The binary contains operator ([) returns 1 (true) or 0 (false) depending on whether the sequence of characters in the right operand is a substring of the left operand. For example:
Set L="Steam Locomotive",S="Steam" Write L[S
-
The binary follows operator (]) tests whether the characters in the left operand come after the characters in the right operand in ASCII collating sequence.
-
The binary sorts after operator (]]) tests whether the left operand sorts after the right operand in numeric subscript collation sequence.
-
The indirection operator (@) allows you to perform dynamic runtime substitution of part or all of a command argument, a variable name, a subscript list, or a pattern. InterSystems IRIS performs the substitution before execution of the associated command.
Commands
This section provides an overview of the commands that you are most likely to use and to see in ObjectScript. These include commands that are similar to those in other languages, as well as others that have no equivalent in other languages.
The names of commands are not case-sensitive, although they are shown in running text in all upper case by convention.
Familiar Commands
ObjectScript provides commands to perform familiar tasks such as the following:
-
To define variables, use SET as shown previously.
-
To remove variables, use KILL as shown previously.
-
To control the flow of logic, use the following commands:
-
IF, ELSEIF, and ELSE, which work together
-
FOR
-
WHILE, which can be used on its own
-
DO and WHILE, which can be used together
-
QUIT, which can also return a value
There are other commands for flow control, but they are used less often.
-
-
To trap errors, use TRY and CATCH, which work together.
-
To write a value, use WRITE. This writes values to the current device (for example, the Terminal or a file).
Used without an argument, this command writes the values of all local variables. This is particularly convenient in the Terminal.
This command can use a small set of format control code characters that position the output. In existing code, you are likely to see the exclamation point, which starts a new line. For example:
write "hello world",!,"another line"
-
To read a value from the current device (for example, the Terminal), use READ.
-
To work with devices other than the principal device, use the following commands:
-
OPEN makes a device available for use.
-
USE specifies an open device as the current device for use by WRITE and READ.
-
CLOSE makes a device no longer available for use.
-
-
To control concurrency, use LOCK. Note that the InterSystems IRIS lock management system is different from analogous systems in other languages. It is important to review how it works; see Locking and Concurrency Control.
You use this command in cases where multiple processes can potentially access the same variable or other item.
-
To manage transactions, use TSTART, TCOMMIT, TROLLBACK, and related commands.
-
For debugging, use ZBREAK and related commands.
-
To suspend execution, use HANG.
Commands for Use with Multidimensional Arrays
In ObjectScript, you can work with multidimensional arrays in the following ways:
-
To define nodes, use the SET command.
-
To remove individual nodes or all nodes, use the KILL command.
For example, the following removes an entire multidimensional array:
kill myarray
In contrast, the following removes the node myarray("2 Dec 2010") and all its children:
kill myarray("2 Dec 2010")
-
To delete a global or a global node but none of its descendent subnodes, use ZKILL.
-
To iterate through all nodes of a multidimensional array and write them all, use ZWRITE. This is particularly convenient in the Terminal. The following sample Terminal session shows what the output looks like:
TESTNAMESPACE>ZWRITE ^myarray ^myarray(1)="value A" ^myarray(2)="value B" ^myarray(3)="value C"
This example uses a global variable rather than a local one, but remember that both can be multidimensional arrays.
-
To copy a set of nodes from one multidimensional array into another, preserving existing nodes in the target if possible, use MERGE. For example, the following command copies an entire in-memory array (sourcearray) into a new global (^mytestglobal):
MERGE ^mytestglobal=sourcearray
This can be a useful way of examining the contents of an array that you are using, while debugging your code.
Special Variables
This section introduces some InterSystems IRIS special variables. The names of these variables are not case-sensitive.
Some special variables provide information about the environment in which the code is running. These include the following:
-
$HOROLOG, which contains the date and time for the current process, as given by the operating system. See Date and Time Values.
-
$USERNAME and $ROLES, which contain information about the username currently in use, as well as the roles to which that user belongs.
write "You are logged in as: ", $USERNAME, !, "And you belong to these roles: ",$ROLES
-
$ZVERSION, which contains a string that identifies the currently running version of InterSystems IRIS.
Others include $JOB, $ZTIMEZONE, $IO, and $ZDEVICE.
Other variables provide information about the processing state of the code. These include $STACK, $TLEVEL, $NAMESPACE, and $ZERROR.
$SYSTEM Special Variable
The special variable $SYSTEM provides easy access to a large set of utility methods.
The special variable $SYSTEM is an alias for the %SYSTEM package, which contains classes that provide class methods that address a wide variety of needs. The customary way to refer to methods in %SYSTEM is to build a reference that uses the $SYSTEM variable. For example, the following command executes the SetFlags() method in the %SYSTEM.OBJOpens in a new tab class:
DO $SYSTEM.OBJ.SetFlags("ck")
Because names of special variables are not case-sensitive (unlike names of classes and their members), the following commands are all equivalent:
DO ##class(%SYSTEM.OBJ).SetFlags("ck")
DO $System.OBJ.SetFlags("ck")
DO $SYSTEM.OBJ.SetFlags("ck")
DO $system.OBJ.SetFlags("ck")
The classes all provide the Help() method, which can print a list of available methods in the class. For example:
TESTNAMESPACE>d $system.OBJ.Help()
'Do $system.OBJ.Help(method)' will display a full description of an individual method.
Methods of the class: %SYSTEM.OBJ
CloseObjects()
Deprecated function, to close objects let them go out of scope.
Compile(classes,qspec,&errorlog,recurse)
Compile a class.
CompileAll(qspec,&errorlog)
Compile all classes within this namespace
....
You can also use the name of a method as an argument to Help(). For example:
TESTNAMESPACE>d $system.OBJ.Help("Compile")
Description of the method:class Compile:%SYSTEM.OBJ
Compile(classes:%String="",qspec:%String="",&errorlog:%String,recurse:%Boolean=0)
Compile a class.
<p>Compiles the class <var>classes</var>, which can be a single class, a comma separated list,
a subscripted array of class names, or include wild cards. If <var>recurse</var> is true then
do not output the intial 'compiling' message or the compile report as this is being called inside
another compile loop.<br>
<var>qspec</var> is a list of flags or qualifiers which can be displayed with
'Do $system.OBJ.ShowQualifiers()'
and 'Do $system.OBJ.ShowFlags()
Locking and Concurrency Control
An important feature of any multi-process system is concurrency control, the ability to prevent different processes from changing a specific element of data at the same time, resulting in corruption. Consequently, ObjectScript provides a lock management system. This section provides a brief summary, also see this longer discussion.
Basics
The basic locking mechanism is the LOCK command. The purpose of this command is to delay activity in one process until another process has signaled that it is OK to proceed.
It is important to understand that a lock does not, by itself, prevent other processes from modifying the associated data; that is, InterSystems IRIS does not enforce unilateral locking. Locking works only by convention: it requires that mutually competing processes all implement locking with the same lock names.
The following describes a common lock scenario: Process A issues the LOCK command, and InterSystems IRIS attempts to create a lock. If process B already has a lock with the given lock name, process A pauses. Specifically, the LOCK command in process A does not return, and no successive lines of code can be executed. When the process B releases the lock, the LOCK command in process A finally returns and execution continues.
The system automatically uses the LOCK command internally in many cases, such as when you work with persistent objects or when you use certain InterSystems SQL commands.
The Lock Table
InterSystems IRIS maintains a system-wide, in-memory table that records all current locks and the processes that own them. This table — the lock table — is accessible via the Management Portal, where you can view the locks and (in rare cases, if needed) remove them.
The lock table cannot exceed a fixed size, which you can specify. For information, see Monitoring Locks. Consequently, it is possible for the lock table to fill up, such that no further locks are possible. Filling the lock table is not generally considered to be an application error; InterSystems IRIS also provides a lock queue, and processes wait until there is space to add their locks to the lock table.
However, if two processes each assert an incremental lock on a variable already locked by the other process, that is a condition called deadlock and it is considered an application programming error. For details, see Avoiding Deadlock.
System Functions
This section highlights some of the most commonly used system functions in ObjectScript.
The names of these functions are not case-sensitive.
The InterSystems IRIS class library also provides a large set of utility methods that you can use in the same way that you use functions. To find a method for a particular purpose, use the InterSystems Programming Tools Index.
See also Date and Time Values.
Value Choice
You can use the following functions to choose a value, given some input:
-
$CASE compares a given test expression to a set of comparison values and then returns the return value associated with the matching comparison value. For example:
TESTNAMESPACE>set myvar=1 TESTNAMESPACE>write $CASE(myvar,0:"zero",1:"one",:"other") one
-
$SELECT examines a set of expressions and returns the return value associated with the first true expression. For example:
TESTNAMESPACE>set myvar=1 TESTNAMESPACE>write $SELECT(myvar=0:"branch A",1=1:"branch B") branch B
Existence Functions
You can use the following functions to test for the existence of a variable or of a node of a variable.
-
To test if a specific variable exists, use the $DATA function.
For a variable that contains multiple nodes, this function can indicate whether a given node exists, and whether a given node has a value and child nodes.
-
To get the value of a variable (if it exists) or get a default value (if not), use the $GET function.
List Functions
ObjectScript provides a native list format. You can use the following functions to create and work with these lists:
-
$LISTBUILD returns a special kind of string called a list. Sometimes this is called $LIST format, to distinguish this kind of list from other kinds (such as comma-separated lists).
The only supported way to work with a $LIST list is to use the ObjectScript list functions. The internal structure of this kind of list is not documented and is subject to change without notice.
-
$LIST returns a list element or can be used to replace a list element.
-
$LISTLENGTH returns the number of elements in a list.
-
$LISTFIND returns the position of a given element, in a given list.
There are additional list functions as well.
If you use a list function with a value that is not a list, you receive the <LIST> error.
The system class %Library.ListOpens in a new tab is equivalent to a list returned by $LISTBUILD. That is, when a class has a property of type %Library.ListOpens in a new tab, you use the functions named here to work with that property. You can refer to this class by its short name, %ListOpens in a new tab.
InterSystems IRIS provides other list classes that are not equivalent to a list returned by $LISTBUILD. These are useful if you prefer to work with classes. For an introduction, see Collection Classes.
String Functions
ObjectScript also has an extensive set of functions for using strings efficiently:
-
$EXTRACT returns or replaces a substring, using a character count.
-
$FIND finds a substring by value and returns an integer specifying its end position in the string.
-
$JUSTIFY returns a right-justified string, padded on the left with spaces.
-
$ZCONVERT converts a string from one form to another. It supports both case translations (to uppercase, to lowercase, or to title case) and encoding translation (between various character encoding styles).
-
$TRANSLATE modifies the given string by performing a character-by-character replacement.
-
$REPLACE performs string-by-string replacement within a string and returns a new string.
-
$PIECE returns a substring from a character-delimited string (often called a pieced string). The following demonstrates how to extract a substring:
SET mystring="value 1^value 2^value 3" WRITE $PIECE(mystring,"^",1)
-
$LENGTH returns the number of characters in a specified string or the number of delimited substrings in a specified string, depending on the parameters used.
For example:
SET mystring="value 1^value 2^value 3" WRITE !, "Number of characters in this string: " WRITE $LENGTH(mystring) WRITE !, "Number of pieces in this string: " WRITE $LENGTH(mystring,"^")
Working with Multidimensional Arrays
You can use the following functions to work with a multidimensional array as a whole:
-
$ORDER allows you to sequentially visit each node within a multidimensional array.
-
$QUERY enables you to visit every node and subnode within an array, moving up and down over subnodes.
To work with an individual node in an array, you can use any of the functions described previously. In particular:
-
$DATA can indicate whether a given node exists and whether a given node has child nodes.
-
$GET gets the value of a given node or gets a default value otherwise.
Character Values
Sometimes when you create a string, you need to include characters that cannot be typed. For these, you use $CHAR.
Given an integer, $CHAR returns the corresponding ASCII or Unicode character. Common uses:
-
$CHAR(9) is a tab.
-
$CHAR(10) is a line feed.
-
$CHAR(13) is a carriage return.
-
$CHAR(13,10) is a carriage return and line feed pair.
The function $ASCII returns the ASCII value of the given character.
Date and Time Values
This section provides a quick overview of date and time values in ObjectScript.
Local Time
To access the date and time for the current process, you use the $HOROLOG special variable. Because of this, in many InterSystems IRIS applications, dates and times are stored and transmitted in the format used by this variable. This format is often called $H format or $HOROLOG format.
$HOROLOG retrieves the date and time from the operating system and is thus always in the local time zone.
The InterSystems IRIS class library includes data type classes to represent dates in more common formats such as ODBC, and many applications use these instead of $H format. Note that InterSystems supports POSIX time via the %Library.PosixTimeOpens in a new tab data type class, which new applications should use to represent date/time values.
UTC Time
InterSystems IRIS also provides the $ZTIMESTAMP special variable, which contains the current date and time as a Coordinated Universal Time value in $H format. This is a worldwide time and date standard; this value is very likely to differ from your local time (and date) value.
Date and Time Conversions
ObjectScript includes functions for converting date and time values.
-
Given a date in $H format, the function $ZDATE returns a string that represents the date in your specified format.
For example:
TESTNAMESPACE>WRITE $ZDATE($HOROLOG,3) 2010-12-03
-
Given a date and time in $H format, the function $ZDATETIME returns a string that represents the date and time in your specified format.
For example:
TESTNAMESPACE>WRITE $ZDATETIME($HOROLOG,3) 2010-12-03 14:55:48
-
Given string dates and times in other formats, the functions $ZDATEH and $ZDATETIMEH convert those to $H format.
-
The functions $ZTIME and $ZTIMEH convert times from and to $H format.
Details of the $H Format
The $H format is a pair of numbers separated by a comma. For example: 54321,12345
-
The first number is the number of days since December 31st, 1840. That is, day number 1 is January 1st, 1841. This number is always an integer.
-
The second number is the number of seconds since midnight on the given day.
Some functions, such as $NOW(), provide a fractional part.
For additional details, including an explanation of the starting date, see $HOROLOG.
Using Macros and Include Files
As noted earlier, you can define macros and use them later in the same class or routine. More commonly, you define them in include files.
Macros
ObjectScript supports macros, which define substitutions. The definition can either be a value, an entire line of code, or (with the ##continue directive) multiple lines.
To define a macro, use the #define directive or other preprocessor directive. For example:
#define macroname <definition>
To refer to a macro, use the following syntax:
$$$macroname
Or:
$$$macroname(arguments)
You use macros to ensure consistency. For example:
#define StringMacro "Hello, World!"
write $$$StringMacro
To give you an idea of what can be done in macros, the following example shows the definition of a macro that is used internally:
#define CALL(%C,%A) $$$INTCALL(%C,%A,Quit sc)
This macro accepts arguments, as many of them do. It also refers to another macro.
Some of the system classes use macros extensively.
The preprocessor directives are documented in ObjectScript Macros and the Macro Preprocessor in Using ObjectScript.
The Management Portal lists the include files with the routines. Include files are not, however, actually routines because they are not executable.
Include Files
You can define macros in a class or routine and use them later in the same class or routine. More commonly, you define them in a central place. To do this, you create and use include files. An include file defines macros and can include other include files and is a document with the extension .inc.
After creating an include file, you can do the following:
-
Include the include file at the start of any routine. That routine can refer to the macros defined in the include file.
-
Include the include file at the start of any class. Methods in that class can refer to the macros.
-
Include the include file at the start of any method. That method can refer to the macros.
The following shows parts of a system include file:
/// Create a success %Status code
#define OK 1
/// Return true if the %Status code is success, and false otherwise
/// %sc - %Status code
#define ISOK(%sc) (+%sc)
/// Return true if the %Status code if an error, and false otherwise
/// %sc - %Status code
#define ISERR(%sc) ('%sc)
To include an include file in a routine or a method, use the #include directive. For example:
#include myincludefile
To include an include file at the start of a class definition, the directive does not include the pound sign. For example:
Include myincludefile
Or:
Include (myincludefile, yourincludefile)
Using Routines
You can think of a routine as an ObjectScript program. Routines can be written from scratch, or they can be generated automatically when a class is compiled.
Procedures, Functions, and Subroutines
Within an ObjectScript routine, a label defines the starting point for one of the following units of code:
-
Procedure (optionally returns a value). The variables defined in a procedure are private to that procedure, which means that they are not available to other code. This is not true for functions and subroutines.
A procedure is also called a procedure block.
-
Function (returns a value).
-
Subroutine (does not return a value).
InterSystems recommends that you use procedures, because this simplifies the task of controlling the scope of variables. In existing code, however, you might also see functions and subroutines, and it is useful to be able to recognize them. The following list shows what all these forms of code look like.
label(args) scopekeyword {
zero or more lines of code
QUIT returnvalue
}
Or:
label(args) scopekeyword {
zero or more lines of code
}
label is the identifier for the procedure.
args is an optional comma-separated list of arguments. Even if there are no arguments, you must include the parentheses.
The optional scopekeyword is one of the following (not case-sensitive):
-
Public. If you specify Public, then the procedure is public and can be invoked outside of the routine itself.
-
Private (the default for procedures). If you specify Private, the procedure is private and can be invoked only by other code in the same routine. If you attempt to access the procedure from another routine, a <NOLINE> error occurs.
returnvalue is an optional, single value to return. To return a value, you must use the QUIT command. If you do not want to return a value, you can omit the QUIT command, because the curly braces indicate the end of the procedure.
A procedure can declare variables as public variables, although this practice is not considered modern. To do this, you include a comma-separated list of variable names in square brackets immediately before scopekeyword. For details, see User-defined Code.
label(args) scopekeyword
zero or more lines of code
QUIT optionalreturnvalue
args is an optional comma-separated list of arguments. Even if there are no arguments, you must include the parentheses.
The optional scopekeyword is either Public (the default for functions) or Private.
label(args) scopekeyword
zero or more lines of code
QUIT
args is an optional comma-separated list of arguments. If there are no arguments, the parentheses are optional.
The optional scopekeyword is either Public (the default for subroutines) or Private.
The following table summarizes the differences among routines, subroutines, functions, and procedures:
Routine | Subroutine | Function | Procedure | |
---|---|---|---|---|
Can accept arguments | no | yes | yes | yes |
Can return a value | no | no | yes | yes |
Can be invoked outside the routine (by default) | yes | yes | yes | no |
Variables defined in it are available after the code finishes execution | yes | yes | yes | depends on nature of the variable |
Variable Availability and Scope has further details.
In casual usage, the term subroutine can mean procedure, function, or subroutine (as defined formally here).
Executing Routines
To execute a routine, you use the DO command, as follows:
do ^routinename
To execute a procedure, function, or subroutine (without accessing its return value), you use the following command:
do label^routinename
Or:
do label^routinename(arguments)
To execute a procedure, function, or subroutine and refer to its return value, you use an expression of the form $$label^routinename or $$label^routinename(arguments). For example:
set myvariable=$$label^routinename(arguments)
In all cases, if the label is within the same routine, you can omit the caret and routine name. For example:
do label
do label(arguments)
set myvariable=$$label(arguments)
In all cases, the arguments that you pass can be either literal values, expressions, or names of variables.
The NEW Command
InterSystems IRIS provides another mechanism to enable you to control the scope of a variable in a routine: the NEW command. The argument to this command is one or more variable names, in a comma-separated list. The variables must be public variables and cannot be global variables.
This command establishes a new, limited context for the variable (which may or may not already exist). For example, consider the following routine:
; demonew
; routine to demo NEW
NEW var2
set var1="abc"
set var2="def"
quit
After you run this routine, the variable var1 is available, and the variable var2 is not, as shown in the following example Terminal session:
TESTNAMESPACE>do ^demonew
TESTNAMESPACE>write var1
abc
TESTNAMESPACE>write var2
write var2
^
<UNDEFINED> *var2
If the variable existed before you used NEW, the variable still exists after the scope of NEW has ended, and it retains its previous value. For example, consider the following Terminal session, which uses the routine defined previously:
TESTNAMESPACE>set var2="hello world"
TESTNAMESPACE>do ^demonew
TESTNAMESPACE>write var2
hello world
Potential Pitfalls
The following items can confuse programmers who are new to ObjectScript, particularly if those who are responsible for maintaining code written by other programmers:
-
Within a routine or a method, every line must be indented by at least one space or one tab unless that line contains a label. That is, if there is text of any kind in the first character position, the compiler and your IDE treat it as a label.
There is one exception: A curly brace is accepted in the first character position.
-
There must be exactly one space (not a tab) between a command and its first argument. Otherwise, your IDE indicates that you have a syntax error:
Similarly, the Terminal displays a syntax error as follows:
TESTNAMESPACE>write 5 WRITE 5 ^ <SYNTAX> TESTNAMESPACE>
-
Operator precedence in ObjectScript is strictly left-to-right; within an expression, operations are performed in the order in which they appear. You can use explicit parentheses within an expression to force certain operations to be carried ahead of others.
Typically you use parentheses even where you do not strictly need them. It is useful to other programmers (and to yourself at a later date) to do this because it makes the intent of your code clearer.
-
For reasons of history, ObjectScript does not consider an empty string ("") to equal the ASCII NULL value. To represent the ASCII NULL value, use $CHAR(0). ($CHAR is a system function that returns an ASCII character, given its decimal-based code.) For example:
write "" = $char(0)
Similarly, when ObjectScript values are projected to SQL or XML, the values "" and $CHAR(0) are treated differently. For information on the SQL projections of these values, see Null and the Empty String. For information on the XML projections of these values, see Handling Empty Strings and Null Values.
-
Some parts of ObjectScript are case-sensitive while others are not. The case-insensitive items include names of commands, functions, special variables, namespaces, and users.
The case-sensitive items include names of most of the elements that you define: routines, variables, classes, properties, and methods. For more details, see Syntax Rules.
-
Most command names can be represented by an abbreviated form. Therefore, WRITE, Write, write, W, and w are all valid forms of the WRITE command. For a list, see Abbreviations Used in ObjectScript.
-
For many of the commands, you can include a postconditional expression (often simply called a postconditional).
This expression controls whether InterSystems IRIS executes the command. If the postconditional expression evaluates to true (nonzero), InterSystems IRIS executes the command. If the expression evaluates to false (zero), InterSystems IRIS ignores the command and continues with the next command.
For example:
Set count = 6 Write:count<5 "Print this if count is less than five" Write:count>5 "Print this if count is greater than five"
The preceding generates the following output: Print this if count is greater than five
Note:If postconditionals are new to you, you might find the phrase “postconditional expression” somewhat misleading, because it suggests (incorrectly) that the expression is executed after the command. Despite the name, a postconditional is executed before the command.
-
You can include multiple commands on a single line. For example:
set myval="hello world" write myval
When you do this, beware that you must use two spaces after any command that does not take arguments, if there are additional commands on that line; if you do not do so, a syntax error occurs.
-
The IF, ELSE, FOR, and DO commands are available in two forms:
-
A newer block form, which uses curly braces to indicate the block. For example:
if (testvalue=1) { write "hello world" }
InterSystems recommends that you use the block form in all new code.
-
An older line-based form, which does not use curly braces. For example:
if (testvalue=1) write "hello world"
-
-
As a result of the preceding items, ObjectScript can be written in a very compact form. For example:
s:$g(%d(3))'="" %d(3)=$$fdN3(%d(3)) q
The class compiler automatically generates compact code of the form shown above (although not necessarily with abbreviated commands as in this example). Sometimes it is useful to look at this generated code, to track down the source of a problem or to understand how something works.
-
There are no truly reserved words in ObjectScript, so it is theoretically possible to have a variable named set, for example. However, it is prudent to avoid names of commands, functions, SQL reserved words, and certain system items; see Syntax Rules.
-
InterSystems IRIS allocates a fixed amount of memory to hold the results of string operations. If a string expression exceeds the amount of space allocated, a <MAXSTRING> error results. See string length limit.
For class definitions, the string operation limit affects the size of string properties. InterSystems IRIS provides a system object (called a stream) that you can use when you need to work with strings that exceed this limit; in such cases, you use the stream interface classes.
See Also
See the ObjectScript Tutorial.
Also see Classes, Objects, and Persistent Objects and SQL.