1. Set operators

In this chapter are presented the operators related to set object. The operators are : access, contains and iteration operators.

1.1. Access operator

The operator of index [] is overloadable by the method opIndex. This method is called with the parameters passed inside of the brackets of the index operator. For example, the following operation a [b, c, d] is rewritten into a.opIndex (b, c, d).

import std::io

class A {
    let dmut i : [i32];

    pub self (a : [i32]) with i = copy a {}

    pub def opIndex (self, x : i32) -> i32 
        throws &OutOfArray
    {
        self.i [x]
    }

    impl Streamable;

}

def main () 
    throws &OutOfArray, &AssertError
{
    let i = A::new ([1, 2, 3]);
    assert (i [2] == 3);
}


One can note from the above example, that the object stored in i is immutable, and that the opIndex is always a right operand. It is possible to modify the values inside the i object (if it is mutable) using the opIndexAssign method. This method rewritte the assignement operation where the left operand is an access operation. The following example presents an example of usage of this method.

import std::io

class A {
    let dmut i : [i32];

    pub self (a : [i32]) with i = copy a {}

    pub def opIndex (self, x : i32) -> i32 
        throws &OutOfArray
    {
        self.i [x]
    }

    pub def opIndexAssign (mut self, x : i32, z : i32)
        throws &OutOfArray
    {
        self.i [x] = z;
    }

    impl Streamable;

}

def main () 
    throws &OutOfArray, &AssertError
{
    let dmut i = A::new ([1, 2, 3]);

    (alias i) [2] = 9; // alias is important, otherwise the method is not callable
    println (i);

    assert (i [2] == 9);
}


Results:

main::A([1, 2, 9])

1.2. Contains operator

The contain operator is a binary operator used to check if an element is inside another one. This operator is defined using the keyword in. Unlike other operator, the rewritte for the overloading is made only once on the right operand, and call the method opContains, for example the expression a in b is rewritten into b.opContains (a). The expression a !in b will be rewritten into !(a in b).

class A {

    let _a : [i32];

    pub self (a : [i32]) with _a = a {}

    pub def opContains (self, i : i32)-> bool {
    for j in self._a {
        if (i == j) return true;
    }
    false
    }    
}

def main () 
    throws &AssertError
{
    let i = A::new ([1, 2, 3]);
    assert (2 in i);
    assert (9 !in i);
}

1.3. Iteration operator

Object can be iterated using a for loop. As for any operators, the for loop used on object is rewritten at compilation time. There are multiple methods to write when writting an iterable class. The following source code present an example of a for loop, and the source code underneath it present its rewritten equivalent.

let a = A::new ();
for i, j in a {
    println (i, " ", j);
}


let a = A::new ();
{
    let dmut iter = a.begin ();
    while !iter.opEquals (a.end ()) {
        let i = iter.get!{0} ();
        let j = iter.get!{1} ();
        {
            println (i, " ", j);
        }
        iter:.next ();
    }
}


In this example, two elements can be highlighted: 1) the iter variable, that stores an iterator object, 2) the begin and end method of the class A. Indeed, an iterable object is an object that contains two methods begin and end, that returns an mutable iterator pointing respectivelly to the beginning and to the end of the iterable set.

The iterator type is a type defined by the user, and that contains the opEquals method, a method next on a mutable instance, and the method get, template method thats returns value pointed by the current iteration.

The following example presents the implementation of a Range that has the same behavior as a r!i32 with a step 1 and a not including the end value.

import std::io;

class Range {
    let _fst : i32;
    let _lst : i32;

    pub self (fst : i32, lst : i32) with _fst = fst, _lst = lst {}

    pub def begin (self)-> dmut &Iterator { // must return a dmut value
        Iterator::new (self._fst)
    }

    pub def end (self)-> &Iterator {
        Iterator::new (self._lst)
    }

    impl Streamable;
}

class Iterator {
    let mut _curr : i32;

    pub self (curr : i32) with _curr = curr {}

    pub def get {0} (self) -> i32 {
        self._curr
    }

    pub def opEquals (self, o : &Iterator) -> bool {
        self._curr == o._curr
    }

    pub def next (mut self) {
        self._curr += 1;
    }
}


def main () {
    let mut r = Range::new (0, 10);
    for i in r {
        println (i);
    }
}


To be more efficient and avoid a new allocation at each iteration, the end method should return a value that is computed once.

Contribution Enhance this section, which is completely unclear. And add information about error handling maybe.

results matching ""

    No results matching ""