/nosyn_array_access_example

An example of how support for arrays can be added to the NoSyn language using operator overloads

Primary LanguageDGNU Lesser General Public License v3.0LGPL-3.0

NoSyn Array Access Operator Overload Example

Alpha 0.1 Array Access Example

This array access exmaple is based on the example NoSyn program described in the Function Inference document. In the document a library of operator overloads was written in order to provide the user with a simple array creation operator as well as Go language style array slicing.

Although not all of the language features used in that example have been implemented into the NoSyn language yet (such as templates and closed aliases), even at this limited stage in development, enough features are present to provide at least some of the functionality.

As there are currently no template types in NoSyn, this example will be limited to just integer arrays. In replacement of closed aliases this example will be using native aliases. Although they are not used for the same purpose, native aliases can be used in a similar fashion to closed aliases.

First we should define exactly what language features we would like to create for our language. As this is NoSyn, there are absolutely no operators defined by default, so one of the most important operators to create first is the assignment operator. For our purposes we will need a couple of operator overloads for this. The first is to assign array values to array variables (examples are marked with the module in which they are defined. Modules are defined based on the directory structure within the src folder):

// standard.assignment line 7
Nothing infix_:=_(IntArray* a, IntArray b)

This overload will allow us to assign a value array ‘b’ to array variable ‘a’ by infixing the two with an operator ‘:=’.

The parameter type being used in this overload is IntArray. This is not a standard type in NoSyn. It is actually a native alias for the D type int[]. This means that in NoSyn it will be recognised as the type IntArray, but once converted into D it will get replaced with the type int[]. This property of holding onto the alias name as the type name within NoSyn is why it can work as a good substitute to closed aliases. If a second alias Foo were also to point to the D type int[], they would be treated as fundamentally different types in NoSyn inspite of their identical underlying structure.

Another overload has been created for the := symbol:

// standard.arrays line 22
Nothing infix_:=_(IntArrayAssignPrep metaData, Int newValue)

From first inspection it is not clear what this overload actually does, until shown in an expression context:

//nosyn line 17
a[0] := 10;

This intuitively means to assign the value 10 to the zeroth element of the array a. It should be noted that in the expression:

// nosyn line 25
writeln(a[0]);

The program will output the value of element 0. This is a good example of function inference in action. In one expression the subexpression a[0] will return an integer value, but in the other it is returning an IntArrayAssignPrep which is a special data structure the := assignment operator can use to find out at what position in an array it needs to put its righthand argument.

This shows that the most basic array functionality has been implemented with relative ease. The main operator of interest in the program however is :.

In the NoSyn module containing the main function we have three expressions that use the : operator:

// nosyn lines 29, 30, 35
b := (1:2)
b := (5:4:3:2:1)
c := b[0:3]

In the first two expressions the : operator is being used as a short hand for creating arrays. In the last expression it is being used for array slicing.

The parentheses on the first two expressions is not just to make things clearer, it is actually a requirement. Apart from the definition of variables, all statements in NoSyn are righthand expression statements.

The expression:

// nosyn line 29
b := (1:2)

does not actually mean to assign the righthand value (1:2) to a lefthand variable b. The underlying expression in this example is actually:

:=(b, :(1,2))

b is just another righthand expression within the righthand function call to :=, which has the side effect of setting b to the array (1:2).

Because at a fundamental level in this language everything is in reality just a function call, some problems arise when attempting to write an entirely righthand expression which mimics the behaviour of lefthand expressions in other languages. Because, by default, no operators hold any meaning in NoSyn, this also means that there is no reason to give them any kind of preferential ordering. Because of this, NoSyn is set up to order operators from left to right in an expression:

((1+2)+3)+4

But this is not the intuitive order for an assignment expression such as the one given:

(b:=1):2

This default ordering will cause a missing function overload error as the only return value for := is Nothing which is not a valid type for a function parameter which it is currently being used for in the infix : function.

The brackets change the order in which the operators are grouped:

// nosyn line 29
b := (1:2)

This expression will be converted to:

:=(b, :(1,2))

In this case there is an overload for := that supports this:

// standard.assignment line 7
Nothing infix_:=_(IntArray* variable, IntArray value)

In the third expression the : is using a different overload in order to work within the square bracket overload. The square bracket has three possible overloads:

// standard.arrays lines 26, 30, 34
Int bracketop_[]_(IntArray array, Int index)
IntArray bracketop_[]_(IntArray array, ArraySlicer sliceData)
IntArrayAssignPrep bracketop_[]_(IntArray array, Int index)

There is also three overloads for the : operator:

// standard.arrays lines 38, 48, 52
IntArray infix_:_(Int elemA, Int elemB)
ArraySlicer infix_:_(Int start, Int end)
IntArray infix_:_(IntArray array, Int newValue)

As can be seen here, the only overload for : which could be used within the square brackets is the second overload which returns an ArraySlicer.

The system of function inference has enabled the restriction for the programmer to only write what is syntactically correct in the square bracket context.

If the programmer attempts to write the syntactically incorrect expression:

c := b[0:3:5]

This will raise an error at compile time because the expression 0:3:5 is not capable of returning either the Int or ArraySlicer values that the square bracket overloads expect.