In general, data is structured as a key
followed by a value
.
Description | Example | |
---|---|---|
Key | Any value in the key position. |
my-key "my value" |
Value | Any text that is not a key or comment . |
"value" |
Unit | Denotes the absence of a value. | my-key null |
Boolean | A true or false value. |
is-boolean true |
Integer | A whole number value. | life 42 |
Float | Any number with a decimal. | pi 3.14 |
String | Any text surrounded by double quotes. | hello "world" |
List | A collection of values. | some-list [1 "two"] |
Dictionary | A collection of key-value pairs. | my-dict { hello "world" } |
Expression | An evaluatable expression. | (+ 1 1) |
Line comment | A comment for a line. | // comment |
Block comment | A multi-line comment. | /* comment */ |
Keys are always treated as Strings during parsing. Keys do not require double quotes, however double quotes can be included for readability. Double quotes are required for keys that include any whitespace character, although whitespace in keys should be avoided whenever possible.
hello
"hello"
- The double quotes will be ignored during parsing
- The double quotes will not be preserved when writing
"hello world"
- The double quotes will be ignored during parsing
- The double quotes will be preserved when writing
"\"hello\""
- The inner, escaped double quotes will be parsed
- The inner and outer double quotes will be preserved when writing
" "
- The double quotes will be ignored during parsing
- The double quotes will be preserved when writing
1
- All text in the
key
position will be treated as a String
- All text in the
{}
- A Dictionary can never exist in the
key
position unless it is surrounded by double quotes
- A Dictionary can never exist in the
[]
- A List can never exist in the
key
position unless it is surrounded by double quotes
- A List can never exist in the
()
- An Expression can never exist in the
key
position unless it is surrounded by double quotes
- An Expression can never exist in the
A value is any of the following:
A unit value that represents the absence of a value denoted by the text null
.
This is useful for cases where only the key
is important.
my-key null
my-dict {
person-a null
person-b null
}
A true or false value denoted by true
or false
. true
and false
are
case-sensitive.
is-boolean true
is-string false
some-bools [
true
false
false
true
]
A whole number. An integer cannot contain any decimals otherwise it will be treated as a Float. Integers can contain underscores for readability, however the underscores will not be preserved when writing.
my-int 1
other-int 100_000
A floating point number. A float must contain a decimal in order to denote it as a float. Floats can contain underscores for readability, however the underscores will not be preserved when writing.
Tot makes no guarantees about floating point error when reading or writing.
my-float 1.0
other-float 1.
decimal-only .1
bigger-float 100_000.01
Text surrounded by double quotes. Only double quotes are allowed to denote Strings. Escaped characters are handled as appropriate.
my-string "hello"
escaped "\"hello\" world\n\t"
empty-string ""
A collection of values. Keys are not allowed. Unit values are ignored and are not preserved when writing.
Newlines are encouraged for readability and are inserted by default when writing.
Commas are not required but can be used for readability. Commas will be ignored when writing.
The entire file can be denoted as a list if the entire file is wrapped in
"[
" and "]
" characters.
int-list [
1, // The comma will be ignored when reading and writing
2
3
]
mixed-list [
1
false
"hello"
]
// This will be expanded when writing
compact-list [true false]
nested-list [
[
1
]
[
2
[
3
]
]
{
key "value"
}
(+ 1 1)
]
[
"this entire file"
"is a list"
{
msg "key value pairs must be placed inside dictionaries"
continued "key value pairs outside of the list will break parsing"
}
]
A collection of key-value pairs. By default, tot
files are considered to be
Dictionaries.
Newlines are encouraged for readability and are inserted by default when writing.
Commas are not required but can be used for readability. Commas will be ignored when writing.
tot
files should not wrap the entire file in "{
" and "}
" characters. If
they are present, this will break parsing.
my-key "my-value"
my-dict {
name "tot"
}
other-dict {
name "tot" // Keys do not clash since they are in different dictionaries
}
nested {
inner {
name "tot"
}
}
commas {
unit null, // Commas are ignored and are not preserved
hello "world"
}
// Dictionaries will be expanded when writing
compact {key "value" int 1 bool true}
A limited, deterministic, non-Turing-complete Lisp usable in Tot.
Expressions are not preserved when writing tot
files.
All expressions will return a Value. Varargs are not supported. Unless specified otherwise, all values in an expression must be of the same type.
(+ value1 value2)
Add two values together.
- Integer
- Float
- String
(+ 1 1)
==2
(+ 1 (+ 1 1))
==3
(+ "hello " "world")
=="hello world"
(- value1 value2)
Subtract value2
from value1
.
- Integer
- Float
(- 1 1)
==0
(- 1 (- 1 1))
==1
(* value1 value2)
Multiply two values together.
- Integer
- Float
(* 2 2)
==4
(* 2 (* 10 1))
==20
(/ value1 value2)
Divide value1
by value2
.
- Integer
- Float
(/ 4 2)
==2
(/ 10 (/ 4 2))
==5
(& key-path)
Looks up the value at key-path
by walking the key
s and taking the
value
at the final key
.
When referring to List values, indexing starts at 0.
All value
s are valid for references.
author "me :)"
version {
major 1
minor 10
patch 100
}
favorite-ints [
2
100
]
nested [
{
secret "potato"
}
]
app-config {
min-patch-version (& version patch) // Resolves to 100
primary-maintainer (& author) // Resolves to "me :)"
some-int (& favorite-ints 0) // Resolves to 2
favorite-food (& nested 0 secret) // Resolves to "potato"
}
Generators are a shortcut for creating lots of data. Generators must be defined at the root level of the file and are invalid when defined in Tot List files.
(gen name [params] value)
gen
is a required keywordname
can be whatever the developer defines- Generator names cannot collide with other built-in expressions
params
is a list of parameter names that can be used when calculatingvalue
value
is what is returned by the generator and must be a Value
Generators are invoked via the defined name
and passing parameters as
necessary. Generators cannot be invoked inside other generators, but the
output of a generator can be passed as a parameter to another generator.
Generators are:
- Shorthand for writing config files
- Simple to write and understand
Generators are not:
- Logic
- Meant to be complicated
If a generator starts to get large and unwieldy, it might be wise to rethink how an application should be handling configs.
Simple:
(gen simple [] 1)
// Resolves to: my-integer 1
my-integer (simple)
No params:
// This could easily be a reference expression, but this is also valid
(gen version [] {
major 1
minor 0
patch 0
})
// Resolves to: config-version {major 1 minor 0 patch 0}
config-version (version)
server1 {
name "east"
software-version (version)
}
server2 {
name "west"
software-version (version)
}
With params:
(gen version [patch] {
major 1
minor 0
patch patch
})
blue {
status "active"
// Resolves to: version {major 1 minor 0 patch 0}
version (version 0)
}
green {
status "inactive"
// Resolves to: version {major 1 minor 0 patch 1}
version (version 1)
}
Inner expression:
(gen square [x] (* x x))
// Resolves to: 4squared 16
4squared (square 4)
Dictionary parameter:
(gen version [v1 v2 v3] {
major v1
minor v2
patch v3
})
(gen server [name env version] {
name name
environment env
version version
})
server-list [
/*
Resolves to:
{
name "dev1"
environment "dev"
version {
major 1
minor 0
patch 0
}
}
*/
(server "dev1" "dev" (version 1 0 0))
(server "dev2" "dev" (version 1 2 3))
(server "qa1" "qa" (version 1 0 0))
]
Nested expression:
(gen esteemed [name] (+ "The esteemed " (+ name " has arrived!")))
// Resolves to: maintainer "The esteemed maintainer has arrived!"
maintainer (esteemed "maintainer")