MODULE DevDebug;
(**
	project	= "BlackBox 2.0, new module"
	organizations	= "Oberon microsystems, BBWFC, OberonCore, BBCP Team"
	contributors	= "Oberon microsystems"
	version	= "System/Rsrc/About"
	copyright	= "System/Rsrc/About"
	license	= "Docu/BB-License"
	changes	= "
	- 20070123, bh, WriteString, ShowArray, & ShowVar changed (Unicode support)
	- 20141027, center #19, full Unicode support for Component Pascal identifiers added
	- 20150326, center #33, adding platform detection for wine, Windows 7, and Windows 8 
	- 20150527, center #54, UnloadThis must use DevCommanders.par.text 
	- 20150929, center #72, speeding up the dump of large data structures by avoiding fold.Flip
	- 20151106, center #84, unloading DevDebug produces a sequence of Traps
	- 20160324, center #111, code cleanups
	- 20161103, center #140, unwrapping import aliasses
	- 20170215, center #139, localization support for error messages
	- 20170623, Nikita Plynskii, remove digitspaces from loaded modules
	- 20180515, center #187, fixing the type of vi in ShowArray
	- 20181207, center #195, support for long identifiers in trap handlers
	- 20210123, bbcp #23, per module string resources
	"
	issues	= "
	- ...
	"

**)

	IMPORT SYSTEM,
		Kernel, Strings, Utf, Librarian, Dates, Files, Fonts, Services, Ports, Stores, Converters,
		Models, Views, Controllers, Properties, Dialog, Containers, Controls,
		Windows, StdDialog, StdFolds, StdLinks,
		TextModels, TextMappers, TextControllers, TextViews, TextRulers, StdLog,
		DevReferences, DevCommanders;
	
	CONST
		refViewSize = 9 * Ports.point;
		
		heap = 1; source = 2; module = 3; modules = 4;	(* RefView types *)
		open = 1; undo = 2; update = 3;	(* RefView commands *)
		
		(* additional scanner types *)
		import = 100; smodule = 101; semicolon = 102; becomes  = 103; stop = 104; comEnd = 105; 

	TYPE
		ArrayPtr = POINTER TO EXTENSIBLE RECORD
			last, t, first: INTEGER;	(* gc header *)
			len: ARRAY 16 OF INTEGER	(* dynamic array length table *)
		END;
		RefView = POINTER TO RECORD (Views.View)
			type: SHORTINT;
			command: SHORTINT;
			back: RefView;
			adr: INTEGER;
			desc: Kernel.Type;
			ptr: ArrayPtr;
			name: Kernel.Utf8Name
		END;
		
		Action = POINTER TO RECORD (Services.Action)
			text: TextModels.Model
		END;
		
		Cluster = POINTER TO RECORD [untagged]	(* must correspond to Kernel.Cluster *)
			size: INTEGER;
			next: Cluster
		END;
		
		FoldContext = RECORD
			prevText: TextModels.Model;
			prevPos: INTEGER
		END;
		

	VAR
		out: TextMappers.Formatter;
		path: ARRAY 4 OF Ports.Point;
		empty: Kernel.Name;


	PROCEDURE NewRuler (): TextRulers.Ruler;
		CONST mm = Ports.mm;
		VAR r: TextRulers.Ruler;
	BEGIN
		r := TextRulers.dir.New(NIL);
		TextRulers.SetRight(r, 140 * mm);
		TextRulers.AddTab(r, 4 * mm); TextRulers.AddTab(r, 34 * mm); TextRulers.AddTab(r, 80 * mm);
		RETURN r
	END NewRuler;

	PROCEDURE NewModRuler (): TextRulers.Ruler;
		CONST mm = Ports.mm;
		VAR r: TextRulers.Ruler;
	BEGIN
		r := TextRulers.dir.New(NIL);
		TextRulers.SetRight(r, 144 * mm);
		TextRulers.AddTab(r, 48 * mm); TextRulers.MakeRightTab(r);
		TextRulers.AddTab(r, 64 * mm); TextRulers.MakeRightTab(r);
		TextRulers.AddTab(r, 76 * mm); TextRulers.AddTab(r, 110 * mm);
		RETURN r
	END NewModRuler;

	PROCEDURE OpenViewer (t: TextModels.Model; title: Views.Title; ruler:TextRulers.Ruler);
		VAR v: TextViews.View; c: Containers.Controller;
	BEGIN
		Dialog.MapString(title, title);
		v := TextViews.dir.New(t);
		v.SetDefaults(ruler, TextViews.dir.defAttr);
		c := v.ThisController();
		IF c # NIL THEN
			c.SetOpts(c.opts - {Containers.noFocus, Containers.noSelection} + {Containers.noCaret})
		END;
		Views.OpenAux(v, title)
	END OpenViewer;
	
	PROCEDURE OpenFold (OUT fc: FoldContext);
	BEGIN
		fc.prevText := out.rider.Base();
		fc.prevPos := out.Pos();
		out.ConnectTo(TextModels.CloneOf(StdLog.buf))
	END OpenFold;

	PROCEDURE CloseFold (IN fc: FoldContext; collapsed: BOOLEAN; IN hidden: ARRAY OF CHAR);
		VAR fold: StdFolds.Fold; t: TextModels.Model; w: TextMappers.Formatter; hiddenx: ARRAY 32 OF CHAR;
	BEGIN
		Dialog.MapString(hidden, hiddenx);
		(* avoid expensive fold.Flip operation *)
		IF collapsed THEN
			fold := StdFolds.dir.New(StdFolds.collapsed, "", out.rider.Base());
			out.ConnectTo(fc.prevText);
			out.WriteView(fold);
			out.WriteString(hiddenx)
		ELSE
			t := TextModels.CloneOf(StdLog.buf);
			w.ConnectTo(t); w.WriteString(hiddenx);
			fold := StdFolds.dir.New(StdFolds.expanded, "", t);
			t := out.rider.Base();
			out.ConnectTo(fc.prevText);
			out.WriteView(fold);
			fc.prevText.Insert(out.Pos(), t, 0, t.Length());
			out.SetPos(out.rider.Base().Length())
		END;
		fold := StdFolds.dir.New(collapsed, "", NIL);
		out.WriteView(fold)
	END CloseFold;

	PROCEDURE WriteHex (n: INTEGER);
	BEGIN
		out.WriteIntForm(n, TextMappers.hexadecimal, 9, "0", TextMappers.showBase)
	END WriteHex;
	
	PROCEDURE WriteString (adr, len, base: INTEGER; zterm, unicode: BOOLEAN);
		CONST beg = 0; char = 1; code = 2;
		VAR ch: CHAR; sc: SHORTCHAR; val, mode: INTEGER; str: ARRAY 16 OF CHAR;
	BEGIN
		mode := beg;
		IF base = 2 THEN SYSTEM.GET(adr, ch); val := ORD(ch) ELSE SYSTEM.GET(adr, sc); val := ORD(sc) END;
		IF zterm & (val = 0) THEN out.WriteString('""')
		ELSE
			REPEAT
				IF (val >= ORD(" ")) & (val < 7FH) OR (val > 0A0H) & (val < 100H) OR unicode & (val >= 100H) THEN
					IF mode # char THEN
						IF mode = code THEN out.WriteString(", ") END;
						out.WriteChar(22X); mode := char
					END;
					out.WriteChar(CHR(val))
				ELSE
					IF mode = char THEN out.WriteChar(22X) END;
					IF mode # beg THEN out.WriteString(", ") END;
					mode := code; Strings.IntToStringForm(val, Strings.hexadecimal, 1, "0", FALSE, str);
					IF str[0] > "9" THEN out.WriteChar("0") END;
					out.WriteString(str); out.WriteChar("X")
				END;
				INC(adr, base); DEC(len);
				IF base = 2 THEN SYSTEM.GET(adr, ch); val := ORD(ch) ELSE SYSTEM.GET(adr, sc); val := ORD(sc) END
			UNTIL (len = 0) OR zterm & (val = 0)
		END;
		IF mode = char THEN out.WriteChar(22X) END
	END WriteString;
	
	PROCEDURE IsIdent (s: ARRAY OF CHAR): BOOLEAN;
		VAR i: INTEGER; ch: CHAR;
	BEGIN
		ch := s[0];
		IF Strings.IsIdentStart(ch) THEN
			i := 1; ch := s[1];
			WHILE Strings.IsIdent(ch) DO
				INC(i); ch := s[i]
			END;
			RETURN (s[i] = 0X) & (i < 256)
		ELSE RETURN FALSE
		END
	END IsIdent;
	
	PROCEDURE OutString (s: ARRAY OF CHAR);
		VAR str: Dialog.String;
	BEGIN
		Dialog.MapString(s, str);
		out.WriteString(str);
	END OutString;

	(* -------------------  variable display ------------------- *)
	
	PROCEDURE FormOf (t: Kernel.Type): SHORTCHAR;
	BEGIN
		IF SYSTEM.VAL(INTEGER, t) DIV 256 = 0 THEN
			RETURN SHORT(CHR(SYSTEM.VAL(INTEGER, t)))
		ELSE
			RETURN SHORT(CHR(16 + t.id MOD 4))
		END
	END FormOf;
	
	PROCEDURE LenOf (t: Kernel.Type; ptr: ArrayPtr): INTEGER;
	BEGIN
		IF t.size # 0 THEN RETURN t.size
		ELSIF ptr # NIL THEN RETURN ptr.len[t.id DIV 16 MOD 16 - 1]
		ELSE RETURN 0
		END
	END LenOf;
	
	PROCEDURE SizeOf (t: Kernel.Type; ptr: ArrayPtr): INTEGER;
	BEGIN
		CASE FormOf(t) OF
		| 0BX: RETURN 0
		| 1X, 2X, 4X: RETURN 1
		| 3X, 5X: RETURN 2
		| 8X, 0AX: RETURN 8
		| 11X: RETURN t.size
		| 12X: RETURN LenOf(t, ptr) * SizeOf(t.base[0], ptr)
		ELSE RETURN 4
		END
	END SizeOf;

	PROCEDURE WriteName (t: Kernel.Type; ptr: ArrayPtr);
		VAR modName, name: Kernel.Name; f: SHORTCHAR;
	BEGIN
		f := FormOf(t);
		CASE f OF
		| 0X: OutString("#Dev:Unknown")
		| 1X: out.WriteString("BOOLEAN")
		| 2X: out.WriteString("SHORTCHAR")
		| 3X: out.WriteString("CHAR")
		| 4X: out.WriteString("BYTE")
		| 5X: out.WriteString("SHORTINT")
		| 6X: out.WriteString("INTEGER")
		| 7X: out.WriteString("SHORTREAL")
		| 8X: out.WriteString("REAL")
		| 9X: out.WriteString("SET")
		| 0AX: out.WriteString("LONGINT")
		| 0BX: out.WriteString("ANYREC")
		| 0CX: out.WriteString("ANYPTR")
		| 0DX: out.WriteString("POINTER")
		| 0EX: out.WriteString("PROCEDURE")
		| 0FX: out.WriteString("STRING")
		| 10X..13X:
			IF (t.mod # NIL) & (t.id DIV 256 # 0) & (t.mod.refcnt >= 0) THEN
				Kernel.GetTypeName(t, name);
				IF name = "!" THEN
					IF f = 11X THEN out.WriteString("RECORD")
					ELSIF f = 12X THEN out.WriteString("ARRAY")
					ELSE OutString("#Dev:Unknown")
					END
				ELSE
					Kernel.GetModName(t.mod, modName);
					out.WriteString(modName); out.WriteChar("."); out.WriteString(name)
				END
			ELSIF f = 11X THEN
				IF t.mod # NIL THEN Kernel.GetModName(t.mod, modName); out.WriteString(modName); out.WriteChar(".") END;
				out.WriteString("RECORD"); 
			ELSIF f = 12X THEN
				out.WriteString("ARRAY "); out.WriteInt(LenOf(t, ptr)); t := t.base[0];
				WHILE (FormOf(t) = 12X) & ((t.id DIV 256 = 0) OR (t.mod = NIL) OR (t.mod.refcnt < 0)) DO
					out.WriteString(", "); out.WriteInt(LenOf(t, ptr)); t := t.base[0]
				END;
				out.WriteString(" OF "); WriteName(t, ptr)
			ELSIF f = 13X THEN
				out.WriteString("POINTER")
			ELSE
				out.WriteString("PROCEDURE")
			END
		| 20X: out.WriteString("COM.IUnknown")
		| 21X: out.WriteString("COM.GUID")
		| 22X: out.WriteString("COM.RESULT")
		ELSE OutString("#Dev:UnknownFormat"); out.WriteInt(ORD(f))
		END
	END WriteName;
	
	PROCEDURE WriteGuid (a: INTEGER);
	
		PROCEDURE Hex (a: INTEGER);
			VAR x: SHORTCHAR;
		BEGIN
			SYSTEM.GET(a, x);
			out.WriteIntForm(ORD(x), TextMappers.hexadecimal, 2, "0", FALSE)
		END Hex;
		
	BEGIN
		out.WriteChar("{");
		Hex(a + 3); Hex(a + 2); Hex(a + 1); Hex(a);
		out.WriteChar("-");
		Hex(a + 5); Hex(a + 4);
		out.WriteChar("-");
		Hex(a + 7); Hex(a + 6);
		out.WriteChar("-");
		Hex(a + 8);
		Hex(a + 9);
		out.WriteChar("-");
		Hex(a + 10);
		Hex(a + 11);
		Hex(a + 12);
		Hex(a + 13);
		Hex(a + 14);
		Hex(a + 15);
		out.WriteChar("}")
	END WriteGuid;
	
	PROCEDURE^ ShowVar (
		ad, ind: INTEGER; f, c: SHORTCHAR; desc: Kernel.Type; ptr: ArrayPtr; back: RefView; VAR name, sel: Kernel.Name);
	
	PROCEDURE^ NewRefView (type, command: SHORTINT; adr: INTEGER; back: RefView;
												desc: Kernel.Type; ptr: ArrayPtr; name: Kernel.Name): RefView;

	PROCEDURE^ InsertRefView (type, command: SHORTINT; adr: INTEGER; back: RefView;
												desc: Kernel.Type; ptr: ArrayPtr; name: Kernel.Name);

	PROCEDURE ShowRecord (a, ind: INTEGER; desc: Kernel.Type; back: RefView; VAR sel: Kernel.Name);
		VAR dir: Kernel.Directory; obj: Kernel.Object; name: Kernel.Name; i, j, n: INTEGER; base: Kernel.Type;
			fc: FoldContext;
	BEGIN
		WriteName(desc, NIL); out.WriteTab;
		IF desc.mod.refcnt >= 0 THEN
			OpenFold(fc);
			n := desc.id DIV 16 MOD 16; j := 0;
			WHILE j <= n DO
				base := desc.base[j];
				IF base # NIL THEN
					dir := base.fields; i := 0;
					WHILE i < dir.num DO
						obj := SYSTEM.VAL(Kernel.Object, SYSTEM.ADR(dir.obj[i]));
						Kernel.GetObjName(base.mod, obj, name);
						ShowVar(a + obj.offs, ind, FormOf(obj.struct), 1X, obj.struct, NIL, back, name, sel);
						INC(i)
					END
				END;
				INC(j)
			END;
			out.WriteString("   ");
			CloseFold(fc, (ind > 1) OR (sel # ""), "#Dev:Fields")
		ELSE
			OutString("#Dev:Unloaded")
		END
	END ShowRecord;
	
	PROCEDURE ShowArray (a, ind: INTEGER; desc: Kernel.Type; ptr: ArrayPtr; back: RefView; VAR sel: Kernel.Name);
		VAR f: SHORTCHAR; i, n, m, size, len: INTEGER; name: Kernel.Name; eltyp, t: Kernel.Type;
			vi: SHORTINT; vs: BYTE; str: Dialog.String; high: BOOLEAN;
			fc: FoldContext;
	BEGIN
		WriteName(desc, ptr); out.WriteTab;
		len := LenOf(desc, ptr); eltyp := desc.base[0]; f := FormOf(eltyp); size := SizeOf(eltyp, ptr);
		IF (f = 2X) OR (f = 3X) THEN	(* string *)
			n := 0; m := len; high := FALSE;
			IF f = 2X THEN
				REPEAT SYSTEM.GET(a + n, vs); INC(n) UNTIL (n = 32) OR (n = len) OR (vs = 0);
				REPEAT DEC(m); SYSTEM.GET(a + m, vs) UNTIL (m = 0) OR (vs # 0)
			ELSE
				REPEAT
					SYSTEM.GET(a + n * 2, vi); INC(n);
					IF vi DIV 256 # 0 THEN high := TRUE END
				UNTIL (n = len) OR (vi = 0);
				n := MIN(n, 32);
				REPEAT DEC(m); SYSTEM.GET(a + m * 2, vi) UNTIL (m = 0) OR (vi # 0)
			END;
			WriteString(a, n, size, TRUE, TRUE);
			INC(m, 2);
			IF m > len THEN m := len END;
			IF high OR (m > n) THEN
				out.WriteString("   ");
				OpenFold(fc);
				out.WriteLn;
				IF high & (n = 32) THEN
					WriteString(a, m, size, TRUE, TRUE);
					out.WriteLn; out.WriteLn
				END;
				WriteString(a, m, size, FALSE, FALSE);
				IF m < len THEN out.WriteString(", ..., 0X") END;
				out.WriteString("   ");
				CloseFold(fc, TRUE, "...")
			END
		ELSE
			t := eltyp;
			WHILE FormOf(t) = 12X DO t := t.base[0] END;
			IF FormOf(t) # 0X THEN
				OpenFold(fc);
				i := 0;
				WHILE i < len DO
					Strings.IntToString(i, str);
					name := "[" + SHORT(str$) + "]";
					ShowVar(a, ind, f, 1X, eltyp, ptr, back, name, sel);
					INC(i); INC(a, size)
				END;
				out.WriteString("   ");
				CloseFold(fc, StdFolds.collapsed, "#Dev:Elements")
			END
		END
	END ShowArray;
	
	PROCEDURE ShowProcVar (a: INTEGER);
		VAR vli, n, ref: INTEGER; m: Kernel.Module; modName, name: Kernel.Name; res: INTEGER; nn: Kernel.Utf8Name;
	BEGIN
		SYSTEM.GET(a, vli);
		Kernel.SearchProcVar(vli, m, vli);
		IF m = NIL THEN
			IF vli = 0 THEN out.WriteString("NIL")
			ELSE WriteHex(vli)
			END
		ELSE
			IF m.refcnt >= 0 THEN
				Kernel.GetModName(m, modName); out.WriteString(modName); ref := m.refs;
				REPEAT Kernel.GetRefProc(ref, n, nn) UNTIL (n = 0) OR (vli < n);
				IF vli < n THEN out.WriteChar("."); Utf.Utf8ToString(nn, name, res); out.WriteString(name) END
			ELSE
				OutString("#Dev:ProcInUnloadedMod");
				Kernel.GetModName(m, modName); out.WriteString( modName); out.WriteString(" !!!")
			END
		END
	END ShowProcVar;

	PROCEDURE ShowPointer (a: INTEGER; f: SHORTCHAR; desc: Kernel.Type; back: RefView; VAR sel: Kernel.Name);
		VAR adr, x: INTEGER; ptr: ArrayPtr; c: Cluster; btyp: Kernel.Type;
	BEGIN
		SYSTEM.GET(a, adr);
		IF f = 13X THEN btyp := desc.base[0] ELSE btyp := NIL END;
		IF adr = 0 THEN out.WriteString("NIL")
		ELSIF f = 20X THEN
			out.WriteChar("["); WriteHex(adr); out.WriteChar("]");
			out.WriteChar(" "); c := SYSTEM.VAL(Cluster, Kernel.Root());
			WHILE (c # NIL) & ((adr < SYSTEM.VAL(INTEGER, c)) OR (adr >= SYSTEM.VAL(INTEGER, c) + c.size)) DO c := c.next END;
			IF c # NIL THEN
				ptr := SYSTEM.VAL(ArrayPtr, adr);
				InsertRefView(heap, open, adr, back, btyp, ptr, sel)
			END
		ELSE
			IF (f = 13X) OR (f = 0CX) THEN x := adr - 4 ELSE x := adr END;
			IF ((adr < -4) OR (adr >= 65536)) & Kernel.IsReadable(x, adr + 16) THEN
				out.WriteChar("["); WriteHex(adr); out.WriteChar("]");
				IF (f = 13X) OR (f = 0CX) THEN
					out.WriteChar(" "); c := SYSTEM.VAL(Cluster, Kernel.Root());
					WHILE (c # NIL) & ((adr < SYSTEM.VAL(INTEGER, c)) OR (adr >= SYSTEM.VAL(INTEGER, c) + c.size)) DO
						c := c.next
					END;
					IF c # NIL THEN
						ptr := SYSTEM.VAL(ArrayPtr, adr);
						IF (f = 13X) & (FormOf(btyp) = 12X) THEN	(* array *)
							adr := SYSTEM.ADR(ptr.len[btyp.id DIV 16 MOD 16]) 
						END;
						InsertRefView(heap, open, adr, back, btyp, ptr, sel)
					ELSE OutString("#Dev:IllegalPointer");
					END
				END
			ELSE OutString("#Dev:IllegalAddress"); WriteHex(adr)
			END
		END
	END ShowPointer;
	
	PROCEDURE ShowSelector (ref: RefView);
		VAR b: RefView; n: INTEGER; a, a0: TextModels.Attributes; res: INTEGER; nn: Kernel.Name;
	BEGIN
		b := ref.back; n := 1;
		IF b # NIL THEN
			WHILE (b.name = ref.name) & (b.back # NIL) DO INC(n); b := b.back END;
			ShowSelector(b);
			IF n > 1 THEN out.WriteChar("(") END;
			out.WriteChar(".")
		END;
		Utf.Utf8ToString(ref.name, nn, res); out.WriteString(nn);
		IF ref.type = heap THEN out.WriteChar("^") END;
		IF n > 1 THEN
			out.WriteChar(")");
			a0 := out.rider.attr; a := TextModels.NewOffset(a0, 2 * Ports.point);
			out.rider.SetAttr(a);
			out.WriteInt(n); out.rider.SetAttr(a0)
		END;
	END ShowSelector;
	
	PROCEDURE ShowVar (
		ad, ind: INTEGER; f, c: SHORTCHAR; desc: Kernel.Type; ptr: ArrayPtr; back: RefView; VAR name, sel: Kernel.Name
	);
		VAR i, j, vli, a: INTEGER; tsel: Kernel.Name; a0: TextModels.Attributes;
			vc: SHORTCHAR; vsi: BYTE; vi: SHORTINT; vr: SHORTREAL; vlr: REAL; vs: SET;
	BEGIN
		out.WriteLn; out.WriteTab; i := 0;
		WHILE i < ind DO out.WriteString("  "); INC(i) END;
		a := ad; i := 0; j := 0;
		IF sel # "" THEN
			WHILE sel[i] # 0X DO tsel[i] := sel[i]; INC(i) END;
			IF (tsel[i-1] # ":") & (name[0] # "[") THEN tsel[i] := "."; INC(i) END
		END;
		WHILE name[j] # 0X DO tsel[i] := name[j]; INC(i); INC(j) END;
		tsel[i] := 0X;
		a0 := out.rider.attr;
		IF c = 3X THEN	(* varpar *)
			SYSTEM.GET(ad, a);
			out.rider.SetAttr(TextModels.NewStyle(a0, {Fonts.italic}))
		END;
		IF name[0] # "[" THEN out.WriteChar(".") END;
		out.WriteString(name);
		out.rider.SetAttr(a0); out.WriteTab;
		IF (c = 3X) & (a >= 0) & (a < 65536) THEN 
			out.WriteTab; out.WriteString("NIL VARPAR");
		ELSIF f = 11X THEN
			Kernel.GetTypeName(desc, name);
			IF (c = 3X) & (name[0] # "!") THEN SYSTEM.GET(ad + 4, desc) END;	(* dynamic type *)
			ShowRecord(a, ind + 1, desc, back, tsel)
		ELSIF (c = 3X) & (f = 0BX) THEN	(* VAR anyrecord *)
			SYSTEM.GET(ad + 4, desc);
			ShowRecord(a, ind + 1, desc, back, tsel)
		ELSIF f = 12X THEN
			IF (desc.size = 0) & (ptr = NIL) THEN SYSTEM.GET(ad, a) END;	(* dyn array val par *)
			IF ptr = NIL THEN ptr := SYSTEM.VAL(ArrayPtr, ad - 8) END;
			ShowArray(a, ind + 1, desc, ptr, back, tsel)
		ELSE
			IF desc = NIL THEN desc := SYSTEM.VAL(Kernel.Type, ORD(f)) END;
			WriteName(desc, NIL); out.WriteTab;
			CASE f OF
			| 0X: (* SYSTEM.GET(a, vli); WriteHex(vli) *)
			| 1X: SYSTEM.GET(a, vc); 
				IF vc = 0X THEN out.WriteString("FALSE")
				ELSIF vc = 1X THEN out.WriteString("TRUE")
				ELSE OutString("#Dev:Undefined"); out.WriteInt(ORD(vc))
				END
			| 2X: WriteString(a, 1, 1, FALSE, FALSE)
			| 3X: WriteString(a, 1, 2, FALSE, TRUE);
					SYSTEM.GET(a, vi);
					IF vi DIV 256 # 0 THEN out.WriteString("  "); WriteString(a, 1, 2, FALSE, FALSE) END
			| 4X: SYSTEM.GET(a, vsi); out.WriteInt(vsi)
			| 5X: SYSTEM.GET(a, vi); out.WriteInt(vi)
			| 6X: SYSTEM.GET(a, vli); out.WriteInt(vli)
			| 7X: SYSTEM.GET(a, vli);
					IF BITS(vli) * {23..30} = {23..30} THEN
						IF BITS(vli) = {23..30} THEN out.WriteString("inf")
						ELSIF BITS(vli) = {23..31} THEN out.WriteString("-inf")
						ELSE out.WriteString("nan  ("); WriteHex(vli); out.WriteString(")")
						END
					ELSE
						SYSTEM.GET(a, vr); out.WriteReal(vr)
					END
			| 8X: IF Kernel.littleEndian THEN SYSTEM.GET(a, vli); SYSTEM.GET(a + 4, i)
					ELSE SYSTEM.GET(a + 4, vli); SYSTEM.GET(a, i)
					END;
					IF BITS(i) * {20..30} = {20..30} THEN
						IF (BITS(i) = {20..30}) & (vli = 0) THEN out.WriteString("inf")
						ELSIF (BITS(i) = {20..31}) & (vli = 0) THEN out.WriteString("-inf")
						ELSE out.WriteString("nan  ("); out.WriteIntForm(i, TextMappers.hexadecimal, 8, "0", TextMappers.hideBase);
							WriteHex(vli); out.WriteString(")")
						END
					ELSE
						SYSTEM.GET(a, vlr); out.WriteReal(vlr)
					END
			| 9X: SYSTEM.GET(a, vs); out.WriteSet(vs)
			| 0AX: IF Kernel.littleEndian THEN SYSTEM.GET(a, vli); SYSTEM.GET(a + 4, i)
					ELSE SYSTEM.GET(a + 4, vli); SYSTEM.GET(a, i)
					END;
					IF (vli >= 0) & (i = 0) OR (vli < 0) & (i = -1) THEN out.WriteInt(vli)
					ELSE out.WriteIntForm(i, TextMappers.hexadecimal, 8, "0", TextMappers.hideBase); WriteHex(vli)
					END
			| 0CX, 0DX, 13X, 20X: ShowPointer(a, f, desc, back, tsel)
			| 0EX, 10X: ShowProcVar(a)
			| 0FX: WriteString(a, 256, 1, TRUE, FALSE)
			| 21X: WriteGuid(a)
			| 22X: SYSTEM.GET(a, vli); WriteHex(vli)
			ELSE 
			END
		END
	END ShowVar;
	
	PROCEDURE WriteTimeStamp (ts: ARRAY OF SHORTINT);
		VAR d: Dates.Date; t: Dates.Time; str: ARRAY 64 OF CHAR;
	BEGIN
		IF ts[0] = 0 THEN
			out.WriteString("      "); OutString("#Dev:Linked")
		ELSE
			d.year := ts[0]; d.month := ts[1]; d.day := ts[2];
			t.hour := ts[3]; t.minute := ts[4]; t.second := ts[5];
			Dates.DateToString(d, Dates.short, str);
			out.WriteString(str); out.WriteString("  ");
			Dates.TimeToString(t, str);
			out.WriteString(str); 
		END
	END WriteTimeStamp;

	PROCEDURE ShowModules;
		VAR m, m1: Kernel.Module; a0: TextModels.Attributes; n, h, t, h1: Kernel.Name; res: INTEGER;
	BEGIN	
		a0 := out.rider.attr;
		out.rider.SetAttr(TextModels.NewStyle(a0, {Fonts.italic}));
		OutString("#Dev:ModuleName"); out.WriteTab;
		OutString("#Dev:BytesUsed"); out.WriteTab;
		OutString("#Dev:Clients"); out.WriteTab;
		OutString("#Dev:Compiled"); out.WriteTab;
		OutString("#Dev:Loaded");
		out.rider.SetAttr(a0); out.WriteTab; out.WriteTab;
		out.rider.SetAttr(TextModels.NewStyle(out.rider.attr, {Fonts.underline}));
		out.rider.SetAttr(TextModels.NewColor(out.rider.attr, Ports.blue));
		out.WriteView(StdLinks.dir.NewLink("DevDebug.UpdateModules"));
		OutString("#Dev:Update");
		out.WriteView(StdLinks.dir.NewLink(""));
		out.rider.SetAttr(a0); out.WriteLn;
		m := Kernel.modList;
		WHILE m # NIL DO
			IF m.refcnt >= 0 THEN
				Utf.Utf8ToString(m.name, n, res); Librarian.lib.SplitName(n, h, t);
				m1 := Kernel.modList; h1 := "*";
				WHILE (m1 # m) & (h1 # h) DO
					IF m1.refcnt >= 0 THEN
						Utf.Utf8ToString(m1.name, n, res); Librarian.lib.SplitName(n, h1, t)
					END;
					m1 := m1.next
				END;
				IF h1 # h THEN
					out.WriteLn;
					m1 := m;
					WHILE m1 # NIL DO
						Utf.Utf8ToString(m1.name, n, res); Librarian.lib.SplitName(n, h1, t);
						IF (h1 = h) & (m1.refcnt >= 0) THEN
							out.WriteString(n); out.WriteTab;
							out.WriteInt(m1.csize + m1.dsize + m1.rsize);
							out.WriteTab;
							out.WriteInt(m1.refcnt);
							out.WriteTab;
							WriteTimeStamp(m1.compTime);
							out.WriteTab;
							WriteTimeStamp(m1.loadTime);
							out.WriteLn
						END;
						m1 := m1.next
					END
				END
			END;
			m := m.next
		END
	END ShowModules;
	
	PROCEDURE ShowGlobals (mod: Kernel.Module);
		VAR ref, x: INTEGER; m, f: SHORTCHAR; name: ARRAY 256 OF CHAR; modName, mname: Kernel.Name; 
			d: Kernel.Type; v: RefView; a0: TextModels.Attributes; res: INTEGER; nn: Kernel.Utf8Name;
	BEGIN
		IF mod # NIL THEN
			Kernel.GetModName(mod, modName); out.WriteString(modName);
			out.WriteTab; out.WriteTab; out.WriteTab;
			a0 := out.rider.attr;
			out.rider.SetAttr(TextModels.NewStyle(out.rider.attr, {Fonts.underline}));
			out.rider.SetAttr(TextModels.NewColor(out.rider.attr, Ports.blue));
			Kernel.GetModName(mod, modName); name := "DevDebug.UpdateGlobals('" + modName + "')";
			out.WriteView(StdLinks.dir.NewLink(name));
			OutString("#Dev:Update");
			out.WriteView(StdLinks.dir.NewLink(""));
			out.rider.SetAttr(a0); out.WriteLn;
			ref := mod.refs; Kernel.GetRefProc(ref, x, nn); Utf.Utf8ToString(nn, mname, res);	(* get body *)
			IF x # 0 THEN
				Kernel.GetModName(mod, modName); v := NewRefView (module, open, 0, NIL, NIL, NIL, modName);
				Kernel.GetRefVar(ref, m, f, d, x, nn); Utf.Utf8ToString(nn, mname, res);
				WHILE m = 1X DO
					ShowVar(mod.data + x, 0, f, m, d, NIL, v, mname, empty);
					Kernel.GetRefVar(ref, m, f, d, x, nn); Utf.Utf8ToString(nn, mname, res);
				END
			END;
			out.WriteLn
		END
	END ShowGlobals;
	
	PROCEDURE ShowObject (adr: INTEGER);
		VAR eltyp: Kernel.Type; ptr: ArrayPtr; desc: ARRAY 64 OF INTEGER; i, n, lev, elsize: INTEGER;
	BEGIN
		SYSTEM.GET(adr - 4, eltyp);
		IF ODD(SYSTEM.VAL(INTEGER, eltyp) DIV 2) THEN
			DEC(SYSTEM.VAL(INTEGER, eltyp), 2);
			ptr := SYSTEM.VAL(ArrayPtr, adr);
			elsize := eltyp.size;
			IF (eltyp.mod.name = "Kernel") & (eltyp.fields.num = 1) THEN
				eltyp := eltyp.fields.obj[0].struct
			END;
			n := (ptr.last - ptr.first) DIV elsize + 1;
			lev := (ptr.first - adr - 12) DIV 4; i := 0;
			WHILE lev > 0 DO	(* dynamic levels *)
				DEC(lev);
				desc[i] := ptr.len[lev]; n := n DIV desc[i]; INC(i);	(* size *)
				desc[i] := 0; INC(i);	(* module *)
				desc[i] := 2; INC(i);	(* id *)
				desc[i] := SYSTEM.ADR(desc[i+1]); INC(i)	(* desc *)
			END;
			IF n > 1 THEN	(* static level *)
				desc[i] := n; INC(i);	(* size *)
				desc[i] := 0; INC(i);	(* module *)
				desc[i] := 2; INC(i);	(* id *)
			ELSE DEC(i)
			END;
			desc[i] := SYSTEM.VAL(INTEGER, eltyp);	(* desc *)
			ShowArray(ptr.first, 1, SYSTEM.VAL(Kernel.Type, SYSTEM.ADR(desc)), ptr, NIL, empty);
			out.WriteLn
		ELSE ShowRecord(adr, 1, eltyp, NIL, empty)
		END;
	END ShowObject;
	
	PROCEDURE ShowPtrDeref (ref: RefView);
		VAR b: RefView; res: INTEGER; name: Kernel.Name;
	BEGIN
		ShowSelector(ref); b := ref.back;
		IF b # NIL THEN
			out.WriteChar(" ");
			Utf.Utf8ToString(b.name, name, res); InsertRefView(b.type, undo, b.adr, b.back, b.desc, b.ptr, name)
		END;
		out.WriteLn; out.WriteLn;
		out.WriteChar("["); WriteHex(ref.adr); out.WriteChar("]"); out.WriteTab;
		IF ref.desc = NIL THEN
			ShowObject(ref.adr)
		ELSIF FormOf(ref.desc) = 12X THEN
			ShowArray(ref.adr, 1, ref.desc, ref.ptr, ref, empty)
		ELSE
			ShowRecord(ref.adr, 1, Kernel.TypeOf(SYSTEM.VAL(ANYPTR, ref.ptr)), ref, empty)
		END;
		out.WriteLn
	END ShowPtrDeref;

	PROCEDURE Scan (VAR s: TextMappers.Scanner);
	BEGIN
		s.Scan;
		IF s.type = TextMappers.string THEN
			IF s.string = "IMPORT" THEN s.type := import
			ELSIF s.string = "MODULE" THEN s.type := smodule
			ELSIF s.string = "THEN" THEN s.type := stop
			ELSIF s.string = "OF" THEN s.type := stop
			ELSIF s.string = "DO" THEN s.type := stop
			ELSIF s.string = "END" THEN s.type := stop
			ELSIF s.string = "ELSE" THEN s.type := stop
			ELSIF s.string = "ELSIF" THEN s.type := stop
			ELSIF s.string = "UNTIL" THEN s.type := stop
			ELSIF s.string = "TO" THEN s.type := stop
			ELSIF s.string = "BY" THEN s.type := stop
			END
		ELSIF s.type = TextMappers.char THEN
			IF s.char = ";" THEN s.type := semicolon
			ELSIF s.char = "|" THEN s.type := stop
			ELSIF s.char = ":" THEN
				IF s.rider.char = "=" THEN s.rider.Read; s.type := becomes END
			ELSIF s.char = "(" THEN
				IF s.rider.char = "*" THEN
					s.rider.Read;
					REPEAT Scan(s) UNTIL (s.type = TextMappers.eot) OR (s.type = comEnd);
					Scan(s)
				END
			ELSIF s.char = "*" THEN
				IF s.rider.char = ")" THEN s.rider.Read; s.type := comEnd END
			END
		END	
	END Scan;
	
	PROCEDURE ShowSourcePos (name: Kernel.Utf8Name; adr: INTEGER);
		VAR loc: Files.Locator; fname: Files.Name; v: Views.View; m: Models.Model; conv: Converters.Converter;
			c: Containers.Controller; beg, end: INTEGER; s: TextMappers.Scanner; w: Windows.Window;
			res: INTEGER; n: Kernel.Name;
	BEGIN
		(* search source by name heuristic *)
		Utf.Utf8ToString(name, n, res); StdDialog.GetSubLoc(n, "Mod", loc, fname);
		Librarian.lib.GetSpec(n, "Mod", loc, fname);
		v := Views.OldView(loc, fname); m := NIL;
		IF v # NIL THEN
			Views.Open(v, loc, fname, NIL);
			m := v.ThisModel();
			IF ~(m IS TextModels.Model) THEN m := NIL END
		END;
		IF m = NIL THEN
			(* search in open windows *)
			w := Windows.dir.First();
			WHILE (w # NIL) & (m = NIL) DO
				v := w.doc.ThisView();
				m := v.ThisModel();
				IF m # NIL THEN
					WITH m: TextModels.Model DO
						s.ConnectTo(m); s.SetPos(0);
						REPEAT
							REPEAT s.Scan UNTIL s.rider.eot OR (s.type = TextMappers.string) & (s.string = "MODULE");
							s.Scan;
						UNTIL s.rider.eot OR (s.type = TextMappers.string) & (s.string = n);
						IF ~s.rider.eot THEN Windows.dir.Select(w, Windows.eager)
						ELSE m := NIL
						END
					ELSE m := NIL
					END
				END;
				w := Windows.dir.Next(w)
			END
		END;
		IF m = NIL THEN
			(* ask user for source file *)
			conv := NIL; v := Views.Old(Views.ask, loc, fname, conv);
			IF v # NIL THEN
				Views.Open(v, loc, fname, conv);
				m := v.ThisModel();
				IF ~(m IS TextModels.Model) THEN m := NIL END
			END
		END;
		IF m # NIL THEN
			(* mark error position in text *)
			WITH m: TextModels.Model DO
				beg := Kernel.SourcePos(Kernel.ThisMod(n), adr);
				IF beg >= 0 THEN
					IF beg > m.Length() THEN beg := m.Length() - 10 END;
					s.ConnectTo(m); s.SetPos(beg);
					Scan(s); beg := s.start; end := beg + 3;
					IF s.type = stop THEN end := s.Pos() - 1
					ELSE
						WHILE (s.type # TextMappers.eot) & (s.type # stop) & (s.type # semicolon) DO
							end := s.Pos() - 1; Scan(s)
						END
					END;
					c := v(TextViews.View).ThisController();
					v(TextViews.View).ShowRange(beg, end, TextViews.any);
					c(TextControllers.Controller).SetSelection(beg, end)
				END
			END
		ELSE Dialog.ShowParamMsg("#Dev:SourcefileNotFound", n, "", "")
		END
	END ShowSourcePos;
	
		
	(* -------------------  RefView ------------------- *)
	
	PROCEDURE (v: RefView) Internalize (VAR rd: Stores.Reader);
		VAR thisVersion: INTEGER;
	BEGIN
		v.Internalize^(rd); IF rd.cancelled THEN RETURN END;
		rd.ReadVersion(0, 0, thisVersion); IF rd.cancelled THEN RETURN END;
		v.command := open;
		rd.ReadSInt(v.type);
		IF v.type = source THEN
			rd.ReadInt(v.adr);
			rd.ReadSString(v.name)
		ELSIF v.type = module THEN
			rd.ReadSString(v.name)
		ELSIF v.type # modules THEN
			v.type := 0
		END
	END Internalize;

	PROCEDURE (v: RefView) Externalize (VAR wr: Stores.Writer);
		VAR t: SHORTINT;
	BEGIN
		v.Externalize^(wr);
		wr.WriteVersion(0);
		t := v.type;
		IF v.command # open THEN t := 0 END;
		wr.WriteSInt(t);
		IF t = source THEN
			wr.WriteInt(v.adr);
			wr.WriteSString(v.name)
		ELSIF t = module THEN
			wr.WriteSString(v.name)
		END
	END Externalize;

	PROCEDURE (v: RefView) CopyFromSimpleView (source: Views.View);
	BEGIN
		(* v.CopyFrom^(source); *)
		WITH source: RefView DO
			v.type := source.type; v.command := source.command; v.adr := source.adr; v.back := source.back;
			v.desc := source.desc; v.ptr := source.ptr; v.name := source.name$;
		END
	END CopyFromSimpleView;

	PROCEDURE (v: RefView) Restore (f: Views.Frame; l, t, r, b: INTEGER);
	BEGIN
		f.DrawPath(path, 4, Ports.fill, Ports.blue, Ports.closedPoly)
	END Restore;
	
	PROCEDURE (v: RefView) GetBackground (VAR color: Ports.Color);
	BEGIN
		color := Ports.background
	END GetBackground;

	PROCEDURE (v: RefView) HandleCtrlMsg (f: Views.Frame; VAR msg: Controllers.Message; VAR focus: Views.View);
		VAR t, t0: TextModels.Model; m: Models.Model; x, y: INTEGER;
			isDown, new: BOOLEAN; mo: SET; script: Stores.Operation;
			res: INTEGER; n: Kernel.Name;
	BEGIN
		WITH msg: Controllers.TrackMsg DO
			IF v.type > 0 THEN
				REPEAT
					f.MarkRect(0, 0, refViewSize, refViewSize, Ports.fill, Ports.hilite, Ports.show);
					IF v.command = undo THEN Dialog.ShowStatus("#Dev:ShowPrecedingObject")
					ELSIF v.command = update THEN Dialog.ShowStatus("#Dev:UpdateWindow")
					ELSIF v.type = module THEN Dialog.ShowStatus("#Dev:ShowGlobalVariables")
					ELSIF v.type = source THEN Dialog.ShowStatus("#Dev:ShowSourcePosition")
					ELSIF v.type = heap THEN Dialog.ShowStatus("#Dev:ShowReferencedObject")
					END;
					REPEAT
						f.Input(x, y, mo, isDown)
					UNTIL (x < 0) OR (x > refViewSize) OR (y < 0) OR (y > refViewSize) OR ~isDown;
					f.MarkRect(0, 0, refViewSize, refViewSize, Ports.fill, Ports.hilite, Ports.hide);
					Dialog.ShowStatus("");
					WHILE isDown & ((x < 0) OR (x > refViewSize) OR (y < 0) OR (y > refViewSize)) DO
						f.Input(x, y, mo, isDown)
					END
				UNTIL ~isDown;
				IF (x >= 0) & (x <= refViewSize) & (y >= 0) & (y <= refViewSize) THEN
					IF v.type = source THEN ShowSourcePos(v.name, v.adr)
					ELSE
						m := v.context.ThisModel();
						new := (v.command = open) & (v.back = NIL)
							OR (Controllers.modify IN msg.modifiers) & (v.command # update)
							OR ~(m IS TextModels.Model) ;
						IF new THEN
							t := TextModels.CloneOf(StdLog.buf); t0 := NIL
						ELSE
							t0 := m(TextModels.Model); t := TextModels.CloneOf(t0);
						END;
						out.ConnectTo(t);
						IF v.type = heap THEN  ShowPtrDeref(v)
						ELSIF v.type = module THEN Utf.Utf8ToString(v.name, n, res); ShowGlobals(Kernel.ThisLoadedMod(n))
						ELSIF v.type = modules THEN ShowModules
						END;
						out.ConnectTo(NIL);
						IF new THEN
							OpenViewer(t, "#Dev:Variables", NewRuler())
						ELSE
							Models.BeginScript(t0, "#Dev:Change", script);
							t0.Delete(0, t0.Length()); t0.Insert(0, t, 0, t.Length());
							Models.EndScript(t0, script)
						END
					END
				END
			END
		| msg: Controllers.PollCursorMsg DO
			msg.cursor := Ports.refCursor
		ELSE
		END
	END HandleCtrlMsg;
	
	PROCEDURE (v: RefView) HandlePropMsg (VAR msg: Properties.Message);
	BEGIN
		WITH msg: Properties.Preference DO
			WITH msg: Properties.ResizePref DO msg.fixed := TRUE
			| msg: Properties.SizePref DO msg.w := refViewSize; msg.h := refViewSize
			| msg: Properties.FocusPref DO msg.hotFocus := TRUE
			ELSE
			END
		ELSE
		END
	END HandlePropMsg;
	
	PROCEDURE NewRefView (type, command: SHORTINT; adr: INTEGER; back: RefView;
												desc: Kernel.Type; ptr: ArrayPtr; name: Kernel.Name): RefView;
		VAR v: RefView; res: INTEGER;
	BEGIN
		NEW(v); v.type := type; v.command := command; v.adr := adr; v.back := back;
		v.desc := desc; v.ptr := ptr; Utf.StringToUtf8(name, v.name, res);
		RETURN v
	END NewRefView;

	PROCEDURE InsertRefView (type, command: SHORTINT; adr: INTEGER; back: RefView;
												desc: Kernel.Type; ptr: ArrayPtr; name: Kernel.Name);
		VAR v: RefView; a0: TextModels.Attributes;
	BEGIN
		v := NewRefView(type, command, adr, back, desc, ptr, name);
		a0 := out.rider.attr;
		out.rider.SetAttr(TextModels.NewOffset(a0, Ports.point));
		out.WriteView(v);
		out.rider.SetAttr(a0)
	END InsertRefView;
	
	PROCEDURE HeapRefView* (adr: INTEGER; name: ARRAY OF CHAR): Views.View;
		VAR ptr: ArrayPtr;
	BEGIN
		ptr := SYSTEM.VAL(ArrayPtr, adr);
		RETURN NewRefView(heap, open, adr, NIL, NIL, ptr, name$)
	END HeapRefView;
	
	(* ----------------------------------------- *)

	PROCEDURE GetMod (VAR mod: Kernel.Module);
		VAR c: TextControllers.Controller; s: TextMappers.Scanner; beg, end: INTEGER;
	BEGIN
		mod := NIL;
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(beg, end);
			s.ConnectTo(c.text); s.SetPos(beg); s.Scan;
			IF s.type = TextMappers.string THEN
				DevReferences.ResolveImportAlias(s.string, c.text);
				mod := Kernel.ThisMod(s.string);
				IF mod = NIL THEN
					Dialog.ShowParamMsg("#Dev:ModuleNotFound", s.string, "", "")
				END				
			ELSE Dialog.ShowMsg("#Dev:NoModuleNameSelected")
			END
		ELSE Dialog.ShowMsg("#Dev:NoSelectionFound")
		END
	END GetMod;

	PROCEDURE ShowLoadedModules*;
	BEGIN
		out.ConnectTo(TextModels.CloneOf(StdLog.buf));
		ShowModules;
		OpenViewer(out.rider.Base(), "#Dev:LoadedModules", NewModRuler());
		out.ConnectTo(NIL)
	END ShowLoadedModules;
	
	PROCEDURE UpdateModules*;
		VAR t, t0: TextModels.Model; script: Stores.Operation;
	BEGIN
		t0 := TextViews.FocusText();
		Models.BeginScript(t0, "#Dev:Change", script);
		t := TextModels.CloneOf(t0);
		out.ConnectTo(t);
		ShowModules;
		(*Stores.InitDomain(t, t0.domain);*) Stores.Join(t, t0);	(* not efficient to init domain before writing *)
		t0.Delete(0, t0.Length()); t0.Insert(0, t, 0, t.Length());
		Models.EndScript(t0, script);
		out.ConnectTo(NIL)
	END UpdateModules;

	PROCEDURE ShowGlobalVariables*;
		VAR mod: Kernel.Module;
	BEGIN
		GetMod(mod);
		IF mod # NIL THEN
			out.ConnectTo(TextModels.CloneOf(StdLog.buf));
			ShowGlobals(mod);
			OpenViewer(out.rider.Base(), "#Dev:Variables", NewRuler());
			out.ConnectTo(NIL)
		END
	END ShowGlobalVariables;
	
	PROCEDURE UpdateGlobals* (name: ARRAY OF CHAR);
		VAR t, t0: TextModels.Model; script: Stores.Operation; mod: Kernel.Module;
	BEGIN
		mod := Kernel.ThisLoadedMod(name);
		IF mod # NIL THEN
			t0 := TextViews.FocusText();
			Models.BeginScript(t0, "#Dev:Change", script);
			t := TextModels.CloneOf(t0);
			out.ConnectTo(t);
			ShowGlobals(mod);
			(*Stores.InitDomain(t, t0.domain);*) Stores.Join(t, t0);	(* not efficient to init domain before writing *)
			t0.Delete(0, t0.Length()); t0.Insert(0, t, 0, t.Length());
			Models.EndScript(t0, script);
			out.ConnectTo(NIL)
		END
	END UpdateGlobals;

	PROCEDURE ShowHeapObject* (adr: INTEGER; title: ARRAY OF CHAR);
	BEGIN
		out.ConnectTo(TextModels.CloneOf(StdLog.buf));
		IF title # "" THEN
			out.WriteString(title); out.WriteLn; out.WriteLn
		END;
		out.WriteChar("["); WriteHex(adr); out.WriteChar("]"); out.WriteTab;
		ShowObject(adr);
		out.WriteLn;
		OpenViewer(out.rider.Base(), "#Dev:HeapObject", NewRuler());
		out.ConnectTo(NIL)
	END ShowHeapObject;
	
	PROCEDURE ShowViewState*;
		VAR ops: Controllers.PollOpsMsg;
	BEGIN
		Controllers.PollOps(ops);
		IF ops.singleton # NIL THEN
			ShowHeapObject(SYSTEM.VAL(INTEGER, ops.singleton), "")
		END
	END ShowViewState;
	
	PROCEDURE LogClients (m: Kernel.Module);
		VAR n: Kernel.Module; name: Kernel.Name; i, j, res: INTEGER;
	BEGIN 
		IF m # NIL THEN
			n := Kernel.modList;
			WHILE n # NIL DO 
				IF n.refcnt >= 0 THEN
					j := n.nofimps;
					i := 0; WHILE (i < j) & (n.imports[i] # m) DO INC(i) END;
					IF i < j THEN
						Utf.Utf8ToString(n.name, name, res); StdLog.String(name); StdLog.Char(' ')
					END
				END;
				n := n.next
			END
		END
	END LogClients;
	
	PROCEDURE UnloadMod (name: TextMappers.String; VAR ok: BOOLEAN);
		VAR mod: Kernel.Module; str: Dialog.String;
	BEGIN
		mod := Kernel.ThisLoadedMod(name);
		IF mod # NIL THEN
			IF name # "DevDebug" THEN
				Dialog.ShowParamStatus("#Dev:Unloading", name, "", "");
				Kernel.UnloadMod(mod)
			END;
			IF mod.refcnt < 0 THEN
				Dialog.MapParamString("#Dev:Unloaded", name, "", "", str);
				StdLog.String(str); StdLog.Ln
			ELSE
				Dialog.ShowParamMsg("#Dev:UnloadingFailed", name, "", "");
				IF mod.refcnt > 0 THEN 
					StdLog.ParamMsg("#Dev:ReferencedBy", name, '', ''); LogClients(mod); StdLog.Ln
				END;
				ok := FALSE
			END
		ELSE
			Dialog.ShowParamMsg("#Dev:NotFound", name, "", "");
			ok := FALSE;
		END
	END UnloadMod;
	
	PROCEDURE Unload*;
		VAR t: TextModels.Model; s: TextMappers.Scanner; ok: BOOLEAN;
	BEGIN
		t := TextViews.FocusText();
		IF t # NIL THEN
			s.ConnectTo(t); s.SetPos(0);
			REPEAT Scan(s) UNTIL (s.type = smodule) OR s.rider.eot;
			IF (s.type = smodule) THEN
				Scan(s);
				IF (s.type = TextMappers.string) & IsIdent(s.string) THEN
					ok := TRUE; UnloadMod(s.string, ok);
					IF ok THEN Dialog.ShowStatus("#Dev:Ok") END;
					Controls.Relink
				ELSE
					Dialog.ShowMsg("#Dev:NoModNameFound");
				END
			ELSE
				Dialog.ShowMsg("#Dev:NoModNameFound");
			END
		END
	END Unload;
	
	PROCEDURE UnloadList(beg, end: INTEGER; text: TextModels.Model);
		VAR s: TextMappers.Scanner; ok, num: BOOLEAN; linked: ARRAY 16 OF CHAR;
	BEGIN
		s.ConnectTo(text); s.SetPos(beg); s.Scan; ok := TRUE; num := FALSE;
		WHILE (s.start < end) & (s.type # TextMappers.invalid) DO
			Dialog.MapString("#Dev:Linked", linked);
			IF (s.type = TextMappers.string) & (s.string # linked) THEN
				IF num & ((s.string = "AM") OR (s.string = "PM")) THEN s.Scan (* skip am & pm *)
				ELSIF IsIdent(s.string) THEN UnloadMod(s.string, ok); s.Scan
				ELSE s.type := TextMappers.invalid
				END
			ELSE
				(* skip numbers to allow selection of list of loaded modules *)
				num := TRUE; s.Scan
			END
		END;
		IF ok THEN Dialog.ShowStatus("#Dev:Ok") END;
		Controls.Relink
	END UnloadList;

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

	PROCEDURE UnloadThis*;
		VAR p: DevCommanders.Par; beg, end: INTEGER;
	BEGIN
		p := DevCommanders.par;
		IF p # NIL THEN
			DevCommanders.par := NIL;
			beg := p.beg; end := p.end;
			UnloadList(beg, end, p.text)
		ELSE Dialog.ShowMsg("#Dev:NoTextViewFound")
		END
	END UnloadThis;

	PROCEDURE Execute*;
		VAR beg, end, res: INTEGER; done: BOOLEAN;
			c: TextControllers.Controller; s: TextMappers.Scanner; cmd: Dialog.String;
	BEGIN
		c := TextControllers.Focus();
		IF (c # NIL) & c.HasSelection() THEN
			c.GetSelection(beg, end);
			s.ConnectTo(c.text); s.SetPos(beg); s.Scan; TextMappers.ScanQualIdent(s, cmd, done);
			IF done THEN
				Dialog.Call(cmd, " ", res)
			ELSIF s.type = TextMappers.string THEN
				Dialog.Call(s.string, " ", res)
			ELSE
				Dialog.ShowMsg("#Dev:StringExpected")
			END
		ELSE Dialog.ShowMsg("#Dev:NoSelectionFound")
		END
	END Execute;

	PROCEDURE ShowStack;
		VAR ref, end, i, j, x, a, b, c: INTEGER; m, f: SHORTCHAR; mod: Kernel.Module; modName, name, sel: Kernel.Name;
			d: Kernel.Type; res: INTEGER; nn: Kernel.Utf8Name; 
	BEGIN
		a := Kernel.pc; b := Kernel.fp; c := 100;
		REPEAT
			mod := Kernel.modList;
			WHILE (mod # NIL) & ((a < mod.code) OR (a >= mod.code + mod.csize)) DO mod := mod.next END;
			IF mod # NIL THEN
				DEC(a, mod.code);
				IF mod.refcnt >= 0 THEN
					Kernel.GetModName(mod, modName); InsertRefView(module, open, 0, NIL, NIL, NIL, modName);
					out.WriteChar(" "); out.WriteString(modName); ref := mod.refs;
					REPEAT Kernel.GetRefProc(ref, end, nn) UNTIL (end = 0) OR (a < end);
					Utf.Utf8ToString(nn, name, res); 
					IF a < end THEN
						out.WriteChar("."); out.WriteString(name);
						sel := mod.name$; i := 0;
						WHILE sel[i] # 0X DO INC(i) END;
						sel[i] := "."; INC(i); j := 0;
						WHILE name[j] # 0X DO sel[i] := name[j]; INC(i); INC(j) END;
						sel[i] := ":"; sel[i+1] := 0X;
						out.WriteString("   ["); WriteHex(a);
						out.WriteString("] ");
						i := Kernel.SourcePos(mod, 0);
						IF i >= 0 THEN
							Kernel.GetModName(mod, modName); InsertRefView(source, open, a, NIL, NIL, NIL, modName);
						END;
						IF name # "$$" THEN
							Kernel.GetRefVar(ref, m, f, d, x, nn); Utf.Utf8ToString(nn, name, res);
							WHILE m # 0X DO
								ShowVar(b + x, 0, f, m, d, NIL, NIL, name, sel);
								Kernel.GetRefVar(ref, m, f, d, x, nn); Utf.Utf8ToString(nn, name, res);
							END
						END;
						out.WriteLn
					ELSE out.WriteString(".???"); out.WriteLn
					END
				ELSE
					out.WriteChar("("); Kernel.GetModName(mod, modName); out.WriteString(modName);
					out.WriteString(")   (pc="); WriteHex(a);
					out.WriteString(",  fp="); WriteHex(b); out.WriteChar(")");
					out.WriteLn
				END
			ELSE
				out.WriteString("<system>   (pc="); WriteHex(a);
				out.WriteString(",  fp="); WriteHex(b); out.WriteChar(")");
				out.WriteLn
			END;
			IF (b >= Kernel.fp) & (b < Kernel.stack) THEN
				SYSTEM.GET(b+4, a);	(* stacked pc *)
				SYSTEM.GET(b, b);	(* dynamic link *)
				DEC(a); DEC(c)
			ELSE c := 0
			END
		UNTIL c = 0
	END ShowStack;

	PROCEDURE (a: Action) Do;	(* delayed trap window open *)
	BEGIN
		Kernel.SetTrapGuard(TRUE);
		OpenViewer(a.text, "#Dev:Trap", NewRuler());
		Kernel.SetTrapGuard(FALSE);
	END Do;
	
	PROCEDURE GetTrapMsg(OUT msg: ARRAY OF CHAR);
		VAR ref, end, a, res: INTEGER; mod: Kernel.Module; nn: Kernel.Utf8Name;
			name, head, tail: Kernel.Name; errstr: ARRAY 12 OF CHAR;
			key: ARRAY Kernel.nameLen * 3 + LEN(errstr) OF CHAR;
	BEGIN
		a := Kernel.pc; mod := Kernel.modList; msg := "";
		WHILE (mod # NIL) & ((a < mod.code) OR (a >= mod.code + mod.csize)) DO mod := mod.next END;
		IF mod # NIL THEN
			DEC(a, mod.code); ref := mod.refs;
			REPEAT Kernel.GetRefProc(ref, end, nn) UNTIL (end = 0) OR (a < end);
			Utf.Utf8ToString(nn, name, res);
			IF a < end THEN
				Kernel.GetModName(mod, head); Librarian.lib.SplitName (head, head, tail);
				Strings.IntToString(Kernel.err, errstr);
				key := "#" + head + tail + ":" + name + "." + errstr;
				Dialog.MapString(key, msg);
				IF Dialog.mapRes IN {4, 5} THEN
					key := "#" + head + ":" + tail + "." + name + "." + errstr;
					Dialog.MapString(key, msg)
				END;
				IF ~(Dialog.mapRes IN {0, 6}) THEN msg := "" END
			END
		END
	END GetTrapMsg;

	PROCEDURE OutAdr (adr: INTEGER);
	BEGIN
		out.WriteString(" ("); OutString("#System:adr"); out.WriteString(" = "); WriteHex(adr); out.WriteChar(")")
	END OutAdr;

	PROCEDURE Trap;
		VAR a0: TextModels.Attributes; action: Action;
		 	msg: ARRAY 512 OF CHAR;
	BEGIN
		out.ConnectTo(TextModels.CloneOf(StdLog.buf));
		a0 := out.rider.attr;
		out.rider.SetAttr(TextModels.NewWeight(a0, Fonts.bold));
		IF Kernel.err = 129 THEN OutString("#System:invalid WITH")
		ELSIF Kernel.err = 130 THEN OutString("#System:invalid CASE")
		ELSIF Kernel.err = 131 THEN OutString("#System:function without RETURN")
		ELSIF Kernel.err = 132 THEN OutString("#System:type guard")
		ELSIF Kernel.err = 133 THEN OutString("#System:implied type guard")
		ELSIF Kernel.err = 134 THEN OutString("#System:value out of range")
		ELSIF Kernel.err = 135 THEN OutString("#System:index out of range")
		ELSIF Kernel.err = 136 THEN OutString("#System:string too long")
		ELSIF Kernel.err = 137 THEN OutString("#System:stack overflow")
		ELSIF Kernel.err = 138 THEN OutString("#System:integer overflow")
		ELSIF Kernel.err = 139 THEN OutString("#System:division by zero")
		ELSIF Kernel.err = 140 THEN OutString("#System:infinite real result")
		ELSIF Kernel.err = 141 THEN OutString("#System:real underflow")
		ELSIF Kernel.err = 142 THEN OutString("#System:real overflow")
		ELSIF Kernel.err = 143 THEN
			OutString("#System:undefined real result"); out.WriteString(" (");
			out.WriteIntForm(Kernel.val MOD 10000H, TextMappers.hexadecimal, 4, "0", TextMappers.hideBase); out.WriteString(", ");
			out.WriteIntForm(Kernel.val DIV 10000H, TextMappers.hexadecimal, 3, "0", TextMappers.hideBase); out.WriteChar(")")
		ELSIF Kernel.err = 144 THEN OutString("#System:not a number")
		ELSIF Kernel.err = 200 THEN OutString("#System:keyboard interrupt")
		ELSIF Kernel.err = 201 THEN
			OutString("#System:NIL dereference")
		ELSIF Kernel.err = 202 THEN
			OutString("#System:illegal instruction"); out.WriteString(": ");
			out.WriteIntForm(Kernel.val, TextMappers.hexadecimal, 5, "0", TextMappers.showBase)
		ELSIF Kernel.err = 203 THEN
			IF (Kernel.val >= -4) & (Kernel.val < 65536) THEN OutString("#System:NIL dereference (read)")
			ELSE OutString("#System:illegal memory read"); OutAdr(Kernel.val)
			END
		ELSIF Kernel.err = 204 THEN
			IF (Kernel.val >= -4) & (Kernel.val < 65536) THEN OutString("#System:NIL dereference (write)")
			ELSE OutString("#System:illegal memory write"); OutAdr(Kernel.val)
			END
		ELSIF Kernel.err = 205 THEN
			IF (Kernel.val >= -4) & (Kernel.val < 65536) THEN OutString("#System:NIL procedure call")
			ELSE OutString("#System:illegal execution"); OutAdr(Kernel.val)
			END
		ELSIF Kernel.err = 257 THEN OutString("#System:out of memory")
		ELSIF Kernel.err = 10001H THEN OutString("#System:bus error")
		ELSIF Kernel.err = 10002H THEN OutString("#System:address error")
		ELSIF Kernel.err = 10007H THEN OutString("#System:fpu error")
		ELSIF Kernel.err < 0 THEN
			OutString("#System:Exception");  out.WriteChar(" ");
			out.WriteIntForm(-Kernel.err, TextMappers.hexadecimal, 3, "0", TextMappers.showBase)
		ELSE
			OutString("#System:TRAP"); out.WriteChar(" "); out.WriteInt(Kernel.err);
			IF Kernel.err = 126 THEN out.WriteString("  ("); OutString("#System:not yet implemented");  out.WriteChar(")")
			ELSIF Kernel.err = 125 THEN out.WriteString("  ("); OutString("#System:call of obsolete procedure");  out.WriteChar(")")
			ELSIF Kernel.err >= 100 THEN out.WriteString("  ("); OutString("#System:invariant violated");  out.WriteChar(")")
			ELSIF Kernel.err >= 60 THEN out.WriteString("  ("); OutString("#System:postcondition violated");  out.WriteChar(")")
			ELSIF Kernel.err >= 20 THEN out.WriteString("  ("); OutString("#System:precondition violated");  out.WriteChar(")")
			END
		END;
		GetTrapMsg(msg);
		IF msg # "" THEN out.WriteLn; out.WriteString(msg) END;

		out.WriteLn; out.rider.SetAttr(a0);
		out.WriteLn; ShowStack;
		NEW(action); action.text := out.rider.Base();
		Services.DoLater(action, Services.now);
		out.ConnectTo(NIL)
	END Trap;

BEGIN
	Kernel.InstallTrapViewer(Trap);
	empty := "";
	path[0].x := refViewSize DIV 2; path[0].y := 0;
	path[1].x := refViewSize; path[1].y := refViewSize DIV 2;
	path[2].x := refViewSize DIV 2; path[2].y := refViewSize;
	path[3].x := 0; path[3].y := refViewSize DIV 2
END DevDebug.
