Wit

Syntax reference

Every form Wit 0.3 accepts, grouped by job. Each block below builds as written. The normative text is the spec; the node set is in core vocabulary.

Prose

WriteGet
a blank linea new paragraph; a single line break is part of the text
_word_italic
*word*bold
~ note at line startline comment; a bare ~ line is empty
~~ note ~~/block comment, works mid-line
\ + punctuationthat character, literally; before a letter, digit or space it is a plain backslash

A mark needs a non-word character outside and a non-space character inside, so 5*6*7 and snake_case stay prose. Any marker no construct can use stays text: emails, C#, URLs with #, (see figure 2), !! at the end of a sentence.

source
This is _italic_ and *bold*. 5*6*7 stays as typed.
~ a note to self
Escape \@name, \#tag, \*stars\* and \(if \@x).
result

This is italic and bold. 5*6*7 stays as typed. Escape @name, #tag, *stars* and (if @x).

Using nodes

FormMeaning
@namebare reference — inserts a definition; also a closeless use
@name … name@use with a body; own lines → block, inside a sentence → inline
(name … name)parenthesis form — the same use, easier to type
@name(k: v, k2: v2)parameters, glued to the name
@name(v1, v2)positional values — needs a definition header
@name(…): … !!value-block use — !! alone on its line closes it
H@sub 2 sub@Oa known node glued to a word
@x.field.0data access
#tip(label)
::label:: ::body::
tip#

(h2 The Keeper h2)

She crossed the @em last em@ threshold — see (a(href: https://x.org) the log a).

@img(src: lamp.png, alt: Note: a ruler, 12 inches long)

@aside
Fresnel lenses rarely fail.
aside@

@tip(label: Tip:):
Keep the wick trimmed.
!!

Water is H@sub 2 sub@O.

Parameters

  • Parens attach with no space: @img(src: a.png). A space before ( makes it prose.
  • key: value pairs, comma-separated. A value runs until the next key: the node declares, so it may hold commas and colons. No quoting.
  • Parentheses inside a value balance, so URLs keep theirs.
  • One pair per line is allowed; commas are then optional. A blank line before ) raises E_UNCLOSED_PAREN.
  • Positional values come first and bind in header order; a positional value cannot hold a comma.
  • A use's body is always content, never parameters.
#cite(author, year)
::author:: (::year::)
cite#

@img(
  src: lamp.png
  alt: The second-order Fresnel lens
)

As @cite(Weil, 1952) argued, and @cite(author: Berger, year: 1972) agreed.

When a use needs a close

UseClose
a definition without ::body::none — closing it is E_BODY_WITHOUT_SLOT
void core names br, hr, imgnone
a definition with ::body::, or a core container, opening a blockrequired — else E_UNCLOSED_NODE
the same, inside a sentenceoptional; without it the use is empty

A close pairs with the nearest open of the same name. The parenthesis form opens only when a known node name and a space, line break or its params follow (, and its name) sits in the same paragraph; (a) first item stays prose.

Defining nodes

FormMeaning
#name … name#block definition
#name(a, literal b)header: parameter order; literal values are used as written
::a::insert a parameter
::body::where the use's body goes; body is reserved
#name: value !!single-line definition; the value may span lines up to !!
#name(a): … ::a:: … !!single-line definition with a header
+#name: entry !!add an entry to the list name

Without a header, parameters are inferred from the ::name:: placeholders and passed by name. A definition cannot sit inside (if …) or (each …) (E_CONDITIONAL_DEFINITION). ... is ordinary prose everywhere.

source
#chapter(number, title)
@h2 ::number:: — ::title:: h2@
::body::
chapter#

#link(literal href, label)
@a(href: ::href::) ::label:: a@
link#

#year: 1923 !!
#epigraph:
The sea does not forgive forgetting.
!!

@chapter(I, The Keeper)
In @year, @link(href: https://x.org/a_b, label: the _log_) began.
chapter@

@epigraph
result

I — The Keeper

In 1923, the log began.

The sea does not forgive forgetting.

Entry lists

+#name entries are collected from every file, in the order met, each once. Render them with a loop.

source
@ol
(each @sources as s)
@li @s li@
(end)
ol@

+#sources: Weil, S. (1952). _Gravity and Grace_. !!
+#sources: Berger, J. (1972). _Ways of Seeing_. !!
result
  1. Weil, S. (1952). Gravity and Grace.
  2. Berger, J. (1972). Ways of Seeing.

Data

FormMeaning
{ k: v, k2: v2 }record; a key may hold spaces, the first : ends it
[ a, b ]collection of values or records
#name: + k: v lines + !!form-fill record definition; lowercase keys
42 0.050number, rendered as written
true false nullboolean, null
"Came, H."quoted — always text
@x.a.0dot path; numbers index from 0; keys match loosely

A record value runs until the next key:, so it may hold commas. Records and collections may span lines, commas then optional, and nest. index, count and last are reserved field names (E_RESERVED_FIELD).

source
#keeper: {
  name: Aldous Vane
  years at post: 31
  history: { incidents: 2 }
} !!
#weil:
  author: Weil, S.
  year: 1952
!!
#themes: [ attention, perception ] !!

@keeper.name served @keeper.years_at_post years (@keeper.history.incidents
incidents). @weil.author (@weil.year). First theme: @themes.0.
result

Aldous Vane served 31 years (2 incidents). Weil, S. (1952). First theme: attention.

Logic

FormMeaning
(if @x) … (end)truthy: defined, non-empty, not false, null or 0
(if @x is v) … (else) … (end)comparison
(each @xs as x) … (end)repeat per item
@x.index @x.count @x.lastloop position (from 0), list length, last item
(if ::p::)test a parameter inside a definition

Statements written inside a line stay inline. A glued ( starting with if, each, else or end is a statement, not parameters. Every statement ends with (end).

source
#book: { status: draft } !!
#authors: [ Smith, Brown, Jones ] !!

(if @book.status is draft)
Draft — do not distribute.
(else)
Final.
(end)

By (each @authors as a)@a(if @a.last)(else), (end)(end).
result

Draft — do not distribute.

By Smith, Brown, Jones.

Raw, scripts, files

FormMeaning
@@name … name@@verbatim body; {{path}} holes still fill
@@@name … name@@@frozen body — nothing expands
@node(type: x, k: v) … node@opaque pass-through for custom renderers
<% expr %>JavaScript; inline, the value is inserted
@scriptCall(fn, arg)call a script function
reference ./path.witpull in another file's definitions and entries
@load(as: rows) alias load@external data — see config

Raw node names: code, pre, math, mathblock, diagram, style, script. A reference line needs a path that starts with ./, ../, / or ~/, or ends in .wit; otherwise it is prose.

source
#theme: { accent: teal } !!
#paper: { words: 4200 } !!

@@style .lead { color: {{theme.accent}} } style@@

The paper has <% lh.data.paper.words %> words.
result

The paper has 4200 words.

Removed in 0.3

wit migrate <file|dir> -w rewrites every form below. Without -w it prints one file's result, or lists the files that need it.

0.2 form0.3 formOld form now gives
@x |k v|, |k - v|, |flag!|@x(k: v), @x(flag: true)E_PIPE_SYNTAX_REMOVED
#x ||a, b|| capture list#x(a, b) headerE_PIPE_SYNTAX_REMOVED
@x { k: v } call@x(k: v)E_REMOVED_SYNTAX
@x(k v) space-separated@x(k: v)E_REMOVED_SYNTAX
@x k:v body x@ colon-scatter@x(k: v) body x@prose
form-fill use body (k: v lines)@x(k: v) + bodyprose
... body slot::body::prose
{ k - v }{ k: v }E_MALFORMED_RECORD
(if @x equals v)(if @x is v)E_MALFORMED_STATEMENT
mid-body pipe switchingchild nodes or closeless markers, e.g. @cue(mood: tense)—

See also

Cheatsheet · Core vocabulary · Data model · Errors · Gotchas.