Run Time Analysis of Modula-3 Programs

In this section, a number of run time analysis tools are applied to the QueryDbase program presented earlier.

Run time checks

Although the QueryDbase program performs little error checking, most problems will be detected immediately by the run time checks (NIL pointers, incorrect array indexing...). The program then stops and the problem location may be identified quickly using the debugger.

For instance, trying to fetch an entry without first opening a database will cause an access through a NIL database object.


cassis>LINUX/QueryDbase
Enter command
fetch key10


***
*** runtime error:
***    attempt to dereference NIL
***

IOT trap
cassis>

Coverage Analysis

Modula-3 programs may be compiled to extract line frequency execution data. This information may then be used to determine which portions were tested adequately or where most of the execution time is spent. The proper option has to be specified in the m3makefile and the program recompiled. The program is then run and creates a file named coverage.out with the desired information.


cassis>head -5 src/m3makefile
m3_option("-Z")

% database is located in our private package collection
override("database","../..")
import("database")
cassis>rm -R LINUX
cassis>m3build
...
cassis>QueryDbase <example2.in >/dev/null
cassis>ls
LINUX/    coverage.out    example2.in    src/

This coverage information is then displayed using analyze_coverage.


cassis>analyze_coverage -S src -L
*************** COVERAGE OF Query.m3
...       
       PROCEDURE PrintDbase(db: Dbase.T) =
     1    VAR
           key, tmp: TEXT;
           cursor: CARDINAL := 0;
         BEGIN
     1      key := Dbase.FirstKey(db);
     1      WHILE key # NIL DO
   100        IF cursor > LAST(keyArray^) THEN
     4          oldKeyArray := keyArray;
     4          keyArray := NEW(REF ARRAY OF TEXT,2 * NUMBER(oldKeyArray^));
     4          SUBARRAY(keyArray^,0,NUMBER(oldKeyArray^)) := oldKeyArray^;
             END;
   100        keyArray[cursor] := key;
   100        INC(cursor);
   100        key := Dbase.NextKey(db);
           END;
       
           (* Use an inefficient sorting algorithm *)
       
     1      FOR i := cursor - 1 TO 0 BY -1 DO
   100        FOR j := 0 TO i - 1 DO
  4950          IF Text.Compare(keyArray[j], keyArray[i]) = 1 THEN
   890            tmp := keyArray[i];
   890            keyArray[i] := keyArray[j];
   890            keyArray[j] := tmp;
               END;
             END;
           END;
       
           (* Print all the keys *)
       
     1      FOR i := 0 TO cursor - 1 DO
   100        Wr.PutText(Stdio.stdout,keyArray[i] & "\n");
           END;
         END PrintDbase;
       
...       
     2    LOOP
   105      Wr.PutText(Stdio.stdout,"Enter command\n");
   105      Wr.Flush(Stdio.stdout);
       
   105      Lex.Skip(Stdio.stdin);
   105      command := Lex.Scan(Stdio.stdin);
       
   105      IF Text.Equal(command,"exit") THEN EXIT;
       
           ELSIF Text.Equal(command,"create") THEN
     0        Lex.Skip(Stdio.stdin);
     0        name := Lex.Scan(Stdio.stdin);
     0        db := Dbase.Create(name);
       
           ELSIF Text.Equal(command,"open") THEN
     1        Lex.Skip(Stdio.stdin);
     1        name := Lex.Scan(Stdio.stdin);
     1        db := Dbase.Open(name);
       
           ELSIF Text.Equal(command,"close") THEN
     1        Dbase.Close(db);
       
           ELSIF Text.Equal(command,"fetch") THEN
     0        Lex.Skip(Stdio.stdin);
     0        key := Lex.Scan(Stdio.stdin);
     0        Wr.PutText(Stdio.stdout,Dbase.Fetch(db,key) & "\n");
       
           ELSIF Text.Equal(command,"store") THEN
   100        Lex.Skip(Stdio.stdin);
   100        key := Lex.Scan(Stdio.stdin);
   100        Lex.Skip(Stdio.stdin);
   100        content := Lex.Scan(Stdio.stdin);
   100        Dbase.Store(db,key,content);
       
           ELSIF Text.Equal(command,"delete") THEN
     0        Lex.Skip(Stdio.stdin);
     0        key := Lex.Scan(Stdio.stdin);
     0        Dbase.Delete(db,key);
       
           ELSIF Text.Equal(command,"print") THEN
     1        PrintDbase(db);
       
           ELSE
     0        Wr.PutText(Stdio.stdout,"Incorrect command\n");
           END;
         END;
       END Query.

This data reveals that some portions (fetching and deleting entries) were not tested (executed frequency is 0) and that most of the time is spent in the inefficient sorting performed in the PrintDbase procedure.

Profiling

The usual profiling tools, for instance gprof, may also be used for finding where the execution time is spent in the call tree of a large program. These tools produce more complete information when all the modules and libraries involved have been compiled with the profiling flag. This may be achieved with the correct m3_option entry in the m3makefile or with a special m3build template which builds the program in a different directory. A special template is interesting because this way the profiled and ordinary versions of programs and libraries may coexist in separate directories.

The procedure is similar to coverage analysis. The program is recompiled and then run creating a file named gmon.out. This data is then processed and displayed using gprof. The explanations have been annotated directly on the output listing portions which follow.


cassis>m3build -b LINUX-PROF
...
cassis>LINUX-PROF/QueryDbase <example2.in >/dev/null
cassis>gprof LINUX-PROF/QueryDbase
...
granularity: each sample hit covers 2 byte(s) for 0.09% of 11.49 seconds

                                  called/total       parents 
index  %time    self descendents  called+self    name    	index
                                  called/total       children

...

# Each module initialization code is called from RunMainBodies.
# __INITM_Query is the body of the main module (Query.m3).

[4]     97.0    0.00       11.14       1         _RTLinker__RunMainBodies
                0.01       11.13       1/1           __INITM_Query [5]
                0.00        0.00       1/1           __INITM_ProcessPosix [112]
                0.00        0.00       1/1           __INITM_Stdio [123]
                0.00        0.00       1/1           __INITM_TimePosix [129]
...

-----------------------------------------------

# __INITM_Query is called by RunMainBodies and calls a number
# of procedures. On a total execution time of 11.13s, PrintDbase
# uses 7.34s.
                0.01       11.13       1/1           _RTLinker__RunMainBodies
[5]     97.0    0.01       11.13       1         __INITM_Query [5]
                1.14        7.34       1/1           _Query__PrintDbase [6]
                0.06        0.97    1004/1004        _Wr__Flush [9]
                0.03        0.71    3005/3005        _Lex__Scan [13]
                0.02        0.63    1000/1000        _Dbase__Store [15]
                0.01        0.13    3005/3005        _Lex__Skip [24]
                0.01        0.04    1004/2004        _Wr__PutText [31]
                0.04        0.00    6015/6015        _Text__Equal [48]
                0.00        0.00       1/1           _Dbase__Create [119]
                0.00        0.00       1/5044        _RTHooks__AllocateOpenArray	<cycle 1> [28]
                0.00        0.00       1/1           _Dbase__Close [584]

-----------------------------------------------

# The bulk of the time spent in PrintDbase is consumed by Text__Compare.

                1.14        7.34       1/1           __INITM_Query [5]
[6]     73.8    1.14        7.34       1         _Query__PrintDbase [6]
                7.06        0.00  499500/499500      _Text__Compare [7]
                0.00        0.22    1000/1000        _Dbase__NextKey [22]
                0.00        0.04    1000/2004        _Wr__PutText [31]
                0.00        0.02    1000/4005        _RTHooks__Concat [33]
                0.00        0.00       7/5044        _RTHooks__AllocateOpenArray	<cycle 1> [28]
                0.00        0.00       1/1           _Dbase__FirstKey [130]
                0.00        0.00       7/7016        _memmove [61]

-----------------------------------------------

# Text__Compare consumes the execution time itself, it does not
# call other procedures.

                7.06        0.00  499500/499500      _Query__PrintDbase [6]
[7]     61.4    7.06        0.00  499500         _Text__Compare [7]

-----------------------------------------------

# _syscall is called from a number of procedures, _write and
# _fcntl being the most frequent. It does not however consume
# too much execution time.

                0.00        0.00       2/5993        _gettimeofday [128]
                0.00        0.00       3/5993        _open [122]
                0.00        0.00       5/5993        _fstat [118]
                0.00        0.00      16/5993        _getrusage [101]
                0.19        0.00     863/5993        _read [23]
                0.45        0.00    2089/5993        _write [17]
                0.65        0.00    3015/5993        _fcntl [14]
[8]     11.3    1.30        0.00    5993         _syscall [8]

...

# Summary of the time spent per procedure.
#

   %  cumulative    self              self    total          
 time   seconds   seconds    calls  ms/call  ms/call name    
 55.2       7.06     7.06   499500     0.01     0.01  _Text__Compare [7]
 10.2       8.36     1.30     5993     0.22     0.22  _syscall [8]
 10.2       9.66     1.30                            mcount (425)
  8.9      10.80     1.14        1  1140.01  8481.51  _Query__PrintDbase [6]
  2.1      11.07     0.27    29821     0.01     0.01  _Rd__GetChar [18]
  1.0      11.20     0.13     1000     0.13     0.14  _dbm_do_nextkey [26]
  0.8      11.30     0.10    38843     0.00     0.00  _RTHooks__LockMutex [30]
...

The profiling information is useful to see which procedure, called from where, consumes most of the execution time. These measures are nevertheless somewhat imprecise since a statistical sampling of the stack is used to evaluate the CPU usage. Furthermore, the profiling information does not tell much about the influence of a number of important factors such as cache memory misses, virtual memory page faults and input-output delays.

Debugging

Although any debugger may be used to trace the execution through a Modula-3 program, it is preferable to have a Modula-3 aware debugger. This way, the Modula-3 syntax for specifying modules, procedures, variables, types and references may be used.

The debugger is used to understand the behavior of a program, thus finding errors, by tracing the execution and the content of variables. It may also be used to call data gathering procedures at certain points in the execution. One application of this is to study the memory allocation.

A session under the Modula-3 aware debugger m3gdb is presented and annotated below.


cassis>m3gdb LINUX/QueryDbase
GDB is free software and you are welcome to distribute copies of it
 under certain conditions; type "show copying" to see the conditions.
There is absolutely no warranty for GDB; type "show warranty" for details.
GDB 940920 (EPC-M2 & SRC-M3) (i486-unknown-linux), 
Copyright 1994 Free Software Foundation, Inc...

# The path to locate the source code is specified.
# An initial break point is specified to examine the PrintDbase procedure

(gdb) dir src
(gdb) dir ../database/src
(gdb) list Query.m3:0
1	MODULE Query EXPORTS Main;
2	
3	(* This program accepts commands to create or open ndbm databases
4	   and to store, fetch or delete elements. A sorted list of keys
(gdb) break Query.PrintDbase
Breakpoint 1 at 0xddf: file Query.m3, line 22.
(gdb) run @M3novm <example2.in >/dev/null
Starting program: LINUX/QueryDbase @M3novm <example2.in >/dev/null

Breakpoint 1, Query.PrintDbase (db=16_e50f4) at Query.m3:22
22	    key, tmp: TEXT;
Current language:  auto; currently m3
(gdb) where
#0  Query.PrintDbase (db=16_e50f4) at Query.m3:22
#1  16_1945 in Query.m3.MAIN (phase=3) at Query.m3:111
#2  16_6799 in RTLinker.RunInitPhase (phase=3) at RTLinker.m3:25
#3  16_6969 in RTLinker.m3.MAIN (phase=3) at RTLinker.m3:67
#4  16_d85 in main (argc=1, argv=16_bffff960, envp=16_bffff968)
    at _m3main.c:2305

# RTutils.Heap prints the number of each object type currently allocated.
# Pathname.T which is the same as TEXT (because of structural equivalence)
# is the most common type here.

(gdb) set lang c
(gdb) print RTutils__Heap()
Code   Count   TotalSize  AvgSize  Name
---- --------- --------- --------- --------------------------
   1      2235     38736        17 Pathname.T
   8         2        24        12 RegularFile.T
  10         1        12        12 Terminal.T
  18         2        32        16 RTutils.Visitor
  26         1        16        16 RefSeq.T
  41         1        28        28 Atom.NewAtomTbl
  50         3        36        12 Thread.Mutex
  54         3       120        40 FileWr.T
  60         1        40        40 FileRd.T
  61         1       148       148 Thread.T
  62         1         8         8 Thread.Condition
...
 145         1        40        40 TextSeq.RefArray
     --------- ---------
          2276     60372
$1 = void

# The PrintDbase function is run to completion and the
# Heap usage examined again.

(gdb) finish
Run till exit from #0  Query.PrintDbase () at Query.m3:22
0x1945 in Query.m3.MAIN () at Query.m3:111
111	      PrintDbase(db);
(gdb) print RTutils__Heap()
Code   Count   TotalSize  AvgSize  Name
---- --------- --------- --------- --------------------------
   1      1929     31484        16 Pathname.T
   8         2        24        12 RegularFile.T
  10         1        12        12 Terminal.T
  18         3        48        16 RTutils.Visitor
  26         1        16        16 RefSeq.T
  41         1        28        28 Atom.NewAtomTbl
  50         3        36        12 Thread.Mutex
  54         3       120        40 FileWr.T
  60         1        40        40 FileRd.T
  61         1       148       148 Thread.T
  62         1         8         8 Thread.Condition
 145         8      8224      1028 TextSeq.RefArray
     --------- ---------
          1980     63716

# RTHeapStats.ReportReachable is called to determine the number
# of active, reachable, objects (i.e. those which would not be
# garbage collected). Of the 1980 objects on the heap, only slightly
# more than half are still alive.

(gdb) print RTHeapStats__ReportReachable()

HEAP: 0xde000 .. 0x120000 => 0.2 Mbytes

Module globals:
 # objects   # bytes  unit
 ---------  --------  -----------------
      1007     26260  Query.m3
        11     16656  Stdio.i3
         2      2012  RTutils.m3
         3       440  RTHeapPosix.m3
...

Global variable roots:
 # objects   # bytes         ref type                location
 ---------  --------  ---------- -----------------   ------------------------
      1001     24108  0x11e004 TextSeq.RefArray    Query.m3 + 32
       513     12300  0xe2004 TextSeq.RefArray    Query.m3 + 36
         3      4168  0xdea10 FileRd.T            Stdio.i3 + 28
         3      4168  0xdfa48 FileWr.T            Stdio.i3 + 32
...
         1        24  0xf2aec Pathname.T          Query.m3 + 56
         1        20  0xf2b04 Pathname.T          Query.m3 + 44
         1        20  0xe50e0 Pathname.T          Query.m3 + 48
         1        20  0xf2ad8 Pathname.T          Query.m3 + 52
         1        16  0xde09c Thread.Mutex        RTHeapPosix.m3 + 464
...

# KeyArray and oldKeyArray point to 1001 and 513 objects respectively
#
# The program is rerun. PrintDbase is executed one line at a time

(gdb) run
Starting program: LINUX/QueryDbase @M3novm <example2.in >/dev/null

Breakpoint 1, Query.PrintDbase () at Query.m3:22
22	    key, tmp: TEXT;
(gdb) set lang m3
(gdb) n
23	    cursor: CARDINAL := 0;
(gdb) n
25	    key := Dbase.FirstKey(db);
(gdb) n
26	    WHILE key # NIL DO
(gdb) n
27	      IF cursor > LAST(keyArray^) THEN
(gdb) n
32	      keyArray[cursor] := key;
(gdb) n
33	      INC(cursor);

# The type and content of variables may be displayed.

(gdb) print key
$5 =  "key515"
(gdb) ptype key
type = BRANDED "Text 1.0" REF Convert.Buffer
(gdb) ptype key^
type = ARRAY OF CHAR
(gdb) 

# This text object (key) is noted to check if it is free later on.

(gdb) print RTHeapDebug.Free(key)
$6 = 0
(gdb) finish
Run till exit from #0  Query.PrintDbase (db=16_e50f4) at Query.m3:33
16_1945 in Query.m3.MAIN (phase=3) at Query.m3:111
111	      PrintDbase(db);
(gdb) print RTHeapDebug__CheckHeap()
Path to 'free' object:
   Ref in root at address 0x48644...
   Object of type TextSeq.RefArray at address 11e34c...
   Free object of type Pathname.T at address 11ea90...
$9 = void
(gdb) set lang m3
(gdb) print keyArray
$10 = 16_11e34c

# Even though the PrintDbase procedure is finished, keyArray still
# exists and maintains alive the keys printed.

In the above example, two allocation problems are identified. For this, the number of expected active objects may be checked against the information produced by RTHeapStats.ReportReachable. Another lead is to assert that a certain object is free and let RTHeapDebug.CheckHeap determine if a path still reaches the object and keeps it from being garbage collected.

A first problem is that the oldKeyArray should be released with oldKeyArray := NIL; once it has been used to initialize the new keyArray. A second problem is that the keyArray is not released once PrintDbase() is finished. It may be set to NIL, or if it is to be reused, its content should be set to NIL (keyArray[0] := NIL, keyArray[1]...).

It is interesting to note that keyArray reaches 1001 objects and oldKeyArray 513 even though the Query module as a whole reaches only 1007 objects. The explanation is that the 512 keys stored in oldKeyArray are also stored in keyArray and thus are not distinct objects.

Graphical tools

Three tools are available to graphically display statistics about Modula-3 programs.

Embedded language

The QueryDbase example accepts simple commands from the user. Storing intermediate results for further queries, defining new commands and other similar enhancements are not supported and would require a significant programming effort to incorporate.

This is where using an existing embeddable interpreter provides a much enhanced functionality with little efforts. An embeddable interpreter for the simple yet powerful Obliq language is available. It is written in Modula-3 and is easy to interface to Modula-3 programs.

The QueryDbase program may thus be replaced by an Obliq interpreter where access functions to the database package have been added. The new program is named OblQuery. Its m3makefile and implementation Main.m3 follow.


import("synloc")
import("obliqrt")
import("obliqlibm3")
import("obliq")

override("database","../..")
import("database")

implementation("Main")

Program("OblQuery")


MODULE Main;
IMPORT ObliqOnline;

IMPORT ObLib, ObLibM3, ObLibM3Help, Text, SynWr, SynLocation,
    ObCommand, ObValue, Dbase;

(* Obliq objects are defined for the Database objects and commands *)

TYPE 
  ValDbase = ObValue.ValAnything BRANDED OBJECT
      value: Dbase.T;
    OVERRIDES Is := IsDbase; Copy := CopyDbase;
    END;

  OpCodes = ARRAY OF ObLib.OpCode;

  QueryCode = {Error, Create, Open, Close, Fetch, Store, Delete, First, Next};

  QueryOpCode = ObLib.OpCode OBJECT
        code: QueryCode;
      END;
    
  PackageQuery = ObLib.T OBJECT
      OVERRIDES
        Eval:=EvalQuery;
      END;

(* Support for Help messages *)

CONST 
  Greetings = 
      "  OblQuery (obliq with database libraries) (say \'help;\' for help)";
  HelpSummary =
      "  db                (the built-in database library)\n";
  HelpText = 
      "  create(file: TEXT): Dbase.T\n" &
      "  open(file: TEXT): Dbase.T\n" &
      "  close(db: Dbase.T)\n" &
      "  fetch(db: Dbase.T; key: TEXT): TEXT\n" &
      "  store(db: Dbase.T; key, content: TEXT)\n" &
      "  delete(db: Dbase.T; key: TEXT)\n" &
      "  first(db: Dbase.T): TEXT\n" &
      "  next(db: Dbase.T): TEXT\n";

VAR queryException: ObValue.ValException;

PROCEDURE HelpQuery(self: ObCommand.T; arg: TEXT; 
      <*UNUSED*>data: REFANY:=NIL)  =
  BEGIN
    IF Text.Equal(arg, "!") THEN
      SynWr.Text(SynWr.out,HelpSummary);
    ELSIF Text.Equal(arg, "?") THEN
      SynWr.Text(SynWr.out, HelpText);
      SynWr.NewLine(SynWr.out);
    ELSE
      SynWr.Text(SynWr.out, "Command " & self.name & ": bad argument: " & arg);
      SynWr.NewLine(SynWr.out);
    END;
  END HelpQuery;

PROCEDURE IsDbase(self: ValDbase; other: ObValue.ValAnything): BOOLEAN =
  BEGIN
    TYPECASE other OF ValDbase(oth)=> RETURN self.value = oth.value;
    ELSE RETURN FALSE END;
  END IsDbase;

PROCEDURE CopyDbase(<*UNUSED*>self: ObValue.ValAnything; 
    <*UNUSED*>tbl: ObValue.Tbl;
    loc: SynLocation.T): ObValue.ValAnything RAISES {ObValue.Error} =
  BEGIN
    ObValue.RaiseError("Cannot copy db database", loc);
  END CopyDbase;

PROCEDURE NewQueryOC(name: TEXT; arity: INTEGER; code: QueryCode)
      : QueryOpCode =
  BEGIN
    RETURN NEW(QueryOpCode, name:=name, arity:=arity, code:=code);
  END NewQueryOC;

(* Define and register the new commands in the db package *)

PROCEDURE PackageSetup() =
  VAR opCodes: REF OpCodes;
  BEGIN
    opCodes := NEW(REF OpCodes, NUMBER(QueryCode));
    opCodes^ :=
      OpCodes{
        NewQueryOC("failure", -1, QueryCode.Error),
        NewQueryOC("create", 1, QueryCode.Create),
        NewQueryOC("open", 1, QueryCode.Open),
        NewQueryOC("close", 1, QueryCode.Close),
        NewQueryOC("fetch", 2, QueryCode.Fetch),
        NewQueryOC("store", 3, QueryCode.Store),
        NewQueryOC("delete", 2, QueryCode.Delete),
        NewQueryOC("first", 1, QueryCode.First),
        NewQueryOC("next",1, QueryCode.Next)
      };
    ObLib.Register(
      NEW(PackageQuery, name:="db", opCodes:=opCodes));
    queryException := NEW(ObValue.ValException, name:="db_failure");
    ObLib.RegisterHelp("db", HelpQuery);
  END PackageSetup;

(* For each database command, check the arguments, call the
   relevant procedure and wrap the result in Obliq objects. *)

PROCEDURE EvalQuery(self: PackageQuery; opCode: ObLib.OpCode; 
    <*UNUSED*>arity: ObLib.OpArity; READONLY args: ObValue.ArgArray; 
    loc: SynLocation.T)
    : ObValue.Val RAISES {ObValue.Error, ObValue.Exception} =
  VAR 
    text1, text2: TEXT;
    dbase: Dbase.T;
  BEGIN
    CASE NARROW(opCode, QueryOpCode).code OF
    | QueryCode.Error => 
        RETURN queryException;

    | QueryCode.Create =>
        TYPECASE args[1] OF | ObValue.ValText(node) => text1:=node.text;
        ELSE ObValue.BadArgType(1, "text", self.name, opCode.name, loc); END;
        dbase := Dbase.Create(text1);
        IF dbase = NIL THEN
          ObValue.RaiseException(queryException, opCode.name, loc);
        END;
        RETURN NEW(ValDbase, what:="<a database>", picklable:=FALSE,
            value:=dbase);

    | QueryCode.Open =>
        TYPECASE args[1] OF | ObValue.ValText(node) => text1:=node.text;
        ELSE ObValue.BadArgType(1, "text", self.name, opCode.name, loc); END;
        dbase := Dbase.Open(text1);
        IF dbase = NIL THEN
          ObValue.RaiseException(queryException, opCode.name, loc);
        END;
        RETURN NEW(ValDbase, what:="<a database>", picklable:=FALSE,
            value:=dbase);

    | QueryCode.Close =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        Dbase.Close(dbase);
        RETURN ObValue.valOk;

    | QueryCode.Fetch =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        TYPECASE args[2] OF | ObValue.ValText(node) => text1:=node.text;
        ELSE ObValue.BadArgType(2, "text", self.name, opCode.name, loc); END;
        text2 := Dbase.Fetch(dbase,text1);
        IF text2 = NIL THEN
          ObValue.RaiseException(queryException, opCode.name, loc);
        END;
        RETURN NEW(ObValue.ValText, text:=text2);

    | QueryCode.Store =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        TYPECASE args[2] OF | ObValue.ValText(node) => text1:=node.text;
        ELSE ObValue.BadArgType(2, "text", self.name, opCode.name, loc); END;
        TYPECASE args[3] OF | ObValue.ValText(node) => text2:=node.text;
        ELSE ObValue.BadArgType(3, "text", self.name, opCode.name, loc); END;
        Dbase.Store(dbase,text1,text2);
        RETURN ObValue.valOk;

    | QueryCode.Delete =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        TYPECASE args[2] OF | ObValue.ValText(node) => text1:=node.text;
        ELSE ObValue.BadArgType(2, "text", self.name, opCode.name, loc); END;
        Dbase.Delete(dbase,text1);
        RETURN ObValue.valOk;

    | QueryCode.First =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        text1 := Dbase.FirstKey(dbase);
        IF text1 = NIL THEN
          ObValue.RaiseException(queryException, opCode.name, loc);
        END;
        RETURN NEW(ObValue.ValText, text:=text1);

    | QueryCode.Next =>
        TYPECASE args[1] OF | ValDbase(node) => dbase:=node.value;
        ELSE ObValue.BadArgType(1, "database", self.name, opCode.name, loc); 
        END;
        text1 := Dbase.NextKey(dbase);
        IF text1 = NIL THEN
          ObValue.RaiseException(queryException, opCode.name, loc);
        END;
        RETURN NEW(ObValue.ValText, text:=text1);

      ELSE
        ObValue.BadOp(self.name, opCode.name, loc);
      END;
  END EvalQuery;

BEGIN
  ObliqOnline.Setup();
  ObLibM3.PackageSetup();
  ObLibM3Help.Setup();
  PackageSetup();

  ObliqOnline.Interact(ObliqOnline.New(Greetings));
END Main.

The full Obliq language now becomes accessible to interact with the database procedures, and to store and process the results.


cassis>LINUX/OblQuery

  OblQuery (obliq with database libraries) (say 'help;' for help)

- help db;
  create(file: TEXT): Dbase.T
  open(file: TEXT): Dbase.T
  close(db: Dbase.T)
  fetch(db: Dbase.T; key: TEXT): TEXT
  store(db: Dbase.T; key, content: TEXT)
  delete(db: Dbase.T; key: TEXT)
  first(db: Dbase.T): TEXT
  next(db: Dbase.T): TEXT
- let d = db_create("testdb");
let d = <a database>
- db_store(d,"key1","content1");
ok
- db_store(d,"key2","content2");
ok
- db_store(d,"key3","content3");
ok
- db_fetch(d,"key2");
"content2"
- db_fetch(d,"bad key");
Uncaught exception (input line 8, char 10)
db_failure (fetch)
Error detected (last input line, char 10)
- db_first(d);
"key1"
- ^D
cassis>


Copyright 1995 Michel Dagenais, dagenais@vlsi.polymtl.ca, Wed Mar 8 14:41:03 EST 1995