Skip to content

Instantly share code, notes, and snippets.

@sir-wabbit
Last active August 27, 2026 01:39
Show Gist options
  • Select an option

  • Save sir-wabbit/aa5b85c23a9b9e40ea01a7c06c1921ef to your computer and use it in GitHub Desktop.

Select an option

Save sir-wabbit/aa5b85c23a9b9e40ea01a7c06c1921ef to your computer and use it in GitHub Desktop.

Mu, in one page

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.

The forms

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.

Lexing, briefly

  • 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).

Strings

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.

One map gotcha

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}.

The configuration convention

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   ^ named

Arguments 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment