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.