Console
Revision as of 09:47, 22 February 2017 by Josef templ (talk | contribs) (added ReadLn and Pause, line prefix improved)
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. Reading of console input and pausing is also supported. Non-ASCII characters are converted to/from the underlying Windows default code page.
MODULE Console; (* low level logging to console window; extended subset of Log/StdLog; supports switching logging on and off and indentation; supports reading from console; if used in Kernel (or other linked module) don't forget to link a new .exe file *) IMPORT WinApi, SYSTEM; VAR res, stdIn, stdOut, lineNr, indent, offAtLineNr: INTEGER; on, prefixed: BOOLEAN; indentStr: ARRAY 10 OF CHAR; PROCEDURE^ LinePrefix; (* 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 str: ARRAY OF CHAR); VAR buf: ARRAY 10000 OF SHORTCHAR; BEGIN IF on & (str # "") THEN LinePrefix; res := WinApi.WideCharToMultiByte(WinApi.CP_OEMCP, {}, str, LEN(str$), 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); res := WinApi.FlushFileBuffers(stdOut) END END String; (* output single character; converted to Windows default code page *) PROCEDURE Char*(ch: CHAR); VAR s: ARRAY 2 OF CHAR; BEGIN s[0] := ch; s[1] := 0X; String(s) END Char; (* output boolean as TRUE or FALSE *) PROCEDURE Bool*(x: BOOLEAN); BEGIN IF x THEN String("TRUE") ELSE String("FALSE") END END Bool; PROCEDURE IntToString(x: LONGINT; OUT s: ARRAY OF SHORTCHAR); (* inspired from Strings *) CONST minLongIntRev = "8085774586302733229"; VAR j, k: INTEGER; ch: SHORTCHAR; a: ARRAY 32 OF SHORTCHAR; 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] := SHORT(CHR(x MOD 10 + ORD("0"))); x := x DIV 10; INC(j) UNTIL x = 0 ELSE a := minLongIntRev; s[0] := "-"; k := 1; j := LEN(minLongIntRev); 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 SHORTCHAR; BEGIN IF on THEN LinePrefix; IntToString(x, str); res := WinApi.WriteFile(stdOut, SYSTEM.ADR(str), LEN(str$), NIL, NIL) END END Int; (* outputs the line number and indentation level followed by repeated indentStr according to indentation level *) PROCEDURE LinePrefix; VAR i: INTEGER; lStr, iStr: ARRAY 12 OF SHORTCHAR; BEGIN IF ~prefixed THEN prefixed := TRUE; IntToString(lineNr, lStr); IntToString(indent, iStr); String(lStr + ":" + iStr + " "); FOR i := 1 TO indent DO String(indentStr) END; END END LinePrefix; (* output line end and increment line number *) PROCEDURE Ln*; VAR crlf: ARRAY 2 OF SHORTCHAR; BEGIN IF on THEN LinePrefix; crlf[0] := 0DX; crlf[1] := 0AX; res := WinApi.WriteFile(stdOut, SYSTEM.ADR(crlf), 2, NIL, NIL); INC(lineNr); prefixed := FALSE; IF (offAtLineNr # -1) & (lineNr = offAtLineNr) THEN on := FALSE END END END Ln; (* read text line from console; terminate line by pressing RETURN; str does not contain end-of-line characters *) PROCEDURE ReadLn*(VAR str: ARRAY OF CHAR); VAR buf: ARRAY 256 OF SHORTCHAR; read: INTEGER; BEGIN str := ""; res := WinApi.ReadFile(stdIn, SYSTEM.ADR(buf), LEN(buf), read, NIL); IF (res # 0) & (read >= 2) THEN buf[read - 2] := 0X; (* removes CRLF *) IF buf # "" THEN res := WinApi.MultiByteToWideChar(WinApi.CP_OEMCP, {}, buf, LEN(buf$), str, LEN(str) - 1); IF (res > 0) & (res < LEN(str)) THEN str[res] := 0X ELSE str := "" END END END END ReadLn; (* press RETURN on the console to continue *) PROCEDURE Pause*; VAR dummy: ARRAY 256 OF CHAR; BEGIN ReadLn(dummy) END Pause; (* reset line number and indentation level, turns logging on if it was off *) PROCEDURE Reset*; BEGIN On; IF prefixed THEN Ln END; lineNr := 1; indent := 0 END Reset; BEGIN res := WinApi.AllocConsole(); stdIn := WinApi.GetStdHandle(WinApi.STD_INPUT_HANDLE); stdOut := WinApi.GetStdHandle(WinApi.STD_OUTPUT_HANDLE); indentStr := " "; Reset END Console.