Declarations
From Monster Wiki
Contents |
Function declarations
(under construction)
Function keywords
The following modifier keywords may be applied to functions. See below for examples.
- static a static function can be called without a class instance.
- abstract - an abstract function is a function without a body. It must be overridden a subclass before it can be called.
- override - signals that this function must override a function in a parent class. Optional, but useful as a defensive programming strategy against typos and other bugs.
- final - makes sure that no child class can override this function.
- native - this function is defined in native code (C++, D or similar), not in script code. Native functions can be viewed as the 'gateways' that scripts use to communicate with the outside world.
- idle - idle functions are, loosely speaking, functions that take time to execute, such as sleeping for a given amount of time, or waiting for a sound/animation to finish. Instead holding up the entire program, they will suspend the current (virtual) thread of execution and return later. Idle functions are implicitly native.
Multiple modifiers can be applied to one function.
Static functions
(not implemented yet) Static functions can be called without a class instance. Because of this, they can only access other static functions and variables. They are not allowed to access normal object variables, functions or states.
int i; static int s; func() {} static sfunc() { i = 3; // error, i is not static func() // error, func is not static s = 4; // allowed sfunc(); // allowed }
Static functions can be called using the class name: ClassName.sfunc(), but they can also be called on object references like non-static members. Even though they can not access normal members directly, static functions still have a 'this' reference, although it might be null:
class MyClass; int i = 5; static sfunc() { if(this == null) writeln("No object"); else writeln("i=", this.i); // 'i' can only be accessed through 'this' } // Usage: MyClass.sfunc(); // prints "No object" var m = new MyClass; m.sfunc(); // prints "i=5"
Abstract functions
Abstract functions have no body:
abstract myfunc();
Calling an abstract function will result in a run-time error. The main purpose of this is to force subclasses to override a function, and to fail if they forget to do so.
Unlike most other languages, Monster allows you to override existing (abstract or non-abstract) functions with an abstract function. This can be used to effectively 'disable' a function in a given subclass.
Native functions
Native functions are functions who's bodies are defined outside of the scripting engine.
native someExternalFunction();
They are typically used to send information between script code and the game engine. Most functions in the built-in modules are native. Native functions are virtual / polymorphic like any other function. A subclass can override a native function with a script function, and vice versa.
Native functions must be bound to the correct class in native code, through the API function MonsterClass.bind(). Calling a function that has not been bound will result in a run-time error, similar to when calling an abstract function.
Idle functions
Idle functions are a special type of native functions, in that they do not have a body defined in script code.
idle waitForSomething();
They are used for operations which normally takes time, and provide an automatic, easy-to-use and yet powerful way of using Monster-script's built-in cooperative multithreading system. For an overview of idle functions, check out this article (not written yet).
Idle functions are virtual like other functions. However idle functions can only override and be overridden by other idles.
Class declarations
There are three kinds of class declarations, representing the three kinds of classes in Monster:
class Name; // Normal class module Name; // Modules - can only have one global instance singleton Name; // Singleton - has one global instance, but can have more
All three declarations declare a class that is inserted into the global scope as a type. However, the three class types have slightly different properties when it comes to object creation:
- class - Acts as a normal class in most other languages. You may create any number of instances (objects) of the class using the 'new' keyword or in native code.
- module - A module has only one global (shared) instance, which is created when the module is compiled. It is not possible to create additional instances of a module. The global instance can be accessed directly through the module name, eg. MyModule.i = 5;
- singleton - A singleton class is similar to the module in that a global instance is created at startup. The difference is that additional instances can also be created, like with a normal class. The global instance can be accessed through the class name, like a module.
All three class types can be imported with the import statement.
There can only be one class declaration in each file, and it must be the first declaration in the file (if present). See File types. The rest of the file contains the declarations belonging to the class. A class file may contain:
- Custom type declarations
- Variable, function and state declarations
- Import statements
Classes as types
(not done)
class MyClass; int i; // member variable func() // member function { i += 2; } // The class may be used as a type name, also within itself MyClass instance; MyClass create() { return new MyClass; } exampleFunction() { instance = create(); instance.i = 3; instance.func(); io.writeln(instance.i); // prints '5' }
Class inheritance
A class can inherit from another class:
class Child : Parent;
Throughout the documentation we'll keep using the terms parent and child, and sometimes super class (for the parent) and subclass (for the child). Inheriting from a class means that all the declarations (functions, variables and so on) in the parent class automatically become present in the child class as well. The inherited declarations act as though they had been defined in the child class. Example:
// In the file 'a.mn': class A; int i; func() {} state s {} // In the file 'b.mn': class B : A; func2() { // All of these are ok: i = 3; func(); state = s; } int i; // error, already defined
Inheritance can be nested to any level; a class C may inherit from B, and another, D, might inherit from C, etc. Also, multiple child classes can inherit from a single parent class.
Note that modules cannot inherit or be inherited from.
Function overriding
The example above shows that you cannot override (redeclare) a variable that exists in a parent class. You can, however, override functions:
// In the file 'a.mn': class A; func() { io.writeln("A is for Ace"); } // In the file 'b.mn': class B : A; func() { io.writeln("B is better!"); }
The overriding function definition must matches the original: it must have the same number and types of arguments, and the same return type (if any).
For those familiar with C++, note that in Monster-script, all functions are virtual!. The virtual-ness of functions is best explained by an example:
// In the file 'a.mn': class A; func() { io.writeln("A is for Ace"); } callIt() { func(); } // In the file 'b.mn': class B : A; func() { io.writeln("B is better!"); }
Usage example:
var a = new A; a.callIt(); // prints "A is for Ace" var b = new B; b.callIt(); // prints "B is better!"
It's always the 'latest' version of a function that's called. Even if callIt() was defined in class A - where only the A version of func() existed - when it is called for class B it still calls the B version of func(), because func has been overridden in this class.
Polymorphism
Object references can be implicitly typecast to a parent class reference. This means that an instance (object) of a class can 'masquerade' as one of its parent classes:
A a; a = new B; // Allowed, since B inherits from A and has all the same members a.func(); // Prints "B is better!" a.callIt(); // Prints "B is better!"
If you're new to object oriented programming and polymorphism, check out the following on Wikipedia:
