# Input/Output

The sys/io subsystem offers a unified API to read and write from and to 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.

I/O structures are optionally "seekable", that is, that they have the concept of
position and size. When an I/O is seekable, :pos, :seek, :size and :resize are
implemented. Otherwise, calling these methods results in an I/O error.

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.

## IO API

Methods:

: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.

  The returned buffer must stay valid as long as a "buffer invalidating"
  operation isn't made on the same handler, that is, :readbuf, :writebuf, :flush
  or :close.

: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)

:pos ( hdl -- pos )
  Yield the current position, in terms of byte offset.

:seek ( n hdl -- )
  Change current position to "n", which is a byte offset.

:size ( hdl -- size )
  Yield size of the data being targeted by the I/O structure, in bytes.

:resize ( n hdl -- )
  Change the size of target I/O structure, in bytes.

Words:

:write ( a n hdl -- written-n )
  Repeatedly call :writebuf until at most "n" bytes have been written from
  buffer "a". Yields "written-n", the number of bytes that have actually been
  written.

:write# ( a n hdl -- )
  Call :write and abort if "written-n" is not the same as "n".

:read ( a n hdl -- read-n )
  Repeatedly call :readbuf until at most "n" bytes have been read into buffer
  "a" (which of course must be big enough). Yields "read-n", the number of bytes
  that have actually been read.

:read# ( a n hdl -- )
  Call :read and abort if "read-n" is not the same as "n".

:readall ( a hdl -- )
  Read until EOF into memory address "a".

:getc ( hdl -- c )
  Read 1 byte from hdl an return it. Advance position by 1 byte. Return -1 on
  EOF. This can be used to poll an interactive (keyboard, serial link, etc) I/O
  device: -1 is returned as long as there's nothing to return.

: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.

: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".

:exec< ( w hdl -- )
  Call "w" after having changed the input stream so that it spits "hdl"'s
  contents. When "w" returns, restore input stream and :close hdl.

:interpret ( hdl -- )
  Run "interpret" with :exec<.

:truncate ( hdl -- )
  Truncate the I/O to current position, inclusively. That is, a read operation
  directly following a :truncate will always be EOF.

:capture ( w hdl -- )
  Execute "w" with "emit" being overridden to "hdl"'s ":putc". Can be safely
  called from another :capture.

## Extending the IO struct

See [doc/usage/ns].

Required methods:

readbuf
writebuf

Optional methods:

flush
close
pos
seek
size
resize

## 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 API:

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.

Words:

: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

## MemIO

MemIO is a structure that extends IO and provides read/write capability to a
memory area. When it reaches the end of its buffer, reads will yield EOF and
writes will fail. You can seek inside the area by setting "ptr" directly.

Fields:

buf(  Lower bound of the buffer
)buf  Upper bound of the buffer
ptr   Where the MemIO is currently pointing

Words:

:new ( a u -- hdl )
  Allocate a new MemIO pointing to address range "a u".

:range ( hdl -- a u )
  Return the range represented by buf( and )buf.

:eof? ( hdl -- f )
  Whether ptr = )buf.

:rewind ( hdl -- )
  Set "ptr" to "buf(".

## SerialIO

Convenience structure that wraps a in<-emit-like words with IO words.

Methods:

:cin ( -- c )
  Read a character
:cout ( c -- )
  Write a character

Words:

:new ( -- self )
  Create a new SerialIO. This is typically not called directly, but by an
  extending struct.

## Console

This unit provides a "console" structbind which is a SerialIO structure that
wraps "in<" and "emit". This allows the operator to conveniently use IO
structures with data coming from "in<" or going to "emit".
