Modules

A C-UP program is arranged in modules, where each module is represented by a single source file. Source files must be UTF-8 or ASCII.

Modules exist inside packages, which are used to arrange related modules in a hierarchy and typically correspond to directories in the host file system.

Each source file should begin with a module declaration.

module Physics.Collision.Box;

The above declares a module called Box in the Collision package, which is itself in the Physics package.

If the module declaration is omitted then it is inferred from the path of the module source file back up to the root folder passed to the compiler, where the file name minus extension is the module name and directory names are package names. See the section on the compiler for details about the root folder setting. Because identifiers in C-UP are case sensitive and some file systems are not, it’s recommended to use explicit module naming rather than relying on this mechanism.

The use of modules means that that there are no separate header and implementation files to maintain. Each file is compiled exactly once, and the declaration and use of program components is order independent.

Modules and packages create a hierarchy of namespaces. To access symbols in a namespace from outside of that namespace you must use the fully qualified name of the symbol. Let’s say you have a class BoxShape defined in the above module. To access this class from another module you’d have to type something like:

module Physics.Collision.Sphere;
Physics.Collision.Box.BoxShape boxShape;    // fully qualified BoxShape name

Imports

Typing in fully qualified names is time consuming and verbose and is usually best avoided by using imports.

The namespace of a particular module can be imported into another module using an import declaration. Any number of modules can be imported but all import statements must directly follow the module declaration at the top of a source file.

Symbols in imported modules can be used without qualification.

module Physics.Collision.Sphere;
import Physics.Collision.Box;
BoxShape boxShape;      // no need to qualify the BoxShape name

If two symbols called BoxShape are found, then the compiler reports that there is an ambiguity. You can resolve the ambiguity by resorting to a fully qualified name. Note that in this regard the order of imports is irrelevant; all imports in the same module are of equal importance so changing their order does not affect the ambiguity. However, order of imports does affect the order of static initialisation (see Static Initialisation section.)

Imports are private by default meaning that a module importing another module will not have unqualified access to symbols imported by the imported module. However, this can be allowed by prefixing the import declaration with the public access modifier.

module Physics.Collision.Sphere;
public import Physics.Collision.Box;

The above makes all symbols defined in Physics.Collision.Box visible to any module importing Physics.Collision.Sphere. In general this isn’t a recommended practice as it increases the likelihood of name clashes (and slows down compilation), but it can be useful when a module depends on another module so fundamentally that they cannot really be used without each other.

Alias Imports

In the above scenario where you want to import 2 modules but that causes name clashes, there is an alternative to just falling back to using fully qualified names. Alias imports allow you to assign a unique identifier to a particular import, and you can then use this identifier to qualify access to members of this module.

import cdBox = Physics.Collision.Box;
import grBox = Renderer.Bounds.Box;

cdBox.BoxShape boxShape;
grBox.BoxShape boxShape2;

Static Initialisation

When a module is loaded by the runtime system, it is possible to initialise module scope/static data and to run module scope/static code once only.

The user is afforded some control over the order of module initialisation as follows.

Processing starts with the module containing the program entry point. When a module starts initialising, all modules it imports are initialised first, in the order of the import declarations. Once imported modules are initialised, static initialisation for the importing module is performed in lexical order (see below). While there are any remaining modules that require initialisation one is selected arbitrarily and initialised (step (2)). Each module is initialised exactly once, so circular dependencies are not an issue.

Static Data

Static data comes in the form of initialisers for static or module scope variables. Such initialisers can rely upon other static or module scope data. Initialisers are run in lexical order and no checking is performed on that order so if you reference data defined later in a module (or in another module that you haven’t explicitly imported) it might have the initial value of zero.

static int MyStaticInt = 10;
static float MyStaticFloat = cast(float)MyStaticInt * 0.5f;

The above declarations could appear at any scope in the program (including local to a function) and will still be executed once at module load time. In particular, local statics are not handled the same way as C (where initialisation happens the first time execution reaches that line) although that behaviour can be manually implemented.

Static Code

Static code is any code appearing in a block ({ } pair) preceded by the static keyword. Like module data it is always called once at module initialisation time in lexical order regardless of its scope. It can appear at module, class or local scopes.

Module StaticInitTest;

static int Number = 100;

static  
{
    Console.WriteLine(“Initialising module StaticInitTest”);
    Console.WriteLine(Number);  // Writes 100
    Console.WriteLine(Number2); // Writes 0, Number2 initialiser is not reached yet
    Number++;
}

static int Number2 = 999;

Class TestClass
{
    int Number3 = 10;       // instance member

    static  
    {
        // non-static local variables can be used but only if declared in this static scope
        for (int i = 0; i < 1; i++)
        {
            Console.WriteLine(Number);  // Writes 101
            Console.WriteLine(Number2); // Writes 999
            Number++;
            Number3++;      // error, can’t access instance value in static code
        }
    }

    void InstanceMethod()
    {
        static
        {
            Console.WriteLine(Number);  // Writes 102
        }

        // do some non-static work
        Console.WriteLine(“xxx”);
    }
}

Unit Tests

Static code blocks in concert with the built in pre-processor can be used to implement unit tests. You need to decide on a pre-processor symbol used to enable unit tests – I suggest UNIT_TEST - and then do this.

#if UNIT_TEST
static
{
    // Assert is just a function in the system library that throws an AssertFailedException
    // if the condition is not met
    Assert(0 == 0);
}
#endif