SnapScript Language Specifications
This specifications page is a work in progress!
int - 32-bit signed integer value; corresponds directly to Java's int data type.
float - 32 or 64-bit signed floating point value; see section 1.2 for further specifications.
bool - boolean data type, value must evaluate to true or false; corresponds directly to Java's boolean data type. According to the Java specification, a boolean type "represents one bit of information, but its "size" isn't something that's precisely defined." The SnapScript engine, however, stores bool data internally as a single byte.
string - a string of 16-bit Unicode characters; corresponds directly to Java's String Object type.
The float type in SnapScript represents a numeric, floating point value. A SnapScript float can be stored as either a Java 32-bit float or 64-bit double. This is configurable when you initialize the script runtime engine (useDoubleStore
=true/false). The SnapScript execution engine, however, carries out ALL computations with floating point numbers as double precision values. This ensures the greatest functional accuracy while allowing for the option of limited memory conservation if storing high volumes of floats. It is recommended, however, that double storage be typically used as this will favour accuracy in data access. Float 32-bit storage should only be used if the script will be storing very large volumes of floating point values in the engine's global storage.
The string type in SnapScript is a direct 1:1 representation of a String object in Java. Likewise, it is immutable and allocates memory in the same fashion. However, there is no representation of NULL values in SnapScript, so you may NOT assign a string variable to NULL as you might in Java. You may, however, assign a string to an empty string literal ("") for generally negligible memory cost (an empty Java String is essentially a char array of size 0 + 8-16 bits for Object overhead).
SnapScript's grammatical constructs follow very closely to that of other C-like languages.
Functional and Definitive Statements
A statement is command or declaration in the language that translates to one or more commands that the interpreter can directly process. All statements that don't define a block (i.e. if conditional, for loop, functions) are called "expressions" and must be followed by a semi-colon:
int n = 12; // variable assignment
println("Hello World"); // function call
n = 0; // variable re-assignment
break; // break command
continue; // continue command
return n; // return command
All expressions in SnapScript ignore whitespace completely outside of individual keywords:
int n=2
is the same as int n = 2
or int n =2
There are two kinds of expressions in SnapScript: Functional and Definitive
A functional expression is a command or operation that may take any number of arguments:
// both of the following are functional statements
println("Hello World");
return 0;
A definitive expression involves the assignment or definition of a variable or construct:
int n = 2;
n = 1;
Operational Statements
Statements that designate an operation that must be carried out on behalf of a segmented portion of code (i.e. a block) are called "operational statements"
if(true) {} // if conditional
for(int i=0;i<10;i++) {} // for loop
Operational statements always must have a given section of code delimited for the appropriate operation.
Argument Delimiters
Arguments or parameters for any given function definition/invocation or language operation are parenthesized:
void MyFunction(int i) {} // Function declaration
MyFunction(0); // Function invocation
if(true) {} // if block condition argument
for(int i=0;i<10;i++) {} // for loop arguments
Block Delimiters
Segmented code, or "blocks," are used for defining functions and operational statements. Blocks are always notated with brace delimiters:
void MyFunction() { // start block
// code for function
} // end block
or for an if statement:
if(cond) {
// if code
} else { // start block
// else code
} // end block
Arguments and parameters are listed in SnapScript using commas ,
to separate them:
Function(arg0, arg1, argN...);
The exception to this is the for-loop statement, which uses semi-colons to separate its arguments:
for(int i=0; i < 10; i++) { //loop code }
Variable or construct definitions are made using the assignment operator =
int n = 2;
for(n=1;n<10;n++){}
Constant fields (section 4.3) are declared using the const
keyword and assignment operator:
const int MyConstant = 1;
Multiple constants can be declared by using block syntax:
const {
int Samples = 8;
float Gamma = 1.1;
bool VerticalSync = true;
string DeviceName = "GeForce GTX 560";
}
Mathematical expressions are valid for int and float types (see section 4.2). They are evaluated according to standard order of operations and infix notation using the following operator definitions:
+ -> add
- -> subtract
* -> multiply
/ -> divide
^ -> power function (a^b)
% -> modulo (integer remainder)
& -> BitAND
| -> BitOR
Expressions are evaluated on the right side of variable assignment or in arguments:
int i = 2 + 2 * 3; // = 8
float f = 3.1*2^2; // = 12.4
float r = sqrt(2+2); // sqrt(4) - 'sqrt' is a function defined in ScriptMath library
If statements are started by the if
keyword and take a single argument as a condition to run the specified code. The argument must be a valid boolean expression or variable.
if(<condition>) {
// execute this code if condition is true
}
The else
keyword is used to specify code to run if the first condition evaluates to false:
if(<condition>) {
} else {
// execute this code if condition is false
}
The else if
keyword specifies additional conditions to check in sequence when the preceding condition evaluates to false:
if(<condition>) {
} else if(<condition>) {
} // an else can still be defined after x many 'else if' statements.
For loops are started with the for
keyword and currently require all three arguments to be specified (this may change in the future). Arguments are separated with a semi-colon ;
. The first argument is variable assignment - this can be a newly declared variable or a pre-existing one. A variable declared in the for loop argument CANNOT be used outside of the loop. The second argument is a condition to evaluate, the third is an operation for each iteration:
for(int i=0; i < 10; i++) {}
The second argument can be any valid boolean variable or expression: bool b = true; for(int i=0; b; i++) {} The third argument (for-operation) can be specified by any of the following:
<var> ++ // increment by 1
<var> -- // decrement by 1
<var> += <exp> // increment by result of evaluated expression
<var> -= <exp> // decrement by result of evaluated expression
You can use the break
and continue
statements to control the flow of a loop. break
will stop execution of the loop and resume execution after the end of its block, and continue
will skip the remainder of the loop's block execution and advance on to the next iteration of the loop:
for(int i=0; i < 10; i++) {
if(i == 2)
continue; // if and only if i is equal to 2, the condition will be re-checked,
// i will be incremented, and execution will proceed from the start of the loop
else if(i > some_var)
break; // if and only if i is greater than 'some_var' (assume that's a variable
// somewhere earlier in the function), loop execution will terminate here
}
String types can be defined through use of string literals - string expressions denoted by quotation marks "
:
string str = "Example string";
Variables of any type (int, float, bool, string) can be referenced from within a string literal using the pipe |
operator. The execution engine will replace the reference with the value of the variable in its representative string form:
int my_int = 12;
string str = "My int is |my_int|";
// at runtime, this will print "My int is 12"
All function declarations are parsed by a pre-compiler before the code compilation process. This allows for any SnapScript function to call another SnapScript function regardless of its position in the source file(s). Function declarations follow a single form:
<return type> <function-name>(<parameter-type> <parameter-name>, ...) {
// function code
}
Function names must begin with a valid alphabetical, non-numeric character.
Static or non-static Java methods may be linked to a SnapScript program to allow SnapScript code to call them.
Defining Linked Methods
Linked methods must bear the ScriptLink
annotation (com.snap2d.script.ScriptLink):
import com.snap2d.script.ScriptLink; // make sure the annotation class is imported
@ScriptLink
// you can specify the argument as false to disable the linked method:
// i.e. @ScriptLink(false)
public static void doJavaStuff() {}
Linked Method Constraints
A linked Java method may only return values or take arguments of supported SnapScript data types (i.e. int, float, double, boolean, String). Float and Double values may both be used, but it should be noted that since the script engine uses double internally, passed float values will be casted from double and vice versa.
Variable declared within a function have "scope" as to where they are valid to be used. All variables in SnapScript declared in the main block of the function, as well as all declared constants, are valid for use anywhere in the subsequent function code. Variable scope in SnapScript is quite simply localized to the block (and all inner blocks) the variable is declared in:
void Function() {
int n = 10;
if(true) {
int a = n; // valid - n is in scope within the function
if(a > 2)
println(a + n); // both a and n are in scope
}
println(a); // error - a is not in scope outside of its conditional block
}
The following conventions are optional in terms of valid code compilation but are highly recommended.
Script Functions
Functions declared and written in SnapScript source should be named using CamelCase (i.e. MyFunction
or SetNewValue
)
Java Methods
Java methods called from SnapScript should follow normal Java method naming conventions; mixed case, strong verb start, getter/setter, etc.
@SnapScript
public static void setJavaProperty() {}
Discerning Function Calls
Using CamelCase for script functions and mixed case for Java functions allows for easy discernment of what code function calls are executing in SnapScript. Adhering to these conventions is highly recommended, as it's very important when writing SnapScript code to keep track of when execution moves to Java.
Type declarations for variables or parameters always precede the naming of the variable:
int n = 0;
void Function(int n, float f) {}
In parameter declarations, repeating type declarations may be omitted as long as the first variable in the list has a defined type:
void MyInts(int n0, n1, n2) {}
void MyFloats(float f0, f1, f2) {}
void MyNumbers(int n0, n1, float f1, f2, bool other) {}
Variables can only be declared and used inside of a single function's scope.
Variables in SnapScript are statically typed and thus MUST include a type in their declaration. The type may be any one of the four defined in Section 1.1.
The right hand side of the variable assignment must evaluate to match the variable's type or be of a similar, compatible type (e.g. an int is compatible for a float variable):
int n0 = 2 + 2; // expression evaluates to int result
int n1 = 2 * 1.2; // illegal - expression evaluates to incompatible float type
float f = n0; // type int is compatible with type float; that is an int value can be stored in a float
n0 = f; // illegal - int variable cannot hold that of float data type
bool b = true; // expression evaluates to boolean result
string s = ""; // expression is string literal
Constants may be declared anywhere in script source outside of a function. You may declare as many constants as you wish, but their identifiers and values can never be changed or deallocated. Once the script runtime is initialized, constants remain stored in its variable tables until a new runtime is created after the script sources have been recompiled.
Constants are declared either using single line syntax:
const int N = 10;
Or using block syntax:
const {
int N = 10;
float Factor = 1.5;
}
Constant declarations are located by the precompiler and then compiled all at once by the compiler before any functions. The compiled bytecode for the constant initializers is passed to the script engine at runtime initialization and evaluated before any functions can be invoked. Constant initializers can be placed anywhere in script sources and still be referenced by any function.
void MyFunc() {
int n = N; // this is valid since the constant field N has already been logged and compiled
}
const int N = 10;
Functions can also be called by constant initializers. This is particularly useful for obtaining data or running calculations from Java code when setting constant values:
const float MyAngle = asin(1/2); // asin is a linked Java function in ScriptMath
Java functions are linked before constant initializers are compiled, so they can always be used legally in initializer code.
Script functions can also be called by constant initializers:
const int N = MyIntFunc();
int MyIntFunc() {
return 2;
}
A script function that references the constant field can NOT be called from its initializer. This will compile but result in a runtime initialization error:
const int N = MyIntFunc();
int MyIntFunc() {
int n = N;
return n * 2;
}
Output when initRuntime
is called:
Error initializing runtime: failed to locate var_id=-214783609
When the script engine tries to execute the initializer for constant 'N', it will try to execute the bytecode for function 'MyIntFunc' and find the circular variable reference to the constant it's trying to initialize. The internal ID for the constant has thus not yet been stored, so the engine will throw a ScriptInvocationException with this error.
The only recommended convention for function variables is that they start with a lower case letter. Use of mixed case or underscores is entirely up to the programmer:
int myInt = 1; // fine
int my_int = 2; // also fine
int MYint = 3; // discouraged
It is legal in SnapScript to start a variable with a number, but should be, by convention, discouraged.
Constant fields should be named with CamelCase:
const float MyFloatConst = 2.1;