In EScript there are several default operator and also the possibility to overload operators. In the following you find a short introduction to operators.
Operator Precendence
The following table summarizes the operator precendence from highest (0) to lowest (22).
Precendence | Operator | Description | Associativity |
---|---|---|---|
0 | @ |
Right | |
1 | . [ ] ( ) |
Member access Indexing Function call |
Left |
2 | new |
Create new instance | Left |
3 | fn |
Function definition | Right |
4 | ++ -- (unary) - ~ ! |
Increment Decrement Unary minus Bitwise not Logical not |
Right |
5 | * / |
Multiplication and Division | Left |
6 | % |
Modulo | Left |
7 | + - |
Addition and Subtraction | Left |
8 | << >> |
Left and Right shift | Left |
9 | < > <= >= ~= |
Relational operators | Left |
10 | == != === !== ---|> -> |
Relational operators Inheritance test Object binder |
Left |
11 | => |
Parameter binder | Left |
12 | & |
Bitwise and | Left |
13 | ^ |
Bitwise xor | Left |
14 | | |
Bitwise or | Left |
15 | && |
Logical and | Right |
16 | || |
Logical or | Right |
17 | ?: |
Ternary operator a?b:c |
Right |
18 | = |
Assignment | Right |
19 | := |
Attribute declaration | Right |
20 | ::= *= /= %= += -= &= ^= |= |
Type attribute declaration Compound assignments |
Right |
21 | ... |
Varargs | Left |
22 | , |
Comma | Left |
Operator Overloading
In EScript it is also possible to overload several operators. This happens on a ‘per instance’ level. This means that an overloaded operator is a function that is called on the object. For unary operators this means that the operator function doesn’t have a parameter and the operand is the this
object. For binary operators, the function has a single argument, which is the operand to the right of the operator. The left operand is the this
object. The following code shows a simple example class, which overrides the +
operator:
static T = new Type;
T.value := 0;
T._constructor ::= fn(value) {
this.value = value;
};
T."+" ::= fn(other) {
return new T(this.value + other.value);
};
One special thing in EScript is that you can overload most operators as unary and binary operators. If it is the binary version, you just write the corresponding operator in quotes, like in the example above. If you want to define the operator as an unary operator in front of the operand, you append _pre
to it, like "+_pre"
. If you want to define an unary operator that comes after the operand you append _post
, like "+_post"
. One special thing here is the unary minus, which is defined by "_-_pre"
.
The following operators exist and can be overloaded with the pattern described earlier. Exceptions and additional notes are described afterwards.
+ - * / %
& | ^
+= -= *= /= %= &= |= ^= ~=
<< >>
< > <= >= == != === !==
! ~
++ --
@ ...
=> ->
---|>
- The unary minus is not defined by
-_pre
, but instead by_-_pre
. - It is not possible to override
!_pre
- Even if it is possible to override
===
and!==
, it is strongly discouraged! - It is advised to return the
this
object for the following binary operators:+= -= *= /= %= &= |= ^= ~=
- It is advised to return a boolean value for the following binary operators:
< > <= >= == != === !==
- It is strongly discouraged to override
->
and---|>
Of course you could do many cool and strange things, but it is also advised to not abuse this cool feature. For example in most cases it doesn’t make much sense to define a +=_pre
operator. Just because you can define it, doesn’t mean you should define it! Only use this feature where it is appropriate. For example it is a good idea override +=
in order to add something to itself. For example the Array
object uses this in order to append a new value.
Simple Example
This simple hand made point class shows you how you could use operator overloading.
static Point = new Type;
Point.x := 0;
Point.y := 0;
Point._constructor ::= fn(Number x = 0, Number y = 0) {
this.x = x;
this.y = y;
};
Point."+" ::= fn([Point, Number] other) {
if(other ---|> Point)
return new Point(x + other.x, y + other.y);
return new Point(x + other, y + other);
};
Point."+=" ::= fn([Point, Number] other) {
if(other ---|> Point) {
x += other.x;
y += other.y;
} else {
x += other;
y += other;
}
return this;
};
Point.toString ::= fn() {
return "(" + x + ", " + y + ")";
};
var a = new Point(10,0);
var b = new Point(0,20);
outln( "a : ", a.toString() );
outln( "b : ", b.toString() );
outln( "a + b : ", (a + b).toString() );
outln( "a+=10 : ", (a+=10).toString() );
outln( "a : ", a.toString() );
You could also do funny things, like this:
Number."%_post" ::= fn() { return this / 100; };
And now you can simply calculate with percentages:
outln(420 * 10%); // Output: 42
You can even define a factorial function:
Number."!_post" ::= fn() {
var result = 1;
for(var i=1; i <= this; i++)
result *= i;
return result;
};
outln(5!); // Output: 120
Indexing Operator
EScript does not only allow operator overloading for ‘simple’ operators, like + and -, but also for more complex ones, like the indexing operator: []
EScript distinguishes between setting and getting a value via the index operator. In order to get a value via the index operator you define a function called _get
with a single parameter, which is the given index. In order to set a value, you define a function called _set
with two parameters, the key and the value.
The following example defines a new type, which can be used like an array, but which uses negative indices to access the content ‘backwards’, so -1 refers to the last item of the underlying array:
var MyArray = new Type;
MyArray.array @(private) := void;
MyArray._constructor ::= fn([Array, void] content) {
if(content) this.array = content;
else this.array = new Array();
};
MyArray._get ::= fn(Number key) {
if(key < 0) key = array.size() + key;
return array[key];
};
MyArray._set ::= fn(Number key, value) {
if(key < 0) key = array.size() + key;
array[key] = value;
};
var arr = new MyArray([1,2,3]);
outln(arr[0]); // first item
outln(arr[-1]); // last item
Call Operator
EScript also allows us to override the call operator. Therefore it is possible to use an object just like a function. This is done by defining a function called _call
. This function must have at least one parameter, which is the caller
. The rest of the parameters can be arbitrary. Example:
static MyType = new Type;
MyType._call ::= fn(caller) {
outln("caller = ", caller);
};
MyType(); // Output: caller = void
var t = new MyType();
t(); // Output: caller = void
var obj = new ExtObject();
obj.myType := new MyType();
obj.myType(); // Output: caller = #ExtObject:ExtObject:0x...