Chapter 6:
I/O

I/O in Daisy

Daisy has a high-level character-stream I/O interface that enable programs to interact with a variety of devices and objects supported by the host operating system, including the keyboard, display, host file system, network connections and external OS processes. A particular I/O instance is a process that produces and/or consumes characters.

There are three types of devices: input (produces a stream of characters read from the device), output (consumes a stream of characters that are issued to the device) and bidirectional (both a producer and consumer). Typically, a Daisy primitive opens a connection to the device and sets up the stream interface. When the device is closed depends on the particular type of device, but all devices associated with an unreferenced stream are closed when the stream is garbage-collected.

Terminal I/O

Terminal I/O is handled using separate interfaces to the keyboard and screen.
console:Iprompt => [C0 C1 C2 ...]
stdin:Iprompt   => [C0 C1 C2 ...]
kbd:Cquit       => [C0 C1 C2 ...]
All keyboard devices return a stream of characters. In the case that the stream is terminated by the user (usually by typing Control-D) before any other input the result is a list containing the EOT character.

console is the usual method for reading characters from the terminal. It provides buffered input with line editing, character translation, echoing, etc. prompt must be an identifier that is used to prompt the user between lines of input.

stdin is like console, but if the input to Daisy was redirected from the host command line then this returns the redirected input instead of opening a new connection to the terminal. kbd provides the lowest level of keyboard input available; no line editing, no character translation, no echo, etc. Cquit is a symbol singleton that when typed by the user terminates the input stream; it is not appended. Nil can also be given as an argument, in which case the stream must be terminated under program control, since there is no way to signal the end of input from the keyboard.

screen:Iprompt => [C0 C1 C2 ...]
stdout:Iprompt => [C0 C1 C2 ...]
stderr:Cquit   => [C0 C1 C2 ...]
screen writes a stream of characters to the screen. stdout writes a stream of characters to the standard output, which may or may not be the screen, depending on whether the user redirected stdout. stderr writes a stream of characters to the standard error channel.

Disk (File) I/O

dski:Ipath          => [C0 C1 C2 ...]
dsko:[Ipath Schars] => []
File I/O is handled using separate interfaces for reading and writing. dski takes a host file name or path as an argument and returns a stream of characters representing the file. dsko takes a path and a stream of characters; it writes the characters to the file.

Network I/O

socki:Iport              => Fsocki
Fsocki:[CO0 CO1 CO2 ...] => [CI0 CI1 CI2 ...]
socko:[Ihost Iport]      => Fsocko
Fsocko:[CO0 CO1 CO2 ...] => [CI0 CI1 CI2 ...]
Daisy has a simple networking interface using stream-based sockets. Two interfaces are provided: one for accepting server-style (incoming) connections, and one for initiating client-style (outgoing) connections. There are currently no facilities for specifying the underlying network protocol, which is usually TCP/IP, or any of the more esoteric networking parameters; nevertheless, reasonably sophisticated client/server type applications can be written entirely in Daisy.

Like other Daisy I/O interfaces, sockets are character-stream oriented. However, the socket interfaces are both bi-directional; once a socket connection is made data flows both to and from the socket. The socket interfaces are created by the primitives socki (incoming) and socko (outgoing), which are higher-level primitives. A call to the appropriate primitive creates a function that, when invoked, attempts the socket connection.

socki takes a port number as argument and returns a closure (Fsocki). When Fsocki is applied to a character stream, it attempts to accept a socket connection on its internal port. If a connection is made, it concurrently writes its argument stream to the socket and returns the character stream read from the socket. The accept is internally non-blocking, so that if there are other concurrent Daisy processes they will continue to run while the Fsocki waits for a connection.

socko takes a host name and a port number as arguments and returns a closure (Fsocko). When Fsocko is applied to a character stream, it attempts to connect to an accepting socket at its internal host and port. If a connection is made, it concurrently writes its argument stream to the socket and returns the character stream read from the socket.

Spawning External (Host) Processes

exec:[Iname Iarg1 Iarg2 ... IargN] => [CI0 CI1 CI2 ...]

pipe:[Iname Iarg1 Iarg2 ... IargN] => Fpipe
Fpipe:[CO0 CO1 CO2 ...]=> [CI0 CI1 CI2 ...]

tpipe:[Iname Iarg1 Iarg2 ... IargN] => Ftpipe
Ftpipe:[CO0 CO1 CO2 ...]=> [CI0 CI1 CI2 ...]
Daisy has a set of interfaces for spawning host OS processes and interacting with them. Using these facilities it is possible to use Daisy to write an interactive command shell, as a shell programming language, or as glue logic for interactive programs (e.g. like expect).

The exec primitive can be used to run an output-only program. exec takes a list of command line arguments, forks a process to execute the command and returns a stream of characters representing the output of the command (i.e. the process' stdout and stderr). The command and arguments provided to exec should evaluate to Daisy symbols (strings). If the command does not specify an absolute pathname, the PATH environment variable is searched to find a matching command.

Two other, second-order primitives are available for invoking processes that require input. The new process' input (stdin) and output (stdout and stderr) streams are redirected so that the interface has a bi-directional character stream similar to that used for Daisy sockets. Like exec, pipe takes a list of command-line arguments, but returns a closure Fpipe. When Fpipe is applied to a character stream it attempts to fork the process and if successful, concurrently writes its argument stream to the process' standard input and reads the process' standard output and standard error, merging the latter two streams into its result.

tpipe stands for terminal pipe. tpipe works identically to pipe with the additional fact that as far as the spawned process is concerned, it appears to be interacting with a terminal. This deception is implemented through the use of a pseudo terminal (at least on Unix systems). Pseudo terminals are slightly more expensive than ordinary pipes in terms of system overhead, so pipe or exec are the preferred methods for dealing with external processes when this capability is not needed.