Skip to content

SnapScript Language Specifications

Brian Groenke edited this page Jan 19, 2014 · 17 revisions

This specifications page is a work in progress!

Section I - Data Types

1.1) Type Overview

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.

1.2) Float 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.

1.3) String Type

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).

Section II - Grammar

SnapScript's grammatical constructs follow very closely to that of other C-like languages.

2.1) Statements

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.

2.2) Delimiters

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

2.3) Argument Lists

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 }

2.4) Definition Syntax

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";
}

2.5) Mathematical Expression Syntax

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

2.6) If Statement Syntax

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.

2.7) For Loop Syntax

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
}

2.8) String Literals

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"

Section III - Functions

3.1) Function Declaration

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.

3.2) Linked Java Methods

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.

3.3) Function Scope

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
}

3.4) Function Naming Conventions

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.

Section IV - Variables

4.1) Variable Declaration

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.

4.2) Variable Types

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

4.3) Constant Fields

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.

4.4) Variable Naming Conventions

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;