~vdupras/duskos

ref: 87585b9b7ee4ad286f5b03b56dfbda9a4c900004 duskos/fs/doc/io.txt -rw-r--r-- 6.2 KiB
87585b9bVirgil Dupras doc/io: clarify and add missing methods 2 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# Input/Output

The sys/io subsystem offers a unified API to read and write any device or
filesystem. All words in this API revolve around a structure that we call the
"IO handle".  This handle represent one "place" we read and write to, and if
needed save any state needed to fulfill the API's specification.

I/O operations are byte based.

When "EOF" is mentioned, it means "end of file". In some cases, it's a misnomer,
but the idea is the same: if all possible data from the I/O source have been
consumed, we're in EOF condition.

Position management is opaque to the I/O words.

The structure of these handles will vary depending on the driver or filesystem,
but they all start with the same structure:

putback
  A holder of the "putback" value. If nonzero, the next call to :getc will
  return this instead of actually reading from the buffer.

:readbuf ( n hdl -- a? read-n )
  Try to read "n" bytes from hdl at current position. Unless EOF is reached, at
  least 1 byte must be read, but otherwise "read-n" can be lower than n, even
  if that doesn't take the handle to EOF. The idea is to take the handle, at
  most, to the end of its internal buffer. Return the number of bytes read and,
  if it was nonzero, return an address to the beginning of that buffer.

  After the read operation, advance current position by "read-n" bytes.

:writebuf ( a n hdl -- written-n )
  Try to write "n" bytes from buffer "a" to hdl at current position, growing
  the file if needed. The idea is the same as with readbuf: the filesystem can
  proceed as is best for its implementation, as long as it writes at least 1
  byte. If no data can be written, return 0.

  After the write operation, advance current position by "written-n" bytes.

:flush ( hdl -- )
  If the hdl's buffer is "dirty" (has been changed compared to the permanent
  storage it represents), save that data to permanent storage and flag the
  handle as clean.

:close ( hdl -- )
  Close hdl and free its resources. When a handle is closed, operations on
  it become noops (read return 0, writes/seeks do nothing)

In addition to those device-specific words, all IO structures share these
convenience words:

:write ( a n hdl -- )
  Repeatedly call :writebuf until 'n' bytes have been written (coming from 'a')
  Aborts if we can't write all bytes.

:read ( a n hdl -- )
  Repeatedly call :readbuf until 'n' bytes have been read (into buffer at 'a',
  which of course must be big enough). Aborts if we can't read all bytes.

:getc ( hdl -- c )
  Read 1 byte from hdl an return it. Advance position by 1 byte. Return -1 on
  EOF. If putback is nonzero, return this value instead, and reset putback to 0.

:putc ( c hdl -- )
  Write 1 byte to hdl. Advance position by 1 byte. Aborts if unable to write.

:puts ( str hdl -- )
  Write str to hdl.

:putback ( c hdl -- )
  Set the putback value to c.

:readline ( hdl -- str-or-0 )
  read hdl for a maximum of STR_MAXSZ-1 characters until LF or EOF is
  encountered, then return a string representing that read line. The LF
  character is not included. Aborts on LNSZ overflow. The string can be zero in
  length if LF is encountered right away. When EOF is encountered in the middle
  of the string, return the string. When it's encountered at the beginning,
  return 0 to indicate an absence of result.

:spit ( dst hdl -- )
  Until EOF is reached, read "hdl" and write its contents to other IO "dst".

## SumIO

The SumIO struct allows you to "spit" another IO into it and extract a result.
It is created using a word with the signature ( res c -- res ) and whenever data
is written to it, it is applied to this function to accumulate a result. It has
the following fields:

fn ( hdl -- 'fn )
  Points to a ( res c -- res ) word that is applied on incoming data.

res ( hdl -- res )
  Where the result is stored.

:new ( initres 'fn -- hdl )
  Create a new SumIO.

If, for example, you want to create a CRC32 calculator, you'd do:

' crc32 SumIO :new const mycrc32io
-1 mycrc32io to SumIO res
mycrc32io otheriohandle IO :spit
mycrc32io SumIO res ^ .x \ prints crc32 of otheriohandle's contents

## SystemIn and SystemOut

SystemIn and SystemOut are structures that wrap around "key" and "emit",
providing an I/O API around them.

## ConsoleIn and ConsoleOut

ConsoleIn and ConsoleOut are values that, by default, point to SystemIn and
SystemOut. When sys/rdln is used, ConsoleIn points to RdlnIn.

Once loaded, the system interpret loop feeds itself from ConsoleIn.

There is a "consoleecho" value, which is initialized to 0. When set to 1, all
characters being read through ConsoleIn are echoed through "emit". This can be
useful for debugging, and it's also used in the test harness.

## StdIn and StdOut

StdIn and StdOut are values at the top of our I/O indirection pyramid. By
default, they point to ConsoleIn and ConsoleOut, but you will often want to
redirect them to somewhere else when running higher level words. At this level,
you can redirect those aliases freely without breaking the interpret loop, but
at the same time, control the flow of most higher level I/Os.

"stdin" is a shortcut for "StdIn IO :getc"
"stdout" is a shortcut for "StdOut IO :putc"
"stdio$" sets StdIn to ConsoleIn and StdOut to ConsoleOut.

## I/O and file loading

When loading a file to be interpreted by the system (with ":fload" or "f<<"),
*both* ConsoleIn and StdIn are set to the handle of the file being read and
those redirections are expected to stay as-is while the file is loaded. For this
reason, unless you know what you are doing, you shouldn't redirect StdIn at the
"interpret" level of a Forth source file.

## printf

An interesting augmentation of the I/O subsystem is implemented in lib/fmt.fs,
IO :printf ( ... fmt hdl -- ). It takes a format string, for example "hello %d"
which contains placeholder elements identified by the character '%' followed by
a letter.

That formatting string must be preceded by a number of arguments equal to the
number of placeholders in the string, in reverse order. Example:

    3 2 1 S" hello %d %d %d" StdOut IO :printf --> prints "hello 1 2 3"

The letter following the '%' chars determines how the argument is formatted and
these letters are supported (others produce an error):

    d - decimal
    x - 4b hexadecimal
    w - 2b hexadecimal
    b - 1b hexadecimal
    s - string