Top-level definitions and statements
Top-level definitions and statements form the actual functional content of source files.
defs :: (def br)* def :: statement | [ "private" ] (var-def | function-def | class-def)
Top-level definitions include variable (and constant), function and class definitions. They may be marked private by prefixing them with the private keyword. Private definitions are only visible in the module that defines them. Definitions that are not private are public. The top-level may also include statements.
As an exception, a function named Main is always private even if the definition is not prefixed with private.
Variable and constant definitions
var-def :: ("var" | "const") id-list [ "=" expression ] id-list :: id ("," id)*
The var keyword introduces global variables and the const keyword introduces global constants. If the initialization expression is omitted, the value of global variables will default to nil, and global constants will be assigned a Constant object, whose name is the fully qualified name of the constant. For example, the uninitialized constant A in module y will receive y::A as its name.
If the initialization expression is provided, the value of this expression is assigned to the variables / constants. The semantics are similar to the assignment statement. Before this expression has been evaluated (see Initialization for details), the value of the variables / constants will be nil.
Constants differ from variables in that they cannot be lvalues, i.e. targets of an assignment (other than the initialization).
Function definitions
function-def :: "sub" id "(" arg-list ")" br block "end" arg-list :: [ var-args ] | argument ("," argument)* [ "," var-args ] var-args :: "*" id argument :: id [ "=" single-expression ]
The sub keyword introduces a function. The name of the function is given after the sub keyword. The formal arguments for the function are within parentheses after the name of the function, and the block after the argument list is the body of the function.
The definition constructs a std::Function object that conforms to the definition and assigns it to a constant specified by the name of the function. This object can be called to execute the body of the function (see section Call expressions for details).
The formal arguments become local variables within the function body. Their values depend on the actual arguments (values) that are provided when the function is called using the () operator.
If an argument has an assignment of an expression, this argument is optional and the expression is used to evaluate the value of the argument if no value is provided by the caller. This expression may refer to global definitions and formal arguments defined before that argument within the function definition.
The formal arguments not prefixed by an asterisk (*) are called fixed arguments. Fixed arguments that are not optional are required arguments. All required arguments must precede any optional arguments in the argument list.
An asterisk is used to define a variable-length argument list. It specifies that the function accepts an unbounded number of arguments. If the number of actual arguments is larger than the number of fixed arguments, the rest of the argument values will be assigned to a newly created Array object, and the object will be assigned to the last formal argument.
Class definitions
class-def :: "class" id [ "is" gvar ] br (cdef br)* "end" cdef :: [ "private" ] (member-var-def | method-def | getter-def | setter-def)
Use the class keyword to define a class (or a type; the terms are used more or less interchangeably). The name of the class is given after the class keyword. If the name is followed by the is keyword and a reference to a class definition, the referenced class is a superclass of the defined class. It is an error if the global variable reference after is does not refer to a visible class definition. If the superclass is not present, it defaults to std::Object.
The class header is followed by the class body. The body may contain member variable, member constant, method and accessor method (getter or setter) definitions.
An instance of the std::Type class representing the defined class is bound to a constant variable defined by the name of the class. This is called the type object of the class.
All definitions in the superclass are inherited by the class. If there is no explicit superclass, the class will inherit definitions from std::Object.
Private definitions are marked with the private keyword. These definitions can only be accessed through the self object in the class that contains the definitions. Other definitions are public and can be accessed without restriction.
Object identity and state
The state of an object consists of the identity of the object, the type of the object (which cannot be modified after object construction) and the values of any value slots, including value slots associated to inherited member variables. Each constructed object has an identity that is separate from any other object. The type specifies the operations (methods and member getters and setters) supported by the object.
If the object does not overload the == operator (with the _eq method), the == operator can be used to test if an object reference is equal to another (i.e. if they have the same identity). This behavior is inherited from std::Object.
Object creation
The type object can be called to create instances of the defined class, i.e. objects with the defined class as their type. The arguments to the type object are the same as to the create method of the class. The create method can be either defined in the body of the class, inherited from the superclass or created automatically (implicit create; see below).
The call of a type object is evaluated in several steps:
- Construct an empty instance of the class, with all value slots initialized to nil.
- Evaluate any initializer expressions of member variables and constants in the superclasses, starting from the top of the class hierarchy.
- Evaluate any initializer expressions of member variables and constants in the current class.
- Call the create method of the class. The original arguments given to the type object are given as the arguments of the method.
- If the create method is evaluated successfully, return a reference to the constructed object as the return value of the original call. If the create method returns a value, it is ignored.
Initializer expressions in a class body are evaluated in the order the definitions appear in the source file.
Implicit create
The implicit create method is defined automatically for classes that have no explicit or inherited create method. It takes a single argument for each uninitialized member variable or constant defined in the class body. If the body contains no such members, it takes no arguments. The method simply assigns the argument values to the uninitialized member variables and constants. The order of the arguments is the same as the order of the corresponding members in the source file, so that the first argument is assigned to the first uninitialized member, and so on.
Method definitions
method-def :: function-def
Methods are defined using the same syntax as functions (see Function definitions above). Their behavior is similar to ordinary functions, but they can only be called in the context of an object of the enclosing class (or one of its subclasses, unless the method is private). This object can be accessed using the self keyword in the body of the method.
A method of an object is accessed using the dot (.) operator. The result is a bound method object that can be called to execute the method (see sections Member references and Call expressions for details).
Member variable and constant definitions
member-var-def :: var-def
Member variables and constants can be defined explicitly with a syntax identical to global variable definitions. Like global variables, they may be given default values.
The value of an explicit member variable or constant is stored in a value slot. Every instance of a class has a separate value slot associated with every explicitly defined member variable and constant. A value slot can refer to any object. All value slots are initialized to nil at the beginning of object creation, including those related to constants without initializers.
A getter method with the same name as the variable is automatically defined for each explicit member variable or constant, and a setter is defined for each explicit member variable. The getter simply returns the value in the slot, and the setter modifies the value in the slot (see section Member references for details). The value of the slot is usually accessed or modified only through getter and setter methods. As an exception, the value of the initialization expression is assigned directly to the value slot, without invoking the setter method. The create method can also assign values to the value slots of member constants defined in the same class that contains the create method.
Getter and setter definitions
getter-def :: "sub" id br block "end" setter-def :: "sub" id "=" id br block "end"
The sub keyword is also used to define a getter method or a setter method for a member. These are used to implement reading and modifying a member variable or constant (see Member references for details). An alternative way of defining member variables and constants was described in the previous section. Note that simply defining a getter or a setter does not allocate a value slot in the instance; only explicit member variables and constants have value slots.
A getter method should always return a value (if no exceptions are raised). The return value of a setter method is ignored.
The additional identifier after the = token in a setter definition names the local variable that will hold the new value assigned to the member. This variable acts like an ordinary method argument, but it may not have a default value.
The body of a getter or a setter is a block, and follows the same rules as method bodies, unless mentioned otherwise above. In particular, the self object can be accessed within the bodies of getters and setters.
Getters and setters are also defined implicitly by every explicit member variable or constant definition. Note that redefining the implicit getter and setter methods of a member variable is possible only by overriding them in a subclass.
It is an error to define a setter for a member that has no getter. However, defining a setter is always optional.
Overriding superclass definitions
Subclasses may override public method, getter and setter definitions located in any superclasses by redefining them. A method in a superclass can be overridden by defining a method with the same name in a subclass. In a similar fashion, a getter or a setter method can be overridden by defining a getter or a setter, respectively, with the same name in a subclass. The method signatures (argument lists) of the original and the overriding (new) definitions do not have to be similar.
Definitions in subclasses usually take precedence over the definitions in the superclasses: all ordinary member references, including those made with the dot (.) operator, direct references using only member names, and references made in inherited methods and accessors, refer to the new definitions.
The super keyword can be used to access the original, overridden definitions of methods and accessors (see Superclass member references).