Copyright 1995-96 Critical Mass, Inc. All rights reserved.
Also (C) 1992, Digital Equipment Corporation
All rights reserved.
See the file COPYRIGHT for a full description.
Last modified on Tue Mar 7 14:38:18 PST 1995 by kalsow
Enhanced by Peter Klein (pk@i3.informatik.rwth-aachen.de) to
parse procedure signatures and connect procedure declarations
in interfaces with their implmentations. - Mar 7, 1995
MODULE BrowserDB;
IMPORT FmtTime, FS, IntRefTbl, IntList, OSError;
IMPORT Pathname, Rd, RTCollector, Text, TextRd, Thread, Wr;
defined in this package...
IMPORT Default, ClassDir, ConfigItem, Derived, Dir, ErrLog, HTML, ID;
IMPORT Loc, Node, OS, Pkg, PkgRoot, Source, Type, Wx;
FROM LexMisc IMPORT ReadUID, SkipBlanks, FmtUID;
TYPE SK = Source.Kind;
------------------------------------------------------ initialization ---
PROCEDURE Init () =
(* create an initial, empty database *)
BEGIN
InitDB (db);
END Init;
------------------------------------------------------ periodic refresh ---
VAR
mu := NEW (MUTEX);
refresh_busy := FALSE;
PROCEDURE Refresh (wx: Wx.T := NIL)
RAISES {Thread.Alerted} =
VAR
now := OS.Now ();
date := FmtTime.Short (OS.FileToM3Time (now));
BEGIN
IF (n_updates < 1) THEN
(* give the browser a chance to start... *)
Thread.Pause (5.0D0);
END;
LOCK mu DO
IF NOT refresh_busy THEN
ErrLog.Msg ("Scanning Packages: ", date, "...");
refresh_busy := TRUE;
TRY
ScanPackages (wx, now);
FINALLY
INC (n_updates);
last_update := now;
refresh_busy := FALSE;
RTCollector.Collect ();
ErrLog.Msg ("scan done: ", FmtTime.Short (OS.FileToM3Time (OS.Now())));
END;
ELSE
ErrLog.Msg ("still scanning: ", date, "...");
END;
END;
END Refresh;
--------------------------------------------------- single root scan ---
VAR
export_mu := NEW (MUTEX);
PROCEDURE ScanRoot (pkg_root: PkgRoot.T; wx: Wx.T)
RAISES {Thread.Alerted} =
VAR s: Scan;
BEGIN
s.now := OS.Now ();
s.wx := wx;
s.n_pkgs := 0;
s.n_unit_refs := 0;
s.unit_refs := NEW (Derived.NodeRefSet, 100);
s.pkg_root := pkg_root;
LOCK export_mu DO
s.new := db; (* start with the current database! *)
ScanRepository (s);
db := s.new;
END;
(* make sure the collector has a chance *)
ResetDB (s.new);
END ScanRoot;
--------------------------------------------------- single package scan ---
PROCEDURE ScanOne (nm: TEXT; pkg_root: PkgRoot.T; wx: Wx.T)
RAISES {Thread.Alerted} =
VAR s: Scan; path: TEXT;
BEGIN
s.now := OS.Now ();
s.wx := wx;
s.n_pkgs := 0;
s.n_unit_refs := 0;
s.unit_refs := NEW (Derived.NodeRefSet, 100);
s.pkg_root := pkg_root;
s.cur_pkg := ID.Add (nm);
path := OS.MakePath (s.pkg_root.path, nm);
LOCK export_mu DO
s.new := db; (* start with the current database! *)
IF OS.IsDirectory (path) THEN ScanPkg (s, path); END;
db := s.new;
END;
(* make sure the collector has a chance *)
ResetDB (s.new);
END ScanOne;
------------------------------------------------------- package browser ---
TYPE
Scan = RECORD
now : OS.FileTime;
wx : Wx.T;
new : DataBase;
n_pkgs : CARDINAL;
pkg_root : PkgRoot.T;
cur_pkg : ID.T;
n_unit_refs : INTEGER;
unit_refs : Derived.NodeRefSet;
END;
PROCEDURE ScanPackages (wx: Wx.T := NIL; now: OS.FileTime)
RAISES {Thread.Alerted} =
VAR s: Scan;
BEGIN
s.now := now;
s.wx := wx;
s.n_pkgs := 0;
s.n_unit_refs := 0;
s.unit_refs := NEW (Derived.NodeRefSet, 100);
InitDB (s.new);
AddBuiltinTypes (s);
IF (n_updates < 1) THEN
(* this is the first database load, might as well let the
user see stuff as soon as we have it.... *)
db := s.new;
END;
s.pkg_root := PkgRoot.First ();
WHILE (s.pkg_root # NIL) DO
ScanRepository (s);
s.pkg_root := s.pkg_root.sibling;
END;
LOCK export_mu DO
db := s.new; (* export the new database *)
END;
Thread.Pause (0.5D0);
OutWx (s, "<P>Package scan completed.\n");
(* make sure the collector has a chance *)
ResetDB (s.new);
END ScanPackages;
PROCEDURE ScanRepository (VAR s: Scan)
RAISES {Thread.Alerted} =
VAR iter: FS.Iterator; nm, path: TEXT;
BEGIN
OutWx (s, "<H2>");
IF (s.wx # NIL) THEN
TRY HTML.PutImg (Node.ClassIcon[s.pkg_root.class()], s.wx);
EXCEPT Wr.Failure => s.wx := NIL;
END;
END;
OutWx (s, " \"", s.pkg_root.printname(), "\" root");
OutWx (s, " (", s.pkg_root.path, ")</H2>\n<PRE>\n");
s.n_pkgs := 0;
TRY
iter := FS.Iterate (s.pkg_root.path);
TRY
WHILE iter.next (nm) DO
path := OS.MakePath (s.pkg_root.path, nm);
IF OS.IsDirectory (path) THEN
s.cur_pkg := ID.Add (nm);
ScanPkg (s, path);
END;
END;
FINALLY
iter.close ();
END;
EXCEPT OSError.E (ec) =>
ErrLog.Msg ("trouble scanning packages in ",
s.pkg_root.path, OS.Err (ec));
END;
IF (s.n_pkgs > 0) THEN OutWx (s, "\n"); END;
OutWx (s, "</PRE>\n<HR>\n");
END ScanRepository;
----------------------------------------------------------- packages ---
PROCEDURE ScanPkg (VAR s: Scan; path: TEXT)
RAISES {Thread.Alerted} =
VAR old_pkg, new_pkg: Pkg.T;
BEGIN
new_pkg := FindPkg (s.new.packages, s.cur_pkg, s.pkg_root);
IF (new_pkg = NIL) THEN
old_pkg := FindPkg (db.packages, s.cur_pkg, s.pkg_root);
IF (old_pkg = NIL) OR PkgChanged (old_pkg, path)
THEN new_pkg := ScanNewPkg (s, path);
ELSE new_pkg := old_pkg;
END;
AddNewPkg (s, new_pkg);
AddPkgNames (s, new_pkg);
NotePkg (s, new_pkg);
IF (n_updates > 0) THEN
Thread.Pause (0.2d0); (* try not to swamp the file system *)
END;
ELSIF (s.new.packages = db.packages) THEN
(* we're scanning directly into the existing database! *)
IF PkgChanged (new_pkg, path) THEN
new_pkg := ScanNewPkg (s, path);
AddNewPkg (s, new_pkg);
END;
AddPkgNames (s, new_pkg);
NotePkg (s, new_pkg);
IF (n_updates > 0) THEN
Thread.Pause (0.2d0); (* try not to swamp the file system *)
END;
END;
END ScanPkg;
PROCEDURE PkgChanged (old: Pkg.T; path: TEXT): BOOLEAN =
BEGIN
RETURN DirChanged (old, path, in_src_dir := FALSE);
END PkgChanged;
PROCEDURE ScanNewPkg (VAR s: Scan; path: TEXT): Pkg.T
RAISES {Thread.Alerted} =
VAR pkg := NEW (Pkg.T, name := s.cur_pkg, parent := s.pkg_root);
BEGIN
ScanNewDir (s, pkg, path, in_src_dir := FALSE);
RETURN pkg;
END ScanNewPkg;
PROCEDURE FindPkg (pkgs: IntRefTbl.T; nm: ID.T; root: PkgRoot.T): Pkg.T =
VAR ref: REFANY; nd: Node.List; pkg: Pkg.T;
BEGIN
IF pkgs.get (nm, ref) THEN
nd := ref;
WHILE (nd # NIL) DO
pkg := nd.head;
IF (pkg.parent = root) THEN RETURN pkg; END;
nd := nd.tail;
END;
END;
RETURN NIL;
END FindPkg;
PROCEDURE AddNewPkg (VAR s: Scan; pkg: Pkg.T) =
VAR ref: REFANY; nd: Node.List; p: Pkg.T;
BEGIN
IF s.new.packages.get (pkg.name, ref) THEN
nd := ref;
WHILE (nd # NIL) DO
p := nd.head;
IF (p = pkg) THEN RETURN; END;
IF (p.parent = pkg.parent) THEN
nd.head := pkg;
ReplacePkg (s.pkg_root, p, pkg);
RETURN;
END;
nd := nd.tail;
END;
(* no match => splice the new guy into the existing list *)
nd := ref;
nd.tail := NEW (Node.List, head := pkg, tail := nd.tail);
ELSE
EVAL s.new.packages.put (pkg.name, NEW (Node.List, head := pkg, tail := NIL));
END;
ReplacePkg (s.pkg_root, NIL, pkg);
END AddNewPkg;
PROCEDURE ReplacePkg (root: PkgRoot.T; old, new: Pkg.T) =
VAR n := root.contents; last_n: Node.Named_T := NIL;
BEGIN
WHILE (n # NIL) DO
IF (n = new) THEN
(* this root already has the new package. *)
RETURN;
END;
IF (n = old) THEN
new.sibling := old.sibling;
IF last_n = NIL
THEN root.contents := new;
ELSE last_n.sibling := new;
END;
RETURN;
END;
last_n := n;
n := n.sibling;
END;
new.sibling := root.contents;
root.contents := new;
END ReplacePkg;
PROCEDURE NotePkg (VAR s: Scan; pkg: Pkg.T)
RAISES {Thread.Alerted} =
VAR
nm := ID.ToText (pkg.name);
len := Text.Length (nm);
BEGIN
IF ConfigItem.X [ConfigItem.T.Verbose_log].bool THEN
ErrLog.Msg ("scanned: ", nm);
END;
OutWx (s, "<a href=\"/", ID.ToText (pkg.parent.arcname()));
OutWx (s, "/", nm, "\">");
IF len > MaxPad THEN
nm := Text.Sub (nm, 0, MaxPad-3) & "...";
len := MaxPad;
END;
OutWx (s, nm, "</A> ", pad := MaxPad - len);
INC (s.n_pkgs);
IF (s.n_pkgs = 4) THEN
OutWx (s, "\n");
s.n_pkgs := 0;
END;
END NotePkg;
------------------------------------------------- directories ---
PROCEDURE DirChanged (old: Dir.T; path: TEXT; in_src_dir: BOOLEAN): BOOLEAN =
VAR n: Node.Named_T; file: TEXT;
BEGIN
IF (old = NIL) OR (OS.LastModified (path) > old.scanned) THEN
RETURN TRUE;
END;
in_src_dir := in_src_dir OR OS.FileNameEq ("src", ID.ToText (old.name));
IF NOT Default.on_unix THEN
(* Windows doesn't update the directory's timestamp when
files are added or deleted, so we need to explicitly
rescan the directory. Damn Windows. *)
IF DirContentsChanged (old, path, in_src_dir) THEN RETURN TRUE; END;
END;
(* if we got this far, the set of names within this
directory have not changed since the last scan.
We just need to make sure they still point to
the right stuff. *)
n := old.contents;
WHILE (n # NIL) DO
TYPECASE n OF
| Dir.T (x) =>
file := OS.MakePath (path, ID.ToText (x.name));
IF DirChanged (x, file, in_src_dir) THEN RETURN TRUE; END;
| Derived.T (x) =>
IF DerivedChanged (x, path) THEN RETURN TRUE; END;
ELSE (* skip *)
END;
n := n.sibling;
END;
(* nothing changed! *)
RETURN FALSE;
END DirChanged;
TYPE
DirNames = REF ARRAY OF TEXT;
PROCEDURE DirContentsChanged (dir: Dir.T; path: TEXT;
in_src_dir: BOOLEAN): BOOLEAN =
VAR
iter : FS.Iterator;
file : TEXT;
names : DirNames;
n_names : INTEGER := MapNames (dir, names);
i : CARDINAL;
BEGIN
TRY
iter := FS.Iterate (path);
TRY
WHILE iter.next (file) DO
i := 0;
LOOP
IF (i >= n_names) THEN
(* we found a new name... *)
IF IsSourceName (path, file, in_src_dir) THEN
RETURN TRUE;
END;
EXIT;
ELSIF OS.FileNameEq (names[i], file) THEN
(* we found a match => delete this name and try the next file *)
DEC (n_names);
names [i] := names [n_names];
names [n_names] := NIL;
EXIT;
END;
INC (i);
END; (*LOOP*)
END; (* WHILE iter.next *)
FINALLY
iter.close ();
END;
EXCEPT OSError.E (ec) =>
ErrLog.Msg ("trouble scanning directory ", path, OS.Err (ec));
END;
RETURN (n_names > 0);
END DirContentsChanged;
PROCEDURE MapNames (dir: Dir.T; VAR names: DirNames): CARDINAL =
VAR cnt := 0; n: Node.Named_T;
BEGIN
(* count the entries that correspond to real directory entries *)
n := dir.contents;
LOOP
TYPECASE n OF
| NULL => EXIT;
| ClassDir.T => (* skip this pseudo entry *)
ELSE INC (cnt);
END;
n := n.sibling;
END;
names := NEW (DirNames, cnt);
(* finally, map the entries that correspond to real directory entries *)
n := dir.contents; cnt := 0;
LOOP
TYPECASE n OF
| NULL => EXIT;
| ClassDir.T => (* skip this pseudo entry *)
ELSE names [cnt] := ID.ToText (n.name); INC (cnt);
END;
n := n.sibling;
END;
<*ASSERT cnt = NUMBER (names^) *>
RETURN cnt;
END MapNames;
CONST
KnownExts = ARRAY [0..15] OF TEXT {
"i3", "m3", "ig", "mg", "c", "h", "tmpl",
"NO.MISC.EXTENSION", "io", "mo", "o", "obj",
"lib", "a", "exe", "bak" };
IsDirFile = ARRAY [0..15] OF BOOLEAN {
TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,
FALSE, FALSE, FALSE, FALSE, FALSE,
TRUE, TRUE, TRUE, FALSE };
PROCEDURE IsSourceName (path, file: TEXT; in_src_dir: BOOLEAN): BOOLEAN =
VAR ext: TEXT;
BEGIN
(* derived file? *)
IF OS.FileNameEq (file, ".M3WEB") THEN RETURN FALSE; END;
(* source file? *)
ext := Pathname.LastExt (file);
IF (ext # NIL) THEN
FOR i := FIRST (KnownExts) TO LAST (KnownExts) DO
IF OS.FileNameEq (KnownExts[i], ext) THEN
RETURN IsDirFile [i];
END;
END;
END;
(* special source file? *)
IF OS.FileNameEq (file, "m3makefile")
OR OS.FileNameEq (file, "m3overrides") THEN
RETURN TRUE;
END;
IF OS.FileNameEq (file, "COPYRIGHT") THEN
RETURN FALSE;
END;
(* subdirectory? *)
path := OS.MakePath (path, file);
IF OS.IsDirectory (path) THEN RETURN TRUE; END;
IF (in_src_dir) AND NOT IsEditorTempFile (file) THEN
RETURN TRUE;
END;
RETURN FALSE;
END IsSourceName;
PROCEDURE ScanNewDir (VAR s: Scan; dir: Dir.T; path: TEXT; in_src_dir: BOOLEAN)
RAISES {Thread.Alerted} =
VAR iter: FS.Iterator; file: TEXT; n: Node.Named_T;
BEGIN
dir.scanned := s.now;
dir.contents := NIL;
in_src_dir := in_src_dir OR OS.FileNameEq ("src", ID.ToText (dir.name));
TRY
iter := FS.Iterate (path);
TRY
WHILE iter.next (file) DO
n := ScanFile (s, path, file, in_src_dir);
IF (n # NIL) THEN
n.parent := dir;
n.sibling := dir.contents;
dir.contents := n;
END;
END;
FINALLY
iter.close ();
END;
EXCEPT OSError.E (ec) =>
ErrLog.Msg ("trouble scanning directory ", path, OS.Err (ec));
END;
AddClassEntries (dir);
END ScanNewDir;
PROCEDURE ScanFile (VAR s: Scan; path, file: TEXT;
in_src_dir: BOOLEAN): Node.Named_T
RAISES {Thread.Alerted} =
VAR ext: TEXT; dir: Dir.T;
BEGIN
(* derived file? *)
IF OS.FileNameEq (file, ".M3WEB") THEN
RETURN ScanNewDerived (s, path);
END;
(* source file? *)
ext := Pathname.LastExt (file);
IF (ext # NIL) THEN
FOR i := FIRST (KnownExts) TO LAST (KnownExts) DO
IF OS.FileNameEq (KnownExts[i], ext) THEN
IF (i < NUMBER (SK)) THEN
RETURN NEW (Source.T, name := ID.Add (file), kind := VAL (i, SK));
END;
RETURN NIL;
END;
END;
END;
(* special source file? *)
IF OS.FileNameEq (file, "m3makefile")
OR OS.FileNameEq (file, "m3overrides") THEN
RETURN NEW (Source.T, name := ID.Add (file), kind := SK.Quake);
END;
IF OS.FileNameEq (file, "COPYRIGHT") THEN
RETURN NIL;
END;
(* subdirectory? *)
path := OS.MakePath (path, file);
IF OS.IsDirectory (path) THEN
dir := NEW (Dir.T, name := ID.Add (file));
ScanNewDir (s, dir, path, in_src_dir);
RETURN dir;
END;
IF (in_src_dir) AND NOT IsEditorTempFile (file) THEN
RETURN NEW (Source.T, name := ID.Add (file), kind := SK.Other);
END;
RETURN NIL;
END ScanFile;
PROCEDURE IsEditorTempFile (nm: TEXT): BOOLEAN =
VAR last_ch := Text.GetChar (nm, Text.Length (nm) - 1);
BEGIN
RETURN (last_ch = '#') OR (last_ch = '~');
END IsEditorTempFile;
TYPE ClassMap = ARRAY Node.Class OF BOOLEAN;
PROCEDURE AddClassEntries (dir: Dir.T) =
VAR seen: ClassMap;
BEGIN
(* build the class pseudo-directories *)
FOR c := FIRST (seen) TO LAST (seen) DO seen[c] := FALSE; END;
ScanDirClasses (dir, seen);
FOR c := FIRST (seen) TO LAST (seen) DO
IF seen[c] AND (Node.ClassID[c] # ID.NoID) THEN
dir.contents := NEW (ClassDir.T, name := Node.ClassID[c], kind := c,
parent := dir, sibling := dir.contents);
END;
END;
END AddClassEntries;
PROCEDURE ScanDirClasses (dir: Dir.T; VAR seen: ClassMap) =
VAR n := dir.contents; c: Node.Class;
BEGIN
WHILE (n # NIL) DO
c := n.class ();
seen[c] := TRUE;
TYPECASE n OF
| Dir.T(x) =>
ScanDirClasses (x, seen);
| Derived.T(x) =>
FOR i := FIRST (x.seen) TO LAST (x.seen) DO
IF x.seen[i] THEN seen [Source.NodeClass [i]] := TRUE; END;
END;
ELSE
(* skip *)
END;
n := n.sibling;
END;
END ScanDirClasses;
-------------------------------------------------------- derived files ---
TYPE
FileInfo = RECORD name: TEXT; time: OS.FileTime; END;
DerivedInfo = RECORD m3web, m3exports: FileInfo; END;
PROCEDURE DerivedChanged (old: Derived.T; dir_path: TEXT): BOOLEAN =
VAR info: DerivedInfo;
BEGIN
IF (old = NIL) THEN RETURN TRUE; END;
info := GetDerivedInfo (dir_path);
IF (info.m3exports.time = OS.NO_TIME)
OR (info.m3web.time = OS.NO_TIME) THEN
(* it looks like it's been deleted... *)
RETURN TRUE;
END;
RETURN (info.m3web.time > old.scanned)
OR (info.m3exports.time > old.scanned);
END DerivedChanged;
PROCEDURE ScanNewDerived (VAR s: Scan; dir_path: TEXT): Derived.T
RAISES {Thread.Alerted} =
VAR
x := NEW (Derived.T);
info := GetDerivedInfo (dir_path);
BEGIN
x.is_pgm := FALSE;
x.scanned := MAX (info.m3web.time, info.m3exports.time);
ScanExports (s, x, info.m3exports.name);
IF (x.name = ID.NoID) THEN
ErrLog.Msg ("unable to determine name of derived object in ", dir_path);
END;
ScanWebInfo (s, info.m3web.name, rd := NIL);
RETURN x;
END ScanNewDerived;
PROCEDURE GetDerivedInfo (path: TEXT): DerivedInfo =
VAR info: DerivedInfo;
BEGIN
info.m3web.name := OS.MakePath (path, ".M3WEB");
info.m3web.time := OS.LastModified (info.m3web.name);
info.m3exports.name := OS.MakePath (path, ".M3EXPORTS");
info.m3exports.time := OS.LastModified (info.m3exports.name);
RETURN info;
END GetDerivedInfo;
------------------------------------------------------------ .M3EXPORTS ---
TYPE
ParseWord = RECORD start, len: INTEGER; END;
ScanLine = ARRAY [0..511] OF CHAR;
VAR
add_intf_id := ID.Add ("_map_add_interface");
add_mod_id := ID.Add ("_map_add_module");
add_gintf_id := ID.Add ("_map_add_generic_interface");
add_gmod_id := ID.Add ("_map_add_generic_module");
add_c_id := ID.Add ("_map_add_c_source");
add_h_id := ID.Add ("_map_add_h_source");
define_lib_id := ID.Add ("_define_lib");
define_pgm_id := ID.Add ("_define_pgm");
PROCEDURE ScanExports (VAR s: Scan; pgm: Derived.T; file: TEXT)
RAISES {Thread.Alerted} =
VAR rd := OS.OpenRd (file);
BEGIN
IF (rd = NIL) THEN RETURN; END;
TRY
TRY
ParseExports (s, pgm, rd);
EXCEPT Rd.Failure(ec) =>
ErrLog.Msg ("Trouble reading \"", file, "\"", OS.Err (ec));
END;
FINALLY
OS.CloseRd (rd);
END;
END ScanExports;
PROCEDURE ParseExports (VAR s: Scan; pgm: Derived.T; rd: Rd.T)
RAISES {Rd.Failure, Thread.Alerted} =
VAR
key : ID.T;
len : INTEGER;
n : INTEGER;
x : ARRAY [0..9] OF ParseWord;
line : ScanLine;
BEGIN
FOR s := FIRST (pgm.seen) TO LAST (pgm.seen) DO pgm.seen[s] := FALSE; END;
WHILE NOT Rd.EOF (rd) DO
len := Rd.GetSubLine (rd, line);
n := ParseLine (line, len, x);
IF (n > 0) THEN
key := ParseID (line, x[0]);
IF (key = add_intf_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.I3] := TRUE;
ELSIF (key = add_mod_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.M3] := TRUE;
ELSIF (key = add_gintf_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.IG] := TRUE;
ELSIF (key = add_gmod_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.MG] := TRUE;
ELSIF (key = add_c_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.C] := TRUE;
ELSIF (key = add_h_id) THEN
AddUnit (s, line, x[1], x[2], x[3]);
pgm.seen [SK.H] := TRUE;
ELSIF (key = define_lib_id) THEN
pgm.is_pgm := FALSE;
pgm.name := ParseID (line, x[1]);
Derived.FixName (pgm);
ELSIF (key = define_pgm_id) THEN
pgm.is_pgm := TRUE;
pgm.name := ParseID (line, x[1]);
Derived.FixName (pgm);
END;
END;
END;
(* grab the derived object's contents *)
pgm.n_elts := s.n_unit_refs;
pgm.contents := NEW (Derived.NodeRefSet, pgm.n_elts);
pgm.contents^ := SUBARRAY (s.unit_refs^, 0, pgm.n_elts);
s.n_unit_refs := 0;
END ParseExports;
PROCEDURE ParseLine (READONLY line: ScanLine; eol: INTEGER;
VAR x: ARRAY [0..9] OF ParseWord): INTEGER =
VAR
cur := 0;
len := 0;
cnt := 0;
ch : CHAR;
BEGIN
FOR i := FIRST (x) TO LAST (x) DO x[i].start := 0; x[i].len := 0; END;
ch := line[cur]; INC (cur);
WHILE (cur <= eol) AND (ch # '\n') DO
IF (ch = '%') THEN
(* comment to end of line *)
WHILE (cur < eol) AND (ch # '\n') DO ch := line[cur]; INC (cur); END;
EXIT;
ELSIF (ch = '(') OR (ch = ',') OR (ch = ')')
OR (ch = ' ') OR (ch = '\t') OR (ch = '"') OR (ch = '\r') THEN
(* misc. punctuation *)
IF (len > 0) THEN
x [cnt].len := len;
INC (cnt);
len := 0;
END;
ELSIF (len <= 0) THEN
(* start a new word *)
x [cnt].start := cur-1;
len := 1;
ELSE
INC (len);
END;
ch := line[cur]; INC (cur);
END;
IF (len > 0) THEN
x [cnt].len := len;
INC (cnt);
len := 0;
END;
RETURN cnt;
END ParseLine;
PROCEDURE AddUnit (VAR s: Scan; READONLY line: ScanLine;
READONLY file, pkg, pkg_dir: ParseWord) =
VAR
pkg_id := ParseID (line, pkg);
dir_id := ParseID (line, pkg_dir);
file_id := ParseID (line, file);
BEGIN
IF (s.n_unit_refs >= NUMBER (s.unit_refs^)) THEN ExpandUnitRefs (s); END;
WITH z = s.unit_refs [s.n_unit_refs] DO
z.loc := Loc.Add (pkg_id, dir_id);
z.file := file_id;
END;
INC (s.n_unit_refs);
END AddUnit;
PROCEDURE ExpandUnitRefs (VAR s: Scan) =
VAR n := NUMBER (s.unit_refs^); new := NEW (Derived.NodeRefSet, n+n);
BEGIN
SUBARRAY (new^, 0, n) := s.unit_refs^;
s.unit_refs := new;
END ExpandUnitRefs;
PROCEDURE ParseID (READONLY line: ScanLine; READONLY x: ParseWord): ID.T =
BEGIN
RETURN ID.FromStr (SUBARRAY (line, x.start, x.len));
END ParseID;
---------------------------------------------------------------- .M3WEB ---
PROCEDURE ScanWebInfo (VAR s: Scan; file: TEXT; rd: Rd.T)
RAISES {Thread.Alerted} =
BEGIN
IF (rd = NIL) THEN rd := OS.OpenRd (file); END;
IF (rd = NIL) THEN RETURN; END;
TRY
TRY
ParseWebInfo (s, rd, file);
EXCEPT Rd.Failure(ec) =>
ErrLog.Msg ("Trouble reading \"", file, "\"", OS.Err (ec));
END;
FINALLY
OS.CloseRd (rd);
END;
END ScanWebInfo;
PROCEDURE ParseWebInfo (VAR s: Scan; rd: Rd.T; file: TEXT)
RAISES {Rd.Failure, Thread.Alerted} =
VAR
is_intf : BOOLEAN;
cur : INTEGER := 0;
xx : INTEGER;
len : INTEGER;
eol : INTEGER;
cur_file : ID.T;
cur_unit : ID.T;
unit : ID.T;
uid : INTEGER;
type_name : ID.T;
lhs, rhs : INTEGER;
super : INTEGER;
line : ScanLine;
BEGIN
IF (rd = NIL) THEN RETURN END;
(* skip the table of contents *)
REPEAT
line[0] := ' ';
len := Rd.GetSubLine (rd, line);
INC (cur, len);
UNTIL (Rd.EOF (rd) OR line[0] = '$');
WHILE NOT Rd.EOF (rd) DO
len := Rd.GetSubLine (rd, line);
eol := len;
WHILE (eol > 0) AND ((line[eol-1] = '\n') OR (line[eol-1] = '\r')) DO
DEC (eol);
END;
xx := 1; (* offset in the current line *)
CASE line[0] OF
| '@' => (* file name *)
cur_file := ID.FromStr (SUBARRAY (line, xx, eol-xx));
| 'A' => (* module name *)
cur_unit := UnitName (line, xx, eol-xx, FALSE);
is_intf := FALSE;
| 'B' => (* interface name *)
cur_unit := UnitName (line, xx, eol-xx, TRUE);
is_intf := TRUE;
| 'C' => (* import *)
unit := UnitName (line, xx, eol-xx, TRUE);
IF NOT is_intf OR (unit # cur_unit) THEN
NoteUse (s.new.importers, cur_unit, unit);
END;
| 'D' => (* export *)
unit := UnitName (line, xx, eol-xx, TRUE);
IF NOT is_intf OR (unit # cur_unit) THEN
NoteUse (s.new.exporters, cur_unit, unit);
END;
| 'E' => (* typename *)
uid := ReadUID (line, xx);
SkipBlanks (line, xx);
type_name := ID.FromStr (SUBARRAY (line, xx, eol-xx));
NoteTypeName (s, uid, type_name, cur_unit);
| 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'Q', 'R', 'Y' =>
uid := ReadUID (line, xx);
NoteType (s, uid, file, cur, cur_unit, line[0]);
| 'O' =>
uid := ReadUID (line, xx);
NoteType (s, uid, file, cur, cur_unit, line[0]);
NoteSubtype (s, uid, Type.ADDRESS_UID);
| 'P' =>
uid := ReadUID (line, xx);
NoteType (s, uid, file, cur, cur_unit, line[0]);
NoteSubtype (s, uid, Type.REFANY_UID);
| 'U', 'V' =>
uid := ReadUID (line, xx);
super := ReadUID (line, xx);
NoteType (s, uid, file, cur, cur_unit, line[0]);
IF (super # 0) THEN NoteSubtype (s, uid, super); END;
| 'Z' =>
lhs := ReadUID (line, xx);
SkipBlanks (line, xx);
rhs := ReadUID (line, xx);
NoteRevelation (s, lhs, rhs);
| '?' => (* builtin type *)
uid := ReadUID (line, xx);
SkipBlanks (line, xx);
type_name := ID.FromStr (SUBARRAY (line, xx, eol-xx));
NoteType (s, uid, file, cur, cur_unit, line[0]);
NoteTypeName (s, uid, type_name, cur_unit);
ELSE (* skip *)
END;
INC (cur, len);
END;
END ParseWebInfo;
PROCEDURE UnitName (READONLY line: ScanLine; start, len: INTEGER;
intf: BOOLEAN): ID.T =
CONST CC = ARRAY BOOLEAN OF CHAR { 'm', 'i' };
VAR xx: ARRAY [0..255] OF CHAR; n := MIN (NUMBER (xx), len);
BEGIN
SUBARRAY (xx, 0, n) := SUBARRAY (line, start, n);
IF (n < NUMBER (xx)) THEN xx[n] := '.'; INC (n); END;
IF (n < NUMBER (xx)) THEN xx[n] := CC[intf]; INC (n); END;
IF (n < NUMBER (xx)) THEN xx[n] := '3'; INC (n); END;
RETURN ID.FromStr (SUBARRAY (xx, 0, n));
END UnitName;
PROCEDURE NoteUse (tbl: IntRefTbl.T; impl, intf: ID.T) =
VAR ref: REFANY; ids: IntList.T;
BEGIN
IF tbl.get (intf, ref) THEN
ids := ref;
WHILE (ids # NIL) DO
IF (ids.head = impl) THEN RETURN; END;
ids := ids.tail;
END;
ids := ref;
ids.tail := IntList.Cons (impl, ids.tail);
ELSE
EVAL tbl.put (intf, IntList.List1 (impl));
END;
END NoteUse;
PROCEDURE NoteTypeName (VAR s: Scan; uid: INTEGER; name, home: ID.T) =
VAR
tipe := NewType (s, uid);
t : Type.T;
nd : Node.List;
ref : REFANY;
BEGIN
(* search for a duplicate *)
t := tipe.names;
WHILE (t # NIL) DO
IF (t.name = name) AND (t.home = home) THEN RETURN END;
t := t.alias;
END;
(* create a new name *)
t := NEW (Type.T, alias := NIL, name := name, home := home, uid := uid);
IF (tipe.names # NIL) THEN
(* preserve the "first" name *)
t.alias := tipe.names.alias;
tipe.names.alias := t;
ELSE
tipe.names := t;
END;
(* register the name in the table *)
IF s.new.type_names.get (name, ref) THEN
nd := ref;
nd.tail := NEW (Node.List, head := t, tail := nd.tail);
ELSE
EVAL s.new.type_names.put (name, NEW (Node.List, head := t, tail := NIL));
END;
END NoteTypeName;
PROCEDURE NoteType (VAR s: Scan; uid: INTEGER; file: TEXT;
start: INTEGER; home: ID.T; class: CHAR) =
VAR tipe := NewType (s, uid);
BEGIN
IF (tipe.home = ID.NoID) THEN
tipe.kind := class;
tipe.home := home;
tipe.info_file := file;
tipe.info_offset := start;
END;
END NoteType;
PROCEDURE NoteRevelation (VAR s: Scan; lhs, rhs: INTEGER) =
VAR info: Type.ObjectInfo; ref: REFANY;
BEGIN
IF s.new.objects.get (rhs, ref) THEN
info := ref;
IF (info.concrete # rhs) THEN
ErrLog.Msg ("?? ", FmtUID (lhs), " == ", FmtUID (rhs));
END;
IF (info.opaque = Type.NO_UID) THEN
info.opaque := lhs;
EVAL s.new.objects.put (lhs, info);
ELSIF (info.opaque # lhs) THEN
ErrLog.Msg ("?? ", FmtUID (lhs), " == ", FmtUID (rhs));
END;
ELSE
info := NEW (Type.ObjectInfo);
info.opaque := lhs;
info.concrete := rhs;
EVAL s.new.objects.put (lhs, info);
EVAL s.new.objects.put (rhs, info);
END;
END NoteRevelation;
PROCEDURE NoteSubtype (VAR s: Scan; subtype, super: INTEGER) =
VAR
sub := NewType (s, subtype);
sup := NewType (s, super);
sub_info := GetObjInfo (s, sub);
sup_info := GetObjInfo (s, sup);
BEGIN
IF (sub_info.supertype = Type.NO_UID) THEN
sub_info.supertype := super;
sub_info.next_peer := sup_info.subtypes;
sup_info.subtypes := subtype;
ELSIF (sub_info.supertype # super) THEN
ErrLog.Msg ("two super types for ", FmtUID(subtype),
" => ", FmtUID (sub_info.supertype) &" and "& FmtUID (super));
END;
END NoteSubtype;
PROCEDURE GetObjInfo (VAR s: Scan; tipe: Type.Info): Type.ObjectInfo =
VAR ref: REFANY; info: Type.ObjectInfo;
BEGIN
IF s.new.objects.get (tipe.uid, ref) THEN
info := ref;
ELSE
info := NEW (Type.ObjectInfo, concrete := tipe.uid);
EVAL s.new.objects.put (tipe.uid, info);
END;
RETURN info;
END GetObjInfo;
PROCEDURE NewType (VAR s: Scan; uid: INTEGER): Type.Info =
VAR tipe: Type.Info; ref: REFANY;
BEGIN
IF s.new.types.get (uid, ref) THEN
tipe := ref;
ELSE
tipe := NEW (Type.Info, uid := uid, kind := '\000');
EVAL s.new.types.put (uid, tipe);
END;
RETURN tipe;
END NewType;
-------------------------------------------------------------- name map ---
PROCEDURE AddPkgNames (VAR s: Scan; pkg: Pkg.T) =
BEGIN
AddDirNames (s, pkg);
END AddPkgNames;
PROCEDURE AddDirNames (VAR s: Scan; dir: Dir.T) =
VAR n := dir.contents;
BEGIN
WHILE (n # NIL) DO
TYPECASE n OF
| Dir.T (x) => AddDirNames (s, x);
| Derived.T (x) => AddPgmName (s, x);
| Source.T (x) => AddName (s.new.units, x);
ELSE (* skip *)
END;
n := n.sibling;
END;
END AddDirNames;
PROCEDURE AddPgmName (VAR s: Scan; pgm: Derived.T) =
BEGIN
IF (pgm # NIL) AND (pgm.name # ID.NoID) THEN
IF (pgm.is_pgm)
THEN AddName (s.new.pgms, pgm);
ELSE AddName (s.new.libs, pgm);
END;
END;
END AddPgmName;
PROCEDURE AddBuiltinTypes (VAR s: Scan)
RAISES {Thread.Alerted} =
BEGIN
ScanWebInfo (s, Type.BuiltinName, TextRd.New (Type.BuiltinInfo));
NoteSubtype (s, Type.UNROOT_UID, Type.ADDRESS_UID);
(* UNTRACED-ROOT <: ADDRESS *)
NoteSubtype (s, Type.ROOT_UID, Type.REFANY_UID);
(* ROOT <: REFANY *)
NoteSubtype (s, Type.NULL_UID, Type.REFANY_UID);
(* NULL <: REFANY *)
(*** too messy for the current data structures ****************
NoteSubtype (s, Type.NULL_UID, Type.ADDRESS_UID);
(* NULL <: ADDRESS *)
***************************************************************)
END AddBuiltinTypes;
PROCEDURE AddName (tbl: IntRefTbl.T; n: Node.Named_T) =
VAR ref: REFANY; nd: Node.List;
BEGIN
IF (n = NIL) THEN
(* skip *)
ELSIF tbl.get (n.name, ref) THEN
nd := ref;
WHILE (nd # NIL) DO
IF (nd.head = n) THEN RETURN; END;
nd := nd.tail;
END;
nd := ref;
nd.tail := NEW (Node.List, head := n, tail := nd.tail);
ELSE
EVAL tbl.put (n.name, NEW (Node.List, head := n, tail := NIL));
END;
END AddName;
------------------------------------------------------------ internal ---
PROCEDURE InitDB (VAR x: DataBase) =
BEGIN
x.packages := NEW (IntRefTbl.Default).init ();
x.libs := NEW (IntRefTbl.Default).init ();
x.pgms := NEW (IntRefTbl.Default).init ();
x.units := NEW (IntRefTbl.Default).init ();
IF (db.types # NIL) THEN
(* preserve any existing import/export & type information *)
x.exporters := db.exporters;
x.importers := db.importers;
x.types := db.types;
x.type_names := db.type_names;
x.objects := db.objects;
ELSE
x.exporters := NEW (IntRefTbl.Default).init ();
x.importers := NEW (IntRefTbl.Default).init ();
x.types := NEW (IntRefTbl.Default).init ();
x.type_names := NEW (IntRefTbl.Default).init ();
x.objects := NEW (IntRefTbl.Default).init ();
END;
END InitDB;
PROCEDURE ResetDB (VAR x: DataBase) =
BEGIN
x.packages := NIL;
x.libs := NIL;
x.pgms := NIL;
x.units := NIL;
x.exporters := NIL;
x.importers := NIL;
x.types := NIL;
x.type_names := NIL;
x.objects := NIL;
END ResetDB;
------------------------------------------------------- low-level stuff ---
CONST
MaxPad = 16;
Blanks = ARRAY [0..MaxPad] OF CHAR { ' ',' ',' ',' ',
' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
PROCEDURE OutWx (VAR s: Scan; s1,s2,s3,s4: TEXT := NIL; pad := 0)
RAISES {Thread.Alerted} =
VAR wx := s.wx;
BEGIN
IF wx = NIL THEN RETURN END;
TRY
wx.put (s1, s2, s3, s4);
IF (pad > 0) THEN
wx.putStr (SUBARRAY (Blanks, 0, pad));
END;
wx.flush ();
EXCEPT Wr.Failure =>
(* don't abort the scan, just quit trying to do any output *)
s.wx := NIL;
END;
END OutWx;
BEGIN
END BrowserDB.