Command-line for talc
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Bryan 9f3d0b5360 the forms pipe passes lines, place, select, saved, tab and title
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 14:19:54 +01:00
examples run answers a status, not a number: printed as nothing, and a value is not a status 2026-10-24 14:19:12 +02:00
scripts shell -session: the terminal and the shell speak forms 2026-10-20 22:16:27 +02:00
syntaxes go instead of cd, and the network and services clients at the prompt 2026-10-20 22:16:27 +02:00
.gitignore Initial commit. 2026-09-26 11:36:42 +02:00
commands.talc run answers a status, not a number: printed as nothing, and a value is not a status 2026-10-24 14:19:12 +02:00
generate.talc Initial commit. 2026-09-26 11:36:42 +02:00
libraries.config go instead of cd, and the network and services clients at the prompt 2026-10-20 22:16:27 +02:00
README README: self-contained wording, no pointers to other documents or working notes 2026-10-25 11:03:28 +01:00
shell.talc the forms pipe passes lines, place, select, saved, tab and title 2026-11-01 14:19:54 +01:00
VERSION Initial commit. 2026-09-26 11:36:42 +02:00

shell: a command line for talc.

  shell.talc          the program
  commands.talc       (commands): run, output, run-command, go; the
                      session's way of running them
  syntaxes/sh.talc    a bash-like syntax, loaded with -l; talc, not built
  syntaxes/words.talc the smallest syntax there is, to copy from
  generate.talc       writes bindings.talc from libraries.config
  libraries.config    the libraries the shell starts with, one name per line
  bindings.talc       generated, do not edit
  scripts/build.sh    builds everything, in order
  scripts/build.talc  the same build, as a talc-shell script
  scripts/regress.sh  plays a terminal at `shell -session` and compares
                      the forms it answers with examples/session.expected
  examples/session.talc   that terminal: a drive on two pipes

Build:

  scripts/build.sh

which needs nothing but the installed toolchain, since it is what
bootstraps the shell in the first place. Once a shell exists,
scripts/build.talc does the same build as a talc-shell script:

  shell scripts/build.talc

and scripts/regress.sh checks the session (below) against the host's sh,
printf, sort, cat and sleep.

It needs the installed toolchain (compiler on PATH) and ../evaluator.

THE BASE. The shell's language is talc. A line is a form, or several, and
the value of each is printed. A form that is not finished continues on the
next line, after "... ". Running a program is a call:

  $ (run "ls" "-la")
  ...
  0
  $ (output "pwd")
  "/home/user"
  $ (run-command '(("ls" "-la") ("grep" "talc") (output "found.txt")))
  0

run-command takes one list: each ("program" "argument" ...) is a stage of
a pipeline, and (input "path"), (output "path") or (append "path") sends
the whole pipeline's stream to a file. go is a function, (go "dir") or
(go) for home, and (exit n) ends the shell. run answers the command's
exit code as a STATUS, a value of its own that is printed as nothing
(bash prints nothing after ls either); status-number gives the number.
`status` is how the last line ended: that number when the line's value
was a status, the code the line failed with, else 0 -- a value such as
42 is not a status.

A failure ends the line, not the shell: each line runs in an isolate
started monitored, with a 64 MiB stack, and the code it failed with is
`status`. Ctrl-C reaches the program that is running.

  shell               reads lines from the console
  shell script        runs the lines of a file as if typed

A SESSION. Started as

  shell -session [-l file]...

the shell is in a window of ../terminal: its standard input and output
are the terminal's, and what goes over them is talc forms, one a line,
not bytes. The terminal sends (line "text") when enter is pressed -- a
line for the shell at a prompt, or for the standard input of the command
running -- (interrupt) and (kill), which reach the process group of the
command and nothing else, (end), which closes the command's input or ends
the shell, and (size columns), which is COLUMNS for the programs started
from then on. The shell answers (prompt "$ ") or (prompt "... "), (text
"bytes") and (error "bytes") for what a command wrote on its two streams,
(value "text") for a value the base printed, (ended n) with `status` when
a line is done, and (bye). The programs the shell runs are given pipes and
write bytes to them as they always did; the shell is where the bytes
become forms, and nothing is escaped by a program. A program that wants
to say what its text IS -- an error, good news, a name that stands out
-- writes (styled role "text") to the handle TERMINAL_FORMS in its
environment names, one more pipe the shell made, which (system
console)'s console-write-styled does for it; so with (link "target"
"text"), text that opens something, and (secret "prompt"), a line wanted
that is not to be shown as it is typed, which the terminal answers with
(secret "text") for the command's input alone. The shell reads each
form there, checks its shape and prints it again -- bytes are never
passed through -- and anything else written there goes on as an error:
a line that is not a form, a role the shell keeps for itself (prompt,
command, value), a line longer than 64 KiB (dropped whole). Only while
a line runs: a program left behind in the background has no say at the
prompt, though what it writes on its two streams still arrives as text.
A command is waited for cooperatively, so that an interruption is read
while it runs. There is no escape sequence anywhere in the session.

A SYNTAX. Above the base, a syntax may be loaded: a talc function that
turns the text of a line into forms for the base. The bash-like command
line lives there, in syntaxes/sh.talc, and so would a fish-like one, or
anything else. The shell holds one syntax at a time and hands it every
line; without one, the base is it.

  shell -l syntaxes/sh.talc
  $ ls -la | grep talc > found.txt
  $ (define count 3)
  count
  $ echo $count files, (+ count 1) if you count $HOME
  3 files, 4 if you count /home/user

-l names a talc file, evaluated in the shell's environment before the
first line is read; several -l are evaluated in order, and (load "file")
at the prompt does the same. A file that defines a syntax ends by
installing it:

  (syntax-set! sh)

and (syntax-set! #f) at the prompt returns to the base. A script given
after the -l files is read through the syntax they installed:

  shell -l syntaxes/sh.talc backup.sh

WRITING ONE. A syntax is a function of the text of a line -- with the
lines before it that were not finished, joined by newlines -- and answers
one of

  #f                   not finished: the shell asks for another line and
                       calls the syntax again with both
  (error "message")    wrong; the message is shown
  (print form ...)     the forms are evaluated in turn, each value printed
  (silent form ...)    the same, nothing printed

The shell binds, for a syntax and anything else loaded:

  (syntax-base text)   the base syntax as a function, so a syntax can hand
                       it the lines it recognises as talc
  (syntax-set! f)      installs a syntax; #f for the base
  (load "path")        evaluates the forms of a talc file; #t, or #f after
                       complaining
  (variable-text 'name)  a variable's text: the shell's own if there is one,
                       else the environment's, else ""
  (value->text v)      a string as it is, anything else as it prints

syntaxes/words.talc is the whole shape in twenty lines: a line that starts
with ( goes to syntax-base, any other is split on blanks and becomes
(silent (run-command '(("program" "argument" ...)))). syntaxes/sh.talc is
the same shape with a scanner in front of it: bytes into the parts of a
word, words and operators into tokens, tokens into stages and
redirections, and those into a form. Copy either to start a syntax of
your own.

A syntax file is talc for the evaluator, not a library: no (library ...),
no (import ...), and no macros, so no define-structure. Everything the
configured libraries export is already bound, as is the reader:
(read-form text start) reads one form out of a line, which is how sh
handles (form) inside a word.

Changing what the shell starts with: edit libraries.config and build
again. Every function and value a listed library exports is bound under
its name; macros cannot be, so `define-structure` is not there, and the
control forms (and, or, when, unless, cond, let*) are the evaluator's own.