Version: 1.0 (informal)
Based on: Zig's std.zon as of Zig 0.16
ZON (Zig Object Notation) is a text format for structured data. A ZON file contains a single expression encoded as UTF-8 text.
Syntax is described in EBNF:
(* Top-level *)
start = expr ;
(* Expressions *)
expr = struct_init | enum_literal
| string | multiline_string
| number | bool | null | char_literal ;
(* Structs *)
struct_init = "." "{" struct_body "}" | "." "{" "}" ;
struct_body = keyed_body | positional_body ;
keyed_body = field_init ("," field_init)* ","? ;
positional_body = expr ("," expr)* ","? ;
field_init = "." identifier "=" expr ;
(* Named values *)
enum_literal = "." identifier ;
identifier = PLAIN_ID | QUOTED_ID ;
(* Text values *)
string = STRING_SINGLE ;
multiline_string = STRING_MULTI+ ;
(* Scalar values *)
bool = "true" | "false" ;
null = "null" ;
char_literal = CHAR_LIT ;
(* Numeric values *)
number = FLOAT_LIT | INT_LIT
| "-" FLOAT_LIT | "-" INT_LIT
| "nan" | "inf" | "-" "inf" ;
A struct body is keyed if it begins with . followed by an identifier then
=. Otherwise it is positional. Mixing named and positional elements in
the same struct is not allowed.
Whitespace (spaces U+0020, tabs U+0009, newlines U+000A) separates
tokens and is otherwise ignored.
Comments begin with // and extend to the end of the line (U+000A or EOF).
They are treated as whitespace.
PLAIN_ID = [a-zA-Z_] [a-zA-Z0-9_]* ;
QUOTED_ID = "@" STRING_SINGLE ;
A plain identifier matches the regex [a-zA-Z_][a-zA-Z0-9_]*. A quoted
identifier is an @ followed by a string literal — the string is unescaped
to produce the identifier value.
STRING_SINGLE = '"' (escape | [^"\\])* '"' ;
STRING_MULTI = "\\\\" [^\n]* ;
CHAR_LIT = "'" (escape | [^'\\]) "'" ;
STRING_MULTI starts with \\ and captures the rest of the line as literal
text — no escape processing is applied. Consecutive STRING_MULTI tokens
are joined with newlines.
Valid inside STRING_SINGLE and CHAR_LIT:
| Sequence | Code point |
|---|---|
\n |
U+000A (newline) |
\r |
U+000D (carriage return) |
\t |
U+0009 (tab) |
\\ |
U+005C (backslash) |
\' |
U+0027 (single quote) |
\" |
U+0022 (double quote) |
\xNN |
U+0000–U+00FF (2 hex digits) |
\u{NN…} |
U+0000–U+10FFFF (1+ hex digits in braces) |
Unrecognised escape sequences are kept as-is.
INT_LIT = HEX_INT | OCT_INT | BIN_INT | DEC_INT ;
HEX_INT = "0x" | "0X" hex_digit (hex_digit | "_")* ;
OCT_INT = "0o" | "0O" oct_digit (oct_digit | "_")* ;
BIN_INT = "0b" | "0B" bin_digit (bin_digit | "_")* ;
DEC_INT = dec_digit (dec_digit | "_")* ;
Where: hex_digit ∈ [0-9a-fA-F], oct_digit ∈ [0-7],
bin_digit ∈ {0, 1}, dec_digit ∈ [0-9].
FLOAT_LIT = HEX_FLOAT_FRAC | HEX_FLOAT_EXP
| DEC_FLOAT_FRAC | DEC_FLOAT_EXP ;
HEX_FLOAT_FRAC = ("0x" | "0X") hex_digit+ "." hex_digit+ exponent_hex? ;
HEX_FLOAT_EXP = ("0x" | "0X") hex_digit+ exponent_hex ;
DEC_FLOAT_FRAC = dec_digit+ "." dec_digit+ exponent_dec? ;
DEC_FLOAT_EXP = dec_digit+ exponent_dec ;
exponent_hex = ("p" | "P") ("-" | "+")? dec_digit+ ;
exponent_dec = ("e" | "E") ("-" | "+")? dec_digit+ ;
Underscores (_) are allowed wherever a digit is expected in any numeric
literal and are ignored during parsing.
.— Dot{— Left brace}— Right brace,— Comma=— Equals@— At (quoted identifier prefix)-— Minus (prefix negation)
The following words are reserved keywords and cannot be used as plain identifiers:
true false null nan inf
Each valid expression evaluates to one of the following types.
true and false.
null — absence of a value.
An arbitrary-precision whole number. May be negated with the - prefix.
-0 is valid.
An IEEE 754 floating-point value. Three special values exist:
| Expression | IEEE 754 value |
|---|---|
nan |
Not-a-number (NaN) |
inf |
+∞ |
-inf |
−∞ |
A sequence of Unicode code points. Escape sequences (see §2.3) are processed
during parsing. Multi-line strings (STRING_MULTI) are literal — no escape
processing.
A single Unicode code point, represented as its numeric value. Supports the same escape sequences as strings.
A dot (.) followed by an identifier. A self-contained value with no
associated data, typically used to represent a choice from a fixed set.
.linux .ReleaseFast .@"kebab-case"
A container of multiple values enclosed in .{ }:
- Positional (array-like): values listed in order, comma-separated.
Trailing commas are allowed:
.{ 1, 2, 3, }. - Keyed (object-like): each value is preceded by
.name =. The name is an identifier (plain or quoted).
An empty struct is .{ }.
A recommended parse pipeline:
- Lex: scan the source text into a token stream. Skip whitespace and comments. Match keywords before identifiers (keywords are reserved).
- Parse: consume tokens according to the grammar in §1. Use recursive descent or a comparable algorithm.
- Unescape: process escape sequences in strings, characters, and quoted identifiers.
- Convert: parse numeric literals — detect base from prefix, strip underscores, convert to the target numeric representation.
Errors should include:
- Byte offset from the start of input
- Line number (1-based) and column number (1-based)
- A message describing the problem
- The offending source line (when available)
Common error conditions:
- Unexpected character — character does not start any valid token.
- Unexpected token — token does not follow the grammar at this position.
- Expected expression — a value was required but not found.
- Expected identifier — an identifier was required but not found.
-nan— must usenanalone;-nanis not a valid literal.
- Quoted identifiers (
@"…") that contain null bytes (U+0000) should be rejected by a conforming parser. - Mixing keyed and positional elements in a single struct is a syntax error.
- The expression
-nanis not valid — usenanfor the NaN value.
See the Zig documentation.