The shell
Booting sysk gives you sysh, its shell (sysk 018), a part of the suite with a name of its own: its commands are a language programs can speak too. What you type decides which programs run, every command is checked before anything starts, and the shell can only ever hand out the pool the manifest set aside: no root, no devices, no other task's channels.
A command, from your keyboard to a job
Select a step, or play it. You type producer | filter | consumer; the picture lights up where the command is, and the cards show what it has become.
One language, and sugar in front of it
The shell reads s-expressions and nothing else, so its grammar fits on a card and a program can drive it without pretending to be a person. What people type goes through a separate step first, sugar, which knows no words, only syntax. Anything that starts with ( passes through it untouched.
The shell's commands
(run (P A ... ARG ...) ...)programs P, each A a pool channel by name, then its arguments;
out is the shell's output(pipe P1 P2 ...)P1's first sending parameter into P2's first receiving one, through a free pool channel of that size, and so on; every other sending parameter to
out(P A ... ARG ...) · (P ARG ...)one program: wired by hand, or as a pipe of one; a pipe's element may be
(P ARG ...) too(catalogue) · (pool)what you can run, and with what; each program's spec and doc are also files,
ls /prg, cat /prg/wc (sysk 057, Files)(cd PATH) · (cd) · (pwd)where a path that does not start at
/ starts, / if none is named; where that is. . and .. are the shell's to take away, in any tree: cd /sys/task/shell, then cat ../../time (sysk 056, Files)(ls PATH) · (ls) · (cat PATH)list a directory (
/ if none is named), print a file; /sys is the kernel's tree (the shell holds a Sys; Files), /dev the devices' (Devices as files), everything else the disk's (Files on the disk). A stream, such as /dev/rng, is read once: its bytes in hex, one line(mkdir PATH) · (rm PATH) · (echo WORD ...)make a directory, remove a file or an empty directory, say the words
(P PATH ARG ...) · (P A.B.C.D ARG ...)program P, with what PATH names lent to it to read, for as long as it runs:
(wc /notes/shopping), (wc /net/eth/addr) (sysk 031; Files for jobs); or, for an address, a conversation dialed to it and only its data lent, to read and write: (ping 10.0.2.2 (times 5)), sugar ping 10.0.2.2 --times 5 (sysk 058, The network)ARG: (NAME N) · (NAME)a program's argument: a number, or a flag; each at most once, in any order, after its channels or path (sysk 036; Arguments)
(> PATH (C ...)) · (>> PATH (C ...))command C's lines into a file on the disk, emptied first or added at its end; the file is made if it is not there, and stored whole when C is done
(& JOB)a job in the background: the commands go on while it runs, and its lines come as they come (sysk 032)
(explain JOB)what JOB would do, and nothing starts: a value for each task (its program, the pool channel each of its channels is bound to, its arguments as they would be), the path it would be lent, or the address dialed, and with what rights, and the job check's verdict, the same that judges it when it runs; why, if refused (sysk 051)
(complete WORD ...)what could come next for the last word: a command (the shell's own and the catalogue's programs), a path in any tree (it starts with
/), or a program's argument (it starts with --, with its range and default). Each choice is a value, (choice (word /sys/task/) (kind dir)), read from what the job check judges by: a program's arguments are its contract's, so what is offered and what is accepted cannot differ. A syst host asks on Tab (syst); anything driving the shell can ask the same way (sysk 050).(watch PATH) · (watch PATH MS) · (watch PATH MS TIMES)the file, and again each time it has changed, read every MS milliseconds (1,000 if not given, at least 100), TIMES reads in all if given; a line typed ends it, and is not run. Any tree:
watch /sys/task/shell/status, watch /net/eth0/stats once there is one. A change is a different count or checksum of the file's first payload: the kernel's tree keeps no versions and cannot answer a read that waits, so the shell reads again (sysk 049). follow, a program, is for files that grow on the disk.Sugar, rewritten by svc/sugar.sysp
producer | filter | consumer→(pipe producer filter consumer)greeter→(greeter)wc /notes/x ?→(explain (wc /notes/x)): a last ? asks what a command would do (sysk 051)catalogue→(catalogue)run (greeter out)→(run (greeter out))ls /sys/task > /notes/tasks→(> /notes/tasks (ls /sys/task))wc /notes &→(& (wc /notes))wc /notes --lines→(wc /notes (lines))follow /log --quiet 30000→(follow /log (quiet 30000))producer | then consumer→(pipe producer then consumer)(run (greeter out))→unchangedA sugared line is a list without its outer parentheses, as in Tcl or rc; | makes it a pipe, and a > or >> outside parentheses on a command's first line sends it into a file (sysk 029); a last & runs it in the background (sysk 032); and --name N is (name N) when N is a number, --name alone (name) (sysk 036), each exactly as long as what it replaces, so sugar rewrites it in place. Sugar is deliberately small: it is the place to try new syntax without touching the shell.
Arguments
A program may take arguments after its channel ends (sysk 036): a number or a flag, each with a name. They are named, never positional, so a command says what each value means wherever it is read: in a transcript, a refusal, or a command another program wrote. Each program says what it takes in its own code, and the catalogue fills in what you leave out:
(fn follow (... (out Tx) (quiet i64))
(requires (... (<= 1000 quiet) (<= quiet 60000))) ...)an argument is a parameter after the channel ends; its contract is its range
(programs ... wc (follow (quiet 5000)))each number's default; a flag (bool) is off unless named
(follow /log (quiet 30000))reads the names, fills the defaults, writes the values into the job's table
job-checkrefuses a number outside its range, reason 7, whatever sent it
(follow k h ... a0)reads it (job-arg), checks the range, calls: sysp proves the call meets the contract
The shell refuses what it can see in the command: a name the program does not take, a name given twice, a flag with a value, a number without one. Whether a number is in range is the job check's alone, so there is one place that decides it. catalogue lists each program's arguments, a line each; a line that would pass the screen's 64 columns goes on below, indented by four (sysk 041):
wc: ask -> stream x 576, answer <- stream x 576,
out -> 1 x 64
(lines): a flag
follow: ask -> stream x 576, answer <- stream x 576,
out -> stream x 64
(quiet N): 1000 to 60000, else 5000
Arguments are numbers from 0 to 999999 and flags; there are no short forms such as -l. A second spelling of every name, letters that clash from one program to the next, and bundles like -la cost more than they save; completion, which knows the catalogue's names, would save the typing instead.
Commands longer than a line
A line is 64 bytes. While a command's parentheses are open, the shell joins the next line to it and shows ...> instead of sysk> ; once they balance, the command runs. A command may take four lines. The squares are the parentheses still open after each line:
Both prompts are unfinished lines: a line whose length byte has its top bit set is written without its newline, so you type after it. A line longer than 64 bytes reaches the shell in pieces; a piece is joined to the next without a space, so no word is split.
When a command cannot run
The shell answers in your command's terms: the program, its argument, the channel by name, never an index. Its own refusals come first (what it cannot read, words it does not know); then the job check's, from Jobs. Nothing of a refused command starts, and the next one runs as if it had never been typed. These are a boot test:
| You type | Why | The shell says |
|---|
Where its lines go
Every line the shell says goes through one function, say: to the screen, or, while a command runs under >, as the next line of the file fid 1 names on the disk, written at the next offset. A job's output, a listing and an error all take the same road, so anything that can be printed can be stored. The first refusal on the way is kept, and said once the command is done: no space: /notes/x, say, and the file is not stored. A > inside a > is refused: one file at a time. The shell asks the disk's server over its own pair of streams, fs-ask and fs-reply, and the devices' over another, dev-ask and dev-reply (sysk 046), and the network's over a third, net-ask and net-reply (sysk 052), so it waits for each reply as a client does (pairs). Which pair a path's requests take, the manifest says: its namespace (sysk 053, Files), a table the shell is handed as text and keeps, and tree-of reads, the longest mount that starts the path. Since sysk 048 the shell holds its whole namespace as one value, Ns: the kernel's Sys, the table, and each server's two ends, so a function that reaches a file takes one argument for all four trees, and a server more is a field more (sysk 052 added /net as two fields); linked says each pair is a server's (served-over).
Values, a *, and where
What the shell says is not laid out by the shell (sysk 044, syst). say marks a line that is one s-expression as a value, and cat says a longer value whole, in pieces, rather than wrapping it for a screen of 64 columns; the terminal, or the host on the person's machine, lays it out. Each command's lines are a region of their own: the shell numbers commands, tells the terminal which region follows before each command and each line of a job in the background, which keeps its command's number (always, not only when it changes: a test of that would be a choice the deadlock model follows at every command). Two forms make values worth having:
cat /sys/task/*/status ; each task's status: a * in place of each name (where (state live) (cat /sys/task/*/status)) ; only the values with that field
A path may have one component that is *. The shell reads the directory before it, one entry at a time, and shows each path made with that name, in the job table's buffer, which is free while a file is shown. where takes a (FIELD VALUE) and a cat, and keeps the values that have that field among theirs; around anything else it says so. Through a host, the run of statuses is a table.
What the shell knows
The shell is user code: it cannot read the kernel's tables. The manifest hands it a text instead, (catalogue), which tools/wire.rkt writes from the jobs clause and each program's contract, so it cannot disagree with them. The shell reads it with the same cursor it reads commands with (lib/sexp.sysp: the next word or parenthesis from a position, nothing built; what is there is a Token, end, open, close, word or other, sysk 041; sx-form finds a text's top-level list by number). Its namespace comes the same way, (namespace), read by the same functions (sysk 053). Here is this system's catalogue, as the last make docs read it:
What a command asks for is one of sixteen kinds, an Action (sysk 037, sysp 028): classify reads the command and answers one, (new Action reply) with the reason in a line when it cannot be run, and perform does what it says, ((is action uptime) …). Before, a kind was a number from 0 to 15 that one comment explained, and a contract bounded it; now sysp knows each by name, and no other value is one.
A job in the background
A job started with & runs while you go on typing (sysk 032). The shell then waits on two sources at once with alt, as the file server does: your next line, and the job's next line or its end. A command runs as it would without the job; one that would start a second job is refused, busy: a job is running, and the job goes on. When the job's output ends the shell says job ended and the prompt comes back. If your input ends first, the shell stops reading commands but still reads the job to its end, so the session finishes as before. One way a session can go, with the lines in the order they might come:
follow /log & is the job to run this way (sysk 033): it prints the file, then each line added to it, as you add them with echo ... >> /log, and ends once the file has been quiet for five seconds, or as long as you tell it: follow /log --quiet 30000 & (Reads that wait; Arguments).
Which comes first, a line of the job's or a command's reply, is the scheduler's to say; each stays whole, and each keeps its own order. A job in the background shows its lines: & around a > is refused, as is & around anything that is not a job. Both sources are the manifest's: since sysk 032 a launcher's (jobs out) gives it its jobs' output as one more alt source, after its others.
One job at a time, by contract
The shell holds one Jobs, the right to start jobs, and it keeps where it is in their protocol in its fields, as a channel handle keeps its count: idle, a job judged, a job running. job-check needs an idle right, job-start one whose job passed, job-end a running one and the job's output read to its end. So the shell's loop can hold the right across commands, and sysp proves it never judges a second job while one runs, never starts one the check refused, and never ends one it has not drained. With a job in the background the right is running while commands run: command is told so (busy), and its contract says the right comes back as it went in, so a command beside a job cannot touch it. The job's output end lives in background, the loop that reads it, and jobs-output (interface 0.6.2) says it is that right's.