The Programming Language DINO: Declarations and Scope Rules Next Previous Contents

4. Declarations and Scope Rules

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:

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

4.1 Variable Declarations

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;

4.2 External Declarations

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;

4.3 Functions and Classes

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.

4.4 Use-Clause

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 () {
               ...
             }
           }

4.5 Singleton Object

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.

4.6 Expose-clause

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.


Next Previous Contents