Skip to content

A dead-simple, efficient, dependency-free TOML parser for Rust.

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

Bright-Shard/boml

Repository files navigation

BOML

A dead-simple, efficient, dependency-free TOML parser for Rust.

Special thanks to cubic and Speykious for their help while developing BOML!

Usage

Parsing

BOML requires no imports - just call boml::parse with the TOML source code, then use the returned Toml value similarly to a hashmap:

fn parse_cargo_toml() {
	let source = include_str!("../Cargo.toml");
	let toml = boml::parse(source).unwrap();

	// Get the package table from the `Cargo.toml` file
	let package = toml.get_table("package").unwrap();
}

Data Types

In the above snippet, we used get_table to read a value, because we knew the value was a table. BOML provides equivalent methods for every TOML type (get_string, get_integer, etc). However, BOML also provides a get method, which allows you to resolve the type yourself. For example:

use boml::prelude::TomlValue;

let source = include_str!("../Cargo.toml");
let toml = boml::parse(source).unwrap();

// Specific types via `.get_<type>`
let package = toml.get_table("package").unwrap();
let name = package.get_string("name").unwrap();
assert_eq!(name, "boml");

// Dynamic types via `.get`
let package_untyped = toml.get("package").unwrap();
// You can then convert the value to a specific type with helper methods or
// pattern matching:
let package = toml.get("package").unwrap().as_table().unwrap();
let name = package.get("name").unwrap().as_string().unwrap();
assert_eq!(name, "boml");

let Some(TomlValue::Table(package)) = toml.get("package") else {
    panic!("I love boilerplate, why would anyone use helper methods");
};
match package.get("name").unwrap() {
	TomlValue::String(name) => assert_eq!(name.as_str(), "boml"),
	TomlValue::Integer(int) => println!("{int} is a pretty weird name, bro"),
	_ => panic!("Expected string or int for package name")
}

You can also determine a value's type without touching its data, via the .ty() method and TomlValueType enum:

use boml::prelude::TomlValueType;

let source = include_str!("../Cargo.toml");
let toml = boml::parse(source).unwrap();

let package = toml.get("package").unwrap();
assert_eq!(package.ty(), TomlValueType::Table);

Error Handling

There are 2 sources of errors in BOML: A parsing error, or an error from one of the get_<type> methods. These use the TomlError and TomlGetError types, respectively.

TomlError, the parsing error type, stores the span of text where the parsing error occurred, and a TomlErrorKind which describes the type of error at that span. Printing the error will show the error kind and the region of TOML that had the error.

TomlGetError is an error from one of the get_<type> methods in tables. It occurs when there's no value for the provided key (InvalidKey) or when the types aren't the same (TypeMismatch - could happen if, for example, you try to get a String value with get_table). A TypeMismatch error stores the actual TOML value and its type, so you can still attempt to use it if possible.

Date and Time Types

TOML supports 4 data types related to dates and times. BOML will parse these types, but performs no validation on those date and time types, because time is hard. BOML does not even check if an hour is between 0 and 23 or if a minute is between 0 and 60.

The only guarantee BOML makes about date/time values is that they are formatted according to RFC 3339. This does not mean the date/time values are actually valid. It just means that the year was a four-digit number, the month was a two-digit number, etc.

You should pass date/time values parsed with BOML to another crate - such as chrono or jiff - before actually using them.

If you enable the crate feature chrono, BOML will provide From and Into implementations to convert TOML date/time types into Chrono date/time types.

TOML Compliance

BOML passes all valid tests cases of the official TOML test suite for TOML 1.0.

BOML does parse some invalid test cases without erroring, meaning it may parse something that's technically invalid TOML as valid TOML.

To run BOML against the TOML test suite yourself, see tests/toml_test.rs.

TOML 1.1 is not currently supported, but support for it will be added if it's released.

Efficiency

BOML aims to be very fast. On a Framework 16, BOML parses the entire TOML test suite - ~1.8k lines of TOML - in ~.003 seconds. You can run this benchmark yourself with cargo +nightly t toml_test_speed --release -- -Zunstable-options --report-time --ignored.

Here's some more in-depth details on BOML's efficiency:

  • BOML only copies data from the original TOML source string in two places:
    1. Strings with escape sequences. Parts of the string that aren't escaped have to be copied to a new string, so the escape can be added.
    2. Floats. Floats are really hard to parse correctly (and efficiently), so BOML uses the standard library's float parser. Unfortunately TOML allows underscores in floats, while the standard library does not, so BOML first has to copy the float to a new buffer and remove any underscores. This process does not allocate.
  • BOML only allocates memory in three places:
    1. Creating hashmaps for TOML tables
    2. Creating vecs for TOML arrays
    3. Copying strings with escape sequences to process the escape sequences

To-Do

  • Support for serializing TOML
  • no_std support? Currently only allocated types from the standard library are used, so it should be possible
  • Improve error messages to be more like rustc or https://github.com/brendanzab/codespan

So what does "BOML" stand for

Yes.

About

A dead-simple, efficient, dependency-free TOML parser for Rust.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages