This is a proposal for VRMLScript, a scripting language for VRML 2.0 Script nodes. VRMLScript grew out of the need for a lightweight script language in VRML. It is a subset of the JavaScript language, with VRML data types supported as JavaScript built-in objects. Because of this it has many advantages over other script languages (such as Java):
Please see the examples section to find out about the freely available source reference implementation of VRMLScript.
The script syntax has similarities to JavaScript as well as several other scripting languages. VRMLScript was designed to be parsed by YACC and is therefore an LALR(1) grammar.
Data in VRMLScript is represented as objects. The object types correspond to the VRML field types. A variable contains an instance of an object, and can be predefined (appearing in the Script node) or defined locally.
A VRMLScript variable holds an instance of an object. If a name is defined as a field or eventOut of the Script node containing the script then there is a variable with that same name available globally to the script. The type of this variable is always the type of the field or eventOut. Assignment to this variable converts the expression to its type or generates a run-time error if a conversion is not possible (see Data Conversion).
The names specified in the declaration of a function (the data value and the timestamp) are local to the function in which they are declared. It is a run-time error to assign to these variables.
Local variables can be created simply by assigning to a name that does not yet exist. Assigning to such a variable causes it take the type of the expression, so these local variables always have the type of the last assignment. Local variables are scoped by the block in which they were first introduced. Once that block is exited, the variable ceases to exist. Variables corresponding to eventOuts or fields of the Script node are global in scope.
Variable names must start with the a lowercase character ('a' through 'z'), an uppercase character ('A' through 'Z'), or an underscore ('_'). Subsequent characters can be any of these or a digit ('0' through '9'). Variable names are case sensitive.
Numeric, boolean, and string literals are allowed. Numeric literals can be integers in decimal (417), hex (0x5C), or octal (0177) notation. They can also be floating point numbers in fixed (1.76) or exponential (2.7e-12) notation. All numeric literals are of the number type. Boolean literals can be 'true' or 'false' and have the boolean type. String literals can be any sequence of UTF8 characters enclosed in single quotes ('), and have the type String. Special (non-printable) characters can be included in a string using the following escape sequences:
Sequence | Meaning |
\b | backspace |
\f | form feed |
\n | new line |
\r | carriage return |
\t | tab |
\' | single quote (apostrophe) |
\" | double quote |
\\ | backslash |
Here are some examples:
Script { field SFFloat aField 0 field SFVec3f aVector 0 0 0 eventOut SFInt32 anEventOut eventIn SFBool event url "vrmlscript: function event(value, timestamp) { if (aField == 1.5) { a = true; // 'a' contains a boolean } if (a) { // this is NOT the same 'a' as above! value = 5; // ERROR, // can't assign to function parameter! } aField = anEventOut; // SFInt32 converted to SFFloat b = aField; // 'b' contains a number b = anEventOut; // 'b' now contains a different number aField = aVector; // ERROR, // can't assign SFVec3f to SFFloat! s = 'Two\nLines'; // 's' contains a String }" }
For each field and eventOut in the Script node containing the script there is a corresponding global variable with the same name. Field variables are persistant; they keep their last stored value across function calls. Local variables, on the other hand, are destroyed on exit from the block in which they were defined. Local variables defined in the outermost block of a function are destroyed when the function exits so they do not persist across function calls.
EventOut variables are very similar to field variables in that their values persist across function calls. But when an assignment is made to an eventOut variable an event is generated.
Every object has a set of properties and methods (see Object and Function Definitions). Properties are names on the object that can be selected (using the '.' operator) then used in an expression or as the target of an expression. Methods are names on the object that can be called (using the function call operator) to perform some operation on the object. For example:
function someFunction() { a = new SFColor(0.5, 0.5, 0.5); b = a.r; // 'b' contains 0.5 a.setHSV(0.1, 0.1, 0.1); // 'a' now contains new properties }
The value a.r selects the property which corresponds to the red component of the color. The value a.setHSV() selects the method which sets the color in HSV space.
For each object type there is a corresponding constructor (see Object and Function Definitions). Constructors typically take a flexible set of parameters to allow construction of objects with any initial value. MF objects are essentially arrays so they always take 0 or more parameters of the corresponding SF object type. A value of a given data type is created using the new keyword with the data type name. For instance:
a = new SFVec3f(0, 1, 0); // 'a' has a SFVec3f containing 0, 1, 0 b = new MFFloat(1, 2, 3, 4) // 'b' has a MFFloat containing 4 floats
Combining objects of different types in a single expression or assignment statement will often perform implicit type conversion. Rules for this conversion are described in the following table:
Type |
Rules |
String |
|
|
|
|
|
SFImage |
|
SFNode |
|
|
|
Most SF objects in VRMLScript have a corresponding MF object. An MFObject is essentially an array of objects, with each element of the array having the type of the corresponding SF object. All MF objects have a length property which returns or sets the number of elements in the MF object. Array indexes start at 0. If vecArray is an MFVec3f object then vecArray[0] is the first SFVec3f object in the array.
Dereferencing an MF object creates a new object of the corresponding SF object type with the contents of the dereferenced element. Assigning an SF object to a dereferenced MF object (which must be of the corresponding type) copies the contents of the SF object into the dereferenced element.
VRMLScript statements are block scoped the same as other C-like languages. A statement can appear alone in the body of an if or for statement. A body with multiple simple statements, or compound statement, must be placed between '{' and '}' characters. This constitutes a new block and all variables defined in this block go out of scope at the end of the block. All simple statements must end with the ';' character.
Example:
if (a < b) c = d; // simple statement, c is local to the if statement else { // compound statement, c is no longer defined here e = f; // e is local to the else block c = h + 1; } // e is no longer defined here
The if statement evaluates an expression, and selects one of two statements for execution. A simple if statement executes the statement following the condition if the result of the expression evaluation is not 0. The if...else statement additionally executes the statement following the else clause if the result of the expression evaluation is 0. For nested if...else statements, the else clause matches the nearest if statement. Braces can be used to override this.
Example
if (a < 0) // simple if statement <statement> if (a == 0) if (b > 5) // if...else statement <statement> else // this else clause matches the 'if (b > 5)' statement <statement> if (a == 0) { if (b > 5) <statement> } else // this else clause matches the 'if (a == 0)' statement <statement>
The for statement contains 3 expressions which control the looping behavior, followed by a statement to which the loop is applied. It executes its first expression once before loop execution. It then evaluates its second expression before each loop and, if the expression evaluates to 0, exits the loop. It then executes the statement, followed by evaluation of the third expression. The loop then repeats, until looping is terminated, either by the second expression evaluating to 0 or until a break statement is encountered. In typical use, the first expression initializes a loop counter, the second evaluates it, and the third increments it.
Example:
for (i = 0; i < 10; ++i) <statement>
The while statement contains a single expression which controls the looping behavior, followed by a statement to which the loop is applied. Before each loop it evaluates the expression and, if the expression evaluates to 0, exits the loop. Otherwise it executes the statement and tests the expression again. The loop continues until terminated by the expression evaluating to 0 or until a break statement is encountered.
Example:
while (i < 10) <statement>
Any valid expression in VRMLScript can be a statement. The 2 most common expressions are the function call and the assignment expression (see below).
The return statement does an immediate return from the function regardless of its nesting level in the block structure. If specified, its expression is evaluated and the result is returned to the calling function.
Example:
if (a == 0) { d = 1; return 5 + d; }
The break statement exits the deepest enclosing looping statement. Execution continues at the statement following the looping statement.
Example:
while (i < 0) { if (q == 5) break; <other statements> } // execution commences here upon break.
The continue statement jumps to the end of the deepest enclosing looping statement. Execution continues at the end of the loop. In the case of the for statement, the third expression is evaluated and then the second expression is tested to see if the loop should be continued. In the case of the for...in statement the next element is assigned to the variable and the loop is continued. In the case of the while statement the expression is tested to see if the loop should be continued.
Example:
for a in colorArray { if (a[0] > 0.5) continue; <other statements> // loop commences here upon continue. }
Expressions combine variables, objects, constants and the results of other expressions with operators. The result of an expression evaluation is always another expression. In this way compound expressions can be built out of simpler ones. Operators combine one (unary), two (binary) or three (tertiary) values. Unary operators are placed before (prefix) or after (postfix) the value to be opertated on. Binary operators are placed between the values to be operated on. Tertiary operators always consist of 2 symbols, with one symbol place between each pair of values to be operated on.
Example:
a = -b; // unary prefix operator a = b++; // unary postfix operator a = b + c; // binary operator a = b ? c : d; // tertiary operator a = b * c + d; // compound expression // the product of b * c produces a value which // is added to d
An expression of the form expression = expression assigns the result of the right-hand expression to the expression on the left-hand side. The left-hand expression must result in a variable into which a value may be stored. This includes simple identifiers, subscripting operators, members of objects, and the return value of a function call.
Examples:
a = 5; // simple assignment a[3] = 4; // subscripted assignment foo()[2] = 3; // function returning an MFField
In addition, a set of shorthand operators exist for doing an binary operation using the left-hand expression and the right-hand expression, then assigning the result to the left-hand expression. These operators are plus-equal ('+='), minus-equal ('-='), times-equal ('*=') divide-equal ('/='), mod-equal ('%='), and-equal ('&='), or-equal ('|='), xor-equal ('^='), shift-left-equal ('<<='), shift-right-equal ('>>='), shift-right-fill-zero-equal ('>>>=').
Examples:
a += 5; // adds 5 to the value of a and assigns it to a a[3] &= 0x0000FFFF; // performs bitwise-and of a[3] and 0x0000FFFF // assigning result to a[3]
Arithmetic operators include negation ('-'), ones-complement ('~'), increment ('++'), decrement ('--') and the operators ('+', '-', '*', '/', '%'). Negation and ones-complement are prefix unary. Increment and decrement are prefix or postfix unary. The rest are binary.
Examples:
5 + b (c + 5) * 7 (-b / 4) % 6 (c & 0xFF) | 256
The increment an decrement operators behave differently depending on whether they are used as prefix or postfix operators. In either case, if the expression to which the operator is applied is a variable, the value of that variable is incremented or decremented. A value is also returned from the expression. When used as a prefix operator the value returned is that of the expression after the increment or decrement. When used as a postfix operator the value returned is that of the expression before the increment or decrement.
Examples
a = 5; // Value of 'a' is 5 b = a++; // Value of 'b' is 5, value of 'a' is 6 c = ++b; // Value of 'c' is 6, value of 'b' is 6
Bitwise operators include and ('&'), or ('|'), exclusive or ('^'), left shift ('<<'), right shift ('>>'), and right shift, zero fill ('>>>'). These are all binary operators and are valid on any scalar type. When they are applied the scalar value is converted to an SFInt32 before the operation, and back to the original expression type after. Therefore roundoff error can occur when applying them to SFFloat or SFTime values. The shift operators shift the operand on the left side the number of bits specified by the operator on the right side. The difference between right shift and right shift, zero fill is that the former preserves the sign of the left operator and the latter always puts a zero in the most significant bits.
Examples:
a & 0x0FF // clears upper 24 bits of 'a' a >> 5 // shift 'a' 5 bits to the right, sign extend
Logical expressions include logical and ('&&'), logical or ('||'), logical not ('!'), and the comparison operators ('<', '<=', '==', '!=', '>=', '>'). Logical not is prefix unary, the rest are binary. Each evaluates to either 0 (false) or 1 (true). The constants true, false, TRUE, and FALSE can also be used.
Examples:
a < 5 b > 0 && c > 1 !((a > 4) || (b < 6))
All the comparison operators can be used to compare Strings for lexicographic order. Additionally the operators '+' and '+=' can be used to concatenate 2 strings. Any expression involving a String and any scalar type will first convert the scalar to a string and then perform the concatentation. Conversion of a String to a scalar type can be done with the functions parseInt() and parseFloat().
Examples:
'A one and ' + 'a two' // result is "A one and a two" 'The magic number is ' + 7 // result is 'The magic number is 7' a = 5; // 'a' contains an SFTime a += 'is correct'; // 'a' is now the String '5 is correct'
Precedence rules are used to order evaluation. In the above compound expression example multiplication ('*') is evaluated before addition ('+'). For operations of equal precedence evaluation order is shown in the table below. The default rules can be overridden with the use of the '(' and ')' characters to bracket operations to be performed first.
Example:
a = b + c * d; // c * d is evaluated first a = (b + c) * d; // b + c is evaluated first a = b * c / d; // b * c is evaluated first // ('*' and '/' have equal precedence, evaluation // order is left-to-right)
Order of precedence is (unless otherwise stated evaluation order is left-to-right):
Operator Type | Operator | Comments |
comma | , | |
assignment | = += -= *= /= %= <<= >>= >>>= &= ^= |= |
right-to-left |
conditional | ?: | tertiary operator |
logical-or | || | |
logical-and | && | |
bitwise-or | | | |
bitwise-xor | ^ | |
bitwise-and | & | |
equality | == != | |
relational | < <= > >= | |
bitwise shift | << >> >>> | |
add/subtract | + - | |
multiply/divide | * / % | |
negate/increment | ! ~ - ++ -- | unary operators |
call, member | () [] . |
The url field of the Script node may contain a URL that references VRMLScript code:
Script { url "http://foo.com/myScript.vs" }
The vrmlscript: protocol allows the script to be placed inline as follows:
Script { url "vrmlscript: function foo() { ... }" }
The url field may contain multiple URLs and thus reference a remote file or in-line code:
Script { url [ "http://foo.com/myScript.vs", "vrmlscript: function foo() { ... }" ] }
The file extension for VRMLScript source code is .vs.
The MIME type for VRMLScript source code is defined as follows:
application/x-vrmlscript
Events sent to the Script node are passed to the corresponding VRMLScript function in the script. It is necessary to specify the script in the url field of the Script node. The function's name is the same as the eventIn and is passed two arguments, the event value and its timestamp (See "Parameter passing and the EventIn function"). If there isn't a corresponding VRMLScript function in the script, the browser's behavior is undefined.
For example, the following Script node has one eventIn field whose name is start:
Script { eventIn SFBool start url "vrmlscript: function start(value, timestamp) { ... }" }
In the above example, when the start eventIn is sent the start() function is executed.
When a Script node receives an eventIn, a corresponding method in the file specified in the url field of the Script node is called, which has two arguments. The value of the eventIn is passed as the first argument and timestamp of the eventIn is passed as the second argument. The type of the value is the same as the type of the EventIn and the type of the timestamp is SFTime.
Authors may define a function named eventsProcessed which will be called after some set of events has been received. Some implementations will call this function after the return from each EventIn function, while others will call it only after processing a number of EventIn functions. In the latter case an author can improve performance by placing lengthy processing algorithms which do not need to be executed for every event received into the eventsProcessed function.
The eventsProcessed function takes no parameters. Events generated from it are given the timestamp of the last event processed.
Authors may define a function named initialize which is called when the corresponding Script node has been loaded and before any events are processed. This can be used to prepare for processing before events are received, such as constructing geometry or initializing external mechanisms.
The initialize function takes no parameters. Events generated from it are given the timestamp of when the Script node was loaded.
Authors may define a function named shutdown which is called when the corresponding Script node is deleted or the world containing the Script node is unloaded or replaced by another world. This can be used to send events informing external mechanisms that the Script node is being deleted so they can clean up files, etc.
The shutdown function takes no parameters. Events generated from it are given the timestamp of when the Script node was deleted.
The fields, eventIns and eventOuts of a Script node are accessible from its VRMLScript functions. As in all other nodes the fields are accessible only within the Script. The Script's eventIns can be routed to and its eventOuts can be routed from. Another Script node with a pointer to this node can access its eventIns and eventOuts just like any other node.
Fields defined in the Script node are available to the script by using its name. It's value can be read or written. This value is persistent across function calls. EventOuts defined in the script node can also be read. The value is the last value sent.
The script can access any exposedField, eventIn or eventOut of any node to which it has a pointer:
DEF SomeNode Transform { } Script { field SFNode node USE SomeNode eventIn SFVec3f pos directOutput TRUE url "... function pos(value) { node.set_translation = value; }" }
This sends a set_translation eventIn to the Transform node. An eventIn on a passed node can appear only on the left side of the assignment. An eventOut in the passed node can appear only on the right side, which reads the last value sent out. Fields in the passed node cannot be accessed, but exposedFields can either send an event to the "set_..." eventIn, or read the current value of the "..._changed" eventOut. This follows the routing model of the rest of VRML.
Assigning to an eventOut sends that event at the completion of the currently executing function. This implies that assigning to the eventOut multiple times during one execution of the function still only sends one event and that event is the last value assigned.
There are a fixed set of objects in VRMLScript, each of which have a fixed set of properties (i.e. values) and methods (i.e. functions). For all object types except Math, there are functions to create an instance of the object. The supported set of objects are:
These 2 functions are provided to convert a String value to an SFInt32 or SFFloat value.
This section lists the methods available in the browser object, which allows scripts to get and set browser information. For descriptions of the methods, see the Browser Interface topic of the Scripting section of the VRML 2.0 spec.
None. One global instance of the object is available. The name of the instance is Browser.
None
The Math object is unique in VRMLScript in that there is exactly one globally available instance of the object, named Math. Properties can be accessed using the syntax Math.<property-name>. Methods can be invoked using the syntax Math.<function-name> ( <argument-list> ).
None. One global instance of the object is available. The name of the instance is Math.
Note number, number1, number2, base, and exponent indicate any expression with a scalar value.
The SFColor object corresponds to a VRML 2.0 SFColor field. All properties are accessed using the syntax sfColorObjectName.<property>, where sfColorObjectName is an instance of a SFColor object. All methods are invoked using the syntax sfColorObjectName.method(<argument-list>), where sfColorObjectName is an instance of a SFColor object.
None
The SFImage object corresponds to a VRML 2.0 SFImage field.
None
The SFNode object corresponds to a VRML 2.0 SFNode field.
Each node may assign values to its eventIns and obtain the last output values of its eventOuts using the sfNodeObjectName.eventName syntax.
The SFRotation object corresponds to a VRML 2.0 SFRotation field.
The String object corresponds to a VRML 2.0 SFString field.
The SFVec2f object corresponds to a VRML 2.0 SFVec2f field. Each component of the vector can be accessed using the x and y properties or using C-style array dereferencing (i.e. sfVec2fObjectName[0] or sfVec2fObjectName[1]).
The SFVec3f object corresponds to a VRML 2.0 SFVec3f field. Each component of the vector can be accessed using the x, y, and z properties or using C-style array dereferencing (i.e. sfVec3fObjectName[0], sfVec3fObjectName[1] or sfVec3fObjectName[2]).
The MFColor object corresponds to a VRML 2.0 MFColor field. It is used to store a one-dimensional array of SFColor objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfColorObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to SFColor(0, 0, 0).
None
The MFFloat object corresponds to a VRML 2.0 MFFloat field. It is used to store a one-dimensional array of SFFloat objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfFloatObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to 0.0.
None
The MFInt32 object corresponds to a VRML 2.0 MFInt32 field. It is used to store a one-dimensional array of SFInt32 objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfInt32ObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to 0.
None
The MFNode object corresponds to a VRML 2.0 MFNode field. It is used to store a one-dimensional array of SFNode objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfNodeObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to NULL.
None
The MFRotation object corresponds to a VRML 2.0 MFRotation field. It is used to store a one-dimensional array of SFRotation objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfRotationObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to SFRotation(0, 0, 1, 0).
None
The MFString object corresponds to a VRML 2.0 MFString field. It is used to store a one-dimensional array of String objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfStringObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to the empty string.
None
The MFVec2f object corresponds to a VRML 2.0 MFVec2f field. It is used to store a one-dimensional array of SFVec2f objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfVec2fObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to SFVec2f(0, 0).
None
The MFVec3f object corresponds to a VRML 2.0 MFVec3f field. It is used to store a one-dimensional array of SFVec3f objects. Individual elements of the array can be referenced using the standard C-style dereferencing operator (e.g. mfVec3fObjectName[index], where index is an integer-valued expression with 0<=index<length and length is the number of elements in the array). Assigning to an element with index > length results in the array being dynamically expanded to contain length elements. All elements not explicitly initialized are set to SFVec3f(0, 0, 0).
None
The VrmlMatrix object provides many useful methods for performing manipulations on 4x4 matrices. Each of element of the matrix can be accessed using C-style array dereferencing (i.e., vrmlMatrixObjectName[0][1] is the element in row 0, column 1). The results of dereferencing a VrmlMatrix object using a single index (i.e. vrmlMatrixObjectName[0]) are undefined.
Source code for a reference implementation of VRMLScript is available in the archive vrmlscript.zip. This source is freely available for use in adding VRMLScript to your browser. Please read the readme.txt file contained in that package. It lists the restrictions for use, which are:
The package contains everything you need to parse and interpret VRMLScript. Also included are classes to implement the data types, including vector and quaternion math, a general MF array class, string functions and time functions. Currently it is packaged for use on a PC with Windows 95 and Microsoft Developer Studio. If you port the code to other environments, please let us know and we will add links to your site.