Difference between revisions of "Console"
Jump to navigation
Jump to search
Josef templ (talk | contribs) (improved version) |
Josef templ (talk | contribs) (added ReadLn and Pause, line prefix improved) |
||
| Line 5: | Line 5: | ||
In order to make it easy to detect new output it adds a line number at the left. | 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. | In order to increase readability it supports indentation of the output. | ||
Non-ASCII characters are converted to the underlying Windows default code page. | Reading of console input and pausing is also supported. | ||
Non-ASCII characters are converted to/from the underlying Windows default code page. | |||
<pre>MODULE Console; | <pre>MODULE Console; | ||
(* low level logging to console window; supports switching on and off and indentation | (* 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 *) | if used in Kernel (or other linked module) don't forget to link a new .exe file *) | ||
| Line 16: | Line 20: | ||
VAR | VAR | ||
res, stdOut, lineNr, indent, offAtLineNr: INTEGER; | res, stdIn, stdOut, lineNr, indent, offAtLineNr: INTEGER; | ||
on: BOOLEAN; | on, prefixed: BOOLEAN; | ||
indentStr: ARRAY 10 OF CHAR; | indentStr: ARRAY 10 OF CHAR; | ||
PROCEDURE^ LinePrefix; | |||
(* sets a non-default indent string, default is " " *) | (* sets a non-default indent string, default is " " *) | ||
| Line 59: | Line 65: | ||
(* output null terminated string; converted to Windows default code page *) | (* output null terminated string; converted to Windows default code page *) | ||
PROCEDURE String*(IN | PROCEDURE String*(IN str: ARRAY OF CHAR); | ||
VAR buf: ARRAY 10000 OF SHORTCHAR; | VAR buf: ARRAY 10000 OF SHORTCHAR; | ||
BEGIN | BEGIN | ||
IF on & ( | IF on & (str # "") THEN | ||
res := WinApi.WideCharToMultiByte(WinApi.CP_OEMCP, {}, | LinePrefix; | ||
res := WinApi.WideCharToMultiByte(WinApi.CP_OEMCP, {}, str, LEN(str$), buf, LEN(buf), NIL, NIL); | |||
IF ~((res > 0) & (res <= LEN(buf))) THEN | IF ~((res > 0) & (res <= LEN(buf))) THEN | ||
buf := "*** error in Console.String ***"; res := LEN(buf$); | buf := "*** error in Console.String ***"; res := LEN(buf$); | ||
END; | END; | ||
res := WinApi.WriteFile(stdOut, SYSTEM.ADR(buf), res, NIL, NIL) | res := WinApi.WriteFile(stdOut, SYSTEM.ADR(buf), res, NIL, NIL); | ||
res := WinApi.FlushFileBuffers(stdOut) | |||
END | END | ||
END String; | END String; | ||
PROCEDURE IntToString(x: LONGINT; OUT s: ARRAY OF | (* 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"; | CONST minLongIntRev = "8085774586302733229"; | ||
VAR j, k: INTEGER; ch: | VAR j, k: INTEGER; ch: SHORTCHAR; a: ARRAY 32 OF SHORTCHAR; | ||
BEGIN | BEGIN | ||
IF x # MIN(LONGINT) THEN | IF x # MIN(LONGINT) THEN | ||
IF x < 0 THEN s[0] := "-"; k := 1; x := -x ELSE k := 0 END; | 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 | j := 0; REPEAT a[j] := SHORT(CHR(x MOD 10 + ORD("0"))); x := x DIV 10; INC(j) UNTIL x = 0 | ||
ELSE | ELSE | ||
a := minLongIntRev; s[0] := "-"; k := 1; | a := minLongIntRev; s[0] := "-"; k := 1; j := LEN(minLongIntRev); | ||
END; | END; | ||
ASSERT(k + j < LEN(s), 23); | ASSERT(k + j < LEN(s), 23); | ||
| Line 89: | Line 110: | ||
(* output integer as decimal number without any padding *) | (* output integer as decimal number without any padding *) | ||
PROCEDURE Int*(x: INTEGER); | PROCEDURE Int*(x: INTEGER); | ||
VAR str | VAR str: ARRAY 20 OF SHORTCHAR; | ||
BEGIN | BEGIN | ||
IF on THEN | IF on THEN | ||
LinePrefix; | |||
IntToString(x, str); | IntToString(x, str); | ||
res := WinApi.WriteFile(stdOut, SYSTEM.ADR(str), LEN(str$), NIL, NIL) | |||
res := WinApi.WriteFile(stdOut, SYSTEM.ADR( | |||
END | END | ||
END Int; | END Int; | ||
(* outputs the line number and indentation level followed by repeated indentStr according to | (* outputs the line number and indentation level followed by repeated indentStr according to indentation level *) | ||
PROCEDURE LinePrefix; | PROCEDURE LinePrefix; | ||
VAR i: INTEGER; | VAR i: INTEGER; lStr, iStr: ARRAY 12 OF SHORTCHAR; | ||
BEGIN | 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; | END LinePrefix; | ||
| Line 111: | Line 136: | ||
BEGIN | BEGIN | ||
IF on THEN | IF on THEN | ||
LinePrefix; | |||
crlf[0] := 0DX; crlf[1] := 0AX; | crlf[0] := 0DX; crlf[1] := 0AX; | ||
res := WinApi.WriteFile(stdOut, SYSTEM.ADR(crlf), 2, NIL, NIL); | res := WinApi.WriteFile(stdOut, SYSTEM.ADR(crlf), 2, NIL, NIL); | ||
INC(lineNr); | INC(lineNr); prefixed := FALSE; | ||
IF (offAtLineNr # -1) & (lineNr = offAtLineNr) THEN on := FALSE END | IF (offAtLineNr # -1) & (lineNr = offAtLineNr) THEN on := FALSE END | ||
END | END | ||
END Ln; | 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 | BEGIN | ||
res := WinApi.AllocConsole(); | res := WinApi.AllocConsole(); | ||
stdIn := WinApi.GetStdHandle(WinApi.STD_INPUT_HANDLE); | |||
stdOut := WinApi.GetStdHandle(WinApi.STD_OUTPUT_HANDLE); | stdOut := WinApi.GetStdHandle(WinApi.STD_OUTPUT_HANDLE); | ||
indentStr := " "; Reset | |||
END Console.</pre> | END Console.</pre> | ||
Revision as of 09:47, 22 February 2017
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.