A Tour of Morfa

Overloading bracket operators

Indexing

We may use array-like indexing also for non-array types by overloading the function $[]. Consider the following simple implementation of a dictionary with text keys and integer values:

class TextIntMap
{
    import morfa.util.Slice;

    struct KVPair
    {
        var key: text;
        var value: int;
    }

    var defaultValue = 0;
    var pairs: KVPair[];

    func new()
    {
        pairs = new KVPair[0];
    }

    func set(key: text, val: int): void
    {
        pairs ~= KVPair(key, val);
    }

    func get(key: text): int
    {
        for (p in pairs[reverse])
            if (p.key == key)
                return p.value;

        return defaultValue;
    }
}

It would be nice to write map["one"] instead of map.get("one"). This can be achieved easily as follows:

func $[] (map: TextIntMap, key: text): int
{
    return map.get(key);
}

unittest
{
    var map = new TextIntMap;
    map.set("one", 1);
    assert (map["one"] == 1);
}

It would be even nicer if we could simply write map["one"] = 1. For this we have to overload the function $[]=:

func $[]= (map: TextIntMap, value: int, key: text): void
{
    map.set(key, value);
}

Note the order of arguments to $[]=: the value goes before the key. This is because we may want $[]= to accept a variable number of key arguments, and the variadic argument has to be the last one on the argument list.

func $[]= (map: TextIntMap, value: int, keys: text[]...): void
{
    // special case: 'map[] = newDefaultValue'
    if (keys.length == 0)
        map.defaultValue = value;

    // set all keys to value
    for (key in keys)
        map.set(key, value);
}

import morfa.util.Range;
unittest
{
    var map = new TextIntMap;
    assert (map["zero"] == 0);

    map[] = -1; // set new default value
    assert (map["zero"] == -1);

    map["one", "two", "three"] = 1;
    assert (map["one"] == 1);
    assert (map["two"] == 1);

    map["two"] = 2;
    map["three"] = 3;
    map["four"] = 4;

    var keys = ["one", "two", "three", "four"];
    var vals = [1,2,3,4];

    for (i in 0..4)
        assert (map[keys[i]] == vals[i]);
}

Three pairs of indexing and assignment brackets are defined by default: (), [] and {}. To define a new pair (chosen from a subset of Unicode bracket characters) use the usual operator syntax:

operator 〔〕{ kind = indexing, precedence = call }

Then overload as usual.

To get you started using new bracket symbols quickly, there are two pairs of characters which are allowed and look nice: ⁅⁆ and 〔〕

Note that the precedence call is fixed for indexing operators.

Surrounding

Surrounding bracket operators allow you to use syntax similar to the array literal in Morfa: [5, 6, 7].

No built-in surrounding bracket operators are defined, also it is illegal to define (), [] or {} as one, since this would conflict with their standard use.

To overload a surrounding bracket operator first you have to declare it using an allowed Unicode bracket character:

operator 〔〕{ kind = surrounding, precedence = max }

The precedence max is fixed for surrounding operators.

Then to overload the 〔〕 operator you may choose one of two flavours which allign with indexing and assignment brackets above, first:

func 〔〕(i: int, j: int): int
{
    return i + j;
}

unittest
{
    assert(〔2,3〕 == 5);
}

and second the assignment version:

operator 〔〕={ kind = surrounding, precedence = max }

func 〔〕=(rhs: text, ref t: text, ref l: int): void
{
    t = rhs;
    l = rhs.length;
}

unittest
{
    var textVariable = "";
    var itsLength = 0;
    〔textVariable, itsLength〕 = "There!";

    assert(textVariable == "There!");
    assert(itsLength == 6);
}