Difference between revisions of "Console"

From BlackBox Framework Wiki
Jump to navigation Jump to search
(improved version)
(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 s: ARRAY OF CHAR);
PROCEDURE String*(IN str: ARRAY OF CHAR);
VAR buf: ARRAY 10000 OF SHORTCHAR;
VAR buf: ARRAY 10000 OF SHORTCHAR;
BEGIN
BEGIN
IF on & (s # "") THEN
IF on & (str # "") THEN
res := WinApi.WideCharToMultiByte(WinApi.CP_OEMCP, {}, s, LEN(s$), buf, LEN(buf), NIL, NIL);
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 CHAR); (* copied from Strings *)
(* 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: CHAR; a: ARRAY 32 OF CHAR;
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);
j := 0; WHILE a[j] # 0X DO INC(j) END
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: ARRAY 20 OF CHAR; sstr: ARRAY 20 OF SHORTCHAR;
VAR str: ARRAY 20 OF SHORTCHAR;
BEGIN
BEGIN
IF on THEN
IF on THEN
LinePrefix;
IntToString(x, str);
IntToString(x, str);
sstr := SHORT(str);
res := WinApi.WriteFile(stdOut, SYSTEM.ADR(str), LEN(str$), NIL, NIL)
res := WinApi.WriteFile(stdOut, SYSTEM.ADR(sstr), LEN(sstr$), NIL, NIL)
END
END
END Int;
END Int;


(* outputs the line number and indentation level followed by repeated indentStr according to the indentation level *)
(* 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
Int(lineNr); String(":"); Int(indent);
IF ~prefixed THEN
FOR i := 1 TO indent DO String(indentStr) END
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); LinePrefix;
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);
lineNr := 1; indent := 1; indentStr := "  "; On;
indentStr := "  "; Reset
LinePrefix
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.