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. 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, or a class. The identifier is then used to refer to the
associated declaration (more correctly with the declaration instance).
Declaration = VarDeclarations
| AccessClause
| ExternDeclarations
| FuncClassExtDeclaration
| IncludeDeclaration
The scope of a declaration is textually from the start (not from the
point of declaration!) to the end of the block to which the
declaration belongs and hence to which the declaration is local. It
excludes the scopes of declarations with the same identifier which are
in nested blocks. In a block, a maximum of one declaration of the
same identifier is possible.
It is important to understand the notion of instantiation of the declaration. This notion reflects program execution, not the static structure of 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 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 (parallel execution branches) 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 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;
func r () {return j;}
f = r;
}
putln (f ());
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 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 public, private, or as friend:
AccessClause = (public | private | friend) AccessList ";"
AccessList = IDENT { "," IDENT }
Examples:
public param1, param2;
private call_count;
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. If the designator is a class
object, its context is a class object, and the search failed,
the search is continued in the covering class object etc. The
exception accessop
occurs if the declaration is not
found with such identifier, or the declaration is private and
the construction is not in the declaration scope and not inside
a friend of the declaration scope.
anode argv atan2
chdir chgmod chomod chumod
clock close cmpv context
cos curr_thread
del
eltype env error_anode errors
except excepts exit exp
fatime fctime fget fgetf
fgetln fgmode fgn file
flush fmtime fomode fprint
fprintln fput fputln fscan
fscanln fsize ftype fumode
fun
gc get getcwd getegn
geteun getgn getgroups getf
getln getpid getun gmatch
gsub
ins inside insv invaccesses
invcalls invexterns invindexes invkeys
invops invparsers invregexps isatty
keys
log log10
main_thread match max min
mkdir
nil_anode
open
parser pclose popen pow
print println put putln
rand readdir remove rename
rev rmdir
scan scanln seek signals
sin sort split split_regex
sprint sprintln sput sputln
sqrt srand stderr stdin
stdout strtime sub subv
syserrors system systemcalls
tell time time_format token
tolower toupper trans
version
The following identifiers are predeclared in the class except
mentioned above.
error
The following identifiers are predeclared in the class error
mentioned above.
deadlock
invaccess invcall invenv invindex
invkey invop
signal syncwait
The following identifiers are predeclared in the class signal
mentioned above.
sigabrt sigfpe sigill sigint
sigsegv sigterm
The following identifiers are predeclared in the class invop
mentioned above.
optype opvalue
The following identifiers are predeclared in the class
invindex mentioned above.
indexop indextype indexvalue
The following identifiers are predeclared in the class invkey
mentioned above.
keyop keyvalue
The following identifiers are predeclared in the class
invcall mentioned above.
callop
eof
internal invenvar invextern invfmt
invinput invparser invregexp invresult
parnumber partype
syncthreadcall syserror systemcall
The following identifiers are predeclared in the class
syserror mentioned above.
eaccess eagain ebadf ebusy
echild edeadlk edom eexist
efault efbig eintr einval
eio eisdir emfile emlink
enametoolong enfile enodev enoent
enoexec enolck enomem enospc
enosys enotdir enotempty enotty
enxio eperm epipe erange
erofs espipe esrch exdev
The following identifiers are predeclared in the class
systemcall mentioned above.
noshell
systemfail
The following identifiers are predeclared in the class
invparser mentioned above.
invgrammar invtoken
pmemory
The following identifiers are predeclared in the class
invregexp mentioned above.
badpat
ebrack ectype eend eescape
eparen erange esize espace
esubreg
The following identifiers are predeclared in the class
invextern mentioned above.
libclose
noextern noexternsupp
The following identifiers are predeclared in the class
invaccess mentioned above.
accessop accessvalue
immutable
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. Assigning of the initial value to the variable instance is made after execution of the previous statements of the block. By default the initial value of variables is the special value nil. The value of the variable can not be changed after its initialization if its declaration contains the keyword final.
VarDeclarations = var VarParList ";"
VarParList = VarPar { "," VarPar }
VarPar = [final] IDENT [ "=" Expr]
Examples:
var i = 0, j, k;
var final constant = 10, final nil_constant, l;
Dino permits to use functions written in other languages, e.g. C. The
functions should have special prototypes and must have to access to
the DINO standard procedural interface (SPI). Dino can also have
access to variables of a special type declared in the source code in
another language. The details of the implementation of such features
and the DINO SPI are not described here (some details are given in
appendix B). As rule, the external functions and variables will be
implemented as dynamically loaded libraries. This is the 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.
ExternDeclarations = extern ExternItem { "," ExternItem } ";"
ExternItem = IDENT
| IDENT "(" ")"
Examples:
extern function (), variable;
A function/class declaration consists of a function/class header and a function/class block (body). The header specifies the function identifier and formal parameters. 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 absent or is not executed, the function returns nil by default. A class call returns an object of the class which can be considered as a block instance of the class body. The return-statement for classes must be without a result. Thread-functions are analogous to general functions. The difference is in that a new execution thread is created during the thread-function call, the return-statement inside thread-function must be without an expression, and the thread-function returns the corresponding (execution) thread. The execution thread finishes when the corresponding thread block finishes. Execution threads are executed parallelly. 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 actual
parameters during a call of the function/class. The function can
be called with any number of actual parameters. If the
number of actual parameters is less than the formal parameters number,
the remaining formal parameters are initialized by the special value
nil. Otherwise if the number of actual parameters is more
than the number of formal parameters, the remaining actual parameter
values are ignored. In order to process all actual parameters, you
should place ...
at the end of the list of formal parameter
declarations. This means 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 less
or equal to the number of formal parameters (not taking the implicit
parameter args
into account), the value of args
will
be the empty vector. The formal parameter can be initialized by a
default value in a way analogous to variable initialization. The
initialization is made only when the corresponding actual parameter
value is nil.
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 if it is declared as public.
You should be remember that although the function may have parameters
and return a value, the garbage collector (or finishing the program)
ignores the result value and does not pass actual parameters. The
single exception when the function destroy
is not called by
finishing the program is the case when memory can not be allocated
more. So the values of the parameters will be nil if the
function is called by the garbage collector (or finishing the
program). You may prevent removing the corresponding object 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 increase of the heap.
Instead of inheritance usually used in object-oriented languages, Dino supports extension. This feature permits to modify function/class behaviour. All code inside an extension body is inserted at the end of body of the function/class declared with the same identifier in the same block in the same order as the extensions are placed in the block. A function/class declared as final can not be extended.
FuncClassExtDeclaration = Header Block
Header = [final] FuncThreadClass IDENT FormalParameters
| ext IDENT
FuncThreadClass = func
| thread
| class
FormalParameters = "(" [ VarParList ] ")"
| "(" VarParList "," "..." ")"
| "(" "..." ")"
Examples:
The following is a parameterless class header:
class stack ()
The following is a class header with an initialization:
class stack (max_height = var a = 1, b = 2;
putln (a, " ", b);
a<=>b;
putln (a, " ", b);
var ar = [1, 2, 3];
println (ar);
ar[0]<=>ar[2];
println (ar);
class s (i) {}
var c1 = s (0), c2 = s (3);
putln (c1.i, ' ', c2.i);
c1.i<=>c2.i;
putln (c1.i, ' ', c2.i);
var t = {"s" : 1, "t" : 2};
putln (t{"s"}, ' ', t{"t"});
t{"s"}<=>t{"t"};
putln (t{"s"}, ' ', t{"t"});
100)
The following is a function with a variable number of parameters:
func print_args (...)
{
for (i = 0; i < #args; i++)
println (args[i]);
}
The following example illustrates the usage of extensions:
class point (x = 0, y = 0) {
}
ext point {
class circle (radius = 1) {
func square () {return 3.14 * radius * radius;}
}
}
ext point {
ext circle {
class ellipse (width) {
func square () {
...
}
}
}
The following example is a class with the function destroy
:
var objs_number = 0;
class obj () {
private n, destroy;
var n = objs_number;
objs_number++;
func destroy () {objs_number--; objs_number--;}
}
The following example illustrates threads:
class buffer (length = 3) {
var b = [length:nil], first = 0, free = 0, empty = 1;
private b, first, free, length;
func consume () {
var res;
wait (!empty);
res = b [first];
first = (first + 1) % length;
wait (1) empty = first == free;
return res;
}
func produce (val) {
wait (empty || free != first);
b [free] = val;
free = (free + 1) % length;
wait (1) empty = 0;
}
}
thread consumer (buffer) {
func produce (val) {
buffer.produce (val);
put ("produce: ");
println (val);
}
produce (10);
produce (10.5);
produce ("string");
produce ('c');
produce (nil);
}
thread producer (buffer) {
var val;
for (;;) {
val = buffer.consume ();
if (val == nil)
break;
put ("consume: ");
println (val);
}
}
var queue = buffer ();
consumer (queue);
producer (queue);