MODULE DevBrowser;
(**
	project	= "BlackBox"
	organization	= "www.oberon.ch"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20070123, bh, StringConst changed to show Unicode strings
	- 20070123, bh, ProcSysFlag call in Structure (sysflags for procedure types)
	- 20141027, center #19, full Unicode support for Component Pascal identifiers added
	- 20150513, center #43, extending the size of code procedures
	- 20150530, center #19, missing Utf-8 conversions added
	- 20160324, center #111, code cleanups
	- 20160803, center #19, missing Utf-8 conversion added in CheckModName
	- 20160820, center #123, Show Extension Interface is not showing *Hook procedures
	- 20161002, center #132, bug in ProperString fixed
	- 20161103, center #140, unwrapping import aliasses
	- 20161104, center #142, adding a disassembler to the ocf importer
	- 20170724, center #167, endless loop in DevBrowser for cyclic pointer type
	- 20170811, center #170, DevBrowser.ImportSymFile should return TextViews.View
	"
	issues	= "
	- ...
	"

**)

	IMPORT
		Kernel, Files, Fonts, Dates, Ports, Stores, Views, Properties, Dialog, Documents,
		TextModels, TextMappers, TextRulers, TextViews, TextControllers, StdDialog, StdFolds,
		DevReferences, DevCPM, DevCPT, DevDecoder386, StdRegistry, Utf;

	CONST
		width = 170 * Ports.mm;	(* view width *)
		height = 300 * Ports.mm;

		(* visibility *)
		internal = 0; external = 1; externalR = 2; inPar = 3; outPar = 4;

		(* object modes *)
		var = 1; varPar = 2; const = 3; field = 4; type = 5; lProc = 6; xProc = 7; cProc = 9; iProc = 10;
		mod = 11; head = 12; tProc = 13; ptrType = 31;

		(* structure forms *)
		undef = 0; bool = 2; sChar = 3; byte = 4; sInt = 5; int = 6; sReal = 7; real = 8;
		set = 9; sString = 10; nilTyp = 11; noTyp = 12; pointer = 13; procTyp = 14; comp = 15;
		char = 16; string = 17; lInt = 18;

		(* composite structure forms *)
		basic = 1; array = 2; dynArray = 3; record = 4;

		(* attribute flags (attr.adr, struct.attribute, proc.conval.setval) *)
		newAttr = 16; absAttr = 17; limAttr = 18; empAttr = 19; extAttr = 20;
		
		modNotFoundKey = "#Dev:ModuleNotFound";
		noSuchItemExportedKey = "#Dev:NoSuchItemExported";
		noSuchExtItemExportedKey = "#Dev:NoSuchExtItemExported";
		noModuleNameSelectedKey = "#Dev:NoModuleNameSelected";

		regKey = "Dev\Browser\";

	TYPE
		TProcList = POINTER TO RECORD
			fld: DevCPT.Object;
			attr: SET;	(* newAttr *)
			next: TProcList
		END;

	VAR
		global: RECORD
			hints: BOOLEAN;	(* display low-level hints such as field offsets *)
			flatten: BOOLEAN;	(* include fields/bound procs of base types as well *)
			hideHooks: BOOLEAN;	(* hide hook procedures and interfaces *)
			clientinterface, extensioninterface: BOOLEAN;
			sectAttr, keyAttr: TextModels.Attributes;	(* formatting of section and other keywords *)
			level: INTEGER;	(* current indentation level *)
			gap: BOOLEAN;	(* request for lazy Ln, issued with next Indent *)
			singleton: BOOLEAN;	(* selectively display one object's definition *)
			thisObj: DevCPT.Name;	(* singleton => selected object *)
			out: TextMappers.Formatter;	(* formatter used to produce definition text *)
			systemUsed: BOOLEAN;
			comUsed: BOOLEAN;
			modUsed: ARRAY 127 OF BOOLEAN;
			pos: INTEGER
		END;
		inp: Files.Reader;
		imp: ARRAY 256 OF Kernel.Utf8Name;
		num, lev, max: INTEGER;

		options*: RECORD
			hints*: BOOLEAN;	(* display low-level hints such as field offsets *)
			flatten*: BOOLEAN;	(* include fields/bound procs of base types as well *)
			formatted*: BOOLEAN;
			shints, sflatten, sformatted: BOOLEAN	(* saved values *)
		END;

	PROCEDURE IsHook(typ: DevCPT.Struct): BOOLEAN;
		VAR cnt: INTEGER; (* for breaking cycles in exotic cases such as T = POINTER TO ARRAY ... OF T *)
	BEGIN
		cnt := 0;
		WHILE ((typ.form = pointer) OR (typ.form = comp)) & (typ.BaseTyp # NIL) & (cnt < 100) DO
			typ := typ.BaseTyp; INC(cnt)
		END;
		RETURN (DevCPT.GlbMod[typ.mno].name^ = "Kernel") & (typ.strobj.name^ = "Hook^")
	END IsHook;

	(* auxilliary *)

	PROCEDURE GetSelection (VAR text: TextModels.Model; VAR beg, end: INTEGER);
		VAR c: TextControllers.Controller;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			text := c.text; c.GetSelection(beg, end)
		ELSE
			text := NIL
		END
	END GetSelection;

	PROCEDURE GetQualIdent (VAR mod, ident: DevCPT.Name; VAR t: TextModels.Model);
		VAR s: TextMappers.Scanner; beg, end, res: INTEGER;
	BEGIN
		GetSelection(t, beg, end);
		IF t # NIL THEN
			s.ConnectTo(t); s.SetOpts({7});	(* acceptUnderscores *)
			s.SetPos(beg); s.Scan;
			IF (s.type = TextMappers.string) & (s.len < LEN(mod)) THEN
				DevReferences.ResolveImportAlias(s.string, t);
				Utf.StringToUtf8(s.string, mod, res); 
				s.Scan;
				IF (s.type = TextMappers.char) & (s.char = ".") & (s.Pos() <= end) THEN
					s.Scan;
					IF (s.type = TextMappers.string) & (s.len < LEN(ident)) THEN
						Utf.StringToUtf8(s.string, ident, res);
					ELSE mod[0] := 0X; ident[0] := 0X
					END
				ELSE ident[0] := 0X
				END
			ELSE mod[0] := 0X; ident[0] := 0X
			END
		ELSE mod[0] := 0X; ident[0] := 0X
		END
	END GetQualIdent;

	PROCEDURE NewRuler (): TextRulers.Ruler;
		VAR ra: TextRulers.Attributes; p: TextRulers.Prop;
	BEGIN
		NEW(p); p.valid := {TextRulers.right, TextRulers.opts};
		p.opts.val := {(*TextRulers.rightFixed*)}; p.opts.mask := p.opts.val;
		p.right := width;
		NEW(ra); ra.InitFromProp(p);
		RETURN TextRulers.dir.New(TextRulers.dir.NewStyle(ra))
	END NewRuler;

	PROCEDURE Append (VAR d: ARRAY OF CHAR; s: ARRAY OF SHORTCHAR);
		VAR l, ld, ls: INTEGER;
	BEGIN
		l := LEN(d); ld := LEN(d$); ls := LEN(s$);
		IF ld + ls >= l THEN
			s[l - ld - 1] := 0X; d := d + s;
			d[l - 2] := "."; d[l - 3] := "."; d[l - 4] := "."
		ELSE
			d := d + s
		END
	END Append;

	PROCEDURE IsLegal (name: POINTER TO ARRAY OF SHORTCHAR): BOOLEAN;
		VAR i: INTEGER;
	BEGIN
		IF name^ = "" THEN RETURN FALSE END;
		IF name[0] = "@" THEN RETURN global.hints END;
		i := 0;
		WHILE name[i] # 0X DO INC(i) END;
		RETURN name[i-1] # "^"
	END IsLegal;


	(* output formatting *)

	PROCEDURE Char (ch: CHAR);
	BEGIN
		global.out.WriteChar(ch)
	END Char;

	PROCEDURE String (IN s: ARRAY OF SHORTCHAR);
		VAR res: INTEGER; n: ARRAY 256 OF CHAR;
	BEGIN
		Utf.Utf8ToString(s, n, res); 
		global.out.WriteString(n)
	END String;

	PROCEDURE StringConst (s: ARRAY OF SHORTCHAR; long: BOOLEAN);
		VAR i, x, y: INTEGER; quoted, first: BOOLEAN;
	BEGIN
		IF long THEN
			i := 0; REPEAT DevCPM.GetUtf8(s, y, i) UNTIL (y = 0) OR (y >= 100H);
			IF y = 0 THEN global.out.WriteString("LONG(") END
		END;
		i := 0; quoted := FALSE; first := TRUE;
		IF long THEN DevCPM.GetUtf8(s, x, i) ELSE x := ORD(s[i]); INC(i) END;
		WHILE x # 0 DO
			IF (x < ORD(" "))
					OR (x >= 7FH) & (x <= 9FH)
					OR (x >= 0D800H) & (x <= 0F8FFH)
					OR (x >= 0FFF0H) THEN	(* nonprintable character *)
				IF quoted THEN global.out.WriteChar('"') END;
				IF ~first THEN global.out.WriteString(" + ") END;
				IF x >= 0A00H THEN global.out.WriteIntForm(x, TextMappers.hexadecimal, 4, "0", FALSE)
				ELSIF x >= 0A0H THEN global.out.WriteIntForm(x, TextMappers.hexadecimal, 3, "0", FALSE)
				ELSE global.out.WriteIntForm(x, TextMappers.hexadecimal, 2, "0", FALSE)
				END;
				global.out.WriteChar("X");
				quoted := FALSE
			ELSE	(* printable character *)
				IF ~quoted THEN
					IF ~first THEN global.out.WriteString(" + ") END;
					global.out.WriteChar('"')
				END;
				global.out.WriteChar(CHR(x));
				quoted := TRUE
			END;
			IF long THEN DevCPM.GetUtf8(s, x, i) ELSE x := ORD(s[i]); INC(i) END;
			first := FALSE
		END;
		IF first THEN global.out.WriteString('""') END;
		IF quoted THEN global.out.WriteChar('"') END;
		IF long & (y = 0) THEN global.out.WriteChar(")") END
	END StringConst;

	PROCEDURE ProperString (s: ARRAY OF SHORTCHAR);
		VAR i, res: INTEGER; str: ARRAY LEN(DevCPT.Name) OF CHAR;
	BEGIN
		IF s # "" THEN
			Utf.Utf8ToString(s, str, res);
			i := 0;
			WHILE (str[i] # 0X) & (str[i] # "^") DO
				global.out.WriteChar(str[i]); INC(i)
			END
		END
	END ProperString;

	PROCEDURE Keyword (s: ARRAY OF CHAR);
		VAR a: TextModels.Attributes;
	BEGIN
		IF global.keyAttr # NIL THEN a := global.out.rider.attr; global.out.rider.SetAttr(global.keyAttr) END;
		global.out.WriteString(s);
		IF global.keyAttr # NIL THEN global.out.rider.SetAttr(a) END
	END Keyword;

	PROCEDURE Section (VAR out: TextMappers.Formatter; s: ARRAY OF CHAR);
		VAR a: TextModels.Attributes;
	BEGIN
		IF global.sectAttr # NIL THEN a := out.rider.attr; out.rider.SetAttr(global.sectAttr) END;
		out.WriteString(s);
		IF global.sectAttr # NIL THEN out.rider.SetAttr(a) END
	END Section;

	PROCEDURE Int (i: INTEGER);
	BEGIN
		global.out.WriteInt(i)
	END Int;

	PROCEDURE Hex (x, n: INTEGER);
	BEGIN
		IF n > 1 THEN Hex(x DIV 16, n - 1) END;
		x := x MOD 16;
		IF x >= 10 THEN global.out.WriteChar(CHR(x + ORD("A") - 10))
		ELSE global.out.WriteChar(CHR(x + ORD("0")))
		END
	END Hex;

	PROCEDURE Ln;
	BEGIN
		global.out.WriteLn
	END Ln;

	PROCEDURE Indent;
		VAR i: INTEGER;
	BEGIN
		IF global.gap THEN global.gap := FALSE; Ln END;
		i := global.level; WHILE i > 0 DO global.out.WriteTab; DEC(i) END
	END Indent;

	PROCEDURE Guid (ext: DevCPT.ConstExt);
	BEGIN
		Char("{");
		Hex(ORD(ext[2]) + 256 * ORD(ext[3]), 4);
		Hex(ORD(ext[0]) + 256 * ORD(ext[1]), 4);
		Char("-");
		Hex(ORD(ext[4]) + 256 * ORD(ext[5]), 4);
		Char("-");
		Hex(ORD(ext[6]) + 256 * ORD(ext[7]), 4);
		Char("-");
		Hex(ORD(ext[8]), 2);
		Hex(ORD(ext[9]), 2);
		Char("-");
		Hex(ORD(ext[10]), 2);
		Hex(ORD(ext[11]), 2);
		Hex(ORD(ext[12]), 2);
		Hex(ORD(ext[13]), 2);
		Hex(ORD(ext[14]), 2);
		Hex(ORD(ext[15]), 2);
		Char("}")
	END Guid;


	(* special marks *)

	PROCEDURE Hint (label: ARRAY OF SHORTCHAR; i: INTEGER; adj: BOOLEAN);
	BEGIN
		IF global.hints THEN
			Char("["); String(label);
			IF adj & (0 <= i) & (i < 10) THEN Char(TextModels.digitspace) END;
			Int(i);
			String("] ")
		END
	END Hint;

	PROCEDURE Hint3 (label1, label2, label3: ARRAY OF SHORTCHAR; i1, i2, i3: INTEGER);
	BEGIN
		IF global.hints THEN
			Char("["); String(label1); Int(i1); String(label2); Int(i2); String(label3); Int(i3); String("] ")
		END
	END Hint3;

	PROCEDURE Vis (i: INTEGER);
	BEGIN
		IF i # external THEN Char("-") END
	END Vis;

	PROCEDURE ProcSysFlag (flag: INTEGER);
	BEGIN
		IF flag # 0 THEN
			String(" [");
			IF flag = -10 THEN String("ccall")
			ELSE Int(flag)
			END;
			Char("]")
		END
	END ProcSysFlag;

	PROCEDURE ParSysFlag (flag: INTEGER);
		VAR pos: INTEGER;
	BEGIN
		IF flag # 0 THEN
			String(" [");
			pos := global.out.Pos();
			IF ODD(flag) THEN String("nil") END;
(*
			IF ODD(flag DIV 2) & (flag # 18) THEN
				IF global.out.Pos() # pos THEN String(", ") END;
				String("in")
			END;
			IF ODD(flag DIV 4) & (flag # 12) THEN
				IF global.out.Pos() # pos THEN String(", ") END;
				String("out")
			END;
*)
			IF flag DIV 8 = 1 THEN
				IF global.out.Pos() # pos THEN String(", ") END;
				String("new")
			ELSIF flag DIV 8 = 2 THEN
				IF global.out.Pos() # pos THEN String(", ") END;
				String("iid")
			ELSIF flag DIV 8 # 0 THEN
				IF global.out.Pos() # pos THEN String(", ") END;
				Int(flag DIV 8 * 8)
			END;
			Char("]")
		END
	END ParSysFlag;

	PROCEDURE StructSysFlag (typ: DevCPT.Struct);
		VAR flag: INTEGER;
	BEGIN
		flag := typ.sysflag;
		IF (flag # 0) & ((typ.form # pointer) OR (flag = 2)) THEN
			String(" [");
			IF flag = 1 THEN String("untagged")
			ELSIF flag = 2 THEN String("handle")
			ELSIF flag = 3 THEN String("noalign")
			ELSIF flag = 4 THEN String("align2")
			ELSIF flag = 5 THEN String("align4")
			ELSIF flag = 6 THEN String("align8")
			ELSIF flag = 7 THEN String("union")
			ELSIF flag = 10 THEN
				IF typ.ext # NIL THEN Char('"'); String(typ.ext^); Char('"')
				ELSE String("interface")
				END
			ELSIF flag = 20 THEN String("som")
			ELSE Int(flag)
			END;
			Char("]")
		END
	END StructSysFlag;

	PROCEDURE SysStrings (obj: DevCPT.Object);
	BEGIN
		IF global.hints & ((obj.entry # NIL) OR (obj.library # NIL)) THEN
			String(' ["');
			IF obj.library # NIL THEN String(obj.library^); String('", "') END;
			IF obj.entry # NIL THEN String(obj.entry^) END;
			String('"]')
		END
	END SysStrings;


	(* non-terminals *)

	PROCEDURE ^ Structure (typ: DevCPT.Struct);

	PROCEDURE Qualifier (mno: INTEGER);
	BEGIN
		IF (mno > 1) OR (mno = 1) & global.singleton THEN
			global.modUsed[mno] := TRUE;
			String(DevCPT.GlbMod[mno].name^); Char(".")
		END
	END Qualifier;

	PROCEDURE LocalName (obj: DevCPT.Object);
	BEGIN
		Qualifier(0); String(obj.name^)
	END LocalName;

	PROCEDURE TypName (typ: DevCPT.Struct; force: BOOLEAN);
	BEGIN
		IF force OR IsLegal(typ.strobj.name) THEN
			IF (typ.mno > 1) OR (typ.mno = 1)
				& ((typ.form >= pointer) OR (typ.BaseTyp # DevCPT.undftyp)) & global.singleton
			THEN
				Qualifier(typ.mno)
			ELSIF typ = DevCPT.sysptrtyp THEN	(* PTR is in SYSTEM *)
				String("SYSTEM.");
				global.systemUsed := TRUE
			ELSIF
				(typ = DevCPT.guidtyp) OR (typ = DevCPT.restyp) OR (typ = DevCPT.iunktyp) OR (typ = DevCPT.punktyp)
			THEN
				String("COM.");
				global.comUsed := TRUE
			END;
			IF force THEN ProperString(typ.strobj.name^) ELSE String(typ.strobj.name^) END
		ELSE
			Structure(typ)
		END
	END TypName;

	PROCEDURE ParList (VAR par: DevCPT.Object);
	BEGIN
		IF par.mode = varPar THEN
			IF par.vis = inPar THEN Keyword("IN")
			ELSIF par.vis = outPar THEN Keyword("OUT")
			ELSE Keyword("VAR")
			END;
			ParSysFlag(par.sysflag); Char(" ")
		END;
		String(par.name^);
		WHILE (par.link # NIL) & (par.link.typ = par.typ) & (par.link.mode = par.mode)
				& (par.link.vis = par.vis) & (par.link.sysflag = par.sysflag) DO

			par := par.link; String(", "); String(par.name^)
		END;
		String(": "); TypName(par.typ, FALSE)
	END ParList;

	PROCEDURE Signature (result: DevCPT.Struct; par: DevCPT.Object);
		VAR paren, res: BOOLEAN;
	BEGIN
		res := result # DevCPT.notyp; paren := res OR (par # NIL);
		IF paren THEN String(" (") END;
		IF par # NIL THEN
			ParList(par); par := par.link;
			WHILE par # NIL DO String("; "); ParList(par); par := par.link END
		END;
		IF paren THEN Char(")") END;
		IF res THEN String(": "); TypName(result, FALSE) END
	END Signature;

	PROCEDURE TProcs (rec: DevCPT.Struct; fld: DevCPT.Object; oldProcs: TProcList; VAR newProcs: TProcList);
		VAR old: DevCPT.Object; p, elem: TProcList;
	BEGIN
		IF fld # NIL THEN
			TProcs(rec, fld.left, oldProcs, newProcs);
			IF (fld.mode = tProc) & IsLegal(fld.name) THEN
				DevCPT.FindBaseField(fld.name^, rec, old);
				IF (old = NIL) OR (fld.typ # old.typ) OR (* (rec.attribute # 0) & *) (fld.conval.setval # old.conval.setval)
				THEN
					IF global.extensioninterface OR global.clientinterface THEN
						(* do not show methods with equal name *)
						p := oldProcs;
						WHILE (p # NIL) & (p.fld.name^ # fld.name^) DO p := p.next END
					END;
					IF p = NIL THEN
						NEW(elem); elem.next := newProcs; newProcs := elem;
						elem.fld := fld;
						IF old = NIL THEN INCL(elem.attr, newAttr) END;
						IF absAttr IN fld.conval.setval THEN INCL(elem.attr, absAttr)
						ELSIF empAttr IN fld.conval.setval THEN INCL(elem.attr, empAttr)
						ELSIF extAttr IN fld.conval.setval THEN INCL(elem.attr, extAttr)
						END
					END
				END
			END;
			TProcs(rec, fld.right, oldProcs, newProcs)
		END
	END TProcs;

(*
	PROCEDURE AdjustTProcs (typ: DevCPT.Struct; fld: DevCPT.Object);
		VAR receiver, old: DevCPT.Object; base: DevCPT.Struct;
	BEGIN
		IF fld # NIL THEN
			AdjustTProcs(typ, fld.left);
			IF (fld.mode = tProc) & IsLegal(fld.name) THEN
				DevCPT.FindBaseField(fld.name^, typ, old);
				IF old # NIL THEN
					IF fld.conval.setval * {absAttr, empAttr, extAttr} = {} THEN
						old.conval.setval := old.conval.setval - {absAttr, empAttr, extAttr}
					ELSIF (extAttr IN fld.conval.setval) & (empAttr IN old.conval.setval) THEN
						(* do not list methods which only change attribute from empty to extensible *)
						old.conval.setval := old.conval.setval + {extAttr} - {empAttr}
					END;
					IF fld.typ # old.typ THEN (* do not show base methods of covariant overwritten methods *)
						old.typ := fld.typ; old.conval.setval := {}
					END;
				END
			END;
			AdjustTProcs(typ, fld.right)
		END;
		IF (typ.BaseTyp # NIL) & (typ.BaseTyp # DevCPT.iunktyp) THEN
			AdjustTProcs(typ.BaseTyp, typ.BaseTyp.link)
		END
	END AdjustTProcs;
*)

	PROCEDURE TypeboundProcs (typ: DevCPT.Struct; VAR cont: BOOLEAN; top: BOOLEAN;
												VAR list: TProcList; recAttr: INTEGER);
		VAR receiver: DevCPT.Object; new, p, q: TProcList;
	BEGIN
		IF (typ # NIL) & (typ # DevCPT.iunktyp) THEN
			TProcs(typ, typ.link, list, new);

			p := list;
			WHILE new # NIL DO
				q := new.next; new.next := p; p := new; new := q
			END;
			list := p;

			IF global.flatten THEN
				TypeboundProcs(typ.BaseTyp, cont, FALSE, list, recAttr);
				IF (typ.BaseTyp = NIL) OR (typ.BaseTyp = DevCPT.iunktyp) THEN
(*
					IF global.extensioninterface & (recAttr IN {absAttr, extAttr}) THEN
						IF cont THEN Char(";") END;
						Ln; Indent;
						String("(a: ANYPTR) FINALIZE-, "); Keyword("NEW"); String(", "); Keyword("EMPTY");
						cont := TRUE
					END
*)
				END
			END;
			IF top THEN (* writeList *)
				WHILE list # NIL DO
					IF ~global.clientinterface & ~global.extensioninterface (* default *)
					OR global.clientinterface & (list.fld.vis = external)
					OR global.extensioninterface & (recAttr IN {absAttr, extAttr}) & (list.attr * {absAttr, empAttr, extAttr} # {})
					THEN

						IF cont THEN Char(";") END;
						Ln;
						receiver := list.fld.link;
						Indent; Hint("entry ", list.fld.num, TRUE);
	(*
						Keyword("PROCEDURE"); String(" (");
	*)
						Char("(");
						IF receiver.mode = varPar THEN
							IF receiver.vis = inPar THEN Keyword("IN") ELSE Keyword("VAR") END;
							Char(" ")
						END;
						String(receiver.name^); String(": ");
						IF IsLegal(receiver.typ.strobj.name) & (receiver.typ.mno = 1) THEN
							String(receiver.typ.strobj.name^)
						ELSE TypName(receiver.typ, FALSE)
						END;
						String(") "); String(list.fld.name^); Vis(list.fld.vis);
						SysStrings(list.fld);
						Signature(list.fld.typ, receiver.link);
						IF newAttr IN list.attr THEN String(", "); Keyword("NEW") END;
						IF absAttr IN list.attr THEN String(", "); Keyword("ABSTRACT")
						ELSIF empAttr IN list.attr THEN String(", "); Keyword("EMPTY")
						ELSIF extAttr IN list.attr THEN String(", "); Keyword("EXTENSIBLE")
						END;
						cont := TRUE
					END;
					list := list.next
				END
			END
		END
	END TypeboundProcs;

	PROCEDURE Flds (typ: DevCPT.Struct; VAR cont: BOOLEAN);
		VAR fld: DevCPT.Object;
	BEGIN
		fld := typ.link;
		WHILE (fld # NIL) & (fld.mode = field) DO
			IF IsLegal(fld.name) THEN
				IF cont THEN Char(";") END;
				Ln;
				Indent; Hint("offset ", fld.adr, TRUE);
				IF typ.mno > 1 THEN String("(* "); TypName(typ, TRUE); String(" *) ") END;
				String(fld.name^);
				WHILE (fld.link # NIL) & (fld.link.typ = fld.typ) & (fld.link.name # NIL) DO
					Vis(fld.vis); fld := fld.link; String(", "); String(fld.name^)
				END;
				Vis(fld.vis); String(": "); TypName(fld.typ, FALSE);
				cont := TRUE
			END;
			fld := fld.link
		END
	END Flds;

	PROCEDURE Fields (typ: DevCPT.Struct; VAR cont: BOOLEAN);
	BEGIN
		IF typ # NIL THEN
			IF global.flatten THEN Fields(typ.BaseTyp, cont) END;
			Flds(typ, cont)
		END
	END Fields;

	PROCEDURE BaseTypes (typ: DevCPT.Struct);
	BEGIN
		IF typ.BaseTyp # NIL THEN
			Char("("); TypName(typ.BaseTyp, TRUE);
			IF global.flatten THEN BaseTypes(typ.BaseTyp) END;
			Char(")")
		END
	END BaseTypes;

	PROCEDURE Structure (typ: DevCPT.Struct);
		VAR cont: BOOLEAN; p: TProcList;
	BEGIN
		INC(global.level);
		CASE typ.form OF
		  pointer:
			Keyword("POINTER"); StructSysFlag(typ); Char(" ");
			Keyword("TO"); Char(" "); DEC(global.level); TypName(typ.BaseTyp, FALSE); INC(global.level)
		| procTyp:
			Keyword("PROCEDURE"); ProcSysFlag(typ.sysflag); Signature(typ.BaseTyp, typ.link)
		| comp:
			CASE typ.comp OF
			  array:
				Keyword("ARRAY"); StructSysFlag(typ); Char(" "); Int(typ.n); Char(" ");
				Keyword("OF"); Char(" "); TypName(typ.BaseTyp, FALSE)
			| dynArray:
				Keyword("ARRAY"); StructSysFlag(typ); Char(" ");
				Keyword("OF"); Char(" "); TypName(typ.BaseTyp, FALSE)
			| record:
				IF typ.attribute = absAttr THEN Keyword("ABSTRACT ")
				ELSIF typ.attribute = limAttr THEN Keyword("LIMITED ")
				ELSIF typ.attribute = extAttr THEN Keyword("EXTENSIBLE ")
				END;
				Keyword("RECORD"); StructSysFlag(typ);
				Char(" "); Hint3("tprocs ", ", size ", ", align ", typ.n, typ.size, typ.align);
				cont := FALSE;
				BaseTypes(typ); Fields(typ, cont); TypeboundProcs(typ, cont, TRUE, p, typ.attribute);
				IF cont THEN Ln; DEC(global.level); Indent; INC(global.level) ELSE Char (" ") END;
				Keyword("END")
			END
		ELSE
			IF (typ.BaseTyp # DevCPT.undftyp) THEN TypName(typ.BaseTyp, FALSE) END	(* alias structures *)
		END;
		DEC(global.level)
	END Structure;

	PROCEDURE Const (obj: DevCPT.Object);
		VAR con: DevCPT.Const; s: SET; i, x, y: INTEGER; r: REAL;
	BEGIN
		Indent; LocalName(obj); SysStrings(obj); String(" = ");
		con := obj.conval;
		CASE obj.typ.form OF
		  bool:
			IF con.intval = 1 THEN String("TRUE") ELSE String("FALSE") END
		| sChar, char:
			IF con.intval >= 0A000H THEN
				Hex(con.intval, 5); Char("X")
			ELSIF con.intval >= 0100H THEN
				Hex(con.intval, 4); Char("X")
			ELSIF con.intval >= 0A0H THEN
				Hex(con.intval, 3); Char("X")
			ELSIF (con.intval >= 32) & (con.intval <= 126) THEN
				Char(22X); Char(CHR(con.intval)); Char(22X)
			ELSE
				Hex(con.intval, 2); Char("X")
			END
		| byte, sInt, int:
			Int(con.intval)
		| lInt:
			y := SHORT(ENTIER((con.realval + con.intval) / 4294967296.0));
			r := con.realval + con.intval - y * 4294967296.0;
			IF r > MAX(INTEGER) THEN r := r - 4294967296.0 END;
			x := SHORT(ENTIER(r));
			Hex(y, 8); Hex(x, 8); Char("L")
		| set:
			Char("{"); i := 0; s := con.setval;
			WHILE i <= MAX(SET) DO
				IF i IN s THEN Int(i); EXCL(s, i);
					IF s # {} THEN String(", ") END
				END;
				INC(i)
			END;
			Char("}")
		| sReal, real:
			global.out.WriteReal(con.realval)
		| sString:
			StringConst(con.ext, FALSE)
		| string:
			StringConst(con.ext, TRUE)
		| nilTyp:
			Keyword("NIL")
		| comp:	(* guid *)
			Char("{"); Guid(con.ext); Char("}")
		END;
		Char(";"); Ln
	END Const;

	PROCEDURE AliasType (typ: DevCPT.Struct);
	BEGIN
		IF global.singleton & IsLegal(typ.strobj.name) THEN
			WHILE typ.BaseTyp # DevCPT.undftyp DO
				Char(";"); Ln; Indent;
				String(typ.strobj.name^); SysStrings(typ.strobj); String(" = ");
				IF typ.form >= pointer THEN
					Structure(typ); typ := DevCPT.int16typ
				ELSE
					typ := typ.BaseTyp;
					TypName(typ, FALSE)
				END
			END
		END
	END AliasType;

	PROCEDURE NotExtRec(typ: DevCPT.Struct): BOOLEAN;
	BEGIN
		RETURN (typ.form = comp) & ((typ.comp # record) OR ~(typ.attribute IN {absAttr, extAttr}))
			OR (typ.form = procTyp)
	END NotExtRec;

	PROCEDURE Type (obj: DevCPT.Object);
	BEGIN
		IF global.hideHooks & IsHook(obj.typ) THEN RETURN END;

		IF global.extensioninterface
		& (NotExtRec(obj.typ)
			OR
			(obj.typ.form = pointer) & ~IsLegal(obj.typ.BaseTyp.strobj.name) &
				NotExtRec(obj.typ.BaseTyp)
		)

		THEN
			IF global.singleton THEN RETURN END;
			global.out.rider.SetAttr(TextModels.NewColor(global.out.rider.attr, Ports.grey50));
			IF global.pos < global.out.rider.Pos() THEN
				global.out.rider.Base().SetAttr(global.pos, global.out.rider.Pos()-1, global.out.rider.attr)
			END
		END;

		Indent; LocalName(obj); SysStrings(obj); String(" = ");
		IF obj.typ.strobj # obj THEN
			TypName(obj.typ, FALSE);
			AliasType(obj.typ)
		ELSIF (obj.typ.form < pointer) & (obj.typ.BaseTyp # DevCPT.undftyp) THEN
			TypName(obj.typ.BaseTyp, FALSE);
			AliasType(obj.typ.BaseTyp)
		ELSE
			Structure(obj.typ)
		END;
		Char(";"); Ln;
		global.gap := TRUE
	END Type;

	PROCEDURE PtrType (obj: DevCPT.Object);
		VAR base: DevCPT.Object;
	BEGIN
		Type(obj);
		base := obj.typ.BaseTyp.strobj;
		IF (base # NIL) & (base # DevCPT.anytyp.strobj) & (base # DevCPT.iunktyp.strobj)
				& (base.vis # internal) & (base.typ.strobj # NIL) & ~(global.hideHooks & IsHook(base.typ)) THEN
			(* show named record with pointer; avoid repetition *)

		IF global.extensioninterface
		& (base.typ.strobj = base) & ~((obj.typ.form < pointer) & (obj.typ.BaseTyp # DevCPT.undftyp))
		& (NotExtRec(base.typ)
			OR
			(base.typ.form = pointer) & ~IsLegal(base.typ.BaseTyp.strobj.name) &
				NotExtRec(base.typ.BaseTyp)
		)

		THEN
			IF global.singleton THEN
				IF global.pos < global.out.rider.Pos() THEN
					global.out.rider.Base().Delete(global.pos, global.out.rider.Pos());
					global.out.rider.SetPos(global.pos)
				END;
				RETURN
			END;
			global.out.rider.SetAttr(TextModels.NewColor(global.out.rider.attr, Ports.grey50));
			IF global.pos < global.out.rider.Pos() THEN
				global.out.rider.Base().SetAttr(global.pos, global.out.rider.Pos()-1, global.out.rider.attr)
			END
		END;

			global.gap := FALSE;
			Indent;
			String(base.name^); SysStrings(base); String(" = ");
			IF base.typ.strobj # base THEN
				TypName(base.typ, FALSE);
				AliasType(obj.typ)
			ELSIF (obj.typ.form < pointer) & (obj.typ.BaseTyp # DevCPT.undftyp) THEN
				TypName(obj.typ.BaseTyp, FALSE);
				AliasType(obj.typ.BaseTyp)
			ELSE
				Structure(base.typ)
			END;
			Char(";"); Ln;
			base.vis := internal;
			global.gap := TRUE
		END
	END PtrType;

	PROCEDURE Var (obj: DevCPT.Object);
	BEGIN
		IF global.hideHooks & IsHook(obj.typ) THEN RETURN END;
		Indent; LocalName(obj); Vis(obj.vis); SysStrings(obj); String(": ");
		TypName(obj.typ, FALSE); Char(";"); Ln
	END Var;

	PROCEDURE Proc (obj: DevCPT.Object);
		VAR ext: DevCPT.ConstExt; i, m: INTEGER;
	BEGIN
		IF global.hideHooks & (obj.link # NIL) & (obj.link.link = NIL) & IsHook(obj.link.typ) THEN RETURN END;
		IF global.extensioninterface & ~((obj.link # NIL) & (obj.link.link = NIL) & IsHook(obj.link.typ)) THEN RETURN END;
		Indent; Keyword("PROCEDURE"); (* Char(" "); *)
		IF obj.mode = cProc THEN String(" [code]")
		ELSIF obj.mode = iProc THEN String(" [callback]")
		ELSE ProcSysFlag(obj.sysflag)
		END;
		Char(" "); LocalName(obj);
		(*IF obj.mode # cProc THEN SysStrings(obj) END;*)
		SysStrings(obj);
		Signature(obj.typ, obj.link);
		IF (obj.mode = cProc) & global.hints THEN
			Ln;
			INC(global.level); Indent;
			ext := obj.conval.ext;
			IF ext # NIL THEN m := LEN(ext^); i := 0;
				WHILE i < m DO
					global.out.WriteIntForm(ORD(ext^[i]), TextMappers.hexadecimal, 3, "0", TextMappers.showBase);
					INC(i);
					IF i < m THEN String(", ") END;
				END;
			END;
			DEC(global.level)
		END;
		Char(";"); Ln
	END Proc;


	(* tree traversal *)

	PROCEDURE Objects (obj: DevCPT.Object; mode: SET);
		VAR m: BYTE; a: TextModels.Attributes;
	BEGIN
		IF obj # NIL THEN
			INC(lev); INC(num); max := MAX(max, lev);
			Objects(obj.left, mode);
			m := obj.mode; IF (m = type) & (obj.typ.form = pointer) THEN m := ptrType END;
			IF (m IN mode) & (obj.vis # internal) & (obj.name # NIL) &
					(~global.singleton OR (obj.name^ = global.thisObj)) THEN
				global.pos := global.out.rider.Pos(); a := global.out.rider.attr;
				CASE m OF
				  const: Const(obj)
				| type: Type(obj)
				| ptrType: PtrType(obj)
				| var: Var(obj)
				| xProc, cProc, iProc: Proc(obj)
				END;
				global.out.rider.SetAttr(a)
			END;
			Objects(obj.right, mode);
			DEC(lev)
		END
	END Objects;


	(* definition formatting *)

	PROCEDURE PutSection (VAR out: TextMappers.Formatter; s: ARRAY OF CHAR);
		VAR buf, def: TextModels.Model; i: INTEGER;
	BEGIN
		buf := global.out.rider.Base();
		IF buf.Length() > 0 THEN
			IF ~global.singleton THEN Ln END;
			def := out.rider.Base(); out.SetPos(def.Length());
			IF s # "" THEN	(* prepend section keyword *)
				i := global.level - 1; WHILE i > 0 DO out.WriteTab; DEC(i) END;
				Section(out, s); out.WriteLn
			END;
			def.Append(buf);
			global.pos := 0;
			global.out.rider.SetPos(0)
		END;
		global.gap := FALSE
	END PutSection;

	PROCEDURE Scope (def: TextModels.Model; this: DevCPT.Name);
		VAR out: TextMappers.Formatter; i: INTEGER; a: TextModels.Attributes;
	BEGIN
		global.gap := FALSE; global.thisObj := this; global.singleton := (this # "");
		global.systemUsed := FALSE; global.comUsed := FALSE;
		i := 1; WHILE i < LEN(global.modUsed) DO global.modUsed[i] := FALSE; INC(i) END;
		out.ConnectTo(def);
		INC(global.level);
		IF ~(global.extensioninterface & global.singleton) THEN
			IF global.extensioninterface THEN
				a := global.out.rider.attr; global.out.rider.SetAttr(TextModels.NewColor(a, Ports.grey50))
			END;
			Objects(DevCPT.GlbMod[1].right, {const});
			IF global.extensioninterface THEN global.out.rider.SetAttr(a) END;
			PutSection(out, "CONST")
		END;
		Objects(DevCPT.GlbMod[1].right, {ptrType});
		Objects(DevCPT.GlbMod[1].right, {type}); PutSection(out, "TYPE");
		IF ~global.extensioninterface THEN
			Objects(DevCPT.GlbMod[1].right, {var}); PutSection(out, "VAR")
		END;
		DEC(global.level);
		num := 0; lev := 0; max := 0;
		Objects(DevCPT.GlbMod[1].right, {xProc, cProc}); PutSection(out, "")
	END Scope;

	PROCEDURE Modules;
		VAR i, j, n: INTEGER;
	BEGIN
		n := 0; j := 2;
		IF global.systemUsed THEN INC(n) END;
		IF global.comUsed THEN INC(n) END;
		WHILE j < DevCPT.nofGmod DO
			IF global.modUsed[j] THEN INC(n) END;
			INC(j)
		END;
		IF n > 0 THEN
			Indent; Section(global.out, "IMPORT"); INC(global.level);
			IF n < 10 THEN Char(" ") ELSE Ln; Indent END;
			i := 0; j := 2;
			IF global.systemUsed THEN
				String("SYSTEM"); INC(i);
				IF i < n THEN String(", ") END
			END;
			IF global.comUsed THEN
				String("COM"); INC(i);
				IF i < n THEN String(", ") END
			END;
			WHILE i < n DO
				IF global.modUsed[j] THEN
					String(DevCPT.GlbMod[j].name^); INC(i);
(*
					IF (DevCPT.GlbMod[j].strings # NIL) & (DevCPT.GlbMod[j].strings.ext # NIL) THEN
						String(' ["'); String(DevCPT.GlbMod[j].strings.ext^); String('"]')
					END;
*)
					IF i < n THEN
						Char(",");
						IF i MOD 10 = 0 THEN Ln; Indent ELSE Char(" ") END
					END
				END;
				INC(j)
			END;
			Char(";"); Ln; Ln
		END
	END Modules;


	(* compiler interfacing *)

	PROCEDURE OpenCompiler (mod: DevCPT.Name; VAR noerr: BOOLEAN);
		VAR null: TextModels.Model; main: DevCPT.Name;
	BEGIN
		null := TextModels.dir.New();
		DevCPM.Init(null.NewReader(NIL), null);
		DevCPT.Init({}); main := "@mainMod"; DevCPT.Open(main);
		DevCPT.processor := 0;	(* accept all sym files *)
		DevCPT.Import("@notself", mod, noerr);
		IF ~DevCPM.noerr THEN noerr := FALSE END
	END OpenCompiler;

	PROCEDURE CloseCompiler;
	BEGIN
		DevCPT.Close;
		DevCPM.Close
	END CloseCompiler;


	PROCEDURE Browse (
		ident: DevCPT.Name; opts: ARRAY OF CHAR; VAR view: Views.View; VAR title: Views.Title
	);
		VAR def, buf: TextModels.Model; v: TextViews.View; h: BOOLEAN;
			p: Properties.BoundsPref; mod: DevCPT.Name; i: INTEGER; 
			str, str1: ARRAY LEN(DevCPT.Name) OF CHAR;
			res: INTEGER; n: DevCPT.Name;
	BEGIN
		mod := DevCPT.GlbMod[1].name$;
		i := 0;
		global.hints := FALSE; global.flatten := FALSE; global.hideHooks := TRUE;
		global.clientinterface := FALSE; global.extensioninterface := FALSE;
		global.keyAttr := NIL; global.sectAttr := NIL;

		IF opts[0] = '@' THEN
			IF options.hints THEN opts := opts + "+" END;
			IF options.flatten THEN opts := opts + "!" END;
			IF options.formatted THEN opts := opts + "/" END
		END;

		WHILE opts[i] # 0X DO
			CASE opts[i] OF
			| '+': global.hints := TRUE
			| '!': global.flatten := TRUE
			| '&': global.hideHooks := FALSE
			| '/':
				(* global.keyAttr := TextModels.NewStyle(TextModels.dir.attr, {Fonts.underline}); *)
				global.keyAttr := TextModels.NewWeight(TextModels.dir.attr, Fonts.bold);
				global.sectAttr := TextModels.NewWeight(TextModels.dir.attr, Fonts.bold)
			| 'c': global.clientinterface := TRUE
			| 'e' : global.extensioninterface := TRUE
			ELSE
			END;
			INC(i)
		END;
		IF global.clientinterface & global.extensioninterface THEN
			global.clientinterface := FALSE; global.extensioninterface := FALSE
		END;
		IF global.extensioninterface THEN global.hideHooks := FALSE END;
		def := TextModels.dir.New();
		buf := TextModels.dir.New(); global.out.ConnectTo(buf);
		IF ident # "" THEN
			h := global.hideHooks; global.hideHooks := FALSE;
			global.level := 0; Scope(def, ident);
			global.hideHooks := h;
			def.Append(buf);
			IF def.Length() > 0 THEN
				v := TextViews.dir.New(def);
				v.SetDefaults(NewRuler(), TextViews.dir.defAttr);
				p.w := Views.undefined; p.h := Views.undefined; Views.HandlePropMsg(v, p);
				n := mod$ + "." + ident; Utf.Utf8ToString(n, title, res);
				view := Documents.dir.New(v, p.w, p.h)
			ELSE
				Utf.Utf8ToString(mod$, str, res);
				Utf.Utf8ToString(ident$, str1, res);
				title := "";
				IF global.extensioninterface THEN
					Dialog.ShowParamMsg(noSuchExtItemExportedKey, str, str1, "")
				ELSE
					Dialog.ShowParamMsg(noSuchItemExportedKey, str, str1, "")
				END
			END
		ELSE
			global.level := 1; Scope(def, "");
			Section(global.out, "END"); Char(" "); String(mod); Char("."); Ln;
			def.Append(buf);
			Section(global.out, "DEFINITION"); Char(" "); String(mod);
			IF (DevCPT.GlbMod[1].library # NIL) THEN
				String(' ["'); String(DevCPT.GlbMod[1].library^); String('"]')
			ELSIF DevCPT.impProc # 0 THEN
				global.out.WriteTab; String("(* nonportable");
				IF DevCPT.impProc = 10 THEN String(" (i386)")
				ELSIF DevCPT.impProc = 20 THEN String(" (68k)")
				END;
				String(" *)")
			END;
			Char(";"); Ln; Ln;
			Modules;
			buf.Append(def);
			v := TextViews.dir.New(buf);
			v.SetDefaults(NewRuler(), TextViews.dir.defAttr);
			Utf.Utf8ToString(mod, title, res);
			view := Documents.dir.New(v, width, height)
		END;
		global.out.ConnectTo(NIL);
		global.keyAttr := NIL; global.sectAttr := NIL
	END Browse;

	PROCEDURE ShowInterface* (opts: ARRAY OF CHAR);
		VAR noerr: BOOLEAN; mod, ident: DevCPT.Name; v: Views.View; title: Views.Title; t: TextModels.Model;
			str: ARRAY 256 OF CHAR; res: INTEGER;
	BEGIN
		GetQualIdent(mod, ident, t);
		IF mod # "" THEN
			OpenCompiler(mod, noerr);
			IF noerr & (DevCPT.GlbMod[1] # NIL) & (DevCPT.GlbMod[1].name^ = mod) THEN
				Browse(ident, opts, v, title);
				IF v # NIL THEN
					IF global.clientinterface THEN Append(title, " (client interface)")
					ELSIF global.extensioninterface THEN Append(title, " (extension interface)")
					END;
					Views.OpenAux(v, title)
				END
			ELSE Utf.Utf8ToString(mod$, str, res); Dialog.ShowParamMsg(modNotFoundKey, str, "", "")
			END;
			CloseCompiler
		ELSE Dialog.ShowMsg(noModuleNameSelectedKey)
		END;
		Kernel.Cleanup
	END ShowInterface;

	PROCEDURE ImportSymFile* (f: Files.File; OUT s: Stores.Store);
		VAR v: Views.View; title: Views.Title; noerr: BOOLEAN;
	BEGIN
		ASSERT(f # NIL, 20);
		DevCPM.file := f;
		OpenCompiler("@file", noerr);
		IF noerr THEN
			Browse("", "", v, title);
			IF v IS Documents.Document THEN v := v(Documents.Document).ThisView() END;
			s := v
		END;
		CloseCompiler;
		DevCPM.file := NIL;
		Kernel.Cleanup
	END ImportSymFile;


	(* codefile browser *)

	PROCEDURE RWord (VAR x: INTEGER);
		VAR b: BYTE; y: INTEGER;
	BEGIN
		inp.ReadByte(b); y := b MOD 256;
		inp.ReadByte(b); y := y + 100H * (b MOD 256);
		inp.ReadByte(b); y := y + 10000H * (b MOD 256);
		inp.ReadByte(b); x := y + 1000000H * b
	END RWord;

	PROCEDURE RNum (VAR x: INTEGER);
		VAR b: BYTE; s, y: INTEGER;
	BEGIN
		s := 0; y := 0; inp.ReadByte(b);
		WHILE b < 0 DO INC(y, ASH(b + 128, s)); INC(s, 7); inp.ReadByte(b) END;
		x := ASH((b + 64) MOD 128 - 64, s) + y
	END RNum;

	PROCEDURE RName (VAR name: ARRAY OF SHORTCHAR);
		VAR b: BYTE; i, n: INTEGER;
	BEGIN
		i := 0; n := LEN(name) - 1; inp.ReadByte(b);
		WHILE (i < n) & (b # 0) DO name[i] := SHORT(CHR(b)); INC(i); inp.ReadByte(b) END;
		WHILE b # 0 DO inp.ReadByte(b) END;
		name[i] := 0X
	END RName;

	PROCEDURE RLink;
		VAR x: INTEGER;
	BEGIN
		RNum(x);
		WHILE x # 0 DO RNum(x); RNum(x) END
	END RLink;

	PROCEDURE RShort (p: INTEGER; OUT x: INTEGER);
		VAR b0, b1: BYTE;
	BEGIN
		inp.ReadByte(b0); inp.ReadByte(b1);
		IF p = 10 THEN x := b0 MOD 256 + b1 * 256	(* little endian *)
		ELSE x := b1 MOD 256 + b0 * 256	(* big endian *)
		END
	END RShort;
	
	PROCEDURE Decode (file: Files.File; VAR view: Views.View; VAR title: Views.Title);
		VAR n, i, p, q, fp, hs, ms, ds, cs, vs, m, res: INTEGER; name: Kernel.Utf8Name; first: BOOLEAN;
			text: TextModels.Model; v: TextViews.View; fold: StdFolds.Fold; d: Dates.Date; t: Dates.Time;
			str: ARRAY 64 OF CHAR;
	BEGIN
		text := TextModels.dir.New(); global.out.ConnectTo(text);
		inp := file.NewReader(NIL);
		inp.SetPos(0); RWord(n);
		IF n = 6F4F4346H THEN
			RWord(p); RWord(hs); RWord(ms); RWord(ds); RWord(cs); RWord(vs);
			RNum(n); RName(name); Utf.Utf8ToString(name, title, res); i := 0;
			WHILE i < n DO RName(imp[i]); INC(i) END;
			String("MODULE "); String(name); Ln;
			String("processor: ");
			IF p = 10 THEN String("80x86")
			ELSIF p = 20 THEN String("68000")
			ELSIF p = 30 THEN String("PPC")
			ELSIF p = 40 THEN String("SH3")
			ELSE Int(p)
			END;
			Ln;
			String("meta size: "); Int(ms); Ln;
			String("desc size: "); Int(ds); Ln;
			String("code size: "); Int(cs); Ln;
			String("data size: "); Int(vs); Ln;
			inp.SetPos(hs + ms + 12);
			RShort(p, d.year); RShort(p, d.month); RShort(p, d.day);
			RShort(p, t.hour); RShort(p, t.minute); RShort(p, t.second);
			String("compiled: ");
			Dates.DateToString(d, Dates.short, str); global.out.WriteString(str); String("  ");
			Dates.TimeToString(t, str); global.out.WriteString(str); Ln;
			Int(n); String(" imports:"); Ln;
			inp.SetPos(hs + ms + ds + cs);
			RLink; RLink; RLink; RLink; RLink; RLink; i := 0;
			WHILE i < n DO
				global.out.WriteTab; String(imp[i]); global.out.WriteTab;
				RNum(q);
				IF q # 0 THEN
					fold := StdFolds.dir.New(StdFolds.expanded, "", TextModels.dir.New());
					global.out.WriteView(fold); Char(" "); first := TRUE;
					WHILE q # 0 DO
						RName(name); RNum(fp);
						IF q = 2 THEN RNum(m) END;
						IF q # 1 THEN RLink END;
						IF name # "" THEN
							IF ~first THEN String(", ") END;
							first := FALSE; String(name)
						END;
						RNum(q)
					END;
					Char(" ");
					fold := StdFolds.dir.New(StdFolds.expanded, "", NIL);
					global.out.WriteView(fold);
					fold.Flip(); global.out.SetPos(text.Length())
				END;
				INC(i); Ln
			END;
			IF p = 10 THEN DevDecoder386.Decode(title, inp, hs, ms, ds, cs, vs, -1, global.out) END
		ELSE String("wrong file tag: "); Hex(n, 8)
		END;
		v := TextViews.dir.New(text);
		v.SetDefaults(NewRuler(), TextViews.dir.defAttr);
		view := Documents.dir.New(v, width, height);
		global.out.ConnectTo(NIL);
		inp := NIL
	END Decode;

	PROCEDURE ShowCodeFile*;
		VAR t: TextModels.Model; f: Files.File; v: Views.View; title: Views.Title; mod, ident: DevCPT.Name;
			name: Files.Name; loc: Files.Locator; str: ARRAY 256 OF CHAR; res: INTEGER;
	BEGIN
		GetQualIdent(mod, ident, t);
		IF mod # "" THEN
			Utf.Utf8ToString(mod$, str, res);
			StdDialog.GetSubLoc(str, "Code", loc, name);
			f := Files.dir.Old(loc, name, Files.shared);
			IF f # NIL THEN
				Decode(f, v, title);
				IF v # NIL THEN Views.OpenAux(v, title) END
			ELSE Dialog.ShowParamMsg(modNotFoundKey, str, "", "")
			END
		ELSE Dialog.ShowMsg(noModuleNameSelectedKey)
		END
	END ShowCodeFile;

	PROCEDURE ImportCodeFile* (f: Files.File; OUT s: Stores.Store);
		VAR v: Views.View; title: Views.Title;
	BEGIN
		ASSERT(f # NIL, 20);
		Decode(f, v, title);
		IF v IS Documents.Document THEN v := v(Documents.Document).ThisView() END;
		s := v
	END ImportCodeFile;

	PROCEDURE SaveOptions*;
	BEGIN
		StdRegistry.WriteBool(regKey + 'hints', options.hints);
		StdRegistry.WriteBool(regKey + 'flatten', options.flatten);
		StdRegistry.WriteBool(regKey + 'formatted', options.formatted);
	END SaveOptions;

	PROCEDURE SaveGuard*(VAR par: Dialog.Par);
	BEGIN
		par.disabled := (options.sflatten = options.flatten)
		& (options.shints = options.hints)
		& (options.sformatted = options.formatted)
	END SaveGuard;

	PROCEDURE Init*;
		VAR res: INTEGER;
	BEGIN
		StdRegistry.ReadBool(regKey + 'hints', options.hints, res);
		IF res # 0 THEN options.hints := FALSE END;
		StdRegistry.ReadBool(regKey + 'flatten', options.flatten, res);
		IF res # 0 THEN options.flatten := TRUE END;
		StdRegistry.ReadBool(regKey + 'formatted', options.formatted, res);
		IF res # 0 THEN options.formatted := FALSE END;
		
		Dialog.Update(options);
		options.sflatten := options.flatten;
		options.shints := options.hints;
		options.sformatted := options.formatted
	END Init;

BEGIN
	Init
END DevBrowser.
