Mu is small, Unicode, S-expression-ish configuration data. The core language only describes structure; a schema decides what that structure means.
(service "api"
:ports [8080 8443]
:env {mode: prod, tracing: true})A document contains zero or more expressions, in order.
| Form | Example | Core value |
|---|---|---|
| Atom | foo, :port, true |
Uninterpreted text |
| String | "hello", 'hello' |
Text |
| Raw string | #"text", #tag"text"tag |
Unescaped text |
| Integer | 42, -1_000 |
Integer |
| Real | 1.5, 1., 1e3, 50% |
Floating-point number |
| Rational | -20/30 |
Reduced fraction -2/3 |
| Group | (tag arg ...) |
Ordered expressions |
| Sequence | [value ...] |
Ordered expressions |
| Mapping | {key: value, ...} |
Ordered expression pairs |
Groups, sequences, and mappings may be empty. Mapping keys may be any expression, and duplicate keys remain distinct in the parsed tree.
- Any Unicode whitespace separates forms.
;starts a comment through the next LF or end of input.- Inside groups, sequences, and mappings, one optional comma may follow each item or field. A trailing comma is fine.
- No separator is required when the next delimiter makes the boundary clear:
a(b)[c]is three expressions. - Atom characters are Unicode alphanumerics plus
_.@/+-$%=!?*#&~^|<>:'. Tokens use maximal munch. - A leading
'starts a quoted string; a leading#starts a raw string.
Numbers use these exact shapes; digits permits internal underscores:
digit := "0" ... "9"
digits := digit | digit {digit | "_"} digit
sign := "+" | "-"
integer := [sign] digits
exp := ("e" | "E") [sign] digit {digit}
rational:= integer "/" digits denominator must not be zero
percent := integer ["." digits] "%"
real := integer "." [digits] [exp]
| integer ["." digits] exp
The entire token must match. Thus .5, 10h, and + are atoms. Rationals
are reduced, and percentages become reals (50% becomes 0.5).
Single- and double-quoted strings are equivalent and may span lines. Supported
escapes are \n, \t, \r, \0, \\, \', \", and \u{HEX}. An
unknown escape keeps its backslash.
A raw string is #TAG"TEXT"TAG, where TAG is zero or more alphanumerics.
There are no escapes; the first matching "TAG ends the string.
For a bare atom key, write either key: value or key : value. Because atoms
use maximal munch, key:value is one atom and is not a map field. A non-atom
key may touch its colon, as in {"key":value}.
At core level, (f x) does not call anything, atoms do not resolve, and
true, none, :name, and ${...} are ordinary atoms or text. Configuration
schemas conventionally read a group as a tagged record:
(service "api" :tracing true)
; ^ positional ^ namedArguments may be positional in field order or introduced by the atom
:field. A named marker needs a value. Fixed fields take one value; variadic
fields greedily take values up to the next marker and may be repeated. Unknown,
duplicate, missing, extra, or ambiguously positional arguments are errors.
After an out-of-order named field, the remaining arguments must also be named.