blob: ac118bff7e39ef920e9fcb0213c72501da61bf18 [file] [log] [blame] [edit]
pub mod parser;
pub mod table;
pub mod text;
pub mod types;
use {crate_prelude::*, std::ops::Deref};
/// BOML's TOML parser. Create a new one with [`new()`] or [`parse()`], then use
/// it just like a [`Table`].
///
/// [`new()`]: Toml::new()
/// [`parse()`]: Toml::parse()
#[derive(Debug)]
pub struct Toml<'a> {
table: Table<'a>,
}
impl<'a> Toml<'a> {
/// A wrapper around [`Toml::parse()`].
#[inline(always)]
pub fn new(text: &'a str) -> Result<Self, Error> {
Self::parse(text)
}
/// Attempts to parse the provided string as TOML.
pub fn parse(text: &'a str) -> Result<Self, Error> {
let mut text = Text { text, idx: 0 };
text.skip_whitespace_and_newlines();
let mut root_table = Table::default();
// (table name, table, if it's a member of an array of tables)
let mut current_table: Option<(Key<'_>, Table<'_>, bool)> = None;
while text.idx < text.end() {
match text.current_byte().unwrap() {
// Comment
b'#' => {
if let Some(newline_idx) = text.excerpt(text.idx..).find(b'\n') {
text.idx = newline_idx;
} else {
// Comment is at end of file
break;
}
}
// Table definition
b'[' => {
if let Some((key, table, array)) = current_table.take() {
insert_subtable(&mut root_table, key, table, array)?;
}
if text.byte(text.idx + 1) == Some(b'[') {
text.idx += 2;
text.skip_whitespace();
let table_name = parser::parse_key(&mut text)?;
text.idx += 1;
text.skip_whitespace();
if text.current_byte() != Some(b']')
|| text.byte(text.idx + 1) != Some(b']')
{
return Err(Error {
start: table_name.text.span().start - 1,
end: table_name.text.span().end,
kind: ErrorKind::UnclosedBracket,
});
}
text.idx += 2;
current_table = Some((table_name, Table::default(), true));
} else {
text.idx += 1;
text.skip_whitespace();
let table_name = parser::parse_key(&mut text)?;
text.idx += 1;
text.skip_whitespace();
if text.current_byte() != Some(b']') {
return Err(Error {
start: table_name.text.span().start - 1,
end: table_name.text.span().end,
kind: ErrorKind::UnclosedBracket,
});
}
text.idx += 1;
current_table = Some((table_name, Table::default(), false));
}
}
// Key definition
_ => {
let (key, value) = parser::parse_assignment(&mut text)?;
let table = if let Some((_, ref mut table, _)) = current_table {
table
} else {
&mut root_table
};
table.insert(key, value);
text.idx += 1;
}
}
text.skip_whitespace_and_newlines();
}
if let Some((key, table, array)) = current_table.take() {
insert_subtable(&mut root_table, key, table, array)?;
}
Ok(Self { table: root_table })
}
/// Consumes the [`Toml<'_>`], producing a [`Table<'_>`].
pub fn into_table(self) -> Table<'a> {
self.table
}
}
impl<'a> Deref for Toml<'a> {
type Target = Table<'a>;
fn deref(&self) -> &Self::Target {
&self.table
}
}
fn insert_subtable<'a>(
root_table: &mut Table<'a>,
key: Key<'a>,
table: Table<'a>,
array: bool,
) -> Result<(), Error> {
let (start, end) = (key.text.span().start, key.text.span().end);
if array {
let Some(TomlValue::Array(array)) =
root_table.get_or_insert_mut(key, TomlValue::Array(Vec::new()))
else {
return Err(Error {
start,
end,
kind: ErrorKind::ReusedKey,
});
};
array.push(TomlValue::Table(table));
} else {
let Some(TomlValue::Table(to_insert)) =
root_table.get_or_insert_mut(key, TomlValue::Table(Table::default()))
else {
return Err(Error {
start,
end,
kind: ErrorKind::ReusedKey,
});
};
for (key, value) in table.map {
let (start, end) = (key.span().start, key.span().end);
let old = to_insert.map.insert(key, value);
if old.is_some() {
return Err(Error {
start,
end,
kind: ErrorKind::ReusedKey,
});
}
}
}
Ok(())
}
/// An error while parsing TOML, and the range of text that caused
/// that error.
#[derive(Debug)]
pub struct Error {
/// The first byte (inclusive) of the text that caused a parsing
/// error.
pub start: usize,
/// The last byte (inclusive) of the text that caused a parsing
/// error.
pub end: usize,
/// The type of parsing error; see the [`ErrorKind`] docs.
pub kind: ErrorKind,
}
/// A type of error while parsing TOML.
#[derive(Debug, PartialEq, Eq)]
pub enum ErrorKind {
/// A bare key (key without quotes) contains an invalid character.
InvalidBareKey,
/// There was a space in the middle of a bare key.
BareKeyHasSpace,
/// There was no `=` sign in a key/value assignment.
NoEqualsInAssignment,
/// There was no key in a key/value assignment.
NoKeyInAssignment,
/// There was no value in a key/value assignment.
NoValueInAssignment,
/// A string literal or quoted key didn't have a closing quote.
UnclosedString,
/// The value in a key/value assignment wasn't recognised.
UnrecognisedValue,
/// The same key was used twice.
ReusedKey,
/// A number was too big to fit in an i64. This will also be thrown
/// for numbers that are "too little", ie, are too negative to fit.
NumberTooLarge,
/// A number has an invalid base or a leading zero. This error will be thrown
/// for floats or times with bases, since they cannot have bases.
NumberHasInvalidBaseOrLeadingZero,
/// A number is malformed/not parseable.
InvalidNumber,
/// A basic string has an unknown escape sequence.
UnknownEscapeSequence,
/// A unicode escape in a basic string has an unknown unicode scalar value.
UnknownUnicodeScalar,
/// A table, inline table, or array didn't have a closing bracket.
UnclosedBracket,
/// There was no `,` in between values in an inline table or array.
NoCommaDelimeter,
}
mod crate_prelude {
pub use super::{
table::Table,
text::{CowSpan, Span, Text},
types::{Key, TomlValue, TomlValueType},
Error, ErrorKind,
};
}
pub mod prelude {
pub use crate::{
table::{Table as TomlTable, TomlGetError},
types::{TomlValue, TomlValueType},
Error as TomlError, ErrorKind as TomlErrorKind, Toml,
};
}