Crates.io | eon |
lib.rs | eon |
version | 0.2.0 |
created_at | 2020-03-09 19:14:31.312387+00 |
updated_at | 2025-08-01 10:05:38.023373+00 |
description | Use the Eon config format with serde |
homepage | https://github.com/emilk/eon |
repository | https://github.com/emilk/eon |
max_upload_size | |
id | 216959 |
size | 117,924 |
This repository contains the definition of Eon, a simple config format designed for human editing.
Eon uses the .eon
file ending.
Eon is aimed to be a replacement for Toml and Yaml.
This repository also contains a Rust crate eon
for using Eon with serde
, and a eonfmt
binary for formatting Eon files.
// Comment
string: "Hello Eon!"
list: [1, 2, 3]
map: {
boolean: true
literal_string: 'Can contain \ and "quotes"'
}
any_map: {
42: "This key is an integer"
[]: "This key is an empty list"
}
hex: 0xdead_beef
special_numbers: [+inf, -inf, +nan]
// Strings come in four flavors:
basic_strings: [
"I'm a string."
"I contain \"quotes\"."
"Newline:\nUnicode: \u{262E} (☮)"
]
multiline_basic_strings: [
"""\
It was the best of strings.
It was the worst of strings."""
// The above is equivalent to:
"It was the best of strings.\n\t\tIt was the worst of strings."
]
literal_strings: {
// What you see is what you get:
windows_path: 'C:\System32\foo.dll'
quotes: 'I use "quotes" in this string'
regex: '[+\-0-9\.][0-9a-zA-Z\.+\-_]*'
}
multiline_literal_strings: {
python: '''
# The first newline is ignored, but everything else is kept
def main():
print('Hello world!')
'''
}
Familiar: strongly resembles JSON
Clean: forgoes unnecessary commas and quotes
Powerful: supports arbitrary map keys and sum types (e.g. Rust enums)
{}
// Comments
Special floats: +inf
, -inf
, +nan
Named sum-type variants
You can format any Eon file using the eonfmt
binary
cargo install eonfmt
eonfmt *.eon
I wanted a format designed for human eyes with
{ }
and [ ]
(like JSON, C, Rust, …). Rules out YAML and TOML.{ }
wrapping the whole file. Rules out JSON5, RON, and others.JSON5 is almost great, but requires wrapping the whole file in an extra { }
block, and indent that. That's too ugly for me.
It also has a bunch of unnecessary commas between values.
RON has the same problem.
The goal of RON is to perfectly map the Rust datatypes, which is cool, but it means it leans more towards being verobse and complex while Eon wants to be lean and simple.
Toml is a very nice and clean language. However, unlike almost every other programming language known, it does not use any indentation to aid the reader, leading to very confusing hierarchies. This means that when (visually) scanning a Toml document it's hard to figure out where one section starts and another ends.
It also means Toml is a bad candidate whenever you have deeply nested structures.
The [[array.of.tables]]
syntax is also quite confusing to newcomers.
Yaml is clean, but over-complicated, inconsistent, and filled with foot guns. It is known.
The Eon language (and library) are designed for config files that humans edit by hand.
There is nothing in the Eon spec that would not make it as fast (or as slow, depending on your perspective) as JSON, but the library has not been optimized for performance (no crazy SIMD stuff etc). It still parses a chunky 1MB file in under 10ms on an M3 MacBook.
I would not recommend using Eon as a data transfer format. For that, use a binary format (like MsgPack or protobuffs), or JSON (which has optimized parser/formatters for every programming language).
Future work, which I may or may not get to (contributions welcome!)
Add special types?
Hex floats?
An Eon document is always encoded as UTF-8.
A document can be one of:
42
or {foo: 1337, bar: 32}
foo: 42, bar: 32
(this is syntactic sugar so you don't have to wrap the document in {}
)32 46 12
(useful for a stream of values, e.g. like ndjson)Commas are optional in Eon, so [1, 2, 3]
is the same as [1 2 3]
.
By convention, commas are included when multiple values are on the same line, but omitted for multi-line maps and lists.
Whitespace is not significant (other than as a token separator).
By convention, we indent everything wrapped in []
, {}
, ()
.
Comments are prefixed with //
.
An Eon value is one of:
null
A special null
value, signifying nothingness.
true
or false
.
Numbers in Eon can have an optional sign (+
or -
) followed by either:
42
)0xbeef
, case insensitive)0b0101
)3.14
, 6.022e23
etc)Numbers can use _
anywhere in them as a visual separator, e.g. 123_456
or 0xdead_beef
.
There is not explicit bounds on the precision and magnitude of supported numbers in an Eon file,
but the eon
crate is currently only support integers in the [-2^127, 2^128)
range, and decimals are limited to the precision of f64
.
Eon also support the special values:
+nan
: IEEE 754 NaN
+inf
: positive infinity-inf
: negative infinityNote that these special values MUST be prefixed with a sign (they are not keywords like true/false/null
are).
Text in Eon comes in four flavors:
"basic string"
"""multiline basic string"""
'literal string'
'''multiline literal string'''
"Basic strings"
Basic strings uses double-quoted, and can contain escape sequences:
"I'm a string."
"I contain \"quotes\"."
"Newline:\nUnicode: \u{262E} (☮)"
"""Multi-line basic strings"""
When you have long text it can be useful to have a string span multiple lines, using triple-quotes.
Escape sequences still work, and a line ending with \
removes the newline and any whitespace at the start of the next line.
This means these two strings are equivalent:
str1: """\
It was the best of strings.
It was the worst of strings."""
str2: "It was the best of strings.\n\tIt was the worst of strings."
'Literal strings'
Literal strings uses single quotes, and no escaping is performed. What you see is exactly what you get:
windows_path: 'C:\System32\foo.dll'
quotes: 'I use "quotes" in this string'
regex: '[+\-0-9\.][0-9a-zA-Z\.+\-_]*'
There is no way to put a single quote inside of a literal strings, but you can in a…
'''Multi-line literal strings'''
A multi-line literal string uses three single quotes. Within that, everything is taken verbatim, except that the very first newline (if any) is ignored:
quote_re: '''(["'])[^"']*$1'''
python: '''
# The first newline is ignored, but everything else is kept
def main():
print('Hello world!')
'''
Lists are written as [ … ]
, with optional commas between values.
Usually the commas are omitted for lists that span multiple lines,
and included for lists that are on a single line.
long_list: [
2
3
5
7
11
]
short_list: [1, 2, 3]
also_fine: [
1,
2,
3,
]
very_terse: [1, 2, 3]
A list can contain any Eon value (including other lists).
Maps are written as { key: value, … }
, again with optional commas between key-value pairs.
Usually the commas are omitted for maps that span multiple lines,
and included for maps that are on a single line.
Maps are used to represent either a record type (like a struct
) or a table type (e.g. a hash map).
For instance, say you have a hash map for looking up country code based on the name of the country. This can be serialized to Eon as:
country_codes: {
"United States": 1
"France": 33
"United Kingdom": 44
"Sweden": 46
"Germany": 49
"Australia": 61
"Japan": 81
"China": 86
"India": 91
}
Both keys and values of an Eon map can be any Eon value. This is significantly different from e.g. JSON, where keys can only be strings. So, a reverse map (from code to country name) can we written as:
country_from_code: {
1: "United States"
33: "France"
44: "United Kingdom"
46: "Sweden"
49: "Germany"
61: "Australia"
81: "Japan"
86: "China"
91: "India"
}
You can even use other maps as keys:
complex_map: {
{"foo": 42, "bar": "mushroom"}: "Yes, this is fine"
}
You are allowed to omit the quotes around map keys (NOT values!) when the keys are identifiers.
An identifier is defined in Eon as any string matching the regex [a-zA-Z_][a-zA-Z0-9_]*
.
This definition matches most programming languages (e.g. what is allowed as a variable name in C).
This means these two are equivalent:
explicit: {
"a": 1
"b": 2
}
same: {
a: 1
b: 2
}
The convention is to use quotes for general strings (e.g. when representing a HashMap<String, …>
and to use identifiers when representing struct fields.
Examples of what is and isn't allowed:
snake_case: true // OK!
kebab-case: false // ERROR! 'kebab-case' is not an identifier, and must be quoted
"kebab-case": true // OK!
string: Hello // ERROR! "Hello" should be in quotes
key: null // OK! 'null' is a special value (it's NOT a string)
42: "forty-two" // OK! Maps the integer 42 to a string
"42": "forty-two" // OK! Maps the string "42" to a string (which is different from the above!)
true: "confusing" // ⚠️ Confusing, but OK. Uses a boolean as key (not a string!).
"true": "fine" // OK! Uses the string "true" as key
null/true/false
as map keysThe last two lines in the above example show that you need to be careful when using key names that matches one of the three keywords (true/false/null
). This is is a (small) footgun in Eon, but hopefully these key names are rare (they are already forbidden identifiers in many programming languages).
Alternative designs I've considered:
struct
). Con: can't serialize a HashMap<String, …>
.null/true/false
to something else (e.g. %null
, %true
, %false
). Would be both surprising and ugly.null/true/false
as map keys. Con: can't serialize HashMap<bool, …>
or a general HashMap<Value, …>
. Feels arbitrary.Let's first consider a simple enum
, like one you would find in C or Java:
enum Side {
Left,
Middle,
Right,
}
The variants are encoded as strings in Eon, e.g.
side: "Middle"
In other words, there is no difference between a string and an enum variant, as far as Eon is concerned.
Now consider this more complicated Rust enum
:
enum Color {
Black,
Gray(u8),
Hsl(u8, u8, u8),
Rgb { r: u8, g: u8, b: u8 },
}
Here a simple string will not suffice, as some of the enum variants contain data.
There are several competing techniques of encoding this in JSON (external tagging, internal tagging, adjaceny tagging, …) all with their own shortcomings.
In Eon, enum variants are written as "Variant"(data)
.
So different values for the above choice would be written as:
"Black"
(equivalent to "Black"()
)"Gray"(128)
"Hsl"(0, 100, 200)
"Rgb"({r: 255, g: 0, b: 0})
Why the quotes, and not just Black
, Gray(128)
, etc?
Consider this hypothetical Eon file:
color: Black
name: Emil
Is name
really a multiple-choice enum? Maybe. Maybe not.
But the Eon parser wouldn't know, so must accept it.
And this will leave the door open to weirdness where some strings need quotes and not others (and we'll end up similar to YAML).
So instead we explicitly forbid the above, and always require quotes for strings (…except for map keys that are identifiers).