Landin library reference source

core/io/programs.ldn

1--  Running another program: a seventh concept beside the six, outside
2--  `world`, through which a program starts a child by its path, with the
3--  arguments and the environment it gives it, connects each of the child's
4--  standard streams, reads what the child writes and waits for it to end.
5
6import core/text
7
8--- A child's standard stream is the parent's own: it reads or writes
9--- where the parent does.
10public inherited: atom
11--- A child's standard stream is connected to nothing: its input reads at
12--- once to its end, and what it writes is thrown away.
13public discarded: atom
14--- A child's standard stream is a pipe the parent holds the other end of:
15--- the parent writes the child's input, or reads its output or errors.
16public piped: atom
17--- How one of a child's standard streams is connected: `inherited`,
18--- `discarded` or `piped`.
19public stream_choice: type = inherited | discarded | piped
20
21--- How each of a child's three standard streams is connected.
22public child_streams: type = struct
23    input: stream_choice
24    output: stream_choice
25    errors: stream_choice
26end child_streams
27
28--- Every stream the parent's own: the child reads and writes where the
29--- parent does.
30public inheriting: child_streams = (input: inherited, output: inherited,
31                                    errors: inherited)
32
33--- Every stream connected to nothing: a child run for how it ends.
34public discarding: child_streams = (input: discarded, output: discarded,
35                                    errors: discarded)
36
37--- The child's output and errors piped to the parent, which reads them with
38--- `read_child`, and its input discarded.
39public capturing: child_streams = (input: discarded, output: piped,
40                                   errors: piped)
41
42--- A started child: a value its caller keeps and edits, passing it `inout`
43--- to `write_child`, `close_input` and `read_child`, and `sink` to `wait`,
44--- which consumes it.  Its fields are the provider's: `handle` names the
45--- child, and `input`, `output` and `errors` the parent's ends of its piped
46--- streams, each open while its `*_open` flag says so, which it is only
47--- for a piped stream.  The child owns its pipes: `close_input` closes its
48--- input, `read_child` closes an output it has read to the end, and `wait`
49--- closes the rest.  A provider whose own files they are, as the hosted
50--- one's are its descriptors, may read and write them through `reader` and
51--- `writer` as well, a line reader over a child's output among them; never
52--- close them through `files`.  A child that is never waited for keeps
53--- what its provider holds for it until the process ends: there are no
54--- destructors.
55public child: type = struct
56    handle: usize
57    input: file
58    output: file
59    errors: file
60    input_open: bool
61    output_open: bool
62    errors_open: bool
63end child
64
65--- What `read_child` read: `count` bytes, from the child's errors when
66--- `errors` is true and from its output when it is not, or nothing and
67--- `finished`, once every piped output stream of the child has reached its
68--- end.
69public child_read: type = struct
70    count: usize
71    errors: bool
72    finished: bool
73end child_read
74
75--- How a child ended: it `exited` with the code its program returned, or
76--- it was `signalled`, ended from outside before it returned one.  What
77--- ended it is the system's detail, which its provider keeps; on POSIX it
78--- is a signal.  A POSIX code is the low eight bits of the program's
79--- value, 0 to 255.
80public ending: type = struct
81    how: variant
82        exited: (code: i32) |
83        signalled
84    end how
85end ending
86
87--- Starting programs and waiting for them.  A program is named by its
88--- path, a host string, never looked up on a search path nor given to a
89--- shell; a child is given exactly the arguments and the environment its
90--- parent builds, and inherits no other open file.  Each of its three
91--- standard streams is inherited, discarded or piped (`child_streams`).
92---
93--- A child that writes more than a pipe holds waits for its parent to read
94--- it, so read a piped output while the child runs, and read output and
95--- errors piped together through `read_child`, which reads whichever has
96--- something: reading one to its end first can wait forever while the
97--- child waits to write the other.  Close a piped input before waiting for
98--- a child that reads it to its end.
99public programs: type = concept (provider: type)
100    --- Start the program at `path` with `arguments`, which it sees after
101    --- its own name, and exactly the variables of `environment`.  A
102    --- missing program is `not_found`, one the caller may not run
103    --- `no_access`; on any failure no child was started.
104    spawn: (self: provider, path: text.host_string,
105            arguments: []text.host_string, environment: []variable,
106            streams: child_streams)
107           -> (started: child) ! not_found | no_access | io_failed
108    --- Write all of `bytes` to the child's piped input, waiting while its
109    --- pipe is full.
110    write_child: (self: provider, inout started: child, bytes: []u8)
111                 -> none ! io_failed
112    --- Close the child's piped input, so that it reads to its end.
113    close_input: (self: provider, inout started: child) -> none ! io_failed
114    --- Read what the child wrote next to whichever of its piped output and
115    --- errors has something, into `into`.
116    read_child: (self: provider, inout started: child, into: []mut u8)
117                -> (taken: child_read) ! io_failed
118    --- Wait for the child to end, close every pipe it still has, and
119    --- report how it ended.  The child is consumed even when this fails.
120    wait: (self: provider, sink started: child)
121          -> (ended: ending) ! io_failed
122end programs
123
124--- Start the program at `path` through `programs` evidence, with
125--- `arguments` after its name and exactly the variables of `environment`,
126--- its standard streams connected as `streams` says.  Wait for every child
127--- started.
128public spawn: (provider: type is programs, host: provider,
129               path: text.host_string, arguments: []text.host_string,
130               environment: []variable, streams: child_streams)
131              -> (started: child) ! not_found | no_access | io_failed =
132    started = try provider.spawn(host, path, arguments, environment, streams)
133end spawn
134
135--- Write all of `bytes` to a child's piped input.  A child that is not
136--- reading waits its parent here once its pipe is full, so write a large
137--- input only to a child that reads it as it comes.  Writing to an input
138--- that is not open is `io_failed`.
139public write_child: (provider: type is programs, host: provider,
140                     inout started: child, bytes: []u8) -> none ! io_failed =
141    try provider.write_child(host, started, bytes)
142end write_child
143
144--- Close a child's piped input, which it then reads to its end.  Closing
145--- an input that is not open is `io_failed`.
146public close_input: (provider: type is programs, host: provider,
147                     inout started: child) -> none ! io_failed =
148    try provider.close_input(host, started)
149end close_input
150
151--- Read the next bytes a child wrote to whichever of its piped output and
152--- errors has something, waiting until one has; zero bytes and `finished`
153--- once both have ended.
154public read_child: (provider: type is programs, host: provider,
155                    inout started: child, into: []mut u8)
156                   -> (taken: child_read) ! io_failed =
157    taken = try provider.read_child(host, started, into)
158end read_child
159
160--- Wait for a child to end and report how it did.  The child is consumed,
161--- with every pipe it still had, even when this reports `io_failed`.
162public wait: (provider: type is programs, host: provider,
163              sink started: child) -> (ended: ending) ! io_failed =
164    ended = try provider.wait(host, started)
165end wait