A Dino program is block structured. Each block introduces a new identifier scope. A block consists of executive statements and declarations and may contain nested blocks. There are also implicit blocks containing each case-part of match statements (see below). Each identifier used in a program should be declared in a declaration in the program, unless it is a predeclared identifier.
Block = "{" StmtList "}"
StmtList = { Stmt }
Stmt = ExecutiveStmt
| Declaration
When declaring an identifier, you also specify certain permanent properties of a declaration, such as whether it is a variable, a function, a class, or a singleton object. The identifier is then used to refer to the associated declaration (more correctly to the declaration instance).
Declaration = VarDeclarations
| AccessClause
| ExternDeclarations
| FuncClassDeclaration
| SingletonObject
| ForwardDeclaration
| IncludeDeclaration
The scope of a declaration is textually from the point of the declaration to the end of the block to which the declaration belongs and hence to which the declaration is local. The declaration scope can stop earlier at the point of another declaration with the same identifier in the same block. The declaration scope excludes the scopes of declarations with the same identifier which are in nested blocks.
It is important to understand the notion of instantiation of the declaration. This notion reflects a program execution, not the static structure of the program. An instance exists in a context. Actually, a context is an execution environment consisting of the covering block instances and/or class objects. A new instance of the block is created when an execution of the block starts. There may be more than one instance of the same block, e.g. when the block is a function or class body (in this case the block instance is a class object), or when the block is executed on different threads (concurrently executed parts of the program) or when there is a reference to a block instance after its execution. When a new instance of the block starts, all the block declarations are instantiated too. For a variable declaration, it means a new instance of the variable is created in the given context. For a function or class declaration, it means that the function or class is bound to the given context.
Example: The following program illustrates a case when a reference to a block instance exists after its execution. The program outputs the result 8.
var i, f;
for (i = 0; i < 10; i++)
if (i % 4 == 0)
{
var j = i;
fun r () {return j;}
f = r;
}
putln (f ());
There are certain rules for declaration accessibility. Declaration is always either private or public. Private declaration is accessible only inside the declaration scope or inside functions or classes which are declared as a friend in the declaration block. A public declaration instance is always accessible when association (see below) of the identifier is successful. By default, (instances of) declarations in a class block are public. In all other places, the (instances of) declarations are private by default. The following constructions are used for declaring an identifier to be a friend:
FriendClause = friend IDENT { "," IDENT } ";"
Examples:
friend class2;
Association of an identifier and the corresponding declaration instance is performed by the following rules:
designator.identifier
is searched in the block instance (e.g. in a class object)
whose value is in the designator. The scope of the found
declaration should always include the block end. The
exception accessop
occurs if the declaration is not
found with such identifier, or the declaration is private and
the construction is not inside a friend of the declaration
scope.
The following identifiers are predeclared on the top level (in an implicit block covering the whole program). They are described in more detail later in the report.
lang io sys math
re yaep
Dino is an imperative language. In other words it has
variables which are named containers of values. A variable
can contain any value. This means that DINO is a dynamically-typed
language. The declaration of a variable also may define the initial
value of the variable. In this case a pattern can stand on
the left side of the assignment instead of just an identifier. The
pattern should contain at least one variable. The pattern variables
are declared at the assignment point. The pattern should match the
assigned value, otherwise the exception patternmatch
is
generated. More details about the patterns, the pattern variables,
and pattern matching are decribed later in this document.
Assigning of the initial value to the variable instance is made after execution of the previous statements of the block and after execution of previous assignments in the variable list. By default the initial value of variables is undefined. Nothing can be done with this value. This value even can not be assigned.
The value of the variable is final and can not be changed after its
initialization if its declaration is in a list starting with
keyword val
.
Keywords pub
and priv
can be used to redefine the
default accessibility. They make the declared variables
correspondingly public and private.
VarDeclarations = [pub | priv] (val | var) VarList ";"
VarList = Var { "," Var }
Var = IDENT | Pattern "=" Expr
Examples:
var i = 0, [el1, el2] = [2, 5], j, k;
val constant = 10, nil_constant = nil;
Dino permits to use functions written on the programming language C.
The functions should have special prototypes and can use the DINO
standard procedural interface (SPI). Dino can also have an access to
variables of a special type declared in the C source code. The
details of the implementation of such features and the DINO SPI are
not described here (some details are given in appendix B). As a rule,
the external functions and variables will be implemented as
dynamically loaded libraries. This is a powerful instrument of DINO
extension. The external functions and variables are declared after
keyword extern
. An external function identifier is followed
by ()
. All external declarations (e.g. in different blocks)
with the same identifier refer the the same external function or
variable. As in the variable declaration, keywords pub
and priv
can be used to redefine the default accessibility of
the external functions and variables..
ExternDeclarations = [pub | priv] extern ExternItems ";"
ExternItems = ExternItem { "," ExternItem }
ExternItem = IDENT
| IDENT "(" ")"
Examples:
extern function (), variable;
A function/class declaration consists of a function/class header, formal parameters, and a function/class block (body). The header specifies the function/class identifier.
A function can return the result with the aid of the statement return. If the result value after the keyword return is absent or the return statement is not executed, the function result is undefined vlaue. An expression-statement as the textually last statement in the function block is actually abbreviation of a return with the expression.
A class call returns the class block instance (an object of the class). Any return-statement in a class must be without a result.
Fiber-functions (we call them also simply fibers for brevity) are analogous to general functions. The difference is in that a new execution thread is created during the fiber call, the return-statement inside the fiber must be without an expression, and the fiber returns the corresponding thread. The thread finishes when the corresponding fiber block finishes. Threads are executed concurrently. Originally only one thread (called the main thread) exists in a DINO program.
The formal parameters are considered to be declared in a
function/class block and to be initialized by values of the
corrsponding actual parameters during a call of the
function/class. By default they can change their value.
Keyword val
prevents changing value of the corresponding
formal parameter after the initialization.
Default accessibility of the formal parameter can be changed by using
keywords pub
or priv
(see accessibility in the
section "Declarations and Scope Rules").
The number of actual parameters should be the same as the
number of formal parameters. There are two exclusions to this
requirement. One exclusion is formal parameters initialized by a
default value in a way analogous to the variable initialization. The
actual parameters corresponding to the formal parameters with default
values can be omitted. In this case, the formal parameters will have
the default value. Another exclusion is an usage of ...
at
the end of the list of formal parameter declarations, the number of
actual parameters can be more the number of formal parameters.
Using ...
results in that the formal parameter with the
identifier args
will be declared implicitly. The value of
the parameter will be a vector whose elements will be the remaining
actual parameter values. If the number of actual parameters is equal
to the number of formal parameters (not taking the implicit
parameter args
into account), the value of args
will
be the empty vector.
All formal parameters with default values should be at the end of the
parameter list. Usage of formal parameters with default values is
prohibited in a case when ...
is used.
If a class contains a function with the name destroy
, the
function will be called when the class object becomes garbage during
the garbage collection process or at the end of the program. The
function can also be called explicitly from outside if it is declared
as public. You should remember that the function is called by the
garbage collector without actual parameters and the garbage collector
(or finishing the program) ignores the result value. So the function
should satisfy the above rules to be called without actual parameters.
You may prevent removing the corresponding object by the garbage
collector in the function destroy by assigning the object to a
variable. It means that the function can be called several times
(during several garbage collections) for the same object. But you
should also avoid creation of objects during the call of
function destroy
because it may result in uncontrolled
increase of the heap.
A function/class declaration can have optional qualifiers. The
qualifier final
can not be mentioned in an use-clause (see
the section "Use-Clause"). Qualifiers pub
and priv
change the default accessibility of declarations.
FuncClassDeclaration = Header FormalParameters Hint Block
Header = [Qualifiers] FuncFiberClass IDENT
Qualifiers = pub | priv | final
| pub final | priv final
| final pub | final priv
FuncFiberClass = fun
| fiber
| class
FormalParameters =
| "(" [ ParList ] ")"
| "(" ParList "," "..." ")"
| "(" "..." ")"
ParList = Par { "," Par}
Par = [pub | priv] [val | var] IDENT [ "=" Expr]
Hint = [ "!" IDENT ]
Examples:
The following is parameterless class:
class stack () {}
class stack {}
The following is a class header with initialization parameters:
class stack (max_height = variable,
val a = 1, priv val b = 2)
The following is a function with a variable number of parameters:
fun print_args (...) {
for (i = 0; i < #args; i++)
println (args[i]);
}
The following example is a class with the function destroy
:
var objs_number = 0;
class object () {
priv var n = objs_number;
objs_number++;
priv fun destroy () {objs_number--;}
}
The following example illustrates the threads:
class buffer (length = 3) {
var b = [length:nil], first = 0, free = 0, empty = 1;
priv b, first, free, length;
fun consume () {
var res;
wait (!empty);
res = b [first];
first = (first + 1) % length;
wait (1) empty = first == free;
return res;
}
fun produce (value) {
wait (empty || free != first);
b [free] = value;
free = (free + 1) % length;
wait (1) empty = 0;
}
}
fiber consumer (buffer) {
fun produce (value) {
buffer.produce (value);
put ("produce: ");
println (value);
}
produce (10);
produce (10.5);
produce ("string");
produce ('c');
produce (nil);
}
fiber producer (buffer) {
var value;
for (;;) {
value = buffer.consume ();
if (value == nil)
break;
put ("consume: ");
println (value);
}
}
var queue = buffer ();
consumer (queue);
producer (queue);
According to Dino scope rules, a declaration should be present before any usage of the declared identifier. Sometimes we need to use function/class identifier before its declaration, e.g. in the case of mutually recursive functions. To do this, Dino has forward declarations.
A forward declaration is just a function/class header followed by semicolon:
ForwardDeclaration = Header ";"
A forward declaration denotes the first declaration with the same
identifier in the same block after the forward declaration. The
forward and the corresponding declaration should be the same
declaration type (function, class, or fiber), and have the same
accessibility and the same qualifier final
.
The corresponding declaration of a forward declaration may be absent.
In this case a call of the function/class results in occuring the
exception abstrcall
. When accessing to such function through
the corresponding object, exception accessvalue
is generated.
A forward declaration without the corresponding declaration can be
usefull to describe abstract classes. The following are
examples of forward declarations:
fun even;
fun odd (i) { if (i == 0) return 0; return even (i - 1);}
fun even (i) { if (i == 0) return 1; return odd (i - 1);}
class operation { // abstract class
fun print_op (); fun apply ();
}
A function/class declaration may have an optimization hint.
Currently, there are only 3 hints. Hint !pure
means that the
function is pure, i.e. it has no side effects and returns a result
depending only on the call arguments. Hint !inline
means
inlining all the function calls. Hint !jit
means a usage of
a JIT compiler for the function/class execution.
Dino has a powerful block composition operator use. Using it inside a class block can emulate (multiple) inheritance, traits, duck typing, and dynamic dispatching. The use-clause has the following syntax:
UseClause = use IDENT { UseItemClause }
UseItemClause = [former | later] UseItem { "," UseItem }
Item = IDENT [ "(" IDENT ")"]
Use-clause provides a safe way to support object oriented programming. It has the following semantics:
The above rules are necessary for correct duck-typing or class sub-typing.
As a class containing an use-clause provides the same interface as the class mentioned in the use-clause, we also call the using class an immediate sub-class of the used class and the used class as an immediate super-class of the using class. A class can be used through a chain of several use-clauses. In this more general case we simply use notions of sub-class and super-class.
The following example illustrates use-clause:
class point (x, y) {
}
class circle (x, y, radius) {
use point former x, y;
fun square () {3.14 * radius * radius;}
}
class ellipse (x, y, radius, width) {
use circle former x, y, radius later square;
fun square () {
...
}
}
Sometimes it is usefull to guarantee that there is only single object
(called singleton object) of a class. Although it can be
achieved by creating of an object of an anonymous class (see the
section "Anonymous Functions and Classes") and assigning it to a
variable declared as val
, Dino has a special declaration for
singleton object:
SingletonObject = [pub | priv] obj IDENT block
The keywords pub
and priv
can be used to redefine
the default accessibility. They make the declared object
correspondingly public and private. Example of singleton object
declaration:
priv obj coord {
val x = 0, y = 10;
}
You can not refers to a singleton object by its name inside the singleton object as the declaration actually occurs after the object block.
Signleton object declarations can be accessed through designator
with .
-- see Designators. Sometimes it can be too
verbose. The expose-clause can make access to public object declarations
more concise by introducing declarations on the clause place which are
actually aliases to the corresponding object declarations.
ExposeClause = expose ExposeItem { "," ExposeItem }
ExposeItem = QualIdent ["(" IDENT ")"] | QaulIdent ".*"
QualIdent = IDENT {"." IDENT}
You can expose one declaration by using QualIdent with an optional alias identfier in parentheses. If the alias is not given, the declaration identifier will be used for the alias. The qualident designates the declaration which is inside one signleton object or inside the nested singleton objects.
You can also expose all public declarations of a signleton object by
their identifiers. In this case, you need to add .*
after
the qualident designating the corresponding object.
It is a good practise to put all package declarations in a singleton object which in this case behaves as name space or modules in other programming langiages.
Example of use-clauses:
expose sys.system (shell), math.*;
shell ("echo sin:" @ sin (3.14) @ "cos: " @ cos (3.14));
In the above example, we make the function system
from
singleton object sys
accessible through
identifier shell
and all public declarations from singleton
object math
including
sin
and cos
accessible by their identifiers.