– Typee Documentation –

3 – Typee Language Description

3.11 – Operators Definitions

Operators have hand in expressions – arithmetic, conditional, strings and a few other ones. In Typee, there is at first two sorts of operators:

  • built-in ones;
  • undefined ones.

The undefined ones are for the free definition and use of programmers. Meanwhile, all the operators in Typee may be redefined by programmers. They mostly will be redefined in classes definitions – to be explained and described in the next section of this documentation.

Here is the formal EBNF specification of operators and their definition rules in Typee:

<operator>              ::= '<='  |  '=='  |  '!='  |  '>='  |
                            '&'   |  '|'   |  '^'  |
                            '++'  |  '--'  |  '#'  |
                            'in'  |
                            <op_add>  |  <op_mul>  |  <op_power>  |
                            <user op> |  <assign op>  |  <cast op>
<operator'>             ::= '<'  |  '>'  |  '<<'  |  '>>'  |  '<<<'  |  '>>>' 


<assign op>             ::= '='  |  <augmented assign op>

<augmented assign op>   ::= '+='   |  '-='   |  '*='  |  '/='   |  '%='  |
                            '&='   |  '|='   |  '^='  |
                            '<<='  |  '<<<=' |  '>>=' |  '>>>=' |
                            '**='  |  '^^='  |  '@='  |
                            '><='  |  '!!='  |  '::=' |  '??=' 

<call operator>         ::= '(' ')' 

<cast op>               ::= 'cast' <dotted name>

<comp operator>         ::= '<='  |  '=='  |  '!='  |  '>='  |
                            'in'  |  'is' [ 'not' ]  |  'not' 'in' 

<comp operator'>        ::=  '<'  |  '>' 

<op_add>                ::= '+'   |  '-' 
<op_mul>                ::= '*'   |  '/'  |  '%' 
<op_power>              ::= '**'  |  '^^' 
<user op>               ::= '@@'   |  '><'  |  '<>'  |  '!!'  |  '::'

<operator declaration>  ::= <forward> 
                                [<access protection qualifier>] ['final']
                                <type> 'operator' ['pre' | post']
                                ( ( (<operator> [<template def>] |
                                     <operator'> [<spaced template def>]) 
                                  <function args declaration> )  |
                                  ( <call operator> [<template def>] ) )
                                ';'

<operator definition>   ::= [<access protection qualifier>] ['final']
                                <type> 'operator' ['pre' | post']
                                ( ( (<operator> [<template def>] |
                                     <operator'> [<spaced template def>]) 
                                  <function args declaration> )  |
                                  ( <call operator> [<template def>] ) )
                                <statements block>

<template def>          ::= '<' <template def'> '>' 
<spaced template def>   ::= ' <' <template def'> '>' 

<template def'>         ::= (<identifier> |
                             <const qualifier> <template def const name>) 
                                  ( ',' (<identifier> | 
                                         <const qualifier>
                                             <template def const name>) )*

<template def const name> ::= <scalar type or dotted name> <identifier>
                                  [ '=' <expression> ]

3.11.1 Built-in operators

We have seen all of them in the previous section on expressions. Let’s copy the related text here.

Arithmetic Operators definitions

They are very usual, as they are in most programming languages.

Unary operators

+ is the unary plus. Numbers are put to positive and values keep their internal sign after this sign. This is the default setting when no unary operator is used.
- is the unary minus. Numbers are put to negative and values change their positive/negative sign after this sign.
~ is the unary negation of binary values. When put after this sign, values get every of their bits changing of value: 0 becomes 1 and 1 becomes 0.
# is the hashing operator. When applied to a literal, it delivers the hash value of this literal. This operator is available also for other types of entities, such as strings and instantiations of classes.
++ is the increment operator. Its built-in form can only be applied to identifiers of integer variables. When put before the identifier, it pre-increments (i.e. it internally adds 1 to) the value of the variable and then returns the new value of this variable. When put after the identifier, it returns the current value of the variable and then post-increments its value (i.e. it internally adds 1 to it) which get modified as well.
-- is the decrement operator. Its built-in form can only be applied to identifiers of integer variables. When put before the identifier, it pre-decrements (i.e. it internally subtracts 1 to) the value of the variable and then returns the new value of this variable. When put after the identifier, it returns the current value of the variable and then post-decrements its value (i.e. it internally subtracts 1 to it) which get modified as well.

Bitwise binary operators

& is the bitwise and operator.
| is the bitwise or operator.
^ is the bitwise xor operator.

Examples:

0b0011 & 0b0101 --> 0b0001
0b0011 | 0b0101 --> 0b0111
0b0011 ^ 0b0101 --> 0b1010

Bit-shift binary operators

<< is the signed left bit-shift operator.
<<< is the unsigned left bit-shift operator.
>> is the signed right bit-shift operator.
>>> is the unsigned right bit-shift operator.

The bit-shift operators shift bits of a value, to the left or to the right, inserting ‘0’ in the newly empty bits of the value, except for the signed right-shift and left-shift which preserve the bit sign (the leftmost bit of the value).

Examples:

0b1010_0011 << 3 --> 0b1001_1000
0b1010_0011 <<< 3 --> 0b0001_1000
0b1010_1100 >> 3 --> 0b1000_0101
0b1010_1100 >>> 3 --> 0b0001_0101

Other binary operators

+ is the addition operator.
- is the subtraction operator.
* is the multiplication operator.
/ is the division operator. Division between integer values evaluates to an integer value, truncated to the low integer value. Division between float values evaluates to a float value. When mixing integer and float values, the integer values are promoted to floats and the division evaluates to a float value.
% is the modulo operator.
** is the power operator.
^^ is the power operator also. Both notations are valid in Typee.

3.11.2 Undefined (user) operators

Typee specifies five new and undefined binary operators. These five undefined operators are specified but not defined as are built-in operators. They can be defined at the convenience of programmers who can use them for any of their specific needs (e.g. matrix multiplication, vector dot product, etc.)

These operators are: @@, ><, <>, !! (double exclamation) and ::.

Their precedence is specified in Typee language description and is the same as for multiplicative operators (i.e. *, / and %).

Before being used, they must be defined – see sub-section operators definition just after the next one.

3.11.3 Operators precedence

As previously seen in the section related to Expressions in this documentation:

Operators get precedence over each others. From top to bottom, these precedence in Typee are:

<op_unary>  ::= '++'  |  '--'  |  '+'  |  '-'  |  '~'  // higher precedence
<op_power>  ::= '**'  |  '^^'
<op_mul>    ::= '*'   |  '/'   |  '%'   |
                '@'   |  '@@'  |  '><'  | '<>'  |   '!!'  |  '::'
<op_add>    ::= '+'   |  '-'
<op_shift>  ::= '<<'  |  '<<<'  | '>>'  |  '>>>'
<op_bitand> ::= '|'
<op_bitxor> ::= '^' 
<op_bitor>  ::= '&'
<op_comp>   ::= '<'  |  '<='  |  '=='  |  '!='  |  '>='  |  '>'
<in>        ::= 'in'
<not>       ::= 'not'
<and>       ::= 'and'
<or>        ::= 'or'                                   // lower precedence

3.11.4 Operators Definition

The formal EBNF specification of operator definition rule in Typee is:

<operator definition>   ::= [<access protection qualifier>] ['final']
                                <type> 'operator' ['pre' | post']
                                ( ( (<operator> [<template def>] |
                                     <operator'> [<spaced template def>]) 
                                  <function args declaration> )  |
                                  ( <call operator> [<template def>] ) )
                                <statements block>

So, the definition of an operator specifies:

  • the access protection level that is associated with this operator, when the operator is defined at the highest level of statements-blocks nesting of the module it is defined in, or when the operator is defined in a class;
  • the overridding possibility or not on this operator;
  • the type of the value this operator returns back to its caller;
  • keyword oparetor
  • either keyword pre or keyword post (mandatory) when overridding auto- increment/decrement operators (i.e. “++” or “--“) as well as when defining any undefined operator if pre-operation or post-operation gets meaning for this;
  • keyword cast if this is a casting operator
  • the identifier of this operator – only Typee specified identifiers of operators are allowed there, except if this is a casting operator in which case any legal identifier is allowed;
  • a list of template arguments when this concept is used;
  • the list of the typed arguments that have to be passed to this operator when it is called;
  • a block of statements which contains all the statements that this operator is to compute.

An operator is designed by its signature rather than by its identifier (which is part of this signature). At call-time, the operator body that is computed is the one associated with the operator signature that matches the call signature.

Those elements are constituting the signature of an operator:

  • the type of the value it returns to its caller;
  • the post or pre nature of this operator functionnality;
  • the identifier of the operator;
  • the template arguments that may be declared and applied to it – if present;
  • the whole ordered list of the arguments types that are declared in its definition.

The operator body, the access protection qualifier and the overridding possibility are not part of a operator signature.

 
Access Protection
The access protection level of an operator is either set locally for it, with protection keywords, or set at a global level, as seen before, between two colons. When set locally at the definition of the operator, the specified visibilty level takes precedence over any global level set before in the same source code file. Access protection levels may only be set at the highest level of blocks nesting within a module. Typee translators will set warnings when any vivibility qualifier will appear in nested blocks of instructions within a module (i.e. within a Typee source code file).

As a reminder, visibility qualifiers in Typee are:

<access protection qualifier> ::= 'hidden' | 'local' | 'private'
                                  'protected' | 'public'

 
Overridding
Overridding possibility is the default in Typee. As will be explained later in this documentation, overridding is a way to modify the definition of an operator (with same signature) within a specified area of code. Typee accepts the definition of operators that should not be overridden. The keyword for indicating this is the same as in Java: final.

 
Returned value type
Operators may return values to their callers. The type of the returned value has to be specified and is considered as belonging to the signature of the operator.

The returned types may be any kind of type, and even containers, instances of classes, etc. Operators always return a typed value.

 
Operator dedicated keywords
The definittion or redefinition (i.e. ovreridding) of an operator is indicated by the use of keyword operator.
When applicable, either keyword pre or keyword post may be specified. They indicate pre- or post- modification of the object these operators are applied to.

Examples:

class Cpt {
  Cpt(){
    me.i = 0;
  }

  final uint8 operator pre ++(){
    me.i += 1;    // could have been coded on a single line:
    return me.i;  // "return ++me.i;"
  }

  final uint8 operator post ++(){
    const uint8 n = me.i;
    me.i += 1 ;    // notice:
    return n;     // could not have been coded as "return me.i++;"
  }

  private uint8 i;
}
Cpt c span class="op">= Cpt();
print( c++, ++c );   // prints: 0 2

 
Cast operator
Keyword cast comes next after keyword operator when we are defining a casting operator. It precedes either a scalar type name or a legal identifier which is an already declared or defined type (i.e. class name or type alias).

 
Operator identifier
Allowed identifiers for operators are specified by Typee grammar rules. They all are listed above in this section.
Remember: programmers may override built-in operators, may overwrite built-in operators and may define undefined operators. The five undefined operators are specified by Typee language to serve this purpose, but are limited to five in their number. Meanwhile, programmers cannot add new operators identifiers to the language grammar, except when defining a casting operator in which case operator identifiers may be scalar type names and already defined or declared types (i.e. class names or types aliases).

 
Templates
Templates will be fully explained later in this documentation. Let’s just talk here about their syntax when defining an operator. Template arguments are part of the signature of the operator. They are declared at its definition time. They are enclosed between angle brackets: < and >. They consist in a list of identifiers which will only be visible in the body of the operator (i.e. its block of statements), separated by commas. Any single identifier within the list is the local identifier of a type which will be inferred at call-time of the operator. Other identifiers, when preceded by a type identifier (either a built-in one or an already defined one), are identifiers of constant values that will be locally visible in the body of the operator, that may get a default value and that will be inferred at call-time of the operator.
The formal EBNF specification of Typee related grammar syntax is:

<template def>            ::= '<' <template def'> '>' 
<template def'>           ::= (<identifier> |
                               'const' <template def const name>) 
                                    ( ',' (<identifier> | 
                                           'const' <template def const name>) )*

<template def const name> ::= <scalar type or dotted name> <identifier>
                                  [ '=' <expression> ]

 
Operator arguments
Operators get passed arguments. The block of statements of the operator processes these arguments or uses these arguments while running through it. The ordered list of the types of the arguments is part of the signature of the operator. At definition- and at declaration- time, each argument is specified by its identifier, which has to be unique in the list of arguments, and by its type. Arguments types may be preceded by keyword const. In this case, the related arguments are considered as not mutable outside of the operator. This means that the values of these arguments may be modified within the operator body, but that they will just be modified internally while their initial value at call-time will not be modified once control will have been returned back to the caller of the operator.

 
Operator body
The body of an operator is the statements block that is associated with it. It is not part of its signature. The formal specification of statements blocks is described in this section of Typee documentation.

3.11.5 Operators Declaration

In Typee, operators may only be declared in a forward statement. The only need to declare an operator is when it is to be defined in a module after it is called in this same module.

The formal EBNF specification of an operator declaration is then:

<operator declaration>  ::= <forward> 
                                [<access protection qualifier>] ['final']
                                <type> 'operator' ['pre' | post']
                                ( ( (<operator> [<template def>] |
                                     <operator'> [<spaced template def>]) 
                                  <function args declaration> )  |
                                  ( <call operator> [<template def>] ) )
                                ';'

Since the operator is only declared there, no statements block (i.e. operator body) is allowed after the declaration of the signature of the operator. This declaration being a statement, it has to be ended with a semi-colon. Very simple.

Such declarations help Typee translators to check types and operators signatures, when they are called, with no delay (this is an early (i.e. a not-lazy) evaluation or checking of types and signatures).

3.11.6 Operators Overloading

We shortly discuss this concept here. There is no specific rule for it in Typee grammar.
Operators overloading has to do with operators signatures. An operator is said to be overloaded as soon as it gets many different signatures with its same identifier.

We stress here that point, generic to any OOP language, because Typee gets a specificity about it. The returned type of operators is part of their signature. This is not the case in most programming languages.

Example:

fwd int64   ><( int64 a, int64 b );
fwd float32 ><( float32 a, float32 b );
fwd float64 ><( float64 a, float64 b  );
fwd float64 ><( float64 a, float32 b  );
fwd float64 ><( float32 a, float64 b  );

3.11.7 Operators Overridding

We shortly discuss this concept here. There is no specific rule for it in Typee grammar.
Operators overridding is the rewriting of the body (i.e. statements block) of an operator while not changing its signature.

Example:

from string import String;  // imports built-in library String

str operator + ( const str s1, const str s2 ){
    // concatenates two strings with a space separator
    return String.format( "%s %s", s1, s2 );
}

which could have also been coded as:

str operator + ( const str s1, const str s2 ){
    // concatenates two strings with a space separator
    return s1 + ' ' + s2;
}

or as:

StrT operator + <StrT> ( const StrT s1, const StrT s2 ){
    // concatenates two strings with a space separator
    return s1 + ' ' + s2;
}

 
Next section formerly explains Classes and their related grammar rules.

< previous (3.10 functions) | (3.12 classes) next >