Language Reference - WheretIB/nullc GitHub Wiki

NULLC Language reference

  1. General
    1. Basic types
    2. Variable alignment
    3. Type inference
  2. Expressions
    1. import expression
    2. typeof expression
      1. Extended typeof expressions
    3. sizeof expression
    4. new expression
      1. Garbage collection
      2. Constructor call
      3. Object finalization
    5. break expression
    6. continue expression
    7. return expression
    8. yield expression
    9. typedef expression
  3. Statements
    1. if statement
      1. Static if
    2. for statement
      1. C-style for
      2. for each
      3. Custom iterator
    3. while statement
    4. do...while statement
    5. switch statement
  4. Declarations
    1. User classes
      1. Accessors
      2. Generic classes
        1. Generic class member type specialization
      3. Class constructor
      4. Class forward declaration
    2. Variables
    3. Functions
      1. Variable argument list
      2. Local functions
      3. Closures
      4. Member functions
      5. Function Literals
      6. Short inline functions
      7. Function overloading
      8. Operator overloading
      9. Coroutines
      10. Generic functions
      11. Generic function specialization
    4. Arrays with implicit size
    5. Enumeration
    6. Namespaces
  5. Special types
    1. auto ref
      1. function call through auto ref
    2. auto[]
    3. typeid
  6. Miscellaneous
    1. Characters
    2. Strings
      1. Unescaped strings
    3. Inline arrays
      1. List comprehension
    4. Escape sequences
    5. Binary numbers
    6. Octal numbers
    7. Hexadecimal numbers
    8. Null pointer
  7. Standard library
    1. std.typeinfo
    2. std.dynamic
    3. std.gc
    4. std.vector
    5. std.list
    6. std.hashmap
    7. std.range
    8. std.file
    9. std.io
    10. std.random
    11. std.time
  8. Appendix
    1. Rules applied to value types in a binary operation
    2. Operator priority
    3. Implicit conversions

1. General

1.1 Basic types

Name Size Default alignment Extra information
void 0 bytes no alignment only allowed as a function return value (returns nothing)
bool 1 byte no alignment values: false (0) or true (1)
char 1 byte no alignment values: -128..127
short 2 bytes usually 2 values: -32768..32767
int 4 bytes usually 4 values: -2147483648..2147483647
long 8 bytes usually 4 or 8 values: -9223372036854775808..9223372036854775807
float 4 bytes usually 4 values: as per IEEE 754
double 8 bytes usually 4 or 8 values: as per IEEE 754

1.2 Variable alignment

Default type alignment can be changed using two statements:
noalign before type name will disable any default type alignment:

noalign type name; // variable "name" of type "type" will not be aligned

align(bytes) before type name will force specified alignment:

align(8) type name; // variable "name" of type "type" will be aligned to a 8 byte boundary.

Alignment must not exceed 16 bytes.


1.3 Type inference

When defining a variable, type name can be replaced with a keyword "auto".
In this case, type will be inferred from r-value type.
Here are some examples:

auto i = 5; // i will have type int
auto n = &i; // n will have type int ref
int[10] arr;
auto copy = arr; // copy will have type int[10]

Alignment of automatic types works just like with explicitly specified types.

auto can be used as a function return type. If a function has different exit points returning different type, an error occurs.
auto can be used as a function argument type as long as this arguments has a default value.

Type inference also works in situations where overloaded function pointer is used. Compiler will infer needed overload in the following context:

  • assignment to a variable with known type
  • return from a function with known return type
  • in default function arguments
  • in function call argument list

2. Expressions

2.1 import expression

import expressions allows code to import functions, classes and variables from other files.

import expressions must be placed at the beginning of a file before any other expression or definition.
A file name without extension must be specified after import. Also, folder name is accepted after import keyword, then a point '.' and another folder\file name expected.

Expression examples:

import a; // builds a.nc file and imports functions, classes and variables from it
import d.e; // builds d\e.nc file and imports functions, classes and variables from it

2.2 typeof operator

typeof(expression) allows to get type of an expression (expression will not be evaluated at run time).

typeof(4) is equal to specifying int.
typeof(4 * 0.2) is equal to specifying double.

2.2.1 Extended typeof expressions

Additional type information can be requested after a typeof expression.

For function types following extended expressions are available:

typeof(expression).argument.first - returns type of the first argument
typeof(expression).argument.last - returns type of the last argument
typeof(expression).argument.size - returns argument count
typeof(expression).argument[N] - returns argument at specified index
typeof(expression).return - returns function return type

For array types and reference types, the following extended expression is available:

typeof(expression).target - returns the type of an array element type or a type the reference points to

For array types, the following extended expression is available:

typeof(expression).arraySize - returns size of an array (-1 for array with implicit size)

Additional extended expressions are available for all types:

typeof(expression).isReference - returns 1 if type is a reference and 0 otherwise
typeof(expression).isArray - returns 1 if type is an array and 0 otherwise
typeof(expression).isFunction - returns 1 if type is a function and 0 otherwise

It is possible to get class typedef target type, class member type or a class constant value by writing a name after the point.

Extended typeof expressions are also available for use immediately after the type name.


2.3 sizeof operator

sizeof(type) returns size of type.
sizeof(expression) returns size of the expression type (it is equal to "sizeof(typeof(expression))")


2.4 new expression

new expression allows to allocate memory from global heap.
There are two versions of this expression - one is used to allocate classes and the second one is used to allocate arrays.

Return type of "new type" is 'type ref'.
Return type of "new type[N]", where N is an expression that results in a number is 'type[]'.

2.4.1 Garbage collection

There is not implicit memory deallocation function, dynamic memory is managed by NULLC and is garbage collected.
Garbage collection is aware that some pointers may point to a memory not managed by NULLC (you can pass pointers to your objects safely to NULLC) and will skip those.

Some form of explicit management is exposed through std.gc module.

2.4.2 Constructor call

If a single object is created, it is possible to call a type constructor in a new expression, by opening parenthesis and writing function arguments.

int ref x = new int(12);

Default class constructor will be called if parentheses are not written.
To define a constructor for a type, add a member function with the name equal to the class name. Read more in a topic about class constructor.

It is possible to define custom construction for an object by writing code inside figure braces {}:

class Foo{ int x; }
auto a = new Foo{ x = 4; };
auto b = new Foo(){ x = 8; };
return a.x + b.x; // 12

2.4.3 Object finalization

If a class implements a "finalize" function which doesn't accept any arguments, then when GC finds that the class instance is not referenced any more, this function will be called to perform any kind of clean-up you may want to do on it.
Finalizable class has a restriction that other classes do not have: it cannot be placed on stack or be a member of some other class, only references to it or arrays with implicit size can be created.

It is strongly not recommended to create new references to an object from its finalizer because finalizer runs automatically only once per class instance.


2.5 break expression

break expression allows to end execution of a cycle.

break; exits current cycle.
break N; where N is number, known at compile time exits from N cycles beginning from where it is written.
break 1; is equal to break;


2.6 continue expression

continue expression allows to skip to the next iteration of a cycle.

continue; skips to the end of current cycle.
continue N; where N is number, known at compile time exits from (N-1) cycles beginning from where it is written and skips to the next iteration of cycle it ends up.
continue 1; is equal to continue;


2.7 return expression

return expression allows to return a value or exit from a function, or to end execution of a program if placed in global scope.

return; // exits function, returning void type (nothing).
return expr; // exits function or ends program execution, returning result of "expr".

return that is placed globally accepts only basic build-un types and cannot return void.

When value is returned from function, it is converted to functions' return type if conversion is possible.
If function return type is auto, function return type is set to the type of "expr".


2.8 yield expression

yield expression allows to return a value (optionally) and execution to the calling function. The difference from return expression is that the next time the function is called; it will continue execution from the point immediately after last yield.

yield; // returning void type (nothing).
yield expr; // returning result of "expr".

Return value conversion and auto return type resolution is performed exactly as if return expression was used.
yield can only be used inside coroutines.


2.9 typedef expression

typedef expression allows to create one-word aliases to other types.

typedef doesn't create new type, it just allows its target type to have different names.

Examples:

typedef int[4] fourInts;
typedef int ref(int, int) drawCallback;

3. Statements

3.1 if statement

if(expr)
    trueBody
else
    falseBody

if statement evaluates result of expression "expr", and if result isn't equal to 0, evaluates expressions put in "trueBody". Otherwise, it evaluates expressions put in "falseBody".
"trueBody" and "falseBody" may consist of a single expression or a block of expressions.
else and "falseBody", may be omitted. In such case, if result of "expr" is zero, nothing is evaluated.

If "expr" type is a user-defined type, a conversion to bool is performed by calling bool(expr) function.

3.1.1 Static if

If an @ is placed before an if expression, then, condition will be evaluated at compilation time (which is not always possible) and the body will be compiled only if condition is true.
This is most useful in generic functions where different code paths can be made for different type groups and not all code paths can be successfully compiled with wrong type groups.


3.2 for statement

3.2.1 C-style for

for(initexpr; condexpr; iterexpr)
    body

for statement is a cycle that evaluates "initexpr", and executes "body" while "condexpr" evaluates to a non-zero value.
"iterexpr" is evaluated at the end of every cycle.

"body", "initexpr" and "iterexpr" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.

If "condexpr" type is a user-defined type, a conversion to bool is performed by calling bool(condexpr) function.

3.2.2 for each

A shorter form of for exists to iterate through all elements of an array, a custom iterator or a coroutine.

for(type refName in expression, type refNameN in expressionN)
    body

type name is optional and should be used when iterator return type is auto ref, to convert it to selected type.
If more than one iterator is specified, loop will terminate when one of the iterators reaches its end.
For example, return value of the following code is 14 (1 * 4 + 2 * 5):

int sum = 0;
for(x in {1, 2, 3}, y in {4, 5})
    sum += x * y;
return sum;

It is possible to iterate through all the elements yielded by a coroutine.
But be careful, just like in a list comprehension, a return expression will stop the iteration, ignoring the result.

3.2.3 Custom iterator

If you wish to iterate over elements of your type or add a contextless custom iterator (for examples of those, see std.range module), you must add a specific function:

type_iterator type:start()
{
    /* code that returns iterator */
}

start() function will be explicitly called for expression to the right of in keyword.

Iterator type must implement two functions:

auto type_iterator:hasnext()
{
    /* return 1 if there are elements left to iterate through, and 0 to end iteration */
}

auto type_iterator:next()
{
    /* code that returns current element and moves on to the next */
}

Keep in mind that next() function should return current iterator element and move on to the next.

Example of an iterator over elements of a single-linked list.

// List node class contains element value and a pointer to the next element
class list_node
{
    list_node ref	next;
    int	value;
}
// list type iterator will contain pointer to the current node he is at.
class list_iterator
{
    list_node ref curr;
}
// Iterator initialization function
auto list_node:start()
{
    // Create iterator
    list_iterator ret;
    // Set its start to this node
    ret.curr = this;
    // return iterator
    return ret;
}
auto list_iterator:hasnext()
{
    // While curent node pointer is valid, signal that there are elements left to iterate through
    return curr ? 1 : 0;
}
// This function will return iterator element and move on to the next
auto list_iterator:next()
{
    // Save element value
    int ret = curr.value;
    // Move to the next element
    curr = curr.next;
    // Return old element value
    return ret;
}
// Create a list of two numbers
list_node list;
list.value = 2;
list.next = new list_node;
list.next.value = 5;
// Compute product of numbers in a list using our custom iterator
int product = 1;
for(i in list)
    product *= i;
return product;

3.3 while statement

while(condexpr)
    body

while statement is a cycle that executes "body" while "condexpr" evaluates to a non-zero value.

"body" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.

If "condexpr" type is a user-defined type, a conversion to bool is performed by calling bool(condexpr) function.


3.4 do...while statement

do
    body
while(condexpr);

do statement is a cycle that executes "body" while "condexpr" evaluates to a non-zero value.
Difference from while statement is that do statement evaluates body at least once, since condition is placed after the body.

"body" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.

If "condexpr" type is a user-defined type, a conversion to bool is performed by calling bool(condexpr) function.


3.5 switch statement

switch(expr)
{
    case A:
        caseBody;
    default:
        defaultBody;
}

switch statement evaluates result of "expr" and compares result with case label values. If result matches case value, a jump to a matched case label is made and all expressions after it are evaluated.
If no case label value is equal to result, a jump to default label is made.
break; expression can be used inside switch statement.
default label can be omitted.
switch without case labels, or without any expressions at all is legal.


4. Declarations

4.1 User classes

class Name
{
    type name, name, ...;
    type function(type arg, type arg, ...){ }
    ...
}

Class consists of variables, functions, typedefs' and constants in any order, but keep in mind, functions can only access members that are defined before them.

Class constants are numeric type members that are defined after a const keyword. Such members do not take any space inside a class, and can be accessed as other members in an extended typeof expression with a difference that their value will be resolved immediately at compilation time.
Constant members must have a default value assigned to them. Assignment can be skipped for integer constants after the first one in a comma-separated list, in which case they will be autoincremented:

class Foo
{
    // A equals 5, B equals 6 and C equals 7
    const int A = 5, B, C;
}
Foo a;
return Foo.B + a.C; // Answer is 13.

Constants can be accessed directly or by using a class instance.
There is no default alignment by default. To specify alignment, put "noalign" of "align(bytes)" before "class" keyword.
Specifying "noalign" is superfluous. Alignment must not exceed 16 bytes.


4.1.1 Accessors

There is a possibility to add virtual class members - members that can be used as any real class member, but access and modification of them is implemented in custom functions.
To add an accessor to your class, use the following syntax:

// read-only accessor
type name{ get{ /* getter body */ } };
// accessor with read/write access
type name{ get{ /* getter body */ } set{ /* setter body */ } };
// accessor with read-write access and a custom name for right-hand value of set function
type name{ get{ /* getter body */ } set(value){ /* setter body */ } };

Example of a read-only accessor.

// Let's create a "sum" accessor to a class consisting of two numbers
class NumberPair
{
    int a, b;
    // accessor is read-only and returns sum of both members. return type can be inferred automatically
    auto sum{ get{ return a + b; } };
}
NumberPair foo;
foo.a = 5;
foo.b = 10;
return foo.sum;	// returns 15

Example of a read-write accessor.

// Suppose we have a two-component floating point vector and we want to add a possibility for swizzling
class vec2
{
    float x, y;
    // xy swizzle is trivial
    auto xy
    {
        get
        {
            return this;
        }
        set
        {
            x = r.x;
            y = r.y;
        }
    };
    // in yx swizzle, a temporary vector should be created
    auto yx
    {
        get
        {
            vec2 tmp;
            tmp.x = y;
            tmp.y = x;
            return tmp;
        }
        set(value)
        {
            y = value.x;
            x = value.y;
        }
    };
}
vec2 foo;
foo.x = 1.5;
foo.y = 2.5;
vec2 bar;
bar.yx = foo.xy;
return foo.x * bar.y; // returns 2.25

It is possible to add accessors to a class outside of a class definition. In this case, getter and setter are defined separately.
A syntax is similar to external member function definition, only '.' is used instead of ':':

// Second accessor of "read-write accessor" example above defined outside of a class
auto vec2.yx()
{
    vec2 tmp;
    tmp.x = y;
    tmp.y = x;
    return tmp;
}
void vec2.yx(vec2 ref value)
{
    y = value.x;
    x = value.y;
}

4.1.2 Generic classes

Generic class is a class that depends on type arguments, a single definition of which can be instanced with different arguments to produce different types.
Type argument list is a list of aliases to a to-be-defined-later types inside an angle-brackets '<' '>':

class Pair<T, U>
{
    T first;
    U second;
}

This is a generic type definition, and it doesn't create any real types yet.
Required type is instanced later, when generic type name is followed with a type list in angle-brackets:

Pair<int, float> a;
Pair<double, long> b;

In this example, variables "a" and "b" have different types.
Nesting generic types are also allowed:

Pair<Pair<int, int>, Pair<double, double>> c;
4.1.2.1 Generic class member type specialization

It is possible to define a special member function implementation for a single instanced type by specifying full type name in an external member function definition:

import std.math;
// define a tuple of two values
class Pair<T, U>
{
    T first;
    U second;
}
// Helper function that returns a Pair object with type according to arguments
auto Pair(@T a, @U b){ Pair<T, U> p; p.first = a; p.second = b; return p; }

// Function compares first with second
bool Pair:Compare(){ return first > second; }
// Specialization for pair of float2 compares vector lengths
bool Pair<float2, float2>:Compare(){ return first.length() > second.length(); }

// Create a pair
auto p = Pair(float2(4, 4), float2(1, 5));
// Compare values
return p.Compare(); // true

4.1.3 Class constructor

A class member function that has the same name as the type is considered to be a class constructor.
As described in a section about new expression, class constructors are functions that can be called after object allocation.
But because an object can be placed on stack instead of heap memory, compiler will call a default constructor for variables that are placed on stack.
Default constructor is simply a constructor that can be called without any arguments (constructor can have default function arguments).

class Foo
{
    int x;
    void Foo(){ x = 12; }
}
Foo a;
return a.x; // 12

If a type has members with default constructor, but the type itself doesn't have a user-defined default constructor, the compiler will create a default constructor that will be used until a user-defined default constructor is created.
Moreover, class member default constructors will be called at the beginning of a user-defined constructor.

class Foo
{
    int x;
    void Foo(){ x = 12; }
}
class Bar
{
    int y;
    Foo z;
    void Bar(){ y = 20; } // default Foo constructor is called automatically
}
Bar a;
return a.z.x * a.y; // 240

Class constructor can be called as a global function. In this case, class constructor return type is ignored and constructor call results in a new temporary object on stack:

class Foo
{
    int x;
    void Foo(int x){ this.x = x; }
}
Foo a = Foo(5);
return a.x; // 5

4.1.4 Class forward declaration

It is possible to define a type name without defining internal class structure. Such definitions are called forward declarations.
A forward-declared type is limited in how it is used: only references to it or arrays with implicit size can be defined until the full class definition.

class List;
class Node
{
    int value;
    Node ref next;
    List ref parent;
}
class List
{
    Node ref first;
}

It is not possible to create a forward declaration for a generic class.


4.2 Variables

Simple variable definition syntax is:

type name;

Arrays are defined like this:

type[N] name;

or

type[] name;

If array size is specified, it must be a number known at compile type.
Second declaration is described in section Arrays with implicit size.

Pointers are defined by using "ref" keyword:

type ref name;

Pointers to functions are defined by using "ref" keyword, followed by a list of function argument types in parenthesis:

type ref(type, type) name;

More than one variable can be defined in one statement, by specifying variable names after a comma.
All variables defined in one statement have equal type.
Different type modificators can be mixed together to form types like, for example:

int[4][2] name; // array of 4 arrays of two integers
int ref(int, int)[4] name; // array of 4 pointers to functions, returning int

A value can be assigned to a variable in place of its definition, by writing "= value" after its name.
If array element is assigned to a type, then it will be a default value of all array elements.


4.3 Functions

Global functions, nested (local) functions, member functions and function literals are supported.

Function definition starts with a function return type, followed by name and a list of arguments in parenthesis.
List of arguments starts with a type that is followed by one or more parameter names, separated by comma. After a comma, a different type can be selected as well.
Function body must be enclosed in { } block.

type name(type a, b, type c, d){ }

Function return type can be any type expression including auto.
Function argument type can be auto only if it has default argument value.
Function default argument value is specified by writing "= value" directly after argument name:

auto function(int a = 5, int b = 5.0){ }

After first argument with a default value, the following arguments must all have a default value.

4.3.1 Variable argument list

It is possible to create a function that accepts an unspecified number of arguments (like using an ellipsis in C++).
To do so, set the type of the last argument of your function to auto ref[].
All excessive arguments will be packed into the auto ref array, so an explicit cast is needed to convert it to the type you want.

Example of sum of integers using function with variable arguments.

// A function that returns sum of all its arguments. Function expects integers.
int sum(auto ref[] args)
{
    int result = 0;
    // iterate through all arguments and sum them
    for(i in args)
        result += int(i);
    return result;
}
return sum(1, 12, 201);	// 214

Argument type can be found using typeid() function.

Example of println function.

// Import module that enables output to console.
import std.io;
// Function returns number of printed arguments
int println(auto ref[] args)
{
    // Assume we are able to print all arguments
    int printedArgs = args.size;
    // iterate through all arguments
    for(i in args)
    {
        // switch by argument type
        switch(typeid(i))
        {
        case int:
            io.out << int(i);
            break;
        case double:
            io.out << double(i);
            break;
        case char[]:
            io.out << char[](i);
            break;
        // unknown type, unable to print it, so we decrement printed argument count
        default:
            printedArgs--;
        }
    }
    // Add newline
    io.out << io.endl;
    // Return printed argument count
    return printedArgs;
}
return println(2, " ", 4, " hello ", 5.0, 3.0f);	// output "2 4 hello 5.0"; 5 is returned

4.3.2 Local functions

Functions can be defined within other functions.
Pointers to such functions remain valid after parent function ends.

4.3.3 Closures

It is possible to take pointers to local functions, assign them to function pointer variables and return them from functions.
Since locals are removed from stack when function ends, all removed locals used by local function are copied to function object and remain valid.

Example of closure creation and usage.

// Import library for text output
import std.io;

// Function creates closure that sequentially returns numbers in a specified range with wrap to minimum after reaching maximum
auto sequence_range(int min, max)
{
    // Initial position is 0
    int pos = 0;
    // This is the function that will be returned
    int generator()
    {
        return (pos++) % (max - min + 1) + min;
    }
    // Return local function
    // All locals used in function (min, max, pos) are saved to function object and remain valid (and unique) for future function calls
    return generator;
}
// Create two range generators
auto from2to10 = sequence_range(2, 10);
auto from5to6 = sequence_range(5, 6);

// Retrieve some numbers
io.out << from2to10() << " " << from5to6() << io.endl; // 2 5
io.out << from2to10() << " " << from5to6() << io.endl; // 3 6
io.out << from2to10() << " " << from5to6() << io.endl; // 4 5

return 0;

4.3.4 Member functions

Functions can be defined not only inside class definitions, but also added to a class later.
The following syntax is used to define class member function outside class definition:

type type:function(/* arguments */){ }

Type before a column must be a direct type name or an alias defined with typedef expression.
By using aliases it is possible to add member functions to array, reference and function types.
Adding member function to array types with explicit size is pointless, because such variables are converted to arrays with implicit size before function call.

4.3.5 Function Literals

Functions can be written inside expressions, this could be used to define an anonymous function as a function argument, or to return function from function:

int generateFrom(int a)
{
    return int gen(){ return a++; };
}

Function name can be omitted if function return type is set to auto:

auto a = auto(int b){ return -b; };
int b = a(5); // b is -5

4.3.6 Short inline functions

When function is written in a function call argument list, special short-hand syntax is available:

int foo(int ref(int, int) f){ return f(3, 4); }
return foo(<i, j>{ i + j; });

Short inline function literal starts with '<', immediately followed by the argument list, which is ended by '>'.
Function argument types can be omitted, and serve a different purpose than argument types in a regular function.
In short inline function literal, argument types are always inferred from the function pointer type that the called function expects, but if you do specify them, a conversion to a specified type will be performed, for example:

int foo(int ref(auto ref, auto ref) f){ return f(3, 4); }
return foo(<int i, int j>{ i < j; });

Here we have a function that calls a function pointer with two arguments of type 'auto ref', but they are implicitly converted to 'int' in our short inline function literal.

Also, note that in a short inline function literal, the last expression implicitly computes a value that is returned from function. This is used in both examples above.

4.3.7 Function overloading

Functions can be overloaded. Better matching function will be selected during overloaded function call.

4.3.8 Operator overloading

Operator overloading allows user to define operators that work with any type.
If operator overloads are placed inside a class or a function, they will be visible until the end of a class or the end of a function.

Following binary operators can be overloaded:
+ - * / % ** < <= << > >= >> == != & | ^ && || ^^ = += -= *= /= **= %= <<= >>= &= |= ^= in
For operators = += -= *= /= **= %= <<= >>= &= |= ^= and [], first argument should be a reference to a type, but this is not necessary.

Example of a binary operator overloading.

// a class for complex numbers
class complex
{
    double re, im;
}
// static constructor for complex number
complex complex(double re, im = 0.0)
{
    complex res;
    res.re = re;
    res.im = im;
    return res;
}
// basic operators
complex operator + (complex ref a, b)
{
    return complex(a.re + b.re, a.im + b.im);
}
complex operator - (complex ref a, b)
{
    return complex(a.re - b.re, a.im - b.im);
}
complex operator * (complex ref a, b)
{
    return complex(a.re * b.re - a.im * b.im, a.im * b.re + a.re * b.im);
}
complex operator / (complex ref a, b)
{
    double magn = b.re * b.re + b.im * b.im;
    return complex((a.re * b.re + a.im * b.im) / magn, (a.im * b.re - a.re * b.im) / magn);
}
// operator test
complex a = complex(4, 3), b = complex(7, 3);
auto arr = { a + b, a - b, a * b, a / b };

For operator && and || overloads, the second argument must be a type that returns a value of a desired type. Here is an example:

Example of a && operator overloading.

import std.io;
class Foo
{
    int x;
    void Foo(int x){ this.x = x; }
}
Foo get(int x)
{
    io.out << "get() called" << io.endl;
    return Foo(x);
}
bool operator &&(Foo a, Foo ref() b)
{
    return a.x && b().x;
}
return get(0) && get(1);

This is implemented so that a short-circuit behavior of these operators is preserved, if you execute this example, you will see that "get" function is called only once;

Following unary operators can be overloaded: + - ~ !

Example of a unary operator overloading.

// a class for complex numbers
class complex
{
    double re, im;
}
// static constructor for complex number
complex complex(double re, im = 0.0)
{
    complex res;
    res.re = re;
    res.im = im;
    return res;
}
// negate operator overload
complex operator - (complex ref a)
{
    return complex(-a.re, -a.im);
}
// operator test
complex a = complex(4, 5);
complex b = -a;
return b.re + b.im; // -9.0

Function call operator () and array indexing operator [] can be overloaded with arbitrary number of parameters.

To get a pointer to an overloaded operator, write an @ before the operators name.

Example of taking function pointer of an overloaded operator.

import std.algorithm;
import std.io;

// A pair of numbers
class IntPair
{
	int a, b;
}
// An external constructor
IntPair IntPair(int a, b)
{
	IntPair r;
	r.a = a;
	r.b = b;
	return r;
}
// Comparison operator
int operator ==(IntPair ref a, b){ return a.a == b.a && a.b == b.b; }
// Lexicographical "less" comparison operator
int operator <(IntPair ref a, b){ return a.a == b.a ? a.b < b.b : a.a < b.a; }
// Lexicographical "greater" comparison operator
int operator >(IntPair ref a, b){ return a.a == b.a ? a.b > b.b : a.a > b.a; }

// Create an array of objects
auto arr = { IntPair(2, 4), IntPair(5, 8), IntPair(2, 3), IntPair(3, 1), IntPair(0, 1), IntPair(9, 1) };

sort(arr, @<); // Sort from 'smallest' to 'largest'
for(i in arr)
	io.out << '(' << i.a << ", " << i.b << "); ";
io.out << io.endl;
// output: (0, 1); (2, 3); (2, 4); (3, 1); (5, 8); (9, 1);

sort(arr, @>); // Sort from 'largest' to 'smallest'
for(i in arr)
	io.out << '(' << i.a << ", " << i.b << "); ";
io.out << io.endl;
// output: (9, 1); (5, 8); (3, 1); (2, 4); (2, 3); (0, 1);

return 1;

4.3.9 Coroutines

Coroutine is a function that can return execution to its caller and when it is called again, resume execution from the point it has left.
When function is resumed, its local variable state is restored, but function arguments are equal to the ones that caller passed to it.

Coroutine acts like a simple function, until yield expression is used.

Because yield expression can accept a return value, function can virtually return multiple values during its execution.
Coroutines can be used to implement cooperative tasks, iterators, infinite lists and pipes.

To implement a coroutine, coroutine must be written before function return type.

Example of an ID generator.

// coroutine, that returns different integer IDs every time it is called
coroutine int generate_id()
{
    int ID = 0x23efdd67; // starting ID
    // 'infinite' loop will return IDs
    while(1)
    {
        yield ID; // return ID
        ID++; // move to the next ID
    }
}
int a = generate_id(); // 0x23efdd67
int b = generate_id(); // 0x23efdd68

Example of a random number generator.

// coroutine, that returns pseudo-random numbers in a range (0..32767)
coroutine int rand()
{
    // starting seed
    int current = 1;
    // 'infinite' loop will return pseudo-random numbers
    while(1)
    {
        current = current * 1103515245 + 12345;
        yield (current >> 16) & 32767;
    }
}
// Generate an array of eight pseudo-random numbers 
int[8] array;
for(i in array)
    i = rand();

It is possible to return coroutines as closures, to create generators with extra context.

Example of a forward iterator over vector contents.

import std.vector;
// Function will return forward iterator over vectors' values
auto forward_iterator(vector ref x)
{
    // Every time the coroutine is called, it will return vector element and advance to the next
    coroutine auto ref iterate()
    {
        // Loop over all elements
        for(int i = 0; i < x.size(); i++)
            yield x[i]; // and return them one after the other
        // return statement can still be used in a coroutine.
        return nullptr; // return null pointer to mark the end
    }
    // return iterator function
    return iterate;
}
// create vector and add some numbers to it
vector a = vector(int);
a.push_back(4);
a.push_back(5);
a.push_back(40);

// Create iterator
auto it = forward_iterator(a);

// Find sum of all vector elements
int sum = 0;
auto ref x; // variable to hold the pointer to current element
while(x = it()) // iterate through all elements
    sum += int(x); // and add them together

return sum; // 49

Using closures, lets rewrite pseudo-random number generator example to support multiple random generators with different seed in parallel.

Example of parallel random number generators.

// Function will return pseudo-random number generator with selected starting seed
auto get_rng(int seed)
{
    // coroutine, that returns pseudo-random numbers in a range (0..32767)
    coroutine int rand()
    {
        // starting seed
        int current = seed;
        // 'infinite' loop will return pseudo-random numbers
        while(1)
        {
            current = current * 1103515245 + 12345;
            yield (current >> 16) & 32767;
        }
    }
    return rand;
}
// Create two pseudo-random number generators
auto rngA = get_rng(1);
auto rngB = get_rng(0xdeaf);

// Generate an array of eight pseudo-random numbers 
int[8] array;
for(int i = 0; i < array.size / 2; i++) // one half - using first RNG
    array[i] = rngA();
for(int i = array.size / 2; i < array.size; i++) // second half - using second RNG
    array[i] = rngB();

4.3.10 Generic functions

Sometimes there is a need for a function that can work for a large set or different types and not be restricted to run-time operations supported with "auto ref" type.
These functions are called generic.
To create a generic function, just use generic keyword as the argument type.
Here is an example of a "map" function, which transforms all array elements by applying a function to them.

import std.vector;

void map(generic array, generic function)
{
	for(i in array) // for every element in an array
		i = function(i); // transform it using the function
}
auto arr1 = { 1, 2, 3, 4 };
auto arr2 = { 1.0, 2.0, 3.0, 4.0 };
map(arr1, auto(int x){ return -x; });
map(arr2, auto(double x){ return -x; });

return 0;

If you want to force an argument to be a reference type, you can use generic ref type. For example, you may write a generic swap function:

void swap(generic ref a, generic ref b)
{
	typeof(b).target tmp = *a;
	*a = *b;
	*b = tmp;
}
int a = 0, b = 0;
swap(a, 8); // a == 8; b == 0;
swap(4, b); // a == 8; b == 4;
swap(a, b); // a == 4; b == 8;
return a - b; // -4

If you wish to reference a generic argument type later, you can use a generic type alias in a form of @ followed by alias name:

// function that creates an array and fills it with a specified value
auto create(@T x, int y)
{
    auto array = new T[x];
    for(i in array)
        i = x;
    return array;
}
auto a = create(4, 16);

4.3.11 Generic function specialization

It is possible to specialize generic functions for a specific group of types they are used on, for example, to create a generic function that accepts any instance of a generic class of a function that accepts a specified amount of arguments or return a specified type.
To perform generic function specialization, just replace generic type argument, function type argument or function return type with either generic type, generic ref type or a generic type alias in a form of @ followed by alias name.

Let's take for example a generic complex class:

class complex<T>
{
    T re, im
    // constructor
    void complex(T a, b){ re = a; im = b; }
}

Now, we add an overload for unary '-' operator that is called only for our complex number class:

// overloaded negation operator, specialized for complex number class
auto operator-(complex<generic> ref c)
{
    return complex<typeof(c.re)>(-c.re, -c.im);
}
// create a test number
auto a = complex<float>(2, 3);
// negate it
auto b = -a;
// result is -5.0
return b.re + b.im;

The code can be improved by using a generic type alias:

auto operator-(complex<@T> ref c)
{
    return complex<T>(-c.re, -c.im);
}

Generic type aliases follow an important rule: equal type aliases in a generic function argument list must resolve to equal type at the call site.


4.4 Arrays with implicit size

Array with implicit type is defined like this: type[] name;

Any array of the same type can be assigned to an array with implicit size.

int[7] arr1;
int[4] arr2;
int[] arr1a = arr1;
arr1a = arr2;

int[7][3] arr1;
int[4][3] arr2;
int[][3] arr1a = arr1;
arr1a = arr2;

type[] consists of a pointer to an array and a size field. To get array size, use its "size" field: int count = arr1a.size;


4.5 Enumeration

Enumeration type or enum is a class that groups a list of named constant numbers.
Enumeration is defined by writing enum, followed by enumeration name and a list of comma-separated constants in a {}:

enum Foo{ A = 5, B = 8, C = 15 }
Foo x = Foo.B;

Constant value is of type int.
Constant value can be skipped, in which case its value will be determined by incrementing previous constant value:

// define Foo.A to be 4, Foo.B to be 5 and so on:
enum Foo{ A = 4, B, C, D }

Moreover, if the value of first constant is skipped, its value will be 0:

// define Foo.A to be 0, Foo.B to be 1 and so on:
enum Foo{ A, B, C, D }

Compiler will generate the following functions to enable conversion to and from int:

int int(enum_type x);
enum_type enum_type(int x);

4.6 Namespaces

A list of declarations and expressions can be enclosed in a namespace.
To access something from outside a namespace, a namespace name followed by '.' must be written before an identifier name:

namespace Space
{
    class Pair
    {
        int x, y;
    }
    Pair x;
    x.x = 5;
}
Space.Pair y;
y.x = 10;
return y.x + Space.x.x; // 15

Namespace can be defined at the global scope or immediately inside another namespace. A short-hand syntax for declaring nested namespaces exists.
Following example defines two variables inside the same namespace:

namespace A
{
    namespace B
    {
        int x = 5;
    }
}
namespace A.B
{
    int y = 10;
}
return A.B.x + A.B.y; // 15

Identifier search goes as follows: first, the name is searched starting at current namespace; then, the name is searched starting at parent namespace and so on, until global namespace is reached:

namespace A
{
    namespace B
    {
        int x = 5;
    }
    namespace C
    {
        namespace D
        {
            // Name search order:
            // A.C.D.B.x - not found
            // A.C.B.x - not found
            // A.B.x - found
            int y = B.x;
        }
    }
}

5. Special types

5.1 auto ref

auto ref type is a pointer with implicit type.
Pointer of any type can be assigned to auto ref.
To retrieve target type, you can use typeid() function.
When type conversions are made, type checking is performed at run time.

Example of various assignments to auto ref type:

float y = 8;
auto ref z;
z = &x; // z points to int x, typeid(z) == int
z = &y; // now z points to float y, typeid(z) == float

Every type has a default constructor from auto ref type, which can be overridden with a custom one.
Default reference type constructor makes a successful conversion if auto ref target type is equal to reference target type.
Default constructor of other types makes a successful conversion if type is equal to auto ref target type.

Example of type constructor from auto ref type.

int x = 4;
auto ref z = &x;
return int(z); // returns 4

Example of reference type constructor from auto ref type.

int negate(int ref x){ return -(*x); }
int x = 4;
auto ref y = &x;
return negate(int ref(y)); // returns -4

Example of custom type constructor from auto ref type.

// Define custom constructor from auto ref for float type
float float(auto ref x)
{
    // If typeid is int, use default int constructor 
    if(typeid(x) == int)
        return int(x);
    // Otherwise, fallback to default behavior
    float ref f = x;
    return *f;
}

int x = 4;
auto ref ptr = &x;

// Use our custom constructor
float e = float(ptr);
float y = 8;
ptr = &y;
// Test default behavior in our custom constructor
float f = float(ptr);
return e + f; // returns 12.0

Value to which auto ref points to can be changed by either converting it to pointer of known type and working with it, or by using dereference operator *.
It latter case, right-hand value type must be equal to auto ref target type and modification-assignment operators cannot be used.

Example of value assignment to auto ref type.

int x = 4;
auto ref z = &x;
*z = 8;
return int(z); // returns 8

When auto ref is used as a function argument, not only pointers, but also value types can be passed through that argument.

Example of extra implicit conversions when auto ref is a function argument type.

int sum(auto ref a, b)
{
    return int(a) + int(b);
}
int x = 5;
return sum(x, 12); // returns 17

When new variable is defined, its value can be taken from auto ref type if auto ref target type matches new variable type.

Example of conversion between type and auto ref.

int x = 5;
auto ref ptr = &x;
int z = ptr;
return x * z;

5.1.1 function call through auto ref

It is possible to make a member function call of an object auto ref points to.
When member function call is made, all member functions with selected name are taken from all classes and the one that fits best is selected.
In runtime, function with the same arguments as the selected will be required from auto ref target type to make a call.

Example of member function call through auto ref.

import std.io;
class A
{
    void run(int x)
    {
        io.out << "A int " << x << io.endl;
    }
}
class B
{
    void run(int x)
    {
        io.out << "B int " << x << io.endl;
    }
}
A a;
B b;
auto ref[2] arr;
arr[0] = &a;
arr[1] = &b;
/* Outputs:
A int 5
B int 5
*/
for(i in arr)
    i.run(5);
return 0;

Because target type is known only at runtime, there are some limitations:

  • Default argument values are unavailable.
  • Function overload is selected at compilation time and implicit type conversions are not made at runtime.

If we change B::run function in the example above to void run(double x){ io.out << "B double " << x << io.endl; } , then we will get a following error at runtime:
ERROR: type 'B' doesn't implement method 'B::run' of type 'void ref(int)'


5.2 auto[]

auto[] type is an array with implicit type.

Following explicit conversions are supported (all checks are done in run-time):

type[] = auto[]; // auto[] element type must be equal to type
type[N] = auto[]; // auto[] element type and array size must be equal
auto[] = type[];
auto[] = type[N];

auto[] array indexing operator[] returns auto ref type with a pointer to selected element.


5.3 typeid

typeid type is a type identifier that can be used to get run-time type information (RTTI).
Value with type of typeid is generated if type name or typeof is used in expression.
typeid constructor from auto ref type returns the type that auto ref points to. Implicit conversions to auto ref type still stand.

typeid x = typeof(4); // typeid of expression is returned at compilation time
typeid y = typeid(4); // typeid of expression is returned at run time
typeid z = int; // type name used as expression
return x == z && y == z; // 1

Only comparison of typeid is available by default. To get extended run-time type information, import std.typeinfo module.


6. Miscellaneous

6.1 Characters

Characters are enclosed in single quotes ''.
No more than one character can be inside quotes (after processing escape sequences).


6.2 Strings

Strings are enclosed in double quotes "".
Type of string is array of (length + 1) character.
Strings are 0-terminated.

6.2.1 Unescaped strings

If a character @ is written before string, the contents of that string will not be escaped.
@"\w\\w" is equal to "\w\\\w".
It is not possible to use a double quote inside an unescaped string literal.


6.3 Inline arrays

Arrays can be defined by writing all array elements, separated by commas in a { } block.
Multidimensional array can be also defined by using this.

It is possible to create multidimensional array, where arrays have different sizes:

auto arr1 = { { 2, 3 }, { 4 }, { 7, 8, 9, 10 } };
auto arr2 = { "Mark", "Frank", "Katherine" };

6.3.1 List comprehension

List comprehension is a way to generate list with an arbitrary number of elements using an expression with a custom rules.
For example, one may generate an array of 32 squared numbers by writing:

int[] sq = { for(i in range(1, 32)) yield i*i; };

A list comprehension is an inline array that starts with a for expression.
Every value that is yield in a list comprehension is added to the array.
Explicit return expression will stop array element generation and the value returned is ignored.


6.4 Escape sequences

Escape sequences can be used inside character and string expressions.
Following escape sequences are supported:

Sequence Meaning
\n CR (Carriage return)
\r LF (Line feed)
\t Tab
\0 0
\' single quote - '
\" double quote - "
\\ backslash - \

6.5 Binary numbers

Numbers can be written in binary, and must be followed by letter 'b':
101b == 5
If int type is insufficient to represent the binary constant as a positive number, then the type is long, otherwise it's int.
If long type is insufficient to represent the binary constant as a positive number, and then if int type can represent the binary constant as a negative number, the type is int, otherwise it's long.


6.6 Octal numbers

Numbers can be written in base 8, and must start with number 0:
077 == 63
If int type is insufficient to represent the octal constant as a positive number, then the type is long, otherwise it's int.
If long type is insufficient to represent the octal constant as a positive number, and then if int type can represent the octal constant as a negative number, the type is int, otherwise it's long.


6.7 Hexadecimal numbers

Numbers can be written in base 16, and must start with '0x':
0x80 == 128
If int type is insufficient to represent the hexadecimal constant as a positive number, then the type is long, otherwise it's int.
If long type is insufficient to represent the hexadecimal constant as a positive number, and then if int type can represent the hexadecimal constant as a negative number, the type is int, otherwise it's long.


6.8 Null pointer

nullptr acts as a constant of null pointer.
nullptr can be assigned to pointers, arrays with implicit size or functions pointers.
nullptr can be strictly compared (== or !=) with pointers, arrays with implicit size and functions pointers.


7. Standard library

std.typeinfo

Global functions.

int isFunction(typeid type);
int isFunction(auto ref type);

These functions return 1 if typeid or auto ref target type is a function type.
Otherwise, the return value is 0.

int isClass(typeid type);
int isClass(auto ref type);

These functions return 1 if typeid or auto ref target type is a class.
Otherwise, the return value is 0.
For typeid, auto ref, auto[] types the return value is also 1.

int isSimple(typeid type);
int isSimple(auto ref type);

These functions return 1 if typeid or auto ref target type is a simple type.
Otherwise, the return value is 0.
Simple types are: void, char, short, int, long, float and double.

int isArray(typeid type);
int isArray(auto ref type);

These functions return 1 if typeid or auto ref target type is an array type.
Otherwise, the return value is 0.
For auto[] the return value is 0.

int isPointer(typeid type);
int isPointer(auto ref type);

These functions return 1 if typeid or auto ref target type is a pointer type.
Otherwise, the return value is 0.
For auto ref the return value is 0.
typeid member functions.

int typeid.size();

Function returns type size.

char[] typeid.name();

Function returns type name.
Functions to use on class types.

int typeid:memberCount();

Function returns class member count. If type is not a class, runtime error occurs.

typeid typeid:memberType(int member);

Function returns selected class member type. If type is not a class, runtime error occurs.

char[] typeid:memberName(int member);

Function returns selected class member name. If type is not a class, runtime error occurs.
Functions to use on array and pointer types.

typeid typeid:subType();

Function returns typeid of array element type or pointer target type. If type is not an array or pointer, runtime error occurs.
Functions to use on array types.

int typeid:arraySize();

Function returns array size. If type is not an array, runtime error occurs.
If type is an array with implicit size, the return value is -1.
Functions to use on function types.

typeid typeid:returnType();

Function returns typeid of function type return type. If type is not a function type, runtime error occurs.

int typeid:argumentCount();

Function returns function type argument count. If type is not a function type, runtime error occurs.

typeid typeid:argumentType(int argument);

Function returns function type argument type for selected argument. If type is not a function type, runtime error occurs.

class member_info
{
    typeid type;
    char[] name;
}

This class contains information that is returned for every class member during iteration over class members.

member_iterator typeid:members();

Function returns iterator over class members to be used in for each expression.
If type is not a class, runtime error occurs

member_iterator ref member_iterator:start();
int member_iterator:hasnext();
member_info member_iterator:next();

These functions implement iteration process.
Iterator return value is the member_info class.

argument_iterator typeid:arguments();

Function returns iterator over function type arguments to be used in for each expression.
If type is not a function type, runtime error occurs

argument_iterator ref argument_iterator:start();
int argument_iterator:hasnext();
typeid argument_iterator:next();

These functions implement iteration process. Iterator return value is the typeid class.

auto ref	typeGetMember(auto ref obj, int member);
auto ref	typeGetMember(auto ref obj, char[] name);

These functions return pointer to a member of the selected variable. First function accepts member position and the second accepts member name.


std.dynamic

void override(auto ref a, b);

Function accepts two functions as its arguments and replaces code of the first function with the code of the second function.
If passed objects are not functions, a runtime error occurs.
If function types are different, a runtime error occurs.
Show example of override function usage.

Show example of override function usage with inline function definition.

void override(auto ref function, char[] code);

Function accepts a function and a new source code for the body of that function as a string.
Function compiles the code that replaces code of the destination function. Because code contains only function body, to access function arguments, use name "argN", where N is the argument number, counting from 0.
If first argument is not a function, a runtime error occurs.
If code compilation fails, a runtime error occurs.
Show example of override function usage with inline function definition.

void eval(char[] code);

Function compiles the code and executes it.
If code compilation fails, a runtime error occurs.
Show example of eval function usage.


std.gc

void NamespaceGC:CollectMemory();

Function performs on-demand garbage collection.

int NamespaceGC:UsedMemory();

Function returns the size of memory currently in use, including memory in use by garbage that is not collected.

double NamespaceGC:MarkTime();

Function returns overall time (in seconds) that GC spent to traverse through all objects and mark the used ones.

double NamespaceGC:CollectTime();

Function returns overall time (in seconds) that GC spent to free memory used up by garbage.
Module contains a global NamespaceStd class instance through which you can call its functions, e.g. GC.CollectMemory().


std.vector

std.vector is a generic class that implements a dynamic array holding elements of the specified type T.

void vector:vector();
void vector:vector(int reserved);

Vector provides a default constructor and a constructor for a vector with a number of reserved elements.

void vector:push_back(T val);

Function appends an element to the end of the vector.

void vector:pop_back();

Function removes last element from vector.
An error is raised if vector is empty.

auto vector:back();

Function returns the pointer to last element of the vector.
An error is raised if vector is empty.

auto vector:front();

Function returns the pointer to first element of the vector.
An error is raised if vector is empty.

auto operator[](vector<generic> ref v, int index);

Operator returns element at selected index (counting from 0).
An error is raised if vector doesn't contain an element at selected index.

auto vector:size();

Function returns element count inside the vector.

auto vector:capacity();

Function returns how many elements can be placed inside a vector before internal buffer will be reallocated.

void vector:reserve(int size);

Function reserves space for a specified amount of elements in an internal buffer.

void vector:resize(int size);

Function changes the size of the vector to a specified amount of elements.

void vector:clear();

Function sets the size of the vector to zero, but doesn't free memory used by internal buffer.

void vector:destroy();

Function sets the size of the vector to zero and frees memory used up by the internal buffer.

auto vector:start();
auto vector_iterator:next();
int vector_iterator:hasnext();

Vector proved functions for iteration ever elements in a for each expression.

auto operator[](vector<@T> ref a, int start, end);
auto operator[](vector_splice<@T> ref a, int index);

You can index a vector with two indexes to create a vector splice: a range of elements that point to vector data.
Second operator allow indexing of the created splice.

std.vector provides following aggregation functions:

auto vector:sum(generic ref(T) f);

This function computes vector element sum by adding numbers returned by a user-specified function. User-specified function is called for every element.

auto vector:average(generic ref(T) f);

This function computes the average vector elements value by adding numbers returned by a user-specified function and dividing the sum by element count. User-specified function is called for every element.

auto vector:min_element();

This function computes the minimal element of a vector. operator < is required that will take two vector elements for comparison.

auto vector:max_element();

This function computes the maximum element of a vector. operator > is required that will take two vector elements for comparison.

auto vector:min_element(generic ref(T) f);

This function computes the minimum of the values returned by a user-specified function. User-specified function is called for every element.

auto vector:max_element(generic ref(T) f);

This function computes the maximum of the values returned by a user-specified function. User-specified function is called for every element.

auto vector:count_if(generic ref(T) f);

This function returns the element count that satisfies a user-specified function.

auto vector:all(generic ref(T) f);

This function returns 1 if all of the elements satisfy a user-specified function.

auto vector:any(generic ref(T) f);

This function returns 1 if any of the elements satisfy a user-specified function.

auto vector:sort(generic ref(T, T) pred);

This function sorts the vector with a user-specified function that compares two elements.
In order to use this function, you must import std.algorithm module along with std.vector

bool operator in(generic x, vector<generic> ref arr);

This operator returns true if a specified element is a part of vector.
operator == that can compare the two elements is required.


std.list

std.list is a generic class that implements a list of double-linked elements of the specified type T.
Some of the std.list functions described below return list_node structure instead of the value. Description of list_node can be found after the std.list function list.

void list:list();

This is std.list default constructor.

void list:push_back(T elem);

Function appends an element to the end of the list.

void list:push_front(T elem);

Function prepends an element before the beginning of the list.

void list:pop_back();

Function removes the last element from list. An error is raised if list is empty.

void list:pop_front();

Function removes the first element from list. An error is raised if list is empty.

void list:insert(list_node<T> ref it, T elem);

Function inserts an element after the specified list node.
An error is raised if the specified list node is not contained within the list.

void list:erase(list_node<T> ref it);

Function erases the specified list node.
An error is raised if the specified list node is not contained within the list.

void list:clear();

Function removes all elements from the list.

auto list:back();

Function returns the value of the last list element.
An error is raised if list is empty.

auto list:front();

Function returns the value of the first list element.
An error is raised if list is empty.

auto list:begin();

Function returns first list node.

auto list:end();

Function returns last list node.

int list:empty();

Function returns 1 is list doesn't contain any elements, otherwise the return value is 0.

auto list_iterator:list_iterator(list_node<T> ref start);
auto list_iterator:start();
auto list_iterator:next();
auto list_iterator:hasnext();
auto list:start();

These helper functions enable iteration over list elements in for each expression.
Iteration can also be started from a specified list node.

auto list:sum(generic ref(T) f);

This function computes list element sum by adding numbers returned by a user-specified function. User-specified function is called for every element.

auto list:average(generic ref(T) f);

This function computes the average list elements value by adding numbers returned by a user-specified function and dividing the sum by element count. User-specified function is called for every element.

auto list:min_element();

This function computes the minimal element of a list. operator < is required that will take two vector elements for comparison.

auto list:max_element();

This function computes the maximum element of a list. operator > is required that will take two vector elements for comparison.

auto list:min_element(generic ref(T) f);

This function computes the minimum of the values returned by a user-specified function. User-specified function is called for every element.

auto list:max_element(generic ref(T) f);

This function computes the maximum of the values returned by a user-specified function. User-specified function is called for every element.

auto list:count_if(generic ref(T) f);

This function returns the element count that satisfies a user-specified function.

auto list:all(generic ref(T) f);

This function returns 1 if all of the elements satisfy a user-specified function.

auto list:any(generic ref(T) f);

This function returns 1 if any of the elements satisfy a user-specified function.

bool operator in(generic x, list<generic> ref arr);

This operator returns true if a specified element is a part of list.
operator == that can compare the two elements is required.
Some functions return a list_node class. This is a class that has references to previous and next node, element value and parent list.
An accessors named prev, next and elem are provided to let user get pointer to previous or next node and to retrieve the node value.
You can use prev and next to iterate through list nodes.


std.hashmap

std.hashmap is a dictionary of key-value elements.
An int hash_value(Key_Type key); function is required to use a type as a key if the default constructor is used.
A hash_value function for char[] type is provided by default.

void hashmap:hashmap();
void hashmap:hashmap(int ref(Key) compute_hash);

A defualt constructor and a constructor that takes a function that computes the hash value are provided.

void hashmap:clear();

Function removes all hashmap elements.

auto operator[](hashmap<@K, @V> ref m, typeof(m).target.Key key);

Operator allows to retrieve or assing a value to an element with the specified key.
If an element doesn't exist, it is created.

void hashmap:remove(Key key);

Function removes an element with the specified key from hashmap.

auto hashmap:find(Key key);

Function allows to find an element using the key in the hashmap.
nullptr is returned if the element doesn't exist.


std.range

range_iterator range(int min, max, step = 1);

Function return range iterator to be used in a for each expression.
It will generate number starting from min, incrementing it by a selected step (1 by default) while it's less than max.
For example, range(1, 5) iterator will generate numbers {1, 2, 3, 4, 5}.


std.file

File File();
File File(char[] name, char[] access);

Two static constructors are available to create a file instance or to create a file instance with specified file opened with a specified mode.
Modes are equal to the modes of fopen function in C.

void File:Open(char[] name, char[] access);

Function openes the file with a specified mode.
Modes are equal to the modes of fopen function in C.

void File:Close();

Function closes the file.

int File:Opened();

Function returns 1 if file is opened and 0 otherwise.

enum Seek
{
    SET,
    CUR,
    END
}
void File:Seek(Seek origin, int shift = 0);

Function allows to seek in the file with one of the seek options specified by enumeration value.

long File:Tell();

Function returns cursor position in the file.

long File:Size();

Function returns the size of the file.

void File:Write(char data);
void File:Write(short data);
void File:Write(int data);
void File:Write(long data);
void File:Write(float data);
void File:Write(double data);

These functions write the argument values into the file.

void File:Read(char ref data);
void File:Read(short ref data);
void File:Read(int ref data);
void File:Read(long ref data);
void File:Read(float ref data);
void File:Read(double ref data);

These function read values from file into the arguments.

void File:Read(char[] arr);

Function reads arr.size bytes from file into the specified array.

void File:Write(char[] arr);

Function writes arr.size bytes from specified array into the file.

void File:Print(char[] arr);

Function writes data from array until 0-terminator.

void File:Read(char[] arr, int offset, int bytes);

Function reads specified amount of bytes from file into the array with the specified offset from the start.
Array must be able to hold the specified amount of bytes from the specified offset.

void File:Write(char[] arr, int offset, int bytes);

Function writes specified amount of bytes from array with the specified offset from the start into the file.
Array must have the specified amount of bytes from the specified offset.


std.io

void Print(char[] text);
void Print(int num, int base = 10);
void Print(double num);
void Print(long num, int base = 10);
void Print(char ch);

These functions print a string, an integer in a specified base, a double, a long in a specified base and a single character to the console window.

int Input(char[] buf);

Function reads a user string into an array.

void Input(int ref num);

Function reads a number from user string into the int.

void Write(char[] buf);

Function outputs the whole buffer without stopping at 0-bytes.

void SetConsoleCursorPos(int x, y);

This function sets the console cursor position to the specified location. (Windows-only)
void GetKeyboardState(char[] state);
This function writes the keyboard and mouse key state into an array. (Windows-only)
It works just like GetKeyboardState function in WinAPI.

void GetMouseState(int ref x, int ref y);

Function writes mouse position into the passed arguments. (Windows-only)
It works just like GetCursorPos function in WinAPI.

bool IsPressed(VK key);
bool IsPressed(char key);

Function returns true if a key specified by its virtual key code (VK enumeration values: for example, VK.F12) or by a character code (only numbers from 0-9 and upper case characters) is pressed. (Windows-only)

bool IsToggled(VK key);
bool IsToggled(char key);

Function returns true if a key specified by its virtual key code (VK enumeration values: for example, VK.F12) or by a character code (only numbers from 0-9 and upper case characters) is toggled. (Windows-only)

StdOut operator <<(StdOut out, char[] ref(StdNonTerminatedTag) wrapper);
StdOut operator <<(StdOut out, char[] str);
StdOut operator <<(StdOut out, const_string str);
StdOut operator <<(StdOut out, StdEndline str);
StdOut operator <<(StdOut out, bool num);
StdOut operator <<(StdOut out, char ch);
StdOut operator <<(StdOut out, short num);
StdOut operator <<(StdOut out, int num);
StdOut operator <<(StdOut out, float num);
StdOut operator <<(StdOut out, double num);
StdOut operator <<(StdOut out, long num);

This set of operators exists to make stream output similar to std::cout in C++.
You have a global StdOut instance to use in output operations: io.out.
To output a number or string, write:

io.out << "The number is " << 4 << io.endl;

To print a newline, use io.endl as in an example above.
To change number base, output one of the following modificators: io.bin, io.oct, io.dec, io.hex of io.base(base).
To print a character array that doesn't end with a 0-terminator, output io.non_terminated(array).


std.random

void srand(int seed);

Sets a random starting point.

int rand();

Generates a pseudorandom number (in a range from 0 to 32767).

int rand(int max);

Generates a pseudorandom number with a specified maximum (it must be less than 32767).


std.time

int clock();

Function returns the elapsed wall-clock time since the start of the process (in milliseconds).

8. Appendix

8.1 Rules applied to value types in a binary operation

Rule A:
Operations are made on three types: double, long and int.
Because of this, different types are converted to these three types:

  • char -> int
  • short -> int
  • float -> double

Rule B:
If a binary operation is done on two different types, arguments are converted in a following fashion:

  • int * double -> double * double
  • long * double -> double * double
  • int * long -> long * long This is done after applying "Rule A"

Rule C:
In variable modification expression (+=, -=, *=, /=, **=, %=, <<=, >>=, &=, |=, ^=) binary operation is performed as described in "Rule B",
And result is converted to type of l-value.

a *= 2.5; // a equals 5

8.2 Operator priority

N Evaluation Operation
1 left-to-right * (unary)
2 left-to-right . []
3 left-to-right & (unary) + (unary) - (unary) ~ ! ++ (prefix) -- (prefix)
4 left-to-right ++ (postfix) -- (postfix)
5 left-to-right **
6 left-to-right / * %
7 left-to-right + -
8 left-to-right << >>
9 left-to-right < <= > >=
10 left-to-right == !=
11 left-to-right &
12 left-to-right ^
13 left-to-right
14 left-to-right &&
15 left-to-right ^^
16 left-to-right
17 left-to-right ?:
18 right-to-left = += -= *= /= **= %= <<= >>= &=

8.3 Implicit conversions

Apart from implicit conversions from one basic type to the other, there are following implicit conversions between complex types:

type[N] ref -> type[] ref
type -> auto ref // only in function argument list
type ref -> auto ref
auto ref -> type ref // except for function argument list
type[N] -> auto[] // only in function argument list
type[] -> auto[] // only in function argument list
⚠️ **GitHub.com Fallback** ⚠️