Console
For logging within the BlackBox framework, in particular the text subsystem, or for logging in very low-level modules the standard BlackBox logging facilities (Log, StdLog) cannot be used.
The following module 'Console' can be used for that purpose. It attaches a Windows Console window to the BlackBox process and outputs the log to the Console window. In order to make it easy to detect new output it adds a line number at the left. In order to increase readability it supports indentation of the output. Non-ASCII characters are converted to the underlying Windows default code page.
MODULE Console; (* low level logging to console window; supports switching on and off and indentation. if used in Kernel (or other linked module) don't forget to link a new .exe file *) IMPORT WinApi, SYSTEM; VAR res, stdOut, lineNr, indent, offAtLineNr: INTEGER; on: BOOLEAN; indentStr: ARRAY 10 OF CHAR; (* sets a non-default indent string, default is " " *) PROCEDURE SetIndentString*(IN s: ARRAY OF CHAR); BEGIN indentStr := s$ END SetIndentString; (* turn logging on; default *) PROCEDURE On*; BEGIN offAtLineNr := -1; on := TRUE END On; (* turn logging on for the specified number of lines *) PROCEDURE OnFor*(nofLines: INTEGER); BEGIN offAtLineNr := lineNr + nofLines; on := TRUE END OnFor; (* turn logging off *) PROCEDURE Off*; BEGIN on := FALSE END Off; (* increment indentation level *) PROCEDURE Indent*; BEGIN INC(indent) END Indent; (* decrement indentation level *) PROCEDURE Undent*; BEGIN DEC(indent) END Undent; (* output null terminated string; converted to Windows default code page *) PROCEDURE String*(IN s: ARRAY OF CHAR); VAR buf: ARRAY 10000 OF SHORTCHAR; BEGIN IF on & (s # "") THEN res := WinApi.WideCharToMultiByte(WinApi.CP_OEMCP, {}, s, LEN(s$), buf, LEN(buf), NIL, NIL); IF ~((res > 0) & (res <= LEN(buf))) THEN buf := "*** error in Console.String ***"; res := LEN(buf$); END; res := WinApi.WriteFile(stdOut, SYSTEM.ADR(buf), res, NIL, NIL) END END String; PROCEDURE IntToString(x: LONGINT; OUT s: ARRAY OF CHAR); (* copied from Strings *) CONST minLongIntRev = "8085774586302733229"; VAR j, k: INTEGER; ch: CHAR; a: ARRAY 32 OF CHAR; BEGIN IF x # MIN(LONGINT) THEN IF x < 0 THEN s[0] := "-"; k := 1; x := -x ELSE k := 0 END; j := 0; REPEAT a[j] := CHR(x MOD 10 + ORD("0")); x := x DIV 10; INC(j) UNTIL x = 0 ELSE a := minLongIntRev; s[0] := "-"; k := 1; j := 0; WHILE a[j] # 0X DO INC(j) END END; ASSERT(k + j < LEN(s), 23); REPEAT DEC(j); ch := a[j]; s[k] := ch; INC(k) UNTIL j = 0; s[k] := 0X END IntToString; (* output integer as decimal number without any padding *) PROCEDURE Int*(x: INTEGER); VAR str: ARRAY 20 OF CHAR; sstr: ARRAY 20 OF SHORTCHAR; BEGIN IF on THEN IntToString(x, str); sstr := SHORT(str); res := WinApi.WriteFile(stdOut, SYSTEM.ADR(sstr), LEN(sstr$), NIL, NIL) END END Int; (* outputs the line number and indentation level followed by repeated indentStr according to the indentation level *) PROCEDURE LinePrefix; VAR i: INTEGER; BEGIN Int(lineNr); String(":"); Int(indent); FOR i := 1 TO indent DO String(indentStr) END END LinePrefix; (* output line end and increment line number *) PROCEDURE Ln*; VAR crlf: ARRAY 2 OF SHORTCHAR; BEGIN IF on THEN crlf[0] := 0DX; crlf[1] := 0AX; res := WinApi.WriteFile(stdOut, SYSTEM.ADR(crlf), 2, NIL, NIL); INC(lineNr); LinePrefix; IF (offAtLineNr # -1) & (lineNr = offAtLineNr) THEN on := FALSE END END END Ln; BEGIN res := WinApi.AllocConsole(); stdOut := WinApi.GetStdHandle(WinApi.STD_OUTPUT_HANDLE); lineNr := 1; indent := 1; indentStr := " "; On; LinePrefix END Console.