# Klammertext — reference for an LLM assistant For a language model helping someone author Klammertext documents. Dense and example-led on purpose: it is not a tutorial, and it motivates nothing. A person wanting to learn Klammertext should read the other documents in `doc/`. **Read §1 and §2 before writing any Klammertext.** §2 exists because a model that has not seen this language will pattern-match it to LaTeX, Markdown, or Lisp and produce confidently wrong syntax. The klammers named here are the **Standard Klammer Set** (SKS), which is what loads by default. A klammer set is user-extensible, so the authority for what exists in any particular setup is `kdesc`, not this file — see §1. Where this file and `kdesc` disagree, `kdesc` is right and this file is stale. Vocabulary: text is **katomized** into **katoms** (the indivisible input units) by **katom rewrite rules**; a **klammer** is the function-like operator; a **target** is an output format; a **klammerset** is a named group of klammer definitions. The three user modes are **author** (`@`), **designer** (`@@`), **programmer** (`@@@`). --- ## 1. Do not guess — the machine will tell you Klammertext is introspectable. `kdesc` **prints the live state of the machine's data structures in digested form**. Query it instead of inferring from examples. | ask | command | |---|---| | every klammer, with parameters and description | `kdesc -k` | | klammers whose name or description mentions X | `kdesc -k X` | | the output targets | `kdesc -t` | | argument types and what each accepts | `kdesc --argtypes` | | shared option groups and who uses them | `kdesc --optionsets` | | which targets each klammer can render to | `kdesc --coverage` | | what a user's own file defines (against the SKS) | `kdesc -i FILE -k` | | the klammersets available, with provenance | `kdesc --klammersets` | | how to write accented / non-English characters | `kdesc -c` | | how to write any Unicode character, with examples | `kdesc -u` | | installed typefaces; install or preview fonts | `kdesc --typeface`, `--typeface help` | | the usage of any command | run it with no arguments (exit 0) | `kdesc -k ` before using an unfamiliar klammer costs one command and replaces a guess. The parameter list it prints is authoritative: a parameter shows as `name.type default`, an option as `:name.type default`. **Check a document without rendering it:** ``` kdiag --process --check doc.kt ``` reports every klammer application whose name, argument count, option names, target coverage, or **literal argument value** is wrong (a value that fails its argument type, such as `:width .5h`) — including inside `@cond` branches this render will not take and inside klammer bodies never applied. A value holding a nested klammer or a `*variable*` is computed at run time and is not judged. It applies nothing. Use it before claiming a document is correct. **Inspect how text is parsed:** `kdiag --type ''` shows the katoms and their types; `kdiag --args ''` shows how text splits into arguments; `kdiag --rewrite ''` names the rewrite rule that fired. This settles arguments about what a piece of syntax means. `kdiag` deliberately loads no klammerset; give it `@read sks/sks.k @` in the input if it needs one. --- ## 2. What you will get wrong if you assume | assumption | reality | |---|---| | It is a macro language like TeX | No catcodes. Katom structure is fixed when a file is read and cannot be changed by anything later. This is why static checking works. | | `\command{arg}` | `@name arg @` — an `@` opens, and a **closing delimiter is required**. | | Arguments are `{a}{b}` | Positional arguments are separated by `\|` — `@sup 2 \| 3 @`. | | Options are `[key=value]` | `:name value`, anywhere in the argument list — `@link URL :text Click @`. | | `%` starts a comment | `#` removes the rest of the line. `##` removes the rest of the file. `#[ … ]#` removes a block (nestable). | | `*x*` is emphasis (Markdown) | `@i x @` is emphasis. `*name*` is **variable substitution** inside a klammer body; in prose write `^*` for a literal asterisk. | | `\\` escapes | `^` quotes: `^@ ^\| ^# ^^ ^: ^*` and **`^` before any ASCII punctuation** gives that character literally. | | `&`, `%`, `_`, `<`, `\` must be escaped for LaTeX/HTML | **Never.** The machine escapes the target's characters. An author quotes only Klammertext's own specials. Inside `@c`/`@code` nothing needs quoting. | | `--` is two hyphens | Under every target `--` is an en dash, `---` an em dash, `` `` `` / `''` curly double quotes, `` ` ``/`'` curly single quotes, `~` a non-breaking space (the LaTeX conventions). A literal `--flag` is `^-^-flag`; a literal tilde `^~`. Verbatim text (`@c`, `@code`, `^'…'^`) is exempt. | | Whitespace is insignificant | It is content, and it is preserved. Blank lines separate paragraphs. | | Text is nested in braces | Text lives between the opening and closing delimiter with no wrapper. | | The document body is the first argument | For `@document` the body goes in the **`:text` option** (or `:files`). A positional body is an error. | | `x@i` is "x, then italic" | A letter may not touch an opening `@`: `x@i` could be the close of a klammer named `x`. Write `x @i`. Brackets and sentence punctuation may abut: `(@i-x)`, `@i x @.` | | `:files ch1` names a file `ch1` | A bare word means **`kt/ch1.kt`** beside the root file. A name with a directory or extension is a pathname. | | `@font` sets the font | Retired. `@typeface name \| text @` is the temporary change; `@document :serif_typeface` etc. set the roles. | --- ## 3. Core syntax Three prefix tiers, distinguished by how many `@`: | written | tier | meaning | |---|---|---| | `@name` | application | use a klammer | | `@@name` | definition | define a klammer | | `@@@name` | system | modify the machine (targets, argument types, state, klammersets) | **Closing a klammer.** The close is `@` for the application tier, `@@` for definitions, `@@@` for system commands. A close may be *named* by putting the klammer's name immediately before it: `@i text i@`. > **When to name the close.** Use a bare `@` by default. Use the named form > `name@` when the body **contains other klammer calls**: if delimiters are > unbalanced, a named close lets the machine say which klammer was left open. > Do not name closes on short, klammer-free bodies — it is noise. ``` @i just text @ ← bare close @s1 A @c fragment c@ heading s1@ ← name it: the body contains a klammer call ``` **Arguments.** ``` @name positional1 | positional2 :option value :flag @ ``` - `|` separates positional arguments. - `:name value` is an optional argument; it may appear anywhere in the list. The value runs to the next `|` or `:option`. - `:name` written alone takes the argument type's "alone" value — for a boolean that is `true`, so `:number` means `:number true`; `@table :hline` means `:hline all`. - An optional argument that is not written takes its default (kdesc shows it). **Abbreviated form.** `@name-arg1-arg2` is `@name arg1 | arg2 @`, valid only while every argument is letters and digits: `@i-italic`, `@sup-2-3`, `@date@`. **Literal klammers** take their content uninterpreted. It is not examined for `@`, `|`, `#`, `^` or `*`, so the machine cannot find the end by counting and **the named close is required**: ``` @code int x = a[0]; // # and @ are safe in here code@ @c a | b c@ ← inline: same rule, and the close must be "c@" ``` `@c` and `@code` are the in-line and block forms of the same thing. Code that lives in a **file** is `@source_listing`, never `@code` (which has no filename argument): ``` @source_listing src/derivations.cpp @ ← the whole file @source_listing src/derivations.cpp :marker fromRdd55 @ ← one region of it ``` `:marker P` lists what lies between two lines that are exactly `//P` in column one. A marker missing or not appearing exactly twice is an **error**, never a fallback. `kdesc -k ` shows a literal parameter as `text.literal`. **Whitespace and removal (`#`).** `#` rest of line, `##` rest of file, `#[ … ]#` block; `#-` removes surrounding whitespace, `#+N` inserts N spaces, `#/N` inserts N newlines (default 1). **Characters (`^`).** One mechanism, four readings, in this order: | form | gives | example | |---|---|---| | `^P` (P any ASCII punctuation) | that character literally | `^-^-flag`, `^~`, `^^`, `^@` | | `^'…'^` | a **literal span**: raw target markup passed through untouched | `^'$\cdot$'^` | | `^UUUU^` (4–6 hex digits) | the code point | `^0418^` И, `^263A^` ☺, `^13000^` 𓀀 | | `^UUUU UUUU …^` | a run of code points, one caret pair | `^9053 53EF 9053^` 道可道 | | `^` + base letter + mark | a diacritic | `^e'` é, `^u"` ü, `^n~` ñ, `^cc` ç, `^oh` ô | | `^xx^` mnemonic | an extended-Latin letter | `^s^` ß, `^ae^` æ, `^o^` ø | `kdesc -c` prints the diacritic, extended-Latin and pinyin tables; `kdesc -u` the code-point notation with examples. The one `^` exception: `^'` opens a literal span, so a literal apostrophe is `^0027^`. A caret form that names nothing is an error, not silence. --- ## 4. A complete document ``` @document :title Normalizing to OpenEXR Standard Attributes :subtitle Predictability and provenance :author Joseph Goldstone :date @date@ :structure article :point_size 12 :text @s1 Principles s1@ Ordinary paragraph text. A blank line starts a new paragraph. Use @i emphasis @ and @b bold @ inline, and @c code_fragment c@ for code. A footnote @footnote Its text. @ is written inline. @link https://openexr.com/ :text the OpenEXR site @ @ul first item | second item | third item ul@ # A comment: removed, and never reaches the output. @image figure_name :width .95w :caption A caption @ @table :caption Sizes :column_width fit fill | Name | Description || alpha | the first || beta | the second table@ @ ``` A long document names its chapters instead of holding them: ``` @document :title The Book :structure book :files preface one two @ ``` which reads `kt/preface.kt`, `kt/one.kt`, `kt/two.kt` beside the root file. **Render it:** ``` ktext doc.kt -t pdf # also: -t html, -t tex ktext doc.kt -t html # writes a DIRECTORY doc/ containing index.html (+css, fonts, js) ktext doc.kt -t txt -d # -d displays on stdout instead of writing -- but this # example errors under txt/ansi: @image has no txt # definition (§5), and coverage is never silently degraded ktext -s '@i-x' -t html -d # -s takes a string; no file needed ktext doc.kt -s '@@version : 3.2.1 @@' -t pdf # -s is processed BEFORE the files: # define a klammer the document uses ``` - Output goes **next to the input file** unless `-o` names a directory and/or basename. String-only input writes to the current directory. - `-s`, `-t`, `-o` take exactly one argument (quote spaces). `--klammersets` takes a list; end it with `--` if filenames follow. - Filenames may contain spaces: quote them on the command line; in a list (`:files`, several input files) separate names with a standalone ` / `. - `@document :structure` is `plain` (default; no TOC), `article` (in-page TOC), `book` (cover, navigation panel in html; chapters, recto/verso in pdf). Headings `@s1`… are `\chapter` in a book and `\section` otherwise. - Role typefaces: `:serif_typeface`, `:sans_typeface`, `:mono_typeface` (family name or store directory name, e.g. `EB Garamond` or `eb-garamond`); `:text_scale` scales all text; `:point_size`, `:leading`, `:paper_size`, `:landscape`, `:two_column`. `kdesc -k document` lists all forty. - **Outside a `@document`** nothing makes paragraphs: a fragment rendered on its own writes `@par text @` explicitly. --- ## 5. Klammers most used when authoring Confirm parameters with `kdesc -k ` — this is names and intent only. Not every klammer exists for every target (`kdesc --coverage`); txt and ansi are the limited ones. | | | |---|---| | inline | `@i` italic, `@b` bold, `@t` typewriter, `@u` underline, `@s` sans, `@r` roman, `@ri` roman italic, `@c` inline code (literal; close `c@`), `@sup base \| script @`, `@sub`, `@ts scale \| text @` resize, `@color rgb :text`, `@typeface name \| text :scale @`, `@cjk` (needs an installed CJK typeface) | | structure | `@s1`…`@s7` numbered headings, `@h1`…`@h7` unnumbered, `@part`, `@preface`, `@par`, `@newpage`, `@vspace lines`, `@vfill`, `@nl` line break | | blocks | `@code` (inline listing), `@source_listing file :marker P`, `@quote text \| attribution @`, `@note`, `@indent :left :right`, `@center`, `@right`, `@block to \| content @` (absolute placement), `@columns items :n @`, `@multicolumn text :n :gap @`, `@lines` | | lists | `@ul :bullet`, `@ol :initial`, `@define :style` term/definition, `@qa question \| answer @` — items separated by `\|` | | tables | `@table` — cells by `\|`, rows by `\|\|` (§6); `@rowcolor` | | figures & references | `@image basename :width :caption`, `@image_grid`, `@fig id`, `@tbl id`, `@reference spec \| name @`, `@target id :text` | | footnotes & links | `@footnote text @`, `@link target :text :footnote`, `@email name \| domain @` | | dates | `@date`, `@datetime` (`:days N`, `:lang de`, `:number`), `@year`, `@timestamp` | | symbols | `@copyright`, `@registered`, `@trademark`, `@euro amount`, `@pi`, `@rightarrow`, `@dot`, `@sp` (space) | Bar depth is the dimension: one `|` separates items, two `||` separates rows. Argument order is free for options, fixed for positionals. **Coverage today** (`kdesc --coverage`; 89 klammers): html, tex and pdf cover nearly all. **txt and ansi cover 40**, among them `@b @i @t @u @s @r @ri @ts @typeface`, `@s1`–`@s7`, `@par @newpage @vspace @vfill @nl`, `@indent @right @lines @qa`, `@ul @ol`, `@table @tbl @reference @define @left_right`, `@footnote @link`, `@date @datetime`, `@c`. Not for txt: `@code @source_listing @image @image_grid @quote @note @center @block @columns @multicolumn @sup @sub @color @h1`–`@h7`. `@u` has no html definition. A klammer applied under a target that lacks it is an **error**, never silently dropped. --- ## 6. Tables ``` @table :caption Quarterly :column_width fit fit fill :cell_hpos l r r :calc 3(1-2) = + 1-2(1-2) | Item | Q1 | Q2 || Widgets | 10.5 | 12 || Gadgets | 3 | 4.25 || Total | | table@ ``` Cell indices are **zero-origin and count the header row**: `3(1-2)` is the fourth row, second and third columns; `1-2(1-2)` the two body rows above it; `-1` is the last row or column. A ranged target repeats the calculation across the range (here: one sum per column). - First row is the header (`:header false` otherwise); header style `:header_style i` by default; `:style r i b` per column. - `:column_width` per column: `fit` (widest entry), `fill` (the remaining width, capped at the widest entry), a fraction of the text width (`.2`), or `*` (the remainder). `fill` may not combine with a fraction or `*`. - `:cell_hpos l c r` per column; `:justify ; …` per cell. - Lines: default under the header and at the bottom; `:hline`/`:vline` replace them (`all`, `none`, or indexed ranges); `:grid` for all lines. - `:colspan`/`:rowspan` with the indexed-range syntax; `:allow_break true` makes a page-breaking table (pdf). - `:calc` — `;`-separated `() = `, prefix `+ - * /` folding left over the operands, which are cell ranges or numbers; **display-precision** (a printed total equals the sum of the printed lines); `notes/calc_notation.md` is the gallery. `:format module.function` formats cells with user Python. `:decimal comma` for comma decimals. - Placement: `:hpos left|center|right|none`, `:offset 2em` (inset from that margin); a caption goes **above** a table (`:caption_side top` default), below a figure. `:number false` for an unnumbered caption. --- ## 7. Targets | target | output | notes | |---|---|---| | `html` | a directory `/` with `index.html`, `css/`, `fonts/`, `js/` | self-contained, fonts localized; `book` structure adds navigation, search | | `tex` | `.tex` | XeLaTeX source; fonts via fontspec from the font store | | `pdf` | `.pdf` | `tex` plus a xelatex run (TeX Live required) | | `txt` | `.txt` | plain text filled to `Target_txt_width` (72), blocks indented by `Target_txt_indent` (3); tables ruled and padded; captions numbered; **40 of 89 klammers** (§5) | | `ansi` | txt + SGR escape sequences | `@b`/`@i`/`@u` and table header styles shown by a terminal; **identical to txt once the escape sequences are stripped**; pick it with `-t ansi -d`; a terminal needs a font with an italic face (macOS Terminal: Menlo, not Monaco) | `k`, `o`, `*` also appear in `kdesc -t`: they are declaration pseudo-targets (interface, option set, all targets), not outputs. State variables an author may set (`@@@state Name :value V @@@` in the document, or `-s`): `Language` (`en`/`de`, for dates), `Image_quality` (JPEG quality of cached photographs, 85), `Target_txt_width`, `Target_txt_indent`, `Image_default_width`, `Image_search_path`. Typefaces: the **font store** is closed-world (no network). The distribution ships eb-garamond, crimson-pro, open-sans, source-sans-3, inconsolata, source-code-pro, noto-sans-egyptian-hieroglyphs, noto-sans-cuneiform (see `kdesc --typeface`); more are installed with `kdesc --typeface install ` into `KLAMMERTEXT_FONTS`. An unknown typeface is an error listing the available ones. CJK is deliberately not shipped. --- ## 8. Defining klammers (only if asked) ``` @@name.k arg1 | arg2 :opt.type default : Description of what it does @@ @@name.html :: *arg1* @@ @@name.tex :: \textbf{*arg1*} @@ @@name.txt :: *arg1* @@ ``` - `.k` declares the interface and documents it; per-target definitions inherit the parameter list with `::`. Every target's list must agree. - `*arg*` substitutes an argument into the body; `*Name*` also reads a state or environment variable. - Several targets may share a body: `@@name.html,tex :: … @@`. A definition with **no suffix** applies to every target (the writer's macro form; its body is escaped for the target, so `@@corp : Smith & Jones @@` is safe). `.*` written out is a *claim* of target independence. - Separators: `:` create, `::` create inheriting parameters, `:::` override an existing definition, `::::` define a default a later `:` may replace. - Argument types: `name.type` — `string` (default), `int`, `float`, `bool`, `list`, `width`/`height` (a bare number = characters / lines in every target; units `em pt px`, `w pw` for widths, `h ph` for heights; the axes are checked), `literal` (raw content, named close required), `rest` (everything left, bars included; `rest(2)` = list of lists), and any `@@@argtype` the klammerset declares (`kdesc --argtypes`). An argtype may carry `:default` and `:alone`; a klammer may override the default, never the alone value. - **Option sets** (`.o`): a named group of optional parameters used by several klammers — `@@name.k arg @caption_args :caption_side top @ : … @@` splices the SKS's caption options with one default overridden. Usable only in a `.k` parameter list. A klammer *application* inside a parameter list is an error. - **`@eval`** is the primitive behind every non-trivial klammer: `@eval python_expr @` (module.function auto-imports; `K` holds the arguments), `@eval :cpp lib.function @`, `@eval :shell cmd @`, `@eval :haskell src @`; `:cwd DIR` runs it there (`:cwd *K_input_dir*`). A Python module is found next to the file that names it. **The result is re-read as Klammertext**: a result holding klammers is *generator* output and its text is escaped for the target; a result with none is *renderer* output and left alone. A literal `@` in a result must be emitted as `^@`. - A klammer whose tex body is target markup with a backslash must not be passed through a generator raw — the SKS carries such things as markers (`@nl`), resolved in an `:after_apply` phase. - **Klammersets**: `@@@klammerset sym | description :requires sks :files a.k b.k @@@` in `sym/sym.k`; loaded with `ktext --klammersets sym doc.kt` (symbols only, resolved in the document's directory, then `KLAMMERTEXT_KLAMMERSETS`, then the distribution; several combine in order, later definitions deciding collisions via the separators above). `kdesc -i file.k -k` shows what a file defines against the SKS. `--klammersets ref` adds the reflection klammers (`@diacritics_examples@`, `@unicode_examples@`, …) that generate the reference tables `kdesc -c`/`-u` print. --- ## 9. Behaviour worth knowing before you debug something - **`@cond` is strict about truth.** True: `true`, `True`, `1`. False: `false`, `False`, `0`, empty. **Anything else is an error**, not "probably false". - **`@cond` is resolved at application time and evaluates only the branch it selects.** A `@read`/`@eval` in the other branch does not run. A top-level `@cond *Flag* | … @` sees the variable's value. - **A state variable is visible from the moment its declaration is read** — in every klammer body applied afterwards and in the top-level text of later input units, but not in the top-level text of the same unit that declares it. Declare early (`-s`, a first file, a klammerset). - **A definition inside a `@cond` branch does not register** — in either branch. Define outside and select between *applications*. - **`@eval :shell` and `:haskell`:** stdout becomes document text; stderr does not (shown under `-v 1`); a nonzero exit is an error. Append `|| true` if a nonzero exit is expected. - **Arguments arrive as written**, nested klammers already rendered; a macro escapes only its own body text. Never escape an output format's characters yourself, anywhere. - **Typographic transforms are per katom and skip verbatim text**: a `--` split by a klammer is two hyphens; inside `@c`/`@code` nothing is transformed. - **Recursion** is guarded: a klammer reaching itself 200 deep is a named `Recursion_error`; a document needing more than 100 top-level passes is an error. - **`@eval` results are trimmed**; a klammer that must emit leading space or pure vertical space uses the txt/tex markers the SKS provides (`phases.txt_nbsp_marker()`, `@vspace`). - The html **page cache** (`/dev/shm/_klammertext_cache/` on Linux) can serve a stale page after a change to a klammer's html; clear it when output looks old (`@document :cache false` disables it). --- ## 10. Diagnostics and what the commands print - Three cases only: **logging** (stderr, `-v` > 0), **an error** (stderr, then exit ≠ 0), **the requested output** (stdout). So `ktext doc.kt -t txt -d | …` is safe; nothing else lands in the document. - **`-v 1`** reports every decision the user could not read off their input: which klammerset loaded and from where, which file a typeface or `:files` name resolved to, how the output path was built, an override of another set's definition. `-v 2`+ is an implementation trace. - An error stops the run and names a file, line and character; messages are justified to 80 columns. There is **no warn-and-continue** except two aggregated heuristics: unknown target coverage, and a table wider than the page. - *"Word not parsed: …"* means a piece of text matched no katom type — usually a letter touching a delimiter (§2) or a punctuation run needing `^`. - Colour on stderr only when it is a terminal; `NO_COLOR` disables it. kdesc's `-c`/`-u` render through `ansi` on a terminal and `txt` in a pipe. **When something is wrong:** `kdiag --process --check doc.kt` first (name, arity, option, literal-value and coverage faults without rendering); then `kdesc -k ` against what was written; then `kdiag --type ''` if the question is how something is parsed; then `ktext … -v 1` if the question is which file or klammerset was used. --- ## 11. Klammertext from Python ```python import klammertext # kpy/ on PYTHONPATH (env/runtime.env) M = klammertext.Machine(target="html", files=["doc.kt"]) # as ktext sets itself up M.read("@@version : 3.2.1 @@") # what "ktext -s" does M.read_files() # the constructor's files, in order html = M.apply() # the rendered text ``` `Machine(target='*', klammersets=None, files=None, output=None, display=False, verbose=0)`; `read(text)`, `read_file(name)`, `read_files()`, `apply(target=None, final_processing=True, escape_characters=True)`. **One Machine at a time** (a second raises `RuntimeError`); `apply()` reduces in place, so a second target needs a second Machine. Errors are `klammertext.Error` subclasses under the C++ names (`Parsing_error`, `Argument_error`, `Definition_error`, `Environment_error`, …) with `kind`, `description`, `filename`, `line`, `column`; `str(e)` is ktext's report; an `@eval`'s Python exception is chained as `__cause__`. --- ## 12. Adjacent tools - **Markdown → Klammertext:** `sks/tns/md_to_sks.py` converts a `.md` file; what it cannot convert exactly is left in the output as `#[MD … ]#` markers, so the draft carries its own worklist (spec: `doc/markdown_to_klammertext.md`). `mdpdf file.md` renders Markdown straight to PDF through a headless Chromium, bypassing Klammertext. - **Editors:** `doc/edit/` holds Emacs, Sublime Text, Vim and VS Code support (highlighting, delimiter matching, reindent, table alignment, a language server); `doc/edit/README.md`. - **Containers:** `akopra/klammertext:latest` (and `:haskell`) hold the three commands, the SKS, TeX Live and the default fonts; `doc/linux_container_install.md`, `doc/macos_container_install.md`. - **Environment:** `KLAMMERTEXT_HOME` (the tree), `KLAMMERTEXT_FONTS`, `KLAMMERTEXT_KLAMMERSETS` (colon-separated search paths); `env/runtime.env` sets them when sourced.