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>
26 KiB
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--flagis^-^-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 formname@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 valueis an optional argument; it may appear anywhere in the list. The value runs to the next|or:option.:namewritten alone takes the argument type's "alone" value — for a boolean that istrue, so:numbermeans:number true;@table :hlinemeans: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
-onames a directory and/or basename. String-only input writes to the current directory. -s,-t,-otake exactly one argument (quote spaces).--klammersetstakes 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 :structureisplain(default; no TOC),article(in-page TOC),book(cover, navigation panel in html; chapters, recto/verso in pdf). Headings@s1… are\chapterin a book and\sectionotherwise.- Role typefaces:
:serif_typeface,:sans_typeface,:mono_typeface(family name or store directory name, e.g.EB Garamondoreb-garamond);:text_scalescales all text;:point_size,:leading,:paper_size,:landscape,:two_column.kdesc -k documentlists all forty. - Outside a
@documentnothing 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 falseotherwise); header style:header_style iby default;:style r i bper column. :column_widthper column:fit(widest entry),fill(the remaining width, capped at the widest entry), a fraction of the text width (.2), or*(the remainder).fillmay not combine with a fraction or*.:cell_hpos l c rper column;:justify <cells> <pos>; …per cell.- Lines: default under the header and at the bottom;
:hline/:vlinereplace them (all,none, or indexed ranges);:gridfor all lines. :colspan/:rowspanwith the indexed-range syntax;:allow_break truemakes 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.mdis the gallery.:format <cells> module.functionformats cells with user Python.:decimal commafor comma decimals.- Placement:
:hpos left|center|right|none,:offset 2em(inset from that margin); a caption goes above a table (:caption_side topdefault), below a figure.:number falsefor 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* @@
.kdeclares 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; unitsem pt px,w pwfor widths,h phfor heights; the axes are checked),literal(raw content, named close required),rest(everything left, bars included;rest(2)= list of lists), and any@@@argtypethe klammerset declares (kdesc --argtypes). An argtype may carry:defaultand: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.kparameter list. A klammer application inside a parameter list is an error. @evalis the primitive behind every non-trivial klammer:@eval python_expr @(module.function auto-imports;Kholds the arguments),@eval :cpp lib.function @,@eval :shell cmd @,@eval :haskell src @;:cwd DIRruns 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_applyphase. - Klammersets:
@@@klammerset sym | description :requires sks :files a.k b.k @@@insym/sym.k; loaded withktext --klammersets sym doc.kt(symbols only, resolved in the document's directory, thenKLAMMERTEXT_KLAMMERSETS, then the distribution; several combine in order, later definitions deciding collisions via the separators above).kdesc -i file.k -kshows what a file defines against the SKS.--klammersets refadds the reflection klammers (@diacritics_examples@,@unicode_examples@, …) that generate the reference tableskdesc -c/-uprint.
9. Behaviour worth knowing before you debug something
@condis strict about truth. True:true,True,1. False:false,False,0, empty. Anything else is an error, not "probably false".@condis resolved at application time and evaluates only the branch it selects. A@read/@evalin 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
@condbranch does not register — in either branch. Define outside and select between applications. @eval :shelland:haskell: stdout becomes document text; stderr does not (shown under-v 1); a nonzero exit is an error. Append|| trueif 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/@codenothing 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. @evalresults 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 falsedisables 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). Soktext doc.kt -t txt -d | …is safe; nothing else lands in the document. -v 1reports every decision the user could not read off their input: which klammerset loaded and from where, which file a typeface or:filesname 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_COLORdisables it. kdesc's-c/-urender throughansion a terminal andtxtin 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
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.pyconverts a.mdfile; 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.mdrenders 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.envsets them when sourced.