MODULE****************** CONST Suffix = ARRAY BOOLEAN, BOOLEAN OF TEXT {(* standard generic module; IMPORT Rd, Text, Fmt, M3Scanner; IMPORT TextList, TextListSort, TextTextTbl; IMPORT M3DB, FilePath; CONST Begin_ref = "<A HREF=\""; End_file = ".html"; End_ref = "\">"; Goto_tag = "#"; Begin_tag = "<A NAME=\""; End_tag = "\">"; End_anchor = "</A>"; Begin_interface = "<interface>"; End_interface = "</interface>"; Begin_module = "<module>"; End_module = "</module>"; Begin_impl = "<implements>"; End_impl = "</implements>"; Begin_gen_intf = "<genericInterface>"; End_gen_intf = "</genericInterface>"; Begin_gen_impl = "<genericModule>"; End_gen_impl = "</genericModule>"; Begin_proc = "<procedure>"; End_proc = "</procedure>"; TYPE M3MarkUp 
ARRAY BOOLEAN OF TEXT { ".m3",     ".mg" },
    (*interface*)     ARRAY BOOLEAN OF TEXT { ".i3",     ".ig" }
  };
*******************)
CONST
  BeginBracket = ARRAY BOOLEAN OF TEXT { "<inModule>\n",  "<inInterface>\n" };
  EndBracket   = ARRAY BOOLEAN OF TEXT { "</inModule>\n", "</inInterface>\n" };
TYPE
  InsList = RECORD  head, tail : Insertion := NIL;  cnt: INTEGER := 0; END;
TYPE
  Info = RECORD
    path         : TEXT;
    key          : TEXT;
    lex          : M3Scanner.T;
    unit         : TEXT;
    id           : TEXT;
    id_offset    : INTEGER;
    id_length    : INTEGER;
    is_interface : BOOLEAN;
    is_generic   : BOOLEAN;
    ins          : InsList;
    choice       : InsList;
    bracket      : Insertion;
  END;
PROCEDURE Get  (rd: Rd.T;  path: TEXT): Insertion =
  VAR z: Info;
  BEGIN
    ResetCache ();
    z.path      := path;
    z.key       := NIL;
    z.lex       := NEW (M3Scanner.Default).initFromRd (rd,
                                                 skip_comments := TRUE,
                                                 split_pragmas := FALSE);
    z.unit      := NIL;
    z.id        := NIL;
    z.id_offset := -1;
    z.id_length := 0;
    z.is_interface := TRUE;
    z.is_generic   := FALSE;
    (* build a list of insertions for the header *)
    AddH (z, "<HTML>\n<HEAD>\n<TITLE>Critical Mass Modula-3: ");
    AddH (z, path);
    AddH (z, "</TITLE>\n</HEAD>\n<BODY bgcolor=\"#ffffff\">\n");
    AddH (z, "<A NAME=\"0TOP0\">\n<H2>");
    AddH (z, path);
    AddH (z, "</H2></A><HR>\n");
    AddH (z, "");   z.bracket := z.ins.tail;
    NextToken (z); (* skip the initial comment *)
    MarkUnit (z);
    (* close the unit's opening bracket *)
    z.id_offset := z.lex.offset+z.lex.length + 10000;
    AddH (z, EndBracket [z.is_interface]);
    (* add the choices *)
    Append (z.ins, z.choice);
    (* and finish (leave blank lines for xmosaic scrolling) *)
    AddH (z, "<PRE>\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n</PRE>\n");
    AddH (z, "</BODY>\n</HTML>\n");
    RETURN SortInsertions (z);
  END Get;
PROCEDURE MarkUnit  (VAR z: Info) =
  BEGIN
    (* MarkCopyright (z); There are different copyrights now... *)
    IF z.lex.token = M3Scanner.TK_Unsafe THEN NextToken (z); END;
    IF z.lex.token = M3Scanner.TK_Generic THEN
      NextToken (z); (*GENERIC*)
      IF z.lex.token = M3Scanner.TK_Interface THEN
        NextToken (z); (*INTERFACE*)
        z.is_generic   := TRUE;
        z.is_interface := TRUE;
        IF NOT GetUnitID (z) THEN RETURN; END;
        AddH (z, Begin_gen_intf);
        z.key := "generic module " & z.unit;
        IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.GenericMod (z.unit)); END;
        AddT (z, End_gen_intf);
        SkipToSemi (z);
        MarkImports (z);
        MarkDecls (z);
      ELSIF z.lex.token = M3Scanner.TK_Module THEN
        NextToken (z); (*MODULE*)
        z.is_generic   := TRUE;
        z.is_interface := FALSE;
        IF NOT GetUnitID (z) THEN RETURN; END;
        AddH (z, Begin_gen_impl);
        z.key := "generic interface " & z.unit;
        IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.GenericIntf (z.unit)); END;
        AddT (z, End_gen_impl);
        SkipToSemi (z);
        MarkImports (z);
        MarkDecls (z);
      ELSE (* error *)
        RETURN;
      END;
    ELSIF z.lex.token = M3Scanner.TK_Interface THEN
      NextToken (z); (*INTERFACE*)
      z.is_generic   := FALSE;
      z.is_interface := TRUE;
      IF NOT GetUnitID (z) THEN RETURN; END;
      AddH (z, Begin_interface);
      z.key := z.unit & "'s implementation ";
      IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.Exports (z.unit)); END;
      AddT (z, End_interface);
      IF z.lex.token = M3Scanner.TK_Semi THEN
        NextToken (z); (* ; *)
        MarkImports (z);
        MarkDecls (z);
      ELSIF z.lex.token = M3Scanner.TK_Equal THEN
        NextToken (z); (* = *)
        MarkGenericInstance (z);
      ELSE RETURN;
      END;
    ELSIF z.lex.token = M3Scanner.TK_Module THEN
      NextToken (z); (*MODULE*)
      z.is_generic   := FALSE;
      z.is_interface := FALSE;
      IF NOT GetUnitID (z) THEN RETURN; END;
      AddH (z, Begin_module);
      IF z.lex.token # M3Scanner.TK_Exports THEN
        AddH (z, Begin_impl);
        z.key := "interface " & z.unit;
        IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.Interface (z.unit)); END;
        AddT (z, End_impl);
      END;
      AddT (z, End_module);
      MarkExports (z);
      IF z.lex.token = M3Scanner.TK_Semi THEN
        NextToken (z); (* ; *)
        MarkImports (z);
        MarkDecls (z);
      ELSIF z.lex.token = M3Scanner.TK_Equal THEN
        NextToken (z); (* = *)
        MarkGenericInstance (z);
      ELSE RETURN;
      END;
    ELSE (* error *)
      RETURN;
    END;
  END MarkUnit;
PROCEDURE GetUnitID  (VAR z: Info): BOOLEAN =
  BEGIN
    IF NOT GetID (z) THEN RETURN FALSE; END;
    z.unit           := z.id;
    z.bracket.insert := BeginBracket [z.is_interface];
    (***
    z.title.insert   := z.unit & Suffix [z.is_interface, z.is_generic];
    z.header.insert  := z.title.insert;
    ***)
    RETURN TRUE;
  END GetUnitID;
PROCEDURE MarkExports  (VAR z: Info) =
  BEGIN
    IF z.lex.token = M3Scanner.TK_Exports THEN
      NextToken (z); (*EXPORTS*)
      WHILE GetIntfID (z) DO
        AddH (z, Begin_impl);
        AddT (z, End_impl);
        IF z.lex.token # M3Scanner.TK_Comma THEN EXIT END;
        NextToken (z); (* , *)
      END;
    END;
  END MarkExports;
PROCEDURE MarkImports  (VAR z: Info) =
  BEGIN
    LOOP
      IF z.lex.token = M3Scanner.TK_Import THEN
        NextToken (z); (*IMPORT*)
        WHILE GetIntfID (z) DO
          IF z.lex.token = M3Scanner.TK_As THEN
            NextToken (z); (*AS*)
            IF z.lex.token = M3Scanner.TK_Ident THEN
              NextToken (z); (*ID*)
            END;
          END;
          IF z.lex.token # M3Scanner.TK_Comma THEN EXIT END;
          NextToken (z); (* , *)
        END;
      ELSIF z.lex.token = M3Scanner.TK_From THEN
        NextToken (z); (*FROM*)
        EVAL GetIntfID (z);
      ELSE EXIT;
      END;
      SkipToSemi (z);
    END;
  END MarkImports;
PROCEDURE GetIntfID  (VAR z: Info): BOOLEAN =
  BEGIN
    IF NOT GetID (z) THEN RETURN FALSE; END;
    z.key := "interface " & z.id;
    IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.Interface (z.id)); END;
    RETURN TRUE;
  END GetIntfID;
PROCEDURE MarkGenericInstance  (VAR z: Info) =
  BEGIN
    IF NOT GetID (z) THEN RETURN END;
    IF z.is_interface THEN
      z.key := "generic interface " & z.id;
      IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.GenericIntf (z.id)); END;
    ELSE
      z.key := "generic module " & z.id;
      IF UnknownRef (z) THEN GenRef (z, NIL, M3DB.GenericMod (z.id)); END;
    END;
    IF z.lex.token # M3Scanner.TK_L_paren THEN RETURN END;
    NextToken (z); (* ( *)
    WHILE GetIntfID (z) DO
      IF z.lex.token # M3Scanner.TK_Comma THEN EXIT END;
      NextToken (z); (* , *)
    END;
    IF z.lex.token # M3Scanner.TK_R_paren THEN RETURN END;
    NextToken (z); (* ) *)
  END MarkGenericInstance;
PROCEDURE MarkDecls  (VAR z: Info) =
  VAR id, unit: TEXT;  eq: BOOLEAN;
  BEGIN
    LOOP
      CASE z.lex.token OF
      | M3Scanner.TK_Type =>
          NextToken (z); (*TYPE*)
          WHILE FindTypeID (z, id, unit, eq) DO
            IF NOT eq THEN
              z.key := Fmt.F ("opaque type %s.%s", z.unit, id);
              IF UnknownRef (z) THEN
                VAR
                  types := M3DB.RevealsType (id);
                  units : TextList.T;
                BEGIN
                  IF z.is_generic
                    THEN units := M3DB.GenericMod (z.unit);
                    ELSE units := M3DB.Exports (z.unit);
                  END;
                  units := And (units, types);
                  IF (units = NIL) THEN
                    units := And (M3DB.RevealsTo (z.unit), types);
                  END;
                  GenRef (z, id, units);
                END;
              END;
            END;
          END;
      | M3Scanner.TK_Procedure =>
          NextToken (z); (*PROCEDURE*)
          IF GetID (z) THEN
            IF z.is_interface THEN
              z.key := Fmt.F ("procedure %s.%s", z.unit, z.id);
              IF UnknownRef (z) THEN
                IF z.is_generic THEN
                  GenRef (z, z.id, And (M3DB.GenericMod (z.unit),
                                        M3DB.DefinesProc (z.id)));
                ELSE
                  GenRef (z, z.id, And (M3DB.Exports (z.unit),
                                        M3DB.DefinesProc (z.id)));
                END;
              END;
              SkipToSemi (z);
            ELSE
              AddH (z, Begin_tag);
              AddH (z, z.id);
              AddH (z, End_tag);
              AddH (z, Begin_proc);
              AddT (z, End_proc);
              AddT (z, End_anchor);
              SkipProc (z, z.id);
            END;
          END;
      | M3Scanner.TK_Reveal =>
          NextToken (z); (*REVEALS*)
          WHILE FindTypeID (z, id, unit, eq) DO
            IF eq THEN
              AddH (z, Begin_tag);
              AddH (z, z.id);
              AddH (z, End_tag);
              AddT (z, End_anchor);
            END;
          END;
      | M3Scanner.TK_EOF, M3Scanner.TK_Error => EXIT;
      ELSE NextToken (z);
      END;
    END;
  END MarkDecls;
<*UNUSED*>PROCEDURE MarkCopyright  (VAR z: Info) =
  VAR id: TEXT;
  BEGIN
    IF (z.lex.token = M3Scanner.TK_Ident) AND (z.lex.length = 9) THEN
      id := Text.FromChars (SUBARRAY (z.lex.buffer^, z.lex.offset,
                                     z.lex.length));
      IF Text.Equal ("Copyright", id) THEN
        z.id_offset := z.lex.offset;
        z.id_length := z.lex.length;
        AddH (z, Begin_ref);
        AddH (z, FilePath.Normalize ("COPYRIGHT.html", z.path));
        AddH (z, End_ref);
        AddT (z, " (C) 1994, Digital Equipment Corp.");
        AddT (z, End_anchor);
        NextToken (z);
      END;
    END;
  END MarkCopyright;
PROCEDURE FindTypeID  (VAR z: Info;  VAR id, unit: TEXT;
                      VAR eq: BOOLEAN): BOOLEAN =
  BEGIN
    LOOP
      CASE z.lex.token OF
      | M3Scanner.TK_Ident =>
          EVAL GetID (z);
          id := z.id;
          IF z.lex.token = M3Scanner.TK_Dot THEN
            NextToken (z); (* . *)
            IF z.lex.token = M3Scanner.TK_Ident THEN
              unit := z.id;
              EVAL GetID (z);
            END;
          END;
          IF z.lex.token = M3Scanner.TK_Equal THEN
            NextToken (z); (* = *)
            eq := TRUE;
            RETURN TRUE;
          ELSIF z.lex.token = M3Scanner.TK_Subtype THEN
            NextToken (z); (* <: *)
            eq := FALSE;
            RETURN TRUE;
          ELSE (* skip *)
          END;
      | M3Scanner.TK_L_paren =>
          SkipParens (z);
      | M3Scanner.TK_Const, M3Scanner.TK_Type, M3Scanner.TK_Exception,
        M3Scanner.TK_Var, M3Scanner.TK_Procedure, M3Scanner.TK_Reveal,
        M3Scanner.TK_Begin, M3Scanner.TK_EOF, M3Scanner.TK_Error =>
          RETURN FALSE;
      ELSE
          NextToken (z);
      END; (*CASE*)
    END; (*LOOP*)
  END FindTypeID;
PROCEDURE SkipParens  (VAR z: Info) =
  VAR depth: INTEGER := 0;
  BEGIN
    LOOP
      IF z.lex.token = M3Scanner.TK_L_paren THEN
        INC (depth);
      ELSIF z.lex.token = M3Scanner.TK_R_paren THEN
        DEC (depth);
        IF (depth <= 0) THEN NextToken (z); RETURN END;
      ELSIF z.lex.token = M3Scanner.TK_EOF THEN
        RETURN;
      ELSIF z.lex.token = M3Scanner.TK_Error THEN
        RETURN;
      END;
      NextToken (z);
    END;
  END SkipParens;
PROCEDURE SkipProc  (VAR z: Info;  proc_id: TEXT) =
  BEGIN
    LOOP
      IF z.lex.token = M3Scanner.TK_End THEN
        NextToken (z); (*END*)
        IF GetID (z) AND Text.Equal (proc_id, z.id) THEN EXIT; END;
      ELSIF z.lex.token = M3Scanner.TK_EOF OR
        z.lex.token = M3Scanner.TK_Error THEN
        EXIT;
      ELSE
        NextToken (z);
      END;
    END;
  END SkipProc;
PROCEDURE SkipToSemi  (VAR z: Info) =
  BEGIN
    WHILE (z.lex.token # M3Scanner.TK_Semi)
      AND (z.lex.token # M3Scanner.TK_EOF)
      AND (z.lex.token # M3Scanner.TK_Error) DO
      NextToken (z);
    END;
    IF (z.lex.token = M3Scanner.TK_Semi) THEN NextToken (z); END;
  END SkipToSemi;
PROCEDURE GetID  (VAR z: Info): BOOLEAN =
  BEGIN
    IF z.lex.token # M3Scanner.TK_Ident THEN  z.id := NIL; RETURN FALSE;  END;
    z.id_offset := z.lex.offset;
    z.id_length := z.lex.length;
    z.id := Text.FromChars (SUBARRAY(z.lex.buffer^, z.id_offset, z.id_length));
    NextToken (z);
    RETURN TRUE;
  END GetID;
PROCEDURE NextToken  (VAR z: Info) =
  BEGIN
    REPEAT
      z.lex.next ();
    UNTIL (z.lex.token # M3Scanner.TK_Begin_pragma);
  END NextToken;
------------------------------------------------------- insertion lists ---
PROCEDURE------------------------------------------------------- HREF generation ---AddH (VAR x: Info; txt: TEXT) = BEGIN AddI (x.ins, x.id_offset, txt); END AddH; PROCEDUREAddT (VAR x: Info; txt: TEXT) = BEGIN AddI (x.ins, x.id_offset + x.id_length, txt); END AddT; PROCEDUREAddC (VAR x: Info; txt: TEXT) = BEGIN AddI (x.choice, 0, txt); END AddC; PROCEDUREAddI (VAR z: InsList; offs: INTEGER; txt: TEXT) = VAR i := NEW (Insertion, next := NIL, offset := offs, insert := txt); BEGIN IF (z.head = NIL) THEN z.head := i; ELSE z.tail.next := i; END; z.tail := i; INC (z.cnt); END AddI; PROCEDUREAppend (VAR a, b: InsList) = VAR x := b.head; offs := a.tail.offset; BEGIN IF (b.cnt <= 0) THEN RETURN END; (* splice the two lists *) INC (a.cnt, b.cnt); a.tail.next := x; a.tail := b.tail; (* fix the offsets *) WHILE (x # NIL) DO x.offset := offs; x := x.next; END; (* empty the old list *) b.head := NIL; b.tail := NIL; b.cnt := 0; END Append;
VAR href_cache := NEW (TextTextTbl.Default); VAR next_multi := 1; PROCEDURE--------------------------------------------------------------- sorting ---ResetCache () = BEGIN EVAL href_cache.init (); next_multi := 1; END ResetCache; PROCEDUREUnknownRef (VAR x: Info): BOOLEAN = VAR dest: TEXT; BEGIN IF NOT href_cache.get (x.key, dest) THEN RETURN TRUE; END; EmitRef (x, dest); RETURN FALSE; END UnknownRef; PROCEDUREGenRef (VAR x: Info; tag: TEXT; targets: TextList.T) = VAR ref: TEXT; BEGIN targets := NormalizeList (targets); IF targets = NIL THEN (* no hits *) ELSIF targets.tail = NIL THEN (* direct hit *) ref := targets.head & End_file; IF (tag # NIL) THEN ref := ref & Goto_tag & tag; END; EVAL href_cache.put (x.key, ref); EmitRef (x, ref); ELSE (* a set of hits *) ref := GenMultiRef (x, tag, targets); IF (ref # NIL) THEN EVAL href_cache.put (x.key, ref); EmitRef (x, ref); END; END; END GenRef; PROCEDUREEmitRef (VAR x: Info; dest: TEXT) = BEGIN AddH (x, Begin_ref); IF (Text.GetChar (dest, 0) = '#') THEN AddH (x, dest); ELSE AddH (x, FilePath.Normalize (dest, x.path)); END; AddH (x, End_ref); AddT (x, End_anchor); END EmitRef; PROCEDUREGenMultiRef (VAR x: Info; tag: TEXT; targets: TextList.T): TEXT = VAR label := "x" & Fmt.Int (next_multi); BEGIN IF (tag = NIL) THEN (* HACK work around xmosaic bug... *) tag := "0TOP0"; END; INC (next_multi); AddC (x, "<HR>\n"); AddC (x, Begin_tag); AddC (x, label); AddC (x, End_tag); AddC (x, x.key); AddC (x, " is in:\n"); AddC (x, End_anchor); AddC (x, "<UL>\n"); WHILE (targets # NIL) DO AddC (x, "<LI>"); AddC (x, Begin_ref); AddC (x, FilePath.Normalize (targets.head, x.path)); AddC (x, End_file); IF (tag # NIL) THEN AddC (x, Goto_tag); AddC (x, tag); END; AddC (x, End_ref); AddC (x, targets.head); AddC (x, End_anchor); AddC (x, "\n"); targets := targets.tail; END; AddC (x, "</UL>\n<P>\n"); RETURN Goto_tag & label; END GenMultiRef; PROCEDURENormalizeList (x: TextList.T): TextList.T = VAR cur, prev: TextList.T; BEGIN x := TextListSort.SortD (x); cur := x; prev := NIL; WHILE (cur # NIL) DO IF (prev # NIL) AND Text.Equal (prev.head, cur.head) THEN prev.tail := cur.tail; (* delete cur *) ELSE prev := cur; END; cur := cur.tail; END; RETURN x; END NormalizeList; PROCEDUREAnd (a, b: TextList.T): TextList.T = (* destroys 'a', and normalizes 'b' *) VAR c, res: TextList.T; BEGIN a := NormalizeList (a); b := NormalizeList (b); res := NIL; WHILE (a # NIL) AND (b # NIL) DO CASE Text.Compare (a.head, b.head) OF | -1 => (* a < b *) a := a.tail; | +1 => (* a > b *) b := b.tail; | 0 => (* a = b *) c := a; a := a.tail; b := b.tail; c.tail := res; res := c; END; END; RETURN res; END And;
PROCEDURESortInsertions (VAR z: Info): Insertion =
Do a simple insertion sort since the list is already nearly sorted
  VAR a, b, c, d, e: Insertion;
  BEGIN
    a := z.ins.head;
    b := a.next;  a.next := NIL;
    WHILE (b # NIL) DO
      (* insert 'b' *)
      c := b.next;
      d := a;  e := NIL;
      WHILE (d # NIL) AND (b.offset < d.offset) DO
        e := d;
        d := d.next;
      END;
      IF (e # NIL)
        THEN e.next := b;
        ELSE a := b;
      END;
      b.next := d;
      b := c;
    END;
    (* reverse the list *)
    b := NIL;
    WHILE (a # NIL) DO
      c := a.next;
      a.next := b;
      b := a;
      a := c;
    END;
    RETURN b;
  END SortInsertions;
---------- debug ---
*********************
PROCEDURE Out (a, b: TEXT := NIL) =
  <*FATAL ANY*>
  BEGIN
    IF (a  # NIL) THEN Wr.PutText (Stdio.stdout, a); END;
    IF (b  # NIL) THEN Wr.PutText (Stdio.stdout, b); END;
    Wr.PutText (Stdio.stdout, \n);
  END Out;
PROCEDURE OutL (a: TEXT := NIL;  l: TextList.T := NIL) =
  <*FATAL ANY*>
  BEGIN
    IF (a  # NIL) THEN Wr.PutText (Stdio.stdout, a); END;
    WHILE (l # NIL) DO
      Wr.PutText (Stdio.stdout, l.head);
      Wr.PutText (Stdio.stdout,  );
      l := l.tail;
    END;
    Wr.PutText (Stdio.stdout, \n);
  END OutL;
*************
BEGIN END M3MarkUp.