Send suggestions to aegis@nerv-un.net. This document is a reference for the second implementation of the SphereScript language. It is not designed to teach beginners how to create a game with Sphere. This document also does not describe the system data types and functions provided by the SphereScript compiler, or any other implementor of the language.
SphereScript is a programming language created to provide a mechanism for customizing the Sphere RPG engine. It is similar to the popular language 'C' and many elements will seem familiar to C, C++, and Java programmers. However, it provides features that simplify common tasks; for example, the string data type.
SphereScript is a line-neutral language. It does not matter how much whitespace the programmer uses, as long as the tokenizer can differentiate between tokens. In fact, an entire program could be defined on one single line.
SphereScript modules are files with extension .ss. Each module is divided into several global definitions. The three types of globals are user data types, global variables, and functions.
The programmer can combine current data types into a new user type. This is useful for storing related fields in what is known as a record. The syntax for creating a user type is as follows:
type identifier { >variable list< }
Example:
type Player { string name; int hit_points, magic_points; }
If you have a variable of a user-defined type, the fields are accessed with the dot (.) operator.
Player p = GetPlayer(2); int temp_hp = p.hit_points; temp_hp -= 10;
Global variables are variables that any function in the source module can access. They must be declared outside of any function. However, they can be declared at any point in the module, even after a function that uses it. Multiple global variables can be declared on one line, as long as they are separated by a comma.
data_type identifier [, identifier2 [, identifier3 [, ...]]];
Example:
int gold;
A function is a logical set of instructions that can receive values, called parameters, and can return a value. A function is defined as follows:
return_type identifier(parameter_list) { >statement list< }
Functions can return 'void' along with any normal data type. A function that returns 'void' simply means it has no return value. There is no limit to the number of parameters allowed.
There are several types of statements. They are enumerated below.
The null statement is just that. It does nothing. A null statement is represented by a single semicolon.
To combine several statements into one, use curly brackets and place the statements between them. A compound statement is sometimes called a block.
Local variables are created on the system stack inside of a block of code. They are destroyed when the block exits. All variables are initialized to default values. Integers are 0, booleans are false, floating points are 0.0, and strings are "". Since variables go out of scope (they can no longer be accessed) when a block exits, a new block can use the same variable names. Therefore, the following code is valid:
void function() { int i; { int j; } { int j; } int j; }
The most common type of statement is an expression. An expression is a set of mathematical operations that operate on terms. A term can either be an l-value or an r-value. L-values are on the left side of assignment expressions. They can be either read from or written to. R-values mean either read-only value or right value.
Operator Precedences | ||
---|---|---|
Operator | Evaluation Order | Precedence |
Assignment Operators | right-to-left | |
= | 1 | |
+= | 1 | |
-= | 1 | |
*= | 1 | |
/= | 1 | |
%= | 1 | |
Comparison Operators | left-to-right | |
== | 2 | |
!= | 2 | |
> | 2 | |
< | 2 | |
>= | 2 | |
<= | 2 | |
Addition Operators | left-to-right | |
+ | 3 | |
- | 3 | |
Multiplication Operators | left-to-right | |
* | 4 | |
/ | 4 | |
%4 | 4 | |
Unary operators | term-level, left-to-right | |
! | 5 | |
+ | 5 | |
- | 5 | |
Index and Parentheses | left-to-right | |
[] | 6 | |
() | 7 |
Operator Meanings | |
---|---|
Operator | Definition |
Assignment Operators | |
Assignment = | Assigns rvalue to lvalue and returns result. |
Subtraction Assignment -= | Subtracts rvalue from lvalue and returns lvalue. |
Multiplication Assignment *= | Multiplies lvalue by rvalue and returns lvalue. |
Division Assignment /= | Divides lvalue by rvalue and returns lvalue. |
Modulus Assignment %= | Stores modulus of lvalue and rvalue in lvalue and returns it. |
Comparison Operators | |
Equals == | Returns true if operands are equal and false otherwise. |
Not Equals != | Returns false if operands are equal and true otherwise. |
Greater Than > | Returns true if left-hand-side is greater than right-hand-side. |
Less Than < | Returns true if right-hand-side is greater than left-hand-side. |
Greater Than or Equal To >= | Returns true if left-hand-side is greater than or equal to left-hand-side. |
Less Than or Equal To <= | Returns true if right-hand-side is greater than or equal to left-hand-side. |
Addition Operators | |
Addition + | Returns sum of two operands. |
Subtraction - | Returns difference of two operands. |
Multiplication operators | |
Multiplication * | Returns product of two operands. |
Division / | Returns quotient of two operands. |
Modulus % | Returns modulus (remainder) of two operands. |
Unary Operators | |
Not ! | Returns logical opposite of operand. !true returns false and !false returns true. |
Positive + | Returns operand with no modification. |
Negative - | Returns negative of operand. |
Index and Parentheses | |
Index [] | Returns value at specified index into array. |
Parentheses () | Precedence Forcing - Forces the expression evaluation within parentheses to occur first. Function calls - Calls a function, passing specified parameters. |
Return statements are used to explicitly exit a function and, if the function has a return type other than void, return a value.
void function:
return;
non-void function:
return expression;
An if statement executes statement1 if boolean-expression evaluates to true and statement2 if else clause is present and boolean-expression evaluates to false.
if (boolean-expression) statement1 else statement2
The for statement evaluates a statement until a specified expression evaluates to false. After each time the statement is executed an update expression is evaluated. Before the statement is evaluated at all the initialization expression is evaluated.
int i; for (i = 0; i < 10; i += 1) { PrintNumber(i); }
While statements execute a statement until the test expression evaluates to false.
string s = “*”; while (StringLength(s) < 32) { s += s; }
Do statements always execute the statement once. Do..while loops execute the statement while the expression evaluates to true. Do..until loops execute the statement until the statement evaluates to false.
int input; do { input = GetInput(); } until (input > 1 && input < 4);
switch (menu_result) { // first menu item case (0) { NewGame(); } // second menu item case (1) { LoadGame(); } }
Native Data Types | |
---|---|
int | 32-bit signed integer, range -2147483648 to 2147483647 |
bool | Boolean logical value, true or false |
float | 32-bit floating-point |
string | variable-length character string |
Arrays are constant-size linear tables of data. They can be multidimensional and made of any data type. Example syntax is as follows:
string[10][10] table; string t = table[4][5];
If an array is specified to hold ten elements, it is accessed via indices 0..9. All indices are zero-based.
Inline comments are necessary for creating maintainable code. SphereScript supports two comment mechanisms that C++ programmers will find familiar. Coincidence? I think not.
// This comment starts at the ‘//’ and ends at the end of the line /* This is the comment */ /* /* Remember, you can’t use nested comments. */
The tokenizer takes a block of text and creates a list of tokens, which are discrete words that can be parsed by the compiler. For example, the following code:
void game() { string s = “blah blah”; }
... will be parsed into the following tokens:
"void" "game" "(" ")" "{" "string" "s" "=" "\"blah blah\"" ";" "}"
The compiler portion of this SphereScript implementation uses the top-down recursion algorithm described in Jack Crenshaw’s "Let’s Build a Compiler" tutorial. http://www.iecc.com/compilers/crenshaw/ Without Crenshaw’s help, SphereScript never would have happened.
The compiling process is broken into two phases. The first phases merely passes over the script on a global level and builds symbol tables for user data types, global variables, and functions. The second phase actually generates code.
Each element of the language is built of several sub-elements and has a function to handle it. For example, the main script is built of several elements called globals. Therefore, the main function simply reads a token from the tokenlist and calls the appropriate sub-element function. The function that compiles a SphereScript function reads some tokens and stores the return value, name of the function, and the parameter list, and then it calls the statement function until the end of the function is reached. This process continues until the smallest element of SphereScript, the term, is reached.
This is a rather simple component that does not have to be explained much. Platform-independant mnemonics generated by the compiler are converted into bytecodes that the Sphere engine virtual machine can interpret. The assembler operates the same way most other assemblers do.