Standard IO

Starting Point

Astrobe for RISC5 provides a trimmed down Texts.mod module, which hard-wires the text output to the Astrobe terminal. Out.mod makes direct use of Texts.mod, hence is also limited to output to the terminal. The Texts.Writer parameter that is passed to all output procedures is kept for compatibility, but not actually used.

Texts.Write is the basis for all text output:

PROCEDURE Write*(VAR W: Writer; ch: CHAR)
  CONST data = -56; stat = -52;
BEGIN
  REPEAT UNTIL SYSTEM.BIT(stat, 1);
  SYSTEM.PUT(data, ORD(ch))
END Write;

Similarly, the input from the Astrobe terminal is hard-wired to the single RS232 device of the RISC5 processor of Astrobe for RISC5 in Input.mod.

Extension of Texts.mod

To be able to use Texts.mod for formatted output to other devices, for example additional serial terminals or LCD screens, Texts.mod is extended and changed as follows:

TYPE
  TextDevice* = POINTER TO TextDeviceDesc;
  TextDeviceDesc* = RECORD END;
  PutCharProc* = PROCEDURE(dev: TextDevice; ch: CHAR);
  Writer* = RECORD
    device: TextDevice;
    putChar: PutCharProc
  END;

PROCEDURE OpenWriter(VAR W: Writer; dev: TextDevice; pcp: PutCharProc)
BEGIN
  W.device := dev;
  W.putChar := pcp
END OpenWriter;

PROCEDURE Write(VAR W: Writer; ch: CHAR);
BEGIN
  W.putChar(W.device, ch)
END Write;

Now any device that extends Texts.TextDevice can be “plugged” into a Texts.Writer, and all output procedures of Texts.mod and Out.mod can be used to write formatted text to that device.

For example, the RS232 device is defined like so in RS232dev.mod:

TYPE
  Device* = POINTER TO DeviceDesc;
  DeviceDesc* = RECORD(Texts.TextDeviceDesc)
    (* ... *)
  END;

See Console.mod below for an application of the RS232 device structure.

Similarly, the driver for LC displays uses this structure:

TYPE
  Display* = POINTER TO DisplayDesc;
  DisplayDesc* = RECORD(Texts.TextDeviceDesc)
    W*: Texts.Writer;
    (* ... *)
  END;

The display example also demonstrates the use of W to allow to use all Texts.mod procedures for output on an LCD.

Console.mod

The above flexibility requires that the standard output device must now be allocated to the W module variable in the system modules, namely Oberon.mod and System.mod.

Console.mod serves this purpose. For simplicity, it also implements the basic input functionality provided by Astrobe’s Input.mod.

MODULE Console;
  IMPORT Texts, RS232dev, RS232u;
  VAR C*: Texts.Writer; dev: RS232dev.Device;

  PROCEDURE Available*(): INTEGER;
  BEGIN
    RETURN RS232u.RxAvailable(dev)
  END Available;

  PROCEDURE Read*(VAR ch: CHAR);
  BEGIN
    RS232.GetChar(ch)
  END Read;

BEGIN
  NEW(dev); RS232dev.Init(dev, RS232dev.Dev1);
  Texts.OpenWriter(C, dev, RS232u.PutChar)
END Console.

RS232dev.Dev1 is the serial device connected to the Astrobe terminal. RS232u is the device driver for unbuffered RS232 devices.

Oberon.mod now imports Console in lieu of Input and uses Console.Available and Console.Read in the Loop, and the module variable W gets set in the body:

MODULE Oberon;
  (* .. *)
  PROCEDURE Loop*;
  (* ... *)
  BEGIN
    REPEAT
      IF Console.Available() > 0 THEN
        Console.Read(ch);
      (* ... *)
    UNTIL FALSE
  END Loop;

BEGIN
  (* ... *)
  W := Console.C
  (* ... *)
END Oberon.

In System.mod, W is also set in its body accordingly.

Two points:

  • Texts.Writer is not a pointer, kept like this as it is defined and used thusly in Oberon. Each client module makes a copy of the record. Might need to be revisited. But passing a pointer as VAR just feels wrong.
  • A possible extension of Console.mod is to add an additional output channel, namely for errors. A program could then print errors to the Astrobe terminal, and its “normal” output to another terminal or display. Unix-y.