Console Redirection in zeptoforth - tabemann/zeptoforth GitHub Wiki

zeptoforth supports console I/O redirection along with error output redirection. Console I/O redirection redirects the words emit, emit?, key, and key? to arbitrary hooks. These hooks are stored in the user variables emit-hook, emit?-hook, key-hook, and key?-hook. Additionally error output is invoked via with-error-console and has hooks stored in the user variables error-emit-hook and error-emit?-hook which, when this is called, temporarily replace the values of emit-hook and emit?-hook.

There is built-in support for console I/O redirection and error output to and from null (the bit bucket in the case of console output and error output, no input in the case of console input), the serial console, streams (akin to pipes in Unix), and files in FAT32 filesystems.

Redirection in the basic case where hooks are modified in the context of an execution token's execution and restored after it completes or when an exception is raised (after which the exception is re-raised) is accomplished with the words with-input, with-output, and with-error-output in the console module. with-input, which temporarily modifies key-hook and key?-hook, takes a key hook, a key? hook, and an execution token. with-output, which temporarily modifies emit-hook, emit?-hook, and flush-console-hook, takes an emit hook, an emit? hook, a flush-console hook, and an execution token. with-error-output, which temporarily modifies error-emit-hook, error-emit?-hook, and error-flush-console-hook, takes an error-emit hook, an error-emit? hook, an error-flush-console hook, and an execution token.

Redirection to and from null is accomplished with the words with-null-input, with-null-output, and with-null-error-output in the console module. with-null-input, which temporarily modifies key-hook to return 0 and key?-hook to always return false, takes an execution token. with-null-output, which temporarily modifies emit-hook to drop its argument without effect, emit?-hookto always return *true*, andflush-console-hookto be a no-op, takes an execution token.with-null-error-output, which temporarily modifies error-emit-hookto drop its argument without effect,error-emit?-hookto always return *true*, anderror-flush-console-hook` to be a no-op, takes an execution token.

Redirection to and from the serial console is accomplished with the words with-serial-input, with-serial-output, and with-serial-error-output in the console module. with-serial-input, which temporarily modifies key-hook and key?-hook to provide serial console input, takes an execution token. with-serial-output, which temporarily modifies emit-hook, emit?-hook, and flush-console-hook to provide serial console output, takes an execution token. with-serial-error-output, which temporarily modifies error-emit-hook, error-emit?-hook, and error-flush-console-hook to provide serial console error output, takes an execution token. Also, serial-console in the int-io module selects serial console I/O for the current task without limiting it to a particular scope.

Redirection to and from the USB CDC console, for full_usb builds, is accomplished with the words with-usb-input, with-usb-output, and with-usb-error-output in the usb module. with-usb-input, which temporarily modifies key-hook and key?-hook to provide USB CDC console input, takes an execution token. with-usb-output, which temporarily modifies emit-hook, emit?-hook, and flush-console-hook to provide USB CDC console output, takes an execution token. with-usb-error-output, which temporarily modifies error-emit-hook, error-emit?-hook, and error-flush-console-hook to provide USB CDC console error output, takes an execution token. Also, usb-console in the usb module selects USB CDC console I/O for the current task without limiting it to a particular scope.

Redirection to and from the USB CDC console, for full_swdcom and mini_swdcom builds, is accomplished with the words with-swd-input, with-swd-output, and with-swd-error-output in the swd module. with-swd-input, which temporarily modifies key-hook and key?-hook to provide swdcom console input, takes an execution token. with-swd-output, which temporarily modifies emit-hook, emit?-hook, and flush-console-hook to provide swdcom console output, takes an execution token. with-swd-error-output, which temporarily modifies error-emit-hook, error-emit?-hook, and error-flush-console-hook to provide swdcom console error output, takes an execution token. Also, swd-console in the swd module selects swdcom console I/O for the current task without limiting it to a particular scope

Redirection to and from streams is accomplished with the words with-stream-input, with-stream-output, and with-stream-error-output in the console module. Each of these are buffered; a buffer is allotted temporarily which stores data being input or output, to reduce the frequency of actual receives or sends on the stream in question. In particular, when data is output, a software alarm is set to send the contents of the buffer after a short delay if it has not already been set, unless the buffer is filled where then the alarm is unset and the entire buffer is immediately sent. with-stream-input, which temporarily modifies key-hook to do a buffered byte receive from a stream and key?-hook to report whether either a buffered byte is available or a byte is available in a stream, takes a stream and an execution token. with-stream-output, which temporarily modifies emit-hook to do a buffered byte send to a stream, emit?-hook to report whether space is available in the buffer for another byte or space is available in a stream, and flush-console-hook to flush buffered output to the stream, takes a stream and an execution token. with-stream-error-output, which temporarily modifies error-emit-hook to do a buffered byte send to a stream, error-emit?-hook to report whether space is available in the buffer for another byte or space is available in a stream, and error-flush-console-hook to flush buffered output to the stream, takes a stream and an execution token.

For an example of redirection to and from a stream, take the following:

begin-module stream-console-test
  
  task import
  console import
  stream import
  
  1024 constant data-size
  data-size stream-size buffer: my-stream
  
  data-size my-stream init-stream
  
  1 cells buffer: out-notify-area
  1 cells buffer: in-notify-area
  
  0 :noname
    my-stream [:
      0 wait-notify drop
      begin
       [char] Z 1+ [char] A ?do i emit loop \ 100 ms
      again
      flush-console
    ;] with-stream-output
  ; 512 128 512 spawn constant out-task
  
  0 :noname
    my-stream [:
      0 wait-notify drop
      begin
        key emit
      again
    ;] with-stream-input
  ; 512 128 512 spawn constant in-task
  
  out-notify-area 1 out-task config-notify
  in-notify-area 1 in-task config-notify
  
  out-task run
  in-task run
  
  0 out-task notify
  0 in-task notify
  
end-module

This, when executed, outputs:

    ok
AenBd-module CD ok
EFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ

and it goes on ad infinitum.

Redirection to and from files is accomplished with the words with-file-input, with-file-output, and with-file-error-output in both the fat32 module and the fat32-tools module. These two sets of words differ in that the fat32 words take an instance of fat32::<fat32-file> in any FAT32 filesystem while the fat32-tools words take the path of a file in the current FAT32 filesystem as returned by fat32-tools::current-fs@. Each of these are buffered; a buffer is allotted temporarily which stores data being input or output, to reduce the frequency of actual reads or writes on the file in question. Note that it is assumed that no other accesses to the file in question will occur; if they do occur undefined behavior will result. In particular, when data is output, a software alarm is set to write the contents of the buffer after a short delay if it has not already been set, unless the buffer is filled where then the alarm is unset and the entire buffer is immediately written. Also note that exhausting filesystem space when using with-file-output or with-file-error-output results in raising fat32::x-no-clusters-free only when emit is executed; these do not preemptively detect filesystem space exhaustion. with-file-input, which temporarily modifies key-hook to do a buffered byte read from a file and key?-hook to report whether either a buffered byte is available or the end of file has not yet been reached, takes either a fat32::<fat32-file> instance or the path of a file in the current FAT32 filesystem. with-file-output, which temporarily modifies emit-hook to do a buffer byte write to a file, emit?-hook to always return true, and flush-console-hook to flush all buffered data to the physical SDHC/SDXC card, takes either a fat32::<fat32-file> instance or the path of a file in the current FAT32 filesystem. with-file-error-output, which temporarily modifies error-emit-hook to do a buffer byte write to a file, error-emit?-hook to always return true, and error-flush-console-hook to flush all buffered data to the physical SDHC/SDXC card, takes either a fat32::<fat32-file> instance or the path of a file in the current FAT32 filesystem.

For an example of writing to a file (which, forgive me, is intolerably slow for reasons I have yet to elucidate) with console redirection, take the following:

begin-module fat32-test

  oo import
  fat32 import
  simple-fat32 import
  
  <simple-fat32-fs> class-size buffer: my-fs
  
  0 constant my-spi
  2 constant spi0-sck-pin
  3 constant spi0-tx-pin
  4 constant spi0-rx-pin
  5 constant cs-pin
  
  : run-test ( -- )
    spi0-sck-pin spi0-tx-pin spi0-rx-pin cs-pin my-spi <simple-fat32-fs> my-fs init-object
    false my-fs write-through!
    my-fs fat32-tools::current-fs!
    s" " s" /HEX.TXT" fat32-tools::create-file
    s" /HEX.TXT" [:
      $1000 $0000 ?do i h.4 loop
      flush-console
    ;] fat32-tools::with-file-output
  ;
  
end-module

This is rather uneventful when executed:

fat32-test::run-test SD card timeout
fat32-test::run-test SD card timeout
fat32-test::run-test SD card timeout
fat32-test::run-test  ok

The "SD card timeout" messages were due to bad electrical connections between the SDHC/SDXC card and the Pico, which were resolved by jostling them around a bit:

To see what was written, then I executed:

s" /HEX.TXT" fat32-tools::dump-file 
00000000  30 30 30 30  30 30 30 31  30 30 30 32  30 30 30 33  |0000000100020003|
00000010  30 30 30 34  30 30 30 35  30 30 30 36  30 30 30 37  |0004000500060007|
00000020  30 30 30 38  30 30 30 39  30 30 30 41  30 30 30 42  |00080009000A000B|
00000030  30 30 30 43  30 30 30 44  30 30 30 45  30 30 30 46  |000C000D000E000F|
00000040  30 30 31 30  30 30 31 31  30 30 31 32  30 30 31 33  |0010001100120013|
[...]
00003FA0  30 46 45 38  30 46 45 39  30 46 45 41  30 46 45 42  |0FE80FE90FEA0FEB|
00003FB0  30 46 45 43  30 46 45 44  30 46 45 45  30 46 45 46  |0FEC0FED0FEE0FEF|
00003FC0  30 46 46 30  30 46 46 31  30 46 46 32  30 46 46 33  |0FF00FF10FF20FF3|
00003FD0  30 46 46 34  30 46 46 35  30 46 46 36  30 46 46 37  |0FF40FF50FF60FF7|
00003FE0  30 46 46 38  30 46 46 39  30 46 46 41  30 46 46 42  |0FF80FF90FFA0FFB|
00003FF0  30 46 46 43  30 46 46 44  30 46 46 45  30 46 46 46  |0FFC0FFD0FFE0FFF|
 ok

(Shortened for brevity's sake.)

For a similar test of redirecting a file to the console input, take the following:

begin-module fat32-test

  oo import
  fat32 import
  simple-fat32 import
  
  <simple-fat32-fs> class-size buffer: my-fs
  
  0 constant my-spi
  2 constant spi0-sck-pin
  3 constant spi0-tx-pin
  4 constant spi0-rx-pin
  5 constant cs-pin
  
  : run-test ( -- )
    spi0-sck-pin spi0-tx-pin spi0-rx-pin cs-pin my-spi <simple-fat32-fs> my-fs init-object
    false my-fs write-through!
    my-fs fat32-tools::current-fs!
    s" /HEX.TXT" [:
      begin key? while key emit repeat
    ;] fat32-tools::with-file-input
  ;
  
end-module

When one runs the following one gets:

fat32-test::run-test 0000000100020003000400050006000700080009000A000B000C000D000E000F[...]

(Also shortened for brevity's sake.)

For a test involving redirecting console output to a file using a fat32::<fat32-file> object, take the following:

begin-module fat32-test

  oo import
  fat32 import
  simple-fat32 import
  
  <simple-fat32-fs> class-size buffer: my-fs
  <fat32-file> class-size buffer: my-file
  
  0 constant my-spi
  2 constant spi0-sck-pin
  3 constant spi0-tx-pin
  4 constant spi0-rx-pin
  5 constant cs-pin
  
  : run-test ( -- )
    spi0-sck-pin spi0-tx-pin spi0-rx-pin cs-pin my-spi <simple-fat32-fs> my-fs init-object
    false my-fs write-through!
    s" /HEX1.TXT" [: my-file swap create-file ;] my-fs with-root-path
    my-file [:
      $1000 $0000 ?do i h.4 loop
      flush-console
    ;] with-file-output
  ;
  
end-module

This is uneventful when the following is executed:

fat32-test::run-test  ok

To see what was written the file, I then executed the following:

fat32-test::my-fs fat32-tools::current-fs!  ok
s" /HEX1.TXT" fat32-tools::dump-file 
00000000  30 30 30 30  30 30 30 31  30 30 30 32  30 30 30 33  |0000000100020003|
00000010  30 30 30 34  30 30 30 35  30 30 30 36  30 30 30 37  |0004000500060007|
00000020  30 30 30 38  30 30 30 39  30 30 30 41  30 30 30 42  |00080009000A000B|
00000030  30 30 30 43  30 30 30 44  30 30 30 45  30 30 30 46  |000C000D000E000F|
00000040  30 30 31 30  30 30 31 31  30 30 31 32  30 30 31 33  |0010001100120013|
[...]
00003FB0  30 46 45 43  30 46 45 44  30 46 45 45  30 46 45 46  |0FEC0FED0FEE0FEF|
00003FC0  30 46 46 30  30 46 46 31  30 46 46 32  30 46 46 33  |0FF00FF10FF20FF3|
00003FD0  30 46 46 34  30 46 46 35  30 46 46 36  30 46 46 37  |0FF40FF50FF60FF7|
00003FE0  30 46 46 38  30 46 46 39  30 46 46 41  30 46 46 42  |0FF80FF90FFA0FFB|
00003FF0  30 46 46 43  30 46 46 44  30 46 46 45  30 46 46 46  |0FFC0FFD0FFE0FFF|
 ok

(Truncated for brevity.)

For a test involving redirecting console input from a file using a fat32::<fat32-file> object, take the following:

begin-module fat32-test

  oo import
  fat32 import
  simple-fat32 import
  
  <simple-fat32-fs> class-size buffer: my-fs
  <fat32-file> class-size buffer: my-file
  
  0 constant my-spi
  2 constant spi0-sck-pin
  3 constant spi0-tx-pin
  4 constant spi0-rx-pin
  5 constant cs-pin
  
  : run-test ( -- )
    spi0-sck-pin spi0-tx-pin spi0-rx-pin cs-pin my-spi <simple-fat32-fs> my-fs init-object
    false my-fs write-through!
    s" /HEX1.TXT" [: my-file swap open-file ;] my-fs with-root-path
    my-file [:
      begin key? while key emit repeat
    ;] with-file-input
  ;
  
end-module

When run one gets the following:

fat32-test::run-test 0000000100020003000400050006000700080009000A000B000C000D000E000F[...]

(Truncated for brevity's sake.)

To temporarily modify the error output redirection to match the current console output redirection, execute with-output-as-error-output in the console module, which takes an execution token. Unlike separately executing, say, with-stream-error-output or with-file-error-output, this both avoids excessive dictionary and return stack space usage, and avoids undesirable results -- in particular, executing with-file-output and with-file-error-output on the same file will result in undefined behavior.

It should be noted that copious amounts of RAM dictionary space and return stack space are needed for redirection to and from streams and files in FAT32 filesystems. Redirection to and from streams requires at least 512 bytes of free RAM dictionary space for the current task, and for redirection to and from files in FAT32 filesystems 1024 bytes of free RAM dictionary space and 1024 bytes of return stack space are needed (in particular, from testing, the previous default of 512 bytes of return stack space is not enough for redirection to and from files in FAT32 filesystems, and redirection to and from files in FAT32 filesystems requires 512 bytes for representing a block of data alone, not counting all the other space requires). For this reason the size of the return stack for the main task has been increased to 1024 bytes as of release 0.64.1.

⚠️ **GitHub.com Fallback** ⚠️