Files
klammertext/doc/klammertext_for_llm.md
T
ackandClaude Fable 5.1 0ead40f272 Width and height argtypes, kdesc --argtypes from ref/, txt @define/@left_right/@columns, kdiag --check value validation (from dev d6993110e9b3)
Also: the klammertext argtype (descriptions kept as source), txt table
wrapping and :hpos/:offset, @c between ‹ ›, txt section headings, the
ktext newline-only trim, capitalize(), and the argtype test suite.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 16:59:49 +02:00

504 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 <name>` 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 '<text>'` shows the katoms and
their types; `kdiag --args '<text>'` shows how text splits into arguments;
`kdiag --rewrite '<text>'` 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 <name>` 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 <name>` — 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 <cells> <pos>; …` 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 `<row>(<col>) = <op> <operands>`, 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 <cells> 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 `<name>/` with `index.html`, `css/`, `fonts/`, `js/` | self-contained, fonts localized; `book` structure adds navigation, search |
| `tex` | `<name>.tex` | XeLaTeX source; fonts via fontspec from the font store |
| `pdf` | `<name>.pdf` | `tex` plus a xelatex run (TeX Live required) |
| `txt` | `<name>.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 <dir>`
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 :: <span>*arg1*</span> @@
@@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 <klammer>`
against what was written; then `kdiag --type '<fragment>'` 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.