The following module demonstrates the use of commands. It implements an abstract data structure Counter that encapsulates a counter variable and provides commands to increment and print its value.
MODULE Counter;
IMPORT Texts, Oberon;
VAR
counter: LONGINT;
w: Texts.Writer;
PROCEDURE Add*; (* takes a numeric argument from the command line *)
VAR s: Texts.Scanner;
BEGIN
Texts.OpenScanner(s, Oberon.Par.text, Oberon.Par.pos);
Texts.Scan(s);
IF s.class = Texts.Int THEN INC(counter, s.i) END
END Add;
PROCEDURE Write*;
BEGIN
Texts.WriteInt(w, counter, 5); Texts.WriteLn(w);
Texts.Append(Oberon.Log, w.buf)
END Write;
BEGIN counter := 0; Texts.OpenWriter(w)
END Counter.
The user may execute the following two commands:
When Counter.Add is invoked for the first time, the module Counter is loaded and its body is executed. Every call of Counter.Add n increments the variable counter by n. Every call of Counter.Write writes the current value of counter to the screen.
Since a module remains loaded after the execution of its commands, there must be
an explicit way to unload it (e.g. when the user wants to substitute the loaded
version by a recompiled version.) The Oberon system provides a command to do
that.
D2. Dynamic Loading of Modules
A loaded module may invoke a command of a still unloaded module by specifying its
name as a string. The specified module is then dynamically loaded and the
designated command is executed. Dynamic loading allows the user to start a
program as a small set of basic modules and to extend it by adding further
modules at run time as the need becomes evident.
A module M0 may cause the dynamic loading of a module M1 without importing it. M1 may of course import and use M0, but M0 need not know about the existence of M1. M1 can be a module that is designed and implemented long after M0.
A garbage collector frees a programmer from the non-trivial task of deallocating data structures correctly and thus helps to avoid errors. However, it requires information about dynamic data at run time (see D5).
DEFINITION Trees;
TYPE
Tree = POINTER TO Node;
Node = RECORD
name: POINTER TO ARRAY OF CHAR;
PROCEDURE (t: Tree) Insert (name: ARRAY OF CHAR);
PROCEDURE (t: Tree) Search (name: ARRAY OF CHAR): Tree;
PROCEDURE (t: Tree) Write;
END;
PROCEDURE Init (VAR t: Tree);
END Trees.
For a record type, the browser also collects all procedures bound to this type
and shows their declaration in the record type declaration.
The dynamic type of a record corresponds to the address of its type descriptor. For dynamically allocated records this address is stored in a so-called type tag which precedes the actual record data and which is invisible for the programmer. If t is a variable of type CenterTree (see example in Ch. 6 (html2)) Figure D5.1 shows one possible implementation of the run time data structures.
Fig. D5.1 A variable t of type CenterTree, the record t^ it points to, and its type descriptor
Since both the table of procedure addresses and the table of pointer offsets must have a fixed offset from the type descriptor address, and since both may grow when the type is extended and further procedures and pointers are added, the tables are located at the opposite ends of the type descriptor and grow in different directions.
A type-bound procedure t.P is called as t^.tag^.ProcTab[IndexP]. The procedure table index of every type-bound procedure is known at compile time. A type test v IS T is translated into v^.tag^.BaseTypes[ExtensionLevelT] = TypeDescrAdrT. Both the extension level of a record type and the address of its type descriptor are known at compile time. For example, the extension level of Node is 0 (it has no base type), and the extension level of CenterNode is 1.