Defining the scripting language and grammar

Types:

integer
float
string
list
object
dict
rtree
exception

integer:

A 32-bit signed integer value. Mutable.

float:

A 64-bit signed floating point value. Mutable. Floats can be
represented in the forms:
1234.56789
1234.0
123.0e+5

string:

A null-terminated character array. Strings are enclosed in double-quote characters ('"') and may be of any length. Use built-in functions to modify strings, which will return a new copy of the string and replace the reference. Semi-mutable.

list:

A primitive container. Lists are enclosed in braces ('{' and '}') and elements are separated by a comma (','). Lists can contain elements of
any type, including other lists. Mutable.

object:

A reference to an object data structure. Object references are not printable, but may be retrieved by asking the registry for an object id (which is an integer). Mutable.

NULL is a special keyword that indicates that there is no reference to the object.

dict:

A dictionary or map type. This associates values with keys. Mutable.

rtree:

A spatial organization type. This associates values with keys in three-space (x, y, z). Mutable.

exception:

A primitive error container. Exceptions contain information about the code that raised the exception (an error string, a string containing the offending line of code, and an error id), which is the exception's string representation. Exceptions can only be created by raising an exception. Immutable.

Exceptions are made up of a parent type and a limited set of sub-types:
Exception Parent exception type; catches any kind of exception
Undef_Exception An exception with an unknown type or unknown cause. A runtime exception.
Type_Exception Caused by attempting to store the wrong type.
Perm_Exception Permission denied
Div_Exception Division by zero
Float_Exception Floating point math error
Field_Exception Field not found on object
Method_Exception Method not found on object; signature doesn't match
Range_Exception Out of bounds on string or list
Null_Exception Invalid reference; NULL reference exception

Expressions:

An expression is defined as any code that produces a value (including literal values):
3 + 4
7
"Some string"
{"list", "of", "mixed", 7183, "type"}
l[1]
Malformed or bad expressions can raise an exception. If the exception is uncaught, it will cause a undefined exception and the script engine will halt execution of the script.

Variable declaration:

Variables are declared prior to use. They may be initialized at declaration, though this is optional. All declared variables are initialized to their default value:
integer 0
float 0.0
string ""
list {}
object NULL
dict {}
rtree {}
exception Undef_Exception

Variables are declared with the following (C-like) syntax:
  type variable;
  type variable = expression;

Variables are local to their scope (code-block in which they are declared).

Variable names may be contain any character inside the set:
  0-9, A-Z, a-z, '_'

Variable names may start with any character inside the set:
  A-Z, a-z, '_'

more properly
  [A-Za-z_][A-Za-z0-9_]*

Variable declarations are expressions and return the default or initialized value of the variable (as part of the behavior of the assignment operator).

Truth:

Values that are true include: Values that are false: The negation operator ('!') will invert the truth type of the expression.

The logical and ('&') and logical or ('|') operators modify truth expressions according to this table:
 

1 & 1 => 1
1 & 0 => 0
0 & 0 => 0


1 | 1 => 1
1 | 0 => 1
0 | 0 => 1
 

Operators and precedence:

The following operators are defined (along with the types on which they operate) in order of highest to lowest precedence:
 
{...} list declaration list
x.y, x.y() property, method object
x[i], x[i..j], @ index, range, splice list and string
- unary negation integer or float
*, /, % mult., div., modulo integer or float
+, - add, cat, subtract integer, float, or string
isa is same heritage any object
in inside list any against list
<, <=, >, >=, ==, != comparison any same (checks content)
! logical negation integer
& logical and any expression
| logical or any expression
= assignment any same

The list declaration operator takes the comma delimited expressions and creates a list type.

The property and method accessors return the value of the property or invocation of the method.

The list and string indexing operator returns the value of the element in the list or string at the given integer index.

The range indexing operator returns a list or string containing copies of the elements inside the list.

The splicing operator allows lists to be merged.

Unary negation converts an integer or float to its negated form.

Multiiplication, division, and modulo operations perform the appropriate math on the integer or float values inside the expression. In mixed types, integers are cast to floats and a float is returned for the entire expression.

Addition and subtraction operations perform the appropriate math on the integer or float values inside the expression. In mixed types, integers are cast to floats and a float is returned for the entire expression.

Concatenation occurs with strings. The left-side string will have the right-side string appended, and a new string value will be returned.

isa operations occur with objects. If the left-side object is a child of the right-hand object, true is returned.

in operations occur with lists (and in for-loops, where it acts as an iterator). If the left-side value is in the list, the index of the value is returned, otherwise 0 is returned.

Comparison operators work on any value of the same type. Strings will be compared against the ASCII table. Integers and floats will be compared numerically. Lists will be compared by length.

The logical negation operator will invert the truth value of an expression.

Logical and and logical or operators are explained above, in the Truth section.

The assignment operator will assign the value of the right-side of the expression to the field or variable on the left side of the expression. The entire assignment expression will be given the value of the right-side of the expression, as well, allowing code like:

  x = y = z = 3

where x, y, and z are all assigned the value of 3.
 

List Expressions:

List indexes are 1-based integers. There are no legal indexes on an empty list--this will throw a Range_Exception.

  list[index]
Index a single element from a list. This will evaluate to the value of the item contained in the list at that index.

  list[integer_1..integer_2]
Index a range of elements from a list. This will evaluate to a new list containing those items inside that range (copies).

  list[index] = expression
Assign a new value into this list at the integer index.

  @list
Splices a list into a list (exposes list contents so they may be placed in another list).

{@{1, 2, 3}} => {1, 2, 3}
  @list_1 = {@list_2, @list_3}
Gives list-1 the values of list-2 and list-3 spliced into one new list.
{1, 2, 3, 4, 5, 6} = {@{1, 2, 3}, @{4, 5, 6}}
  @list_1 = {list_2, @list_3}
Gives list-1 the values of list-2 as a sublist and list-3 as a splice.
{{1, 2, 3}, 4, 5, 6} = {{1, 2, 3}, @{4, 5, 6}}
  expression in list
Evaluates to the index of the expression in the list. The expression must match type and value.
"baz" in {"foo", "bar", "baz", "frobble"} => 3
"xyzzy" in {"foo", "bar", "baz", "frobble"} => 0
 

String Expressions:

String indexes are 1-based integers. There are no legal indexes on an empty string--this will throw a Range_Exception.

  string[index]
Index a single substring (character) from a list. This will evaluate to the character contained in the string at that index.

  string[start_index..end_index]
Returns a substring from the start index to the end index.
 

Object Values:

Most objects will be addressed by a variable that is the object reference.
This will typically be assigned by looking up the object id (and getting a reference) or  through creation of the object. Object ids are only available for objects managed by the object registry.

Some special object values are available, as well:
 
this retrieves a reference to the object containing the method
super refers to the parent class for the object, but is only valid for invoked a parent implementation of an overloaded method
registry retrieves a reference to the object registry

Class References by Name

Classes may be referred to by name. The class names are unique by server. Classes may be accessed from other servers--this will make a copy of the class definition on the local server if the class name does not exist locally. See defining classes below.

Object Instance References by ID

There are four kinds of object ids: transient, persistent, and network.

Transient objects are not written to the database and have temporary references (object ids). They can be converted to persistent objects with the make_persistent() function call. This will break references to the transient object but will store its data in a persistant manner.

Persistent objects are written to the database and have permanent references (object ids). They can be converted to transient objects with the make_transient() function call. This will break references to the persistant object and its data will no longer be stored in a persistant manner.

Persistant objects also may have an aliased form.

Network objects are references to objects on other servers. They may be transient or persistent on their home server, but are considered transient on this server--that is, their contents are not saved to the local database.

object o = #transient_id Assigns the transient object given by the id to the o object variable.
object o = #persistent_id Assigns the persistent object given by the id to the o object variable.
object o = $object_alias Assigns the persistent object given by the alias to the o object variable.
object o = server_name#object_id Assigns the network object given by the id to the o object variable.

Transient object ids are made up of alphabetic characters. These values are not guaranteed to be identical between instances of the server.

  \#[A-Za-z]+
Example:   #hsyUJwjsIOl

Persistent object ids are made up of numeric characters and are guaranteed to be the same between instances of the server.

  \#[0-9]+
Example:   #928372912

Aliased object ids are made up of alphanumeric characters, starting with the $ character followed by an alphabetic character. These are guaranteed to be identical between instances of the server and apply to persistent objects only.

  \$[A-Za-z][A-Za-z0-9]*
Example:   $avatar

Network objects are made up of a server name and either a persistent or transient object id. Server names are either DNS names, IP addresses, or hostnames.

  (([A-Za-z][A-Za-z0-9\-\.]*)|([0-9][0-9]?[0-9]?\.?)+)
Example:   server.domain.org#291827

Aliasing Object IDs with Names

Object aliases are only available for persistent objects. These names may be referred to locally or across a network. These are registered with the add_alias() function call. Objects may then be referred to by their aliased name:
  \$[A-Za-z][A-Za-z0-9]*
Example:   $avatar Example:   server.domain.org$avatar

Creating Objects from Classes

New objects are created from classes with the create() function call.

Objects can change classes with the chparent() function call. Properties (fields) missing on the new parent class will be removed with the potential loss of data.

Defining Classes

Classes are identified locally with a string name. Remote classes use the network name of the server following by an "@" sign and the classname.

New classes are defined with the add_class() function call. Classes are redefined with the same function call. Classes can only be created locally.

Classes may be removed with the rm_class() function call. Classes may not be removed if they have any child classes or objects that reference them.

Classes may change their parents with the chclass() function call. Classes may only change to a new parent no property or method definitions are lost in this change.

Field Values:

Retrieving field values is done using the object and the property to be retrieved.

  object.field_name
  object.(string_field_name)

In the first form, the field name is hard coded. In the second, the field name is specified by a string literal or variable.

Setting a field value may be done by:

  object.field_name = expression
  object.(string_field_name) = expression

As with any assignment, the value of the assignment expression is available as the value of this entire expression.

Method Calls:

Methods may be invoked on objects. Method definitions are statements.

  expression = object.method(expression_n, ...)

Methods return one value of any one type. Methods, by default, will return integer 0 in the absence of any other return statement. If the method called was not found on the object, Method_Exception will be thrown.

Methods may also be called with a string literal expression (like field values):

  expression = object.(method)(expression_n, ...)

A special method call is available to invoke the parent implementation of this same method.

  expression pass(expression)

This will invoke the parent implementation with the expression as arguments (comma delimited values). If there is no parent implementation, Method_Exception will be thrown. The pass statement will return the value returned by the parent method.
 

Built-In Calls:

A number of functions are built in. These are defined elsewhere.

  expression = function(param_n, ...)

Statements:

Statements perform useful, non-value-producing operations. Statements are terminated with a semicolon character (';').

The simplest, legal statement is a semicolon:

  ;
The next simplest statement is an expression followed by a semicolon:
  expression;
In this case, the expression is evaluated and its value is ignored.

A code block is defined as code within an opening curly brace ('{') and closing curly brace ('}'). These allow a number of statements to execute within the same scope, as well as limit variables to a local scope.

  { statements }

Statements containing expressions that raise exceptions will throw the expression to its containing code block.

Conditional Statements:

"if" allows the testing of truth in expressions.

  if (expression) {
    statements
  }

  if (expression) {
    statements
  } else {
    statements
  }

  if (expression) {
    statements
  } else if (expression) {
    statements
  } else {
    statements
  }
 

Example:

if (x < 5) {
  <statements>
} else if (x > 1) {
  <statements>
} else {
  <statements>
}
 

Looping Statements:

"while" allows looping based on a test condition (while expression is
true):

  while (expression) {
    statements
  }
 

list evens = {};
integer n = 1;
while (n <= 5) {
  evens = {@evens, 2 * n};
}
 
evens => {2, 4, 6, 8, 10}
 

 

"for" allows looping across a list expression:

  for variable in (list_expression) {
    statements
  }

The variable is given the value of the list element under consideration.
 

list odds = {1, 3, 5, 7, 9};
list evens = {};
integer n;
for n in (odds) {
  evens = {@evens, n + 1};
}


evens => {2, 4, 6, 8, 10}
 

 

"for" also allows looping across an integer range:

  for variable in [range_low..range_high] {
    statements
  }

list evens = {};
integer n;
for n in [1..5] {
  evens = {@evens, n * 2};
endfor


evens => {2, 4, 6, 8, 10}
 

 

All loop structures allow the statements "break" and "continue" to modify loop behavior.

  break;

Break breaks out of the loop and continues executing the script after the loop.

  continue;

Continue skips the rest of this iteration of the loop and continues with the next iteration.
 
 

Testing for Exceptions:


In order to catch exceptions, they must be inside a code block defined
by an exception handler ("try").

  try {
    statements
  } catch (exception-type variable) {
    statements
  } finally {
    statements
  }

This will declare the specified variable as an exception of the specified exception type and assign the variable's value to the be that of the exception thrown.

To catch any kind of exception, declare the exception of type "Exception", otherwise, to catch a specific type of exception, declare it to that type. You may specify as many catch clases as needed.

The finally clause will execute regardless of any exceptions thrown and is optional.
 

integer x;
try {
  x = 1; // this is legal
  x = "foo!"; // this is illegal
} catch (Type_Exception e) {
  // notify the user about the exception
} finally {
  x = 0; // make sure that the value is legal regardless
}

You can use multiple catch clauses for different exceptions, but
only one finally clause per try statement. You can embed try
statements in outer try statements in order to catch additional
exceptions.

Declaring and coding methods:

Methods are created on an object using the built-in object manipulation functions. The exact syntax used to code a method depends on the editor used.

Methods have signatures that uniquely identify how they are invoked. A signature is composed of the declaring object, the method name, and the method arguments (defined by order and type).

A method may access its arguments with the special list variable args which is created when the method is invoked.
 

// get the first argument, defined as a string in the signature
string name = args[1];


// get a count of the number of arguments passed into this method
integer argcount = length(args);


// pass the argument list to the parent implementation of this method
pass(@args);
 

Returning values and exiting early from methods:

To return a value from a method, use the "return" statement.

  return;

  return expression;

Return followed by some expression will immediately exit the method and return the provided expression. Return without an expression will return an integer 0.

return; => 0
return "Foo!"; => "Foo!"

Comments:

To put comments in your code, begin the comment with the '//' characters (two slashes). The rest of the line will be treated as a comment and ignored.
 

Server Functions:

Asynchronous Tasks:

Asynchronous tasks run independently of the method that started
them.

Starting an asynchronous task:

To begin an asynchronous task, you use the "fork" function.

  fork () {
    statements
  }

  fork (integer_seconds) {
    statements
  }

fork will start a new asynchronous task after the specified number of seconds have elapsed, or at the earliest opportunity. This task will execute the code contained in the fork statement's code block at that time.

Code following the fork function will be executed immediately following the fork function call and will not wait for the fork to finish processing.

fork returns the integer task id of the running task so that it can
be managed.

Getting a task id:

Code may retrieve the task id for the task in which it is running
with the "get_task" function.

  integer get_task()

Pausing an asynchronous task:

You use the "suspend" function to pause execution of a task.

  integer suspend(integer_seconds)

  integer suspend(integer_seconds, integer_task_id)

This will cause the specified task to pause execute for at least the given number of seconds. If the number of seconds specified is less than 1, the task will pause for an indefinite period.

If no task_id was provided, the currently running task will be paused.

This function returns true if the task was successfully paused.

If you attempt to modify a task you don't own, a Perm_Exception is thrown.

Restarting a paused task:

You can use the "resume" function to resume a paused task.

  integer resume(integer_task_id)

This will attempt to resume a paused task immediately.

This function returns true if the task was successfully resumed.

If you attempt to modify a task you don't own, a Perm_Exception is
throw.

Stopping a task:

You can use the "kill" function to stop execution of a task.

  integer kill(integer_task_id)

This will cause the specified task to stop execution immediately and exit.

This function returns true if the task was successfully resumed.

If you attempt to modify a task you don't own, a Perm_Exception is
throw.

Working with types:

Casting Values:

There are a number of functions provided for changing one type to
another.

  string to_string(expression)
  float to_float(expression)
  integer to_integer(expression)

These functions will perform with a greater or lesser degree of success depending on the expression provided. Lists will have their members concatenated into one single string expression prior to evaluation. Strings will evaluate to a float or integer if in the set of characters:

  0-9, '-', '+', 'e+', 'e-', '.'
  ([0-9]+|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?)


At the first non-numeric-legal character, the remainer of the string
is ignored.
 

to_integer("4552foo") => 4552
to_float("4552foo") => 4552.0


to_integer("32.56980") => 32
to_float("32.56980") => 32.5698


to_integer("washu100") => 0
to_float("washu100") => 0.0
 

List Functions:

  integer length(list_expression)

Returns the length of a list.
 

String Functions:

  integer length(string_expression)

Returns the length of a string.

Object Functions:

  integer add_alias(persistent_id, string alias)

Aliases the specified object with the given string alias. Returns true (1) if the alias was set and available, false (0) otherwise. Use unalias_object() clear an alias.

  integer add_class(string class_name, string parent_class)

Creates a new class name. Returns true (1) if the class was successfully added, false (0) otherwise (for instance, if the class name was already defined). Only local classes may be created, but the parent class can be a network class assuming that the network class name (when localized...that is, with the network name removed) does not conflict with local classes. This will copy the parent class to the local server class name does not conflict.

  integer chclass(string class_name, string new_parent)

Changes the class heritage. Returns true (1) if the class was successfully changed, false (0) otherwise. This will not be possible if any inherited method definitions (signatures) or field declarations (names) change.

  integer chparent(object_id, string new_parent)

Changes the object's parent. Returns true (1) if the class was successfully changed, falase (0) otherwise. Fields and methods that are not present on the new parent class will be shed.

  object_id create(string class_name, persistent_indicator)

Creates a persistent or transient object as a child of the given class_name. If the persistent_indicator is true (1), the object will be a persistent object, else if will be a transient object.

  persistent_id make_persistent(transient_id)

Converts a transient object and data contents into a persistent object. The data is copied from the old object to the new object. Changes to the presistent data do not propagate to the transient object and vice versa.

  transient_id make_transient(persistent_id)

Converts a persistant object and data contents into a transient object. The data is copied from the old object to the new object. Changes to the persistent data do not propagate to the transient object and vice versa.

  integer rename_class(old_class_name, new_class_name)

Renames the class. Returns true (1) on success, false (0) on failure. The new class name may not conflict with any other existing class names or this will fail.

  integer rm_alias(string alias)

Removes the specified alias from the registered aliases. Returns true (1) if successful, false (0) if the alias was not present.

  integer rm_class(string class_name)

Removes the specified class from the system if it has no child classes or child objects. Returns true (1) on success, false (0) otherwise.

  integer valid(object_id)

Returns true (1) if the object with the given id exists, false (0) otherwise. If the object id provided is an alias that does not exist, returns false (0).