Expressions are constructs denoting rules of computation of a value from other values by the application of operators. Expressions consist of operands and operators. Parentheses may be used to express specific associations of operators and operands.
All Dino values are first class values, i.e. they can be assigned to a variable, can be passed as a parameter of a function/class, and can be returned by functions. Operators require operands whose values are of given type and return the value of the result type. Most values have a representation in Dino. When a value representation is encountered in an expression during the expression evaluation, the new value is generated.
There are values of structured types, i.e. values which are
built from other values. The value of a structured type may be
mutable or immutable. A value or sub-value of a
mutable value can be changed. An immutable value can not be changed
after its generation. You can make a mutable value immutable as a
side effect by applying the operator final
(the table key is
also made immutable as a side effect of writing to the table). In all
cases, the operator returns the operand value as the result. If you
try to change an immutable value, the exception immutable
is
generated. You can make a new mutable value as a side effect of
applying operator new
. The operator returns a new value
equal to the operand value.
Expr = final Expr
| new Expr
Structured value types are also shared value types. This notion means that if two or more different variables (array elements or table elements) refer to the same value and the value is changed through one variable, the value which is referred through the other variables is changed too. There is no difference between the notion "the same value" and the notion "equal values" for non-shared type values. For the shared type operands, equality means that the operands have the same structure (e.g. vectors with the same length) and the corresponding element values are the same.
Examples:
new 5
new ['a', 'b', 'c']
new "abc"
new tab ["key0" : 10, "key1" : 20]
final 5
final ['a', 'b', 'c']
final "abc"
final tab["key0" : 10, "key1" : 20]
Dino has the following types of values:
nil
.
Expr = nil
Character
in the
section Vocabulary and Representation.
Expr = CHARACTER
Integer
in the section Vocabulary and
Representation. It is always stored as a 64-bit
integer value.
Expr = INTEGER
Long
in the section Vocabulary and
Representation. Long values are signed
multi-precision integer. The maximal and minimimal values
are constrained only by the overall computer memory size.
Expr = LONG
FloatingPointNumber
in the
section Vocabulary and Representation. It is
always stored as an IEEE double (64-bit) floating point
value.
Expr = FLOATINGPOINTNUMBER
:
. The
repetition value is converted into an integer value by
default. If the repetition value after the conversion is
not integer, the exception optype
is
generated. If the repetition value is negative or zero,
the element value will be absent in the vector. Elements
of a vector are accessed by their indexes. Indexes always
starts with 0. Vectors in Dino are heterogenous,
i.e. elements of a vector may be of different types. A
string represents an immutable vector all of whose
elements are characters in the string. Elements of
mutable vectors can be added to or removed from the vector
(see predefined functions ins, insv, and del).
Expr = "[" ElistPartsList "]"
| STRING
ElistPartsList = [ Expr [":" Expr ] {"," Expr [":" Expr ] } ]
Examples:
"aaab"
['a', 'a', 'a', 'b']
[3 : 'a', 'b']
[3.0 : 'a', 'b']
["3" : 'a', 'b']
['a', 10, 10.0, "abcd", {}]
[]
[
and ]
with optional element
values with a preceding :
. By default the
element value is equal to nil
. It is not allowed
to have elements with equal keys in a table. If this
constraint is violated in a table constructor, the
exception keyvalue
is generated. Elements of
tables are accessed by their keys. Elements of mutable
tables can be added to or removed from the table
correspondingly by assigning values and with the aid of
the function del. The side effect of the table
constructor execution is that the keys
become immutable.
Expr = tab "[" ElistPartsList "]"
Examples:
tab ['a', 'b', 10:[10]]
tab ['a' : nil, 'b' : nil, 10 : [10]]
tab [[10, 'a', tab [10]] : 10, [10] : tab [20:20]]
tab []
this
immediately inside
the class.type (expression)
.
Expr = char
| int
| long
| float
| hide
| hideblock
| vec
| tab
| fun
| fiber
| class
| thread
| obj
| type
There are the following type values:
nil
. There is no value
representing type of nil
. So use the
construction type (nil)
to get it.char
.int
.long
.float
.vec
.tab
.fun
.fiber
.class
.obj
.thread
.hide
.hideblock
.type
.There is a special Dino construction called a designator. A designator refers for a vector or table element or for a declaration. If the designator refers to a vector or table element or for a variable declaration, it can stand in the left hand side of an assignment statement. If the designator stands in an expression, the corresponding value is used (the vector/table element value, variable value, function, fiber, or class). When the designator referring to a table element stands up in the left hand side of an assignment statement, its key value becomes immutable.
A designator referring to a vector or table element has the following syntax:
Designator = (Designator | Call) "[" Expr "]"
The value of the construction before the brackets must be a vector or table.
Otherwise, the exception indexop
is generated.
If the value of the construction before the brackets is a vector, the
value of expression in the brackets (so called index) is
converted to an integer value. If this is not possible, the
exception indextype
is generated. If the index is negative
or greater than or equal to the vector length, the
exception indexvalue
is generated. The value of the
designator will be the vector element value with given index (the
indexes starts with zero). Examples:
vector [1]
vector ["1"]
vector [1.0]
If the value of the construction before the brackets is a table, the
value of expression in the brackets is called key. The value
of the designator will be the table element value with the key which
is equal to given key. If the element with the given key is absent in
the table, the exception keyvalue
is generated. Examples:
table ['c']
table [10]
table ["1"]
table [1.0]
The remaining forms of designator refer to a declaration. See the section Declarations and Scope Rules for a description on how they work.
Designator = (Designator | Call) "." IDENT
| IDENT
Examples:
value
value.f
Dino also has an extended form of the designator described above. This form is called a vector slice:
Expr = ExtDesignator
ExtDesignator = Designator {Slice}
Slice = "[" [Expr] ":" [Expr] [":" Expr] "]"
The slice is applied to a vector, otherwise the
exception sliceform
is generated. The slice refers for a
part of vector given by the start index, the bound, and the step. If
they are not integer, the exception slicetype
is generated.
The negative bound means counting the index from the vector
end. -1
corresponds to the vector length, -2
corresponds to the vector length - 1, -3
corresponds to the
vector length -2, and so on. If the bound is more than the vector
length, the bound is assumed to be equal to the vector length. If the
bound is omitted, it is believed to be equal -1
, or in other
words, to the vector length.
If the start index is less than zero, the exception sliceform
is generated. If the start index is omitted, it is believed to be
zero. If the step is omitted, it is believed to be one.
The slice refers to vector elements with
indexes start
, start + abs (step)
, start + 2 *
abs (step)
, start + 3 * abs (step)
and so on while the
index is less than the bound. If the step value is negative, the
elements are considered in the reverse order. Examples:
v[:] // all vector elements
v[1:] // all vector elements except the first one
v[:-2] // all vector elements except the last one
v[::-1] // all vector elements in reverse order
v[0::2] // all vector elements with even indexes
More one slice can be given after the designator. In this case, next slice is applicable to each element of the previous slice. That means that each element and the next slice should abide by the above rules. The first slice is called the first dimension slice, the second one is called the second dimension slice and so on. Examples of multi-dimensional slices:
m[:][:] // all matrix elements
m[:][::2] // matrix elements with even columns
m[::2][::] // matrix elements with even rows
m[::2][::] // matrix elements with even rows and columns
The slices are actually not real reference values. They can be considered as an attribute which exists in a statement at most. If vector with slices is an operand of an operation which has a slice variant, the operation is applied to each element of the vector referenced by the slices in given order. The result is a new vector with the same dimension slices referencing all elements with the step value equal to one.
When a vector with slices is an operand of an operation, the new vector consisting of elements referenced by the slices is created. The new vector will have the same dimension slices referencing for all new vector elements with the step equal one. This is done to avoid unexpected side-effects of function-calls inside a statement with slices.
One form of expression is a call of a function, a fiber, or a class.
The value of the designator before the actual parameters should be a
function, a fiber, or a class. Otherwise, the
exception callop
is generated. An instance of the block
corresponding to the body of the function, fiber, or class is created.
The actual parameter values are assigned to the corresponding formal
parameters. If the corresponding function, fiber, or class has no
default formal parameter args
or parameters with default
values (see the section Declarations), the number of the
actual parameters should be equal to the formal parameter number.
Otherwise, a vector whose elements are the remaining parameter values
is created and assigned to the parameter args
. If there is
no corresponding actual parameter for a formal parameter with a
default value, the default parameter value (see the
section Declarations) is assigned to the formal parameter.
After the parameter initialization the statements in the block are
executed. If it is the call of a fiber, a new execution thread is
created, and the statements of the block is executed in the new
thread. The value of call of the fiber is the corresponding thread.
It is returned before starting the execution of statements in the new
thread.
The execution of the body is finished by reaching the block end or by execution of a return-statement. Finishing of the fiber results in finishing the corresponding thread. The return-statement in a fiber or in class should be without an expression. The call of a class returns the created object. A function call returns the value of the expression in the executed return-statement. Otherwise, the function return value is undefined.
Expr = Call
Call = Designator ActualParameters
ActualParameters = "(" [ Expr { "," Expr } ] ")"
Examples:
f ()
f (10, 11, ni, [])
obj.objf ()
Expressions consist of operands and operators. The order in which
operators are executed in an expression is defined by their
priority and associativity. That means that the
expression a op1 b op2 c
when the operator
op2
has higher priority than op1
is analogous to
a op1 (b op2 c)
. Dino operators have analogous priorities to
the ones in the language C. The following Dino operators are placed
in the order of their priority (the higher the line on which the
operator is placed, the higher its priority).
! # ~ final new
* / %
+ -
@
<< >> >>>
< > <= >=
== != === !==
&
^
|
in
&&
||
:
?
All binary operators have left associativity in Dino. That means that
the expression a op1 b op2 c
when operators op1
and
op2
have the same priority is analogous to (a op1 b) op2
c
. Parentheses may be used to express specific associations of
operators and operands.
Expr = "(" Expr ")"
Most of the Dino operators require the operands to be of given types.
If an operand is not of given type, the conversion of it into a value
of the type needed may be made. If after the possible conversions the
operands are still not of necessary types, the
exception optype
is generated. The following conversions may
be made by default:
opvalue
is generated. If the
operand is a floating point number, its fractional part is
thrown away and integral part becomes integer. If the operand
is a vector of characters, the corresponding string is believed
to be the decimal representation of an integer and is converted
into the corresponding integer value. If the corresponding
string is not a correct integer representation, the result is
undefined. If the corresponding string represents an integer
whose representation requires more 64 bits, the
exception erange
may be generated. In all remaining
cases the results of conversion coincide with the operand.l
or L
) or a floating point number (i.e. contains an
exponent or fraction), the result will be the corresponding long
integer or floating point number instead of an integer. If the
result of conversion to an integer from the string is out of
range of 64-bit representation or the result of conversion to
floating-point number from the string is out of IEEE double
format range, the exception erange
may be generated.
Additionally if the operand is in a non-short circuit binary operator (non-logical operators) and another operand is a floating point number after the conversion, the first operand is converted into a floating point number too. In this case, if the first operand is a long integer out of IEEE double format range, the result is undefined. Otherwise, if another operand is a long integer after the conversion, the first operand is converted into long integer too.
Logical operators produce the integer result 1 which means
true or 0 which means false. Logical `or'
||
and logical `and' &&
are short circuit
operators. That means that the second operand is evaluated depending
on the result of the first operand. When the operands of the
operators are evaluated, the arithmetic conversion is made.
If the first operand of logical `or' is nonzero (integer, long integer, or floating point), the result will be 1. Otherwise, the second operand is evaluated. If the second operand is nonzero, the result will be 1. Otherwise, the result will be 0.
If the first operand of logical `and' is zero (integer, long integer, or floating point), the result will be 0. Otherwise, the second operand is evaluated. If the second operand is nonzero, the result will be 1. Otherwise, the result will be 0.
Logical negation !
makes an implicit arithmetic conversion of
the operand. If the operand is zero (integer, long integer or
floating point), the result will be 1. Otherwise, the result will be
0.
Operator in
checks that there is an element with the given
key (the first operand) in the given table (the second operand). If
the element is in the table, the result will be 1. Otherwise the
result will be 0. If the second operand is not a table, the exception
keyop
is generated.
Expr = Expr "||" Expr
| Expr "&&" Expr
| Expr in Expr
| "!" Expr
Examples:
!(type (i) == int && type (a) == tab && i >= 0 && i < #a)
k in t && t[k] == 0
0.0 || another_try
0 || another_try
The following operators work on integers (implicit integer conversion
is made) and return an integer result. Operators | ^ & ~
denote correspondingly bitwise or, bitwise exclusive or, bitwise
and, and bitwise negation of 64-bit integers.
Operators << >>> >>
denote correspondingly logical left
bit shift, logical right bit shift, and arithmetic right bit
shift (with sign extension) of given number (the first operand)
by given number of the bits (the second operand). The value of the
second operand must be non-negative, otherwise the result is
undefined.
Expr = Expr "|" Expr
| Expr "^" Expr
| Expr "&" Expr
| Expr "<<" Expr
| Expr ">>" Expr
| Expr ">>>" Expr
| "~" Expr
Examples:
(i >> shift) & mask
i & ~mask | (value << shift) & mask
i >>> 2
i << 2
All comparison operators return a logical value (integer 0 which means false or integer 1 which means true).
Operators equality ==
and inequality !=
may make
some conversion of the operands. If one of the two operands is
string, then the string conversion is applied to the other operand
before the comparison. Otherwise, standard arithmetic conversion is
applied to the operands if one operand is of an arithmetic type. The
operators do not generate exceptions (but the conversions may). The
operands are equal if they have the same type and equal values (see
the section Types and Values). For instances, functions and
classes, the equality requires also the same context.
Operator identity ===
or unidentity !==
returns 1 if
the operands have (or not) the same value or 0 otherwise. The
operators never generate exceptions and no any conversion is made.
By default the arithmetic conversion is applied to the operands of
operators < > <= >=
. There is no exception if the operands
after the conversion are of integer, long integer, or floating point
type. So the operands should be characters, integers, long integers,
floating point numbers, or strings representing integers, long
integers, or floating point numbers.
Expr = Expr "==" Expr
| Expr "!=" Expr
| Expr "===" Expr
| Expr "!==" Expr
| Expr "<" Expr
| Expr ">" Expr
| Expr "<=" Expr
| Expr ">=" Expr
Examples:
10 == 10
10 === 10
10 == 10l
10 == 10.0
10 !== 10l
10 !== 10.0
10 <= 'c'
p != nil
'c' == "c"
10 < "20.0"
[10, 20] == [10, 20]
[10, 20] !== [10, 20]
The following operators return integer, long integer, or floating
point numbers. Before the operator execution, an implicit arithmetic
conversion is made on the operands. The binary operators + - * /
%
denote correspondingly integer, long integer, or floating point
addition, subtraction, multiplication, division, and evaluation of
remainder. Unary operator -
denotes arithmetic negation.
The unary operator +
is given for symmetry and it returns
simply the operand after the conversion. It can be used for
conversion of a string into an integer, a long integer, or floating
point number.
Expr = Expr "+" Expr
| Expr "-" Expr
| Expr "*" Expr
| Expr "/" Expr
| Expr "%" Expr
| "+" Expr
| "-" Expr
Examples:
+"0"
+"10l"
+"10."
+"1e1"
-i
(value + m - 1) / m * m
index % bound
The Dino conditional expression is analogous to the language C one.
An implicit arithmetic conversion is made for the first expression
followed by ?
. If the value of the expression is non zero
(integer, long integer, or floating point value), the second
expression with following :
is evaluated and it will be the
result of the condition expression. Otherwise, the third expression
is evaluated and it becomes the result.
The operator #
can be applied to a vector or a table. It returns
the length of the vector or the number of elements in the table.
The operator @
denotes a concatenation of two vectors into a
new vector. Before the concatenation an implicit string conversion of
the operands is made.
The remaining operators look like function calls. Operator
type
returns the expression type. Never is an exception
generation possible during the operator evaluation.
The operator char
is used to conversion of a value into a
character. First, an implicit integer conversion is applied to the
operand. The operand should be an integer after the conversion.
Otherwise, the exception optype
will be generated. The
integer is transformed into the character with the corresponding code.
If the code is too big to be a character or is negative, the exception
erange
is generated.
The operator int
is used for a conversion of a value into an
integer. Implicit integer conversion is applied to the operand. The
operand should be an integer after the conversion. Otherwise, the
exception optype
will be generated. If the code is too big
to be an integer, the exception erange
is generated.
The operator float
is used for a conversion of a value into a
floating-point number. The first, an implicit arithmetic conversion
is applied to the operand. The operand should be an integer, an long
integer, or a floating-point number after the conversion. Otherwise,
the exception optype
will be generated. An integer is
transformed the corresponding floating-point number. The same is done
for a long integer but if the number is too big or too small to be a
floating-point number, the result is undefined.
The operator vec
is used for a conversion of a value into a
vector. First, an implicit string conversion is applied to the
operand. The optional second expression defines the format used only
for the string conversion of a character, an integer, a long integer,
a floating point number, or a string. The second parameter value
should be a string after an implicit string conversion. The format
should not be given for a table. The first operand should be a table
or a vector after the conversion. The table is transformed into a new
vector which consists of pairs (one pair for each element in the
table). The first element of the pair is a key of the corresponding
element, and the second one is the element itself. The order of pairs
in the result vector is undefined.
The operator tab
is used for a conversion of a value into
table. First, a string conversion is applied to the operand. The
operand should be a vector or a table after the conversion. The
vector is transformed into a new table whose elements are equal to the
vector elements that have integer keys equal to the corresponding
vector indexes.
Expr = Expr "?" Expr ":" Expr
| "#" Expr
| Expr "@" Expr
| type "(" Expr ")"
| char "(" Expr ")"
| int "(" Expr ")"
| float "(" Expr ")"
| vec "(" Expr ["," Expr] ")"
| tab "(" Expr ")"
Examples:
i < 10 ? i : 10
#tab ["a", 'b']
#["a", 'b']
"concat this " @ "and this"
type (type)
type (10)
char (12)
vec (10)
vec (10, "%x")
vec (tab ["1":1, "2":2])
tab ([1, 2, 3, 4])
There are slice variants of all operators of mentioned above except
for the short circuit operators &&
and ||
and
the conditional expression.
For unary operators, operator execution on slices creates a new vector structure consisting of elements referenced by the slices in given order, creates the same dimension slices referencing all elements of the new vector structure and execute the operator on each element of the created slices which will be the result of the operator. Examples:
var v = [1, 2, "3"], m = [[1, 2.], [3, 4, 5]];
! v[:];
- m[1:][::2];
float (m[:][:]);
Binary operators on the slices are a bit more complicated. If the
both operands have slices, they should have the same form
(dimension, number of elements in each corresponding sub-vector
referenced by the slices), otherwise the exception sliceform
is generated. The operator execution can be considered as creating a
new vector structure consisting of elements referenced by the slices
in and creating the same dimension slices referencing all elements of
the new vector structure. The same is done for the second slice.
Than the elements of the new vector structure created for the first
operand is changed by the result of the operator applied to this
element and the corresponding element from the slices created for the
second operand. The slices created for the first operand will be the
result. Examples:
var v = [1, 2, 3], m = [[4, 5], [6, 7]];
v[:] * v[:];
m[:][0::2] + m[:][1::2];
If only one operand has the slices, the operator execution can be considered as creating a new vector structure consisting of elements referenced by the slices and creating the same dimension slice referencing all elements of the new vector structure. Than the elements of the new vector structure is changed by the result of the operator applied to this element and another operand in given operand order. The created slices will be the result. Examples:
var v = [1, 2, 3], m = [[4, 5], [6, 7]];
1 - v[:];
v[:] * v[:] * 2;
m[:][0::2] + 1;
Dino has additional unary operators which work only on the slices:
Expr = ".+" Expr
| ".*" Expr
| ".&" Expr
| ".^" Expr
| ".|" Expr
The are called fold operators. If the operand has no
slices, the exception vecform
is generated. The operator execution
is to apply the corresponing non-fold operator to all elements
referenced by the slices. Examples:
var v = [1, 2, 3], m = [[4, 5], [6, 7]];
.+ v[:];
.* v[:];
.& m[:][:];
The instance of the block immediately surrounding the current program
point can be accessed by using the keyword this
.
Expr = this
Example of usage of this
:
class c (x) { putln (this.x, "===", x); }
fun f (pub x) { return this; }
Instead of declaring a function/class and using its identifier once in an expression, an anonymous function/class can be used. The anonymous functions/classes are regular expressions. They have the same syntax as the corresponding declarations. The only difference is an absence of the function/class identifier:
Expr = AnonHeader FormalParameters Block
AnonHeader = [Qualifiers] FuncFiberClass
Examples:
fun (a) {a > 0;}
class (x, y) {}
fiber (n) {for (var i = 0; i < n; i++) putln (i);}
An example of anonymous function usage:
fold (fun (a, b) {a * b;}, v, 1);
Dino has two ways to process the exceptions. One way is described in the section Try-block. Another way is to use a try-operator. The try-operator has the following syntax:
Expr = try "(" ExecutiveStmt [ "," ExceptClassList] ")"
The operator returns non-zero if no exceptions occurs in the statement
given as the first operand. The operator returns zero if an exception
occurs and its class is a sub-class (see the function isa
) of
one exception class given by the subsequent operands. If there is no
matched operand class, the exception is propagated further (see
the try-block description for more details). If the optional
subsequent operands are omitted, any occured exception in the
statement results in the zero result value.
Here is the example of usage of the try-operator:
var ln;
for (; try (ln = getln (), eof);) putln (ln);