* fix: allow toml parsing of HEX value with repeating b characters * fix: allow toml parsing of HEX value with repeating b characters * toml: preserve invalid numeric rejection beside repeated hex digits --------- Co-authored-by: Alexander Medvednikov <alexander@medvednikov.com>
Description
toml is a fully fledged TOML v1.0.0 compatible parser written in pure V.
The module is tested against the official compliance tests.
Usage
Parsing files or strings containing TOML is easy.
Simply import the toml module and do:
doc1 := toml.parse_text(<string content>) or { panic(err) }
doc2 := toml.parse_file(<file path>) or { panic(err) }
Hexadecimal integers use the 0x prefix and allow repeated digits, including b:
color = 0xbbbbbb. Binary and octal literals still accept only digits valid for their base.
Parser AST and table views
toml.ast.DateTime keeps its public date and time components, including their
text and source positions, alongside the complete date-time text.
The public Parser.find_table, find_sub_table and find_in_table methods return
!&map[string]ast.Value. Each reference has a heap header sharing the table's storage.
Insertions, deletions and reservations through a view are visible through other views.
Growing the parent table does not invalidate the reference: it holds no pointer into
the parent's inline values. Keep shared table storage alive until all views are finished;
use .clone() when an independent table is needed.
Parser.double_array_of_tables_contents(target_key) returns a one-element
[]ast.Value containing the parsed table body. It leaves the following table header
available for the caller to continue parsing.
Example
Here we parse the official TOML example and print out some values.
import toml
const toml_text = '# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [ 8000, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# Indentation (tabs and/or spaces) is allowed but not required
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]'
fn main() {
doc := toml.parse_text(toml_text) or { panic(err) }
title := doc.value('title').string()
println('title: "${title}"')
ip := doc.value('servers.alpha.ip').string()
println('Server IP: "${ip}"')
}
Struct conversion
toml.decode[T] decodes TOML text into a struct and toml.encode[T] encodes a struct
as TOML. A field maps to the key of the same name; @[toml: <name>] changes the key
and @[skip] ignores the field. Fields missing from the document keep their default
values. A from_toml(toml.Any) or to_toml() string method on T replaces the
generic conversion.
Partial tables preserve the existing defaults inside a present optional struct.
When that optional field is none, a table initializes it from the struct's own defaults.
TOML has no struct embedding, so the fields of an embedded struct are read from and
written to the same table as the fields of the embedding struct. When decoding, a
table named after the embedded struct (e.g. [Db]) is accepted too and takes
precedence. Embedded toml.Date, toml.Time and toml.DateTime are scalars, stored
under their type name (e.g. Date = 2026-09-30).
Array elements and map values may be scalars, enums, structs, arrays or maps, at any
nesting depth, so []JobTitle, map[string][]int and [][]map[string]Item all decode.
An element that does not fit the element type, such as a number where a struct is
expected, is skipped. An enum element must be an integer, so a string, boolean,
float, array or table in an enum position is skipped rather than coerced; a
plain enum field still follows Any.int(), which reads a non-numeric value as
0.
import toml
struct Db {
host string
port int
}
struct Config {
Db
name string @[toml: app_name]
secret string @[skip]
}
config := toml.decode[Config]('app_name = "app"\nhost = "localhost"\nport = 5432')!
assert config.host == 'localhost' // promoted from Db
assert config.name == 'app'
encoded := toml.encode(config)
assert encoded == 'host = "localhost"\nport = 5432\napp_name = "app"'
assert toml.decode[Config](encoded)! == config
String values are encoded as TOML basic strings with quotes, backslashes, and control characters escaped. Non-ASCII text is preserved.
Map fields support string, boolean, and integer keys up to 64 bits. TOML keys are strings;
integer keys are parsed as decimal numbers with the destination type's range,
including full-width i64 and u64 keys. Invalid or out-of-range keys are skipped,
as are out-of-range narrow integer values. Boolean keys must be true or false.
Other map key types are rejected at compile time. Nested maps retain their own key
types.
Value retrieval
The toml module supports easy retrieval of values from
TOML documents by using a small and simple query syntax
as argument to the value() function.
Keys in map entries are denoted by . and array entries
uses [<int>]. Quoted keys are also supported by using
the delimiters " or '.
doc.value('table.array[0].a."b.c"')
To query for a value that might not be in the document you
can use the .default_to(...) function to provide a
default value.
For cases where a default value might not be appropriate or
to check if a value exists you can use doc.value_opt('query')!
instead.
import toml
const toml_text = '
val = true
[table]
array = [
{ a = "A" },
{ b = "B" }
]
'
doc := toml.parse_text(toml_text) or { panic(err) }
assert doc.value('val').bool() == true
assert doc.value('table.array[0].a').string() == 'A'
// Provides a default value
assert doc.value('non.existing').default_to(false).bool() == false
// Check if value exist
// doc.value_opt('should.exist') or { ... }
// or
if value := doc.value_opt('table.array[1].b') {
assert value.string() == 'B'
}
// You can pass parts of the TOML document around
// and still use .value()/.value_opt() to get the values
arr := doc.value('table.array')
assert arr.value('[1].b').string() == 'B'
Conversion
Any TOML value can be converted to a V equivalent type.
TOML values are represented as the toml.Any sum-type that
can be converted to a V type.
| TOML value | V conversion (via toml.Any) |
|---|---|
| val = "Basic string" | .string() |
| val = 'Literal string' | .string() |
| val = true | .bool() |
| val = 1979-05-27T07:32:00Z | .datetime() (toml.DateTime) |
| val = 1979-05-27 | .date() (toml.Date) |
| val = 07:32:59 | .time() (toml.Time) |
| val = 3.14 | .f32() / .f64() |
| val = 100 | .int() / .i64() / .u64() |
Read more about values in the TOML specification.
TOML to JSON
The toml.to module supports easy serialization of any TOML to JSON.
import toml
import toml.to
const toml_text = '
val = true
[table]
array = [
{ a = "A" },
{ b = "B" }
]
'
doc := toml.parse_text(toml_text) or { panic(err) }
assert to.json(doc) == '{ "val": true, "table": { "array": [ { "a": "A" }, { "b": "B" } ] } }'
toml.scanner.Scanner.peek(0) returns the byte at the current scanner position without
advancing it, or end_of_text when the position is at the end of the input.
Negative offsets look behind that position. An offset outside the input returns end_of_text.