refactor: split cargo workspace
This commit is contained in:
parent
5db14936e7
commit
1c21bd1259
695 changed files with 32023 additions and 44964 deletions
11
crates/toml-parser/src/lib.rs
Normal file
11
crates/toml-parser/src/lib.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
mod spanned_ext;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod toml_lexer;
|
||||
pub mod toml_parser;
|
||||
pub mod toml_span;
|
||||
pub mod toml_value;
|
||||
mod value_ext;
|
||||
pub mod value_parser;
|
||||
|
||||
pub use spanned_ext::SpannedErrorExt;
|
||||
64
crates/toml-parser/src/spanned_ext.rs
Normal file
64
crates/toml-parser/src/spanned_ext.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use crate::{
|
||||
toml_span::Spanned,
|
||||
toml_value::Value,
|
||||
value_parser::{ParseResult, Parser},
|
||||
};
|
||||
|
||||
impl Spanned<&Value> {
|
||||
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||
self.value.parse(self.span, parser)
|
||||
}
|
||||
|
||||
pub fn parse_map<P: Parser, E>(
|
||||
&self,
|
||||
parser: &mut P,
|
||||
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||
where
|
||||
<P as Parser>::Error: Into<E>,
|
||||
{
|
||||
self.parse(parser).map_spanned_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Spanned<Value> {
|
||||
pub fn parse<P: Parser>(&self, parser: &mut P) -> ParseResult<P> {
|
||||
self.as_ref().parse(parser)
|
||||
}
|
||||
|
||||
pub fn parse_map<P: Parser, E>(
|
||||
&self,
|
||||
parser: &mut P,
|
||||
) -> Result<<P as Parser>::Value, Spanned<E>>
|
||||
where
|
||||
<P as Parser>::Error: Into<E>,
|
||||
{
|
||||
self.as_ref().parse_map(parser)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpannedErrorExt {
|
||||
type T;
|
||||
type E;
|
||||
|
||||
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||
where
|
||||
F: FnOnce(Self::E) -> U;
|
||||
}
|
||||
|
||||
impl<T, E> SpannedErrorExt for Result<T, Spanned<E>> {
|
||||
type T = T;
|
||||
type E = E;
|
||||
|
||||
fn map_spanned_err<U, F>(self, f: F) -> Result<Self::T, Spanned<U>>
|
||||
where
|
||||
F: FnOnce(Self::E) -> U,
|
||||
{
|
||||
match self {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(Spanned {
|
||||
span: e.span,
|
||||
value: f(e.value),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
136
crates/toml-parser/src/tests.rs
Normal file
136
crates/toml-parser/src/tests.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
use {
|
||||
crate::{
|
||||
toml_parser::{ErrorHandler, ParserError, parse},
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
bstr::{BStr, ByteSlice},
|
||||
std::{
|
||||
os::unix::ffi::OsStrExt,
|
||||
panic::{AssertUnwindSafe, catch_unwind},
|
||||
str::FromStr,
|
||||
},
|
||||
walkdir::WalkDir,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let mut have_failures = false;
|
||||
let mut num = 0;
|
||||
let tests = std::path::Path::new(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../toml-config/toml-test/tests/valid"
|
||||
));
|
||||
if !tests.exists() {
|
||||
eprintln!("skipping TOML conformance tests because fixtures are not present");
|
||||
return;
|
||||
}
|
||||
for path in WalkDir::new(tests) {
|
||||
let path = path.unwrap();
|
||||
if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") {
|
||||
num += 1;
|
||||
let res = catch_unwind(AssertUnwindSafe(|| {
|
||||
have_failures |= run_test(prefix.as_bstr());
|
||||
}));
|
||||
if res.is_err() {
|
||||
eprintln!("panic while running {}", prefix.as_bstr());
|
||||
}
|
||||
}
|
||||
}
|
||||
if have_failures {
|
||||
panic!("There were test failures");
|
||||
}
|
||||
eprintln!("ran {num} tests");
|
||||
}
|
||||
|
||||
fn run_test(prefix: &BStr) -> bool {
|
||||
let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap();
|
||||
let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap();
|
||||
|
||||
let json: serde_json::Value = serde_json::from_str(&json).unwrap();
|
||||
let json_as_toml = json_to_value(json);
|
||||
let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix)) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("toml could not be parsed in test {}", prefix);
|
||||
NoErrorHandler(prefix).handle(e);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
if toml != json_as_toml {
|
||||
eprintln!("toml and json differ in test {}", prefix);
|
||||
eprintln!("toml: {:#?}", toml);
|
||||
eprintln!("json: {:#?}", json_as_toml);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn json_to_value(json: serde_json::Value) -> Spanned<Value> {
|
||||
let span = Span { lo: 0, hi: 0 };
|
||||
let val = match json {
|
||||
serde_json::Value::String(_)
|
||||
| serde_json::Value::Number(_)
|
||||
| serde_json::Value::Null
|
||||
| serde_json::Value::Bool(_) => panic!("Unexpected type"),
|
||||
serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()),
|
||||
serde_json::Value::Object(v) => {
|
||||
if v.len() == 2 && v.contains_key("type") && v.contains_key("value") {
|
||||
let ty = v.get("type").unwrap().as_str().unwrap();
|
||||
let val = v.get("value").unwrap().as_str().unwrap();
|
||||
match ty {
|
||||
"string" => Value::String(val.to_owned()),
|
||||
"integer" => Value::Integer(i64::from_str(val).unwrap()),
|
||||
"float" => Value::Float(f64::from_str(val).unwrap()),
|
||||
"bool" => Value::Boolean(bool::from_str(val).unwrap()),
|
||||
_ => panic!("unexpected type {}", ty),
|
||||
}
|
||||
} else {
|
||||
Value::Table(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (k.spanned(span), json_to_value(v)))
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
};
|
||||
val.spanned(span)
|
||||
}
|
||||
|
||||
struct NoErrorHandler<'a>(&'a BStr);
|
||||
|
||||
impl<'a> ErrorHandler for NoErrorHandler<'a> {
|
||||
fn handle(&self, err: Spanned<ParserError>) {
|
||||
eprintln!(
|
||||
"{}: An error occurred during validation: {}",
|
||||
self.0,
|
||||
FormatError(err.span, Some(err.value)),
|
||||
);
|
||||
}
|
||||
|
||||
fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
|
||||
eprintln!(
|
||||
"{}: Redefinition: {}",
|
||||
self.0,
|
||||
FormatError(err.span, Some(err.value)),
|
||||
);
|
||||
eprintln!(
|
||||
"{}: Previous: {}",
|
||||
self.0,
|
||||
FormatError(prev, None),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct FormatError(Span, Option<ParserError>);
|
||||
|
||||
impl std::fmt::Display for FormatError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(cause) = &self.1 {
|
||||
write!(f, "{cause}: ")?;
|
||||
}
|
||||
write!(f, "span {}..{}", self.0.lo, self.0.hi)
|
||||
}
|
||||
}
|
||||
191
crates/toml-parser/src/toml_lexer.rs
Normal file
191
crates/toml-parser/src/toml_lexer.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use crate::toml_span::{Span, Spanned, SpannedExt};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Token<'a> {
|
||||
Dot,
|
||||
Equals,
|
||||
Comma,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LiteralString(&'a [u8]),
|
||||
CookedString(&'a [u8]),
|
||||
Literal(&'a [u8]),
|
||||
}
|
||||
|
||||
impl Token<'_> {
|
||||
pub fn name(self, value_context: bool) -> &'static str {
|
||||
match self {
|
||||
Token::Dot => "`.`",
|
||||
Token::Equals => "`=`",
|
||||
Token::Comma => "`,`",
|
||||
Token::LeftBracket => "`[`",
|
||||
Token::RightBracket => "`]`",
|
||||
Token::LeftBrace => "`{`",
|
||||
Token::RightBrace => "`}`",
|
||||
Token::LiteralString(_) | Token::CookedString(_) => "a string",
|
||||
Token::Literal(_) if value_context => "a literal",
|
||||
Token::Literal(_) => "a key",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
input: &'a [u8],
|
||||
pos: usize,
|
||||
peek: Option<Spanned<Token<'a>>>,
|
||||
peek_value_context: bool,
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a [u8]) -> Self {
|
||||
Self {
|
||||
input,
|
||||
pos: 0,
|
||||
peek: None,
|
||||
peek_value_context: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pos(&mut self) -> usize {
|
||||
self.skip_ws();
|
||||
self.pos
|
||||
}
|
||||
|
||||
fn skip_ws(&mut self) {
|
||||
while let Some(char) = self.input.get(self.pos).copied() {
|
||||
match char {
|
||||
b' ' | b'\t' | b'\n' => self.pos += 1,
|
||||
b'#' => {
|
||||
self.pos += 1;
|
||||
while let Some(char) = self.input.get(self.pos).copied() {
|
||||
self.pos += 1;
|
||||
if char == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||
let next = self.next(value_context);
|
||||
self.peek = next;
|
||||
self.peek_value_context = value_context;
|
||||
next
|
||||
}
|
||||
|
||||
pub fn next(&mut self, value_context: bool) -> Option<Spanned<Token<'a>>> {
|
||||
if let Some(peek) = self.peek.take() {
|
||||
if self.peek_value_context == value_context {
|
||||
return Some(peek);
|
||||
}
|
||||
self.pos = peek.span.lo;
|
||||
}
|
||||
|
||||
use Token::*;
|
||||
|
||||
macro_rules! get {
|
||||
($off:expr) => {
|
||||
self.input.get(self.pos + $off).copied()
|
||||
};
|
||||
}
|
||||
|
||||
self.skip_ws();
|
||||
|
||||
let c = get!(0)?;
|
||||
let pos = self.pos;
|
||||
|
||||
macro_rules! span {
|
||||
() => {
|
||||
Span {
|
||||
lo: pos,
|
||||
hi: self.pos,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
'simple: {
|
||||
let t = match c {
|
||||
b'.' => Dot,
|
||||
b',' => Comma,
|
||||
b'=' => Equals,
|
||||
b'[' => LeftBracket,
|
||||
b']' => RightBracket,
|
||||
b'{' => LeftBrace,
|
||||
b'}' => RightBrace,
|
||||
_ => break 'simple,
|
||||
};
|
||||
self.pos += 1;
|
||||
return Some(t.spanned(span!()));
|
||||
}
|
||||
|
||||
macro_rules! try_string {
|
||||
($delim:expr, $escaping:expr, $ident:ident) => {
|
||||
if c == $delim {
|
||||
'ml_string: {
|
||||
let delim = ($delim, Some($delim), Some($delim));
|
||||
if (c, get!(1), get!(2)) != delim {
|
||||
break 'ml_string;
|
||||
}
|
||||
self.pos += 3;
|
||||
if get!(0) == Some(b'\n') {
|
||||
self.pos += 1;
|
||||
}
|
||||
let start = self.pos;
|
||||
let end = loop {
|
||||
let c = match get!(0) {
|
||||
Some(c) => c,
|
||||
_ => break self.pos,
|
||||
};
|
||||
self.pos += 1;
|
||||
if $escaping && c == b'\\' {
|
||||
self.pos += 1;
|
||||
} else if c == $delim {
|
||||
if (c, get!(0), get!(1)) == delim && get!(2) != Some($delim) {
|
||||
self.pos += 2;
|
||||
break self.pos - 3;
|
||||
}
|
||||
}
|
||||
};
|
||||
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||
}
|
||||
self.pos += 1;
|
||||
let start = self.pos;
|
||||
let end = loop {
|
||||
let c = match get!(0) {
|
||||
Some(c) => c,
|
||||
_ => break self.pos,
|
||||
};
|
||||
self.pos += 1;
|
||||
if $escaping && c == b'\\' {
|
||||
self.pos += 1;
|
||||
} else if c == $delim {
|
||||
break self.pos - 1;
|
||||
}
|
||||
};
|
||||
return Some($ident(&self.input[start..end]).spanned(span!()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try_string!(b'\'', false, LiteralString);
|
||||
try_string!(b'"', true, CookedString);
|
||||
|
||||
let start = self.pos;
|
||||
while let Some(c) = get!(0) {
|
||||
match c {
|
||||
b' ' | b'\t' | b'\n' | b'#' | b',' | b'=' | b'{' | b'}' | b'[' | b']' => break,
|
||||
b'.' if !value_context => break,
|
||||
_ => {}
|
||||
}
|
||||
self.pos += 1;
|
||||
}
|
||||
let end = self.pos;
|
||||
|
||||
Some(Literal(&self.input[start..end]).spanned(span!()))
|
||||
}
|
||||
}
|
||||
530
crates/toml-parser/src/toml_parser.rs
Normal file
530
crates/toml-parser/src/toml_parser.rs
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
use {
|
||||
crate::{
|
||||
toml_lexer::{Lexer, Token},
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
bstr::ByteSlice,
|
||||
indexmap::{
|
||||
IndexMap,
|
||||
map::{RawEntryApiV1, raw_entry_v1::RawEntryMut},
|
||||
},
|
||||
std::{collections::VecDeque, mem, str::FromStr},
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
pub trait ErrorHandler {
|
||||
fn handle(&self, err: Spanned<ParserError>);
|
||||
fn redefinition(&self, err: Spanned<ParserError>, prev: Span);
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ParserError {
|
||||
#[error("Unexpected end of file")]
|
||||
UnexpectedEof,
|
||||
#[error("Expected a key")]
|
||||
MissingKey,
|
||||
#[error("Expected {0} but found {1}")]
|
||||
Expected(&'static str, &'static str),
|
||||
#[error("Duplicate key overwrites the previous definition")]
|
||||
Redefined,
|
||||
#[error("Literal is not valid UTF-8")]
|
||||
NonUtf8Literal,
|
||||
#[error("Could not parse the literal")]
|
||||
UnknownLiteral,
|
||||
#[error("Ignoring key due to following error")]
|
||||
IgnoringKey,
|
||||
#[error("Unnecessary comma")]
|
||||
UnnecessaryComma,
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
input: &[u8],
|
||||
error_handler: &dyn ErrorHandler,
|
||||
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let parser = Parser {
|
||||
lexer: Lexer::new(input),
|
||||
error_handler,
|
||||
last_span: None,
|
||||
};
|
||||
parser.parse()
|
||||
}
|
||||
|
||||
struct Parser<'a, 'b> {
|
||||
lexer: Lexer<'a>,
|
||||
error_handler: &'b dyn ErrorHandler,
|
||||
last_span: Option<Span>,
|
||||
}
|
||||
|
||||
type Key = VecDeque<Spanned<String>>;
|
||||
|
||||
impl<'a> Parser<'a, '_> {
|
||||
fn parse(mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
self.parse_document()
|
||||
}
|
||||
|
||||
fn unexpected_eof(&self) -> Spanned<ParserError> {
|
||||
let span = self.last_span.unwrap_or(Span { lo: 0, hi: 0 });
|
||||
ParserError::UnexpectedEof.spanned(span)
|
||||
}
|
||||
|
||||
fn next(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||
match self.lexer.next(value_context) {
|
||||
Some(t) => {
|
||||
self.last_span = Some(t.span);
|
||||
Ok(t)
|
||||
}
|
||||
_ => Err(self.unexpected_eof()),
|
||||
}
|
||||
}
|
||||
|
||||
fn peek(&mut self, value_context: bool) -> Result<Spanned<Token<'a>>, Spanned<ParserError>> {
|
||||
match self.lexer.peek(value_context) {
|
||||
Some(t) => Ok(t),
|
||||
_ => Err(self.unexpected_eof()),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_value(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let token = self.peek(true)?;
|
||||
match token.value {
|
||||
Token::LiteralString(s) => self.parse_literal_string(s),
|
||||
Token::CookedString(s) => self.parse_cooked_string(s),
|
||||
Token::LeftBracket => self.parse_array(),
|
||||
Token::Literal(l) => self.parse_literal_value(l),
|
||||
Token::LeftBrace => self.parse_inline_table(),
|
||||
Token::Dot | Token::Equals | Token::Comma | Token::RightBrace | Token::RightBracket => {
|
||||
Err(ParserError::Expected("a value", token.value.name(true)).spanned(token.span))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_literal_value(
|
||||
&mut self,
|
||||
literal: &[u8],
|
||||
) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let Ok(s) = std::str::from_utf8(literal) else {
|
||||
return Err(ParserError::NonUtf8Literal.spanned(span));
|
||||
};
|
||||
if s == "true" {
|
||||
return Ok(Value::Boolean(true).spanned(span));
|
||||
}
|
||||
if s == "false" {
|
||||
return Ok(Value::Boolean(false).spanned(span));
|
||||
}
|
||||
let s = s.replace('_', "");
|
||||
if let Ok(n) = i64::from_str(&s) {
|
||||
return Ok(Value::Integer(n).spanned(span));
|
||||
}
|
||||
'radix: {
|
||||
let b = s.as_bytes();
|
||||
if b.len() >= 2 && b[0] == b'0' {
|
||||
let radix = match b[1] {
|
||||
b'x' => 16,
|
||||
b'o' => 8,
|
||||
b'b' => 2,
|
||||
_ => break 'radix,
|
||||
};
|
||||
if let Ok(n) = i64::from_str_radix(&s[2..], radix) {
|
||||
return Ok(Value::Integer(n).spanned(span));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok(n) = f64::from_str(&s) {
|
||||
return Ok(Value::Float(n).spanned(span));
|
||||
}
|
||||
Err(ParserError::UnknownLiteral.spanned(span))
|
||||
}
|
||||
|
||||
fn parse_literal_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let s = s.as_bstr().to_string();
|
||||
Ok(Value::String(s).spanned(span))
|
||||
}
|
||||
|
||||
fn parse_cooked_string(&mut self, s: &[u8]) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let span = self.next(true)?.span;
|
||||
let s = self.cook_string(s);
|
||||
Ok(Value::String(s).spanned(span))
|
||||
}
|
||||
|
||||
fn cook_string(&self, s: &[u8]) -> String {
|
||||
use std::io::Write;
|
||||
|
||||
if !s.contains(&b'\\') {
|
||||
return s.as_bstr().to_string();
|
||||
}
|
||||
let mut res = vec![];
|
||||
let mut pos = 0;
|
||||
while pos < s.len() {
|
||||
let c = s[pos];
|
||||
pos += 1;
|
||||
match c {
|
||||
b'\\' => {
|
||||
let c = s[pos];
|
||||
pos += 1;
|
||||
match c {
|
||||
b'\\' => res.push(b'\\'),
|
||||
b'"' => res.push(b'"'),
|
||||
b'b' => res.push(0x8),
|
||||
b'f' => res.push(0xc),
|
||||
b'n' => res.push(b'\n'),
|
||||
b'r' => res.push(b'\r'),
|
||||
b't' => res.push(b'\t'),
|
||||
b'e' => res.push(0x1b),
|
||||
b'x' | b'u' | b'U' => 'unicode: {
|
||||
let len = match c {
|
||||
b'x' => 2,
|
||||
b'u' => 4,
|
||||
_ => 8,
|
||||
};
|
||||
if s.len() - pos >= len
|
||||
&& let Ok(s) = std::str::from_utf8(&s[pos..pos + len])
|
||||
&& let Ok(n) = u32::from_str_radix(s, 16)
|
||||
&& let Some(c) = char::from_u32(n)
|
||||
{
|
||||
pos += len;
|
||||
let _ = write!(res, "{c}");
|
||||
break 'unicode;
|
||||
}
|
||||
res.extend_from_slice(&s[pos - 2..]);
|
||||
}
|
||||
b' ' | b'\t' | b'\n' => {
|
||||
let mut t = pos;
|
||||
let mut saw_nl = c == b'\n';
|
||||
while t < s.len() && matches!(s[t], b' ' | b'\t' | b'\n') {
|
||||
saw_nl |= s[t] == b'\n';
|
||||
t += 1;
|
||||
}
|
||||
if saw_nl {
|
||||
pos = t;
|
||||
} else {
|
||||
res.extend_from_slice(&[b'\\', c]);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
res.extend_from_slice(&[b'\\', c]);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => res.push(c),
|
||||
}
|
||||
}
|
||||
res.as_bstr().to_string()
|
||||
}
|
||||
|
||||
fn parse_array(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let lo = self.next(true)?.span.lo;
|
||||
let mut entries = vec![];
|
||||
let mut consumed_comma = false;
|
||||
loop {
|
||||
if let Some(v) = self.lexer.peek(true) {
|
||||
if v.value == Token::RightBracket {
|
||||
let _ = self.next(true);
|
||||
let hi = v.span.hi;
|
||||
let span = Span { lo, hi };
|
||||
return Ok(Value::Array(entries).spanned(span));
|
||||
}
|
||||
if entries.len() > 0 && !mem::take(&mut consumed_comma) {
|
||||
self.error_handler.handle(
|
||||
ParserError::Expected("`,` or `]`", v.value.name(true)).spanned(v.span),
|
||||
);
|
||||
}
|
||||
}
|
||||
match self.parse_value() {
|
||||
Ok(v) => {
|
||||
entries.push(v);
|
||||
consumed_comma = self.skip_comma(true);
|
||||
}
|
||||
Err(e) => {
|
||||
self.skip_tree(Token::LeftBracket, Token::RightBracket);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inline_table(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let lo = self.next(true)?.span.lo;
|
||||
let mut map = IndexMap::new();
|
||||
let mut consumed_comma = false;
|
||||
loop {
|
||||
let token = match self.peek(false) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
self.error_handler.handle(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
if token.value == Token::RightBrace {
|
||||
let _ = self.next(false);
|
||||
break;
|
||||
}
|
||||
if !map.is_empty() && !mem::take(&mut consumed_comma) {
|
||||
self.error_handler.handle(
|
||||
ParserError::Expected("`,` or `}`", token.value.name(false))
|
||||
.spanned(token.span),
|
||||
);
|
||||
}
|
||||
let res = match self.parse_key_value_with_recovery() {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
self.skip_tree(Token::LeftBrace, Token::RightBrace);
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
if let Some((mut key, value)) = res {
|
||||
self.insert(&mut map, &mut key, value, false, false);
|
||||
};
|
||||
consumed_comma = self.skip_comma(false);
|
||||
}
|
||||
let hi = self.last_span().hi;
|
||||
let span = Span { lo, hi };
|
||||
Ok(Value::Table(map).spanned(span))
|
||||
}
|
||||
|
||||
fn skip_comma(&mut self, value_context: bool) -> bool {
|
||||
if let Some(token) = self.lexer.peek(value_context) {
|
||||
if token.value != Token::Comma {
|
||||
return false;
|
||||
}
|
||||
let _ = self.next(value_context);
|
||||
}
|
||||
while let Some(token) = self.lexer.peek(value_context) {
|
||||
if token.value != Token::Comma {
|
||||
break;
|
||||
}
|
||||
let _ = self.next(value_context);
|
||||
self.error_handler
|
||||
.handle(ParserError::UnnecessaryComma.spanned(token.span));
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn parse_document(&mut self) -> Result<Spanned<Value>, Spanned<ParserError>> {
|
||||
let mut map = IndexMap::new();
|
||||
self.parse_table_body(&mut map)?;
|
||||
while self.lexer.peek(false).is_some() {
|
||||
let (mut key, append) = self.parse_table_header()?;
|
||||
let mut inner_map = IndexMap::new();
|
||||
self.parse_table_body(&mut inner_map)?;
|
||||
let value = Value::Table(inner_map).spanned(key.span);
|
||||
self.insert(&mut map, &mut key.value, value, true, append);
|
||||
}
|
||||
let hi = self.last_span().hi;
|
||||
let span = Span { lo: 0, hi };
|
||||
Ok(Value::Table(map).spanned(span))
|
||||
}
|
||||
|
||||
fn parse_table_header(&mut self) -> Result<(Spanned<Key>, bool), Spanned<ParserError>> {
|
||||
let lo = self.next(false)?.span.lo;
|
||||
let mut append = false;
|
||||
if let Some(token) = self.lexer.peek(false)
|
||||
&& token.value == Token::LeftBracket
|
||||
{
|
||||
let _ = self.next(false);
|
||||
append = true;
|
||||
}
|
||||
let key = self.parse_key()?;
|
||||
let mut hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||
if append {
|
||||
hi = self.parse_exact(Token::RightBracket, false)?.hi;
|
||||
}
|
||||
let span = Span { lo, hi };
|
||||
Ok((key.spanned(span), append))
|
||||
}
|
||||
|
||||
fn parse_table_body(
|
||||
&mut self,
|
||||
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> Result<(), Spanned<ParserError>> {
|
||||
while let Some(e) = self.lexer.peek(false) {
|
||||
if e.value == Token::LeftBracket {
|
||||
return Ok(());
|
||||
}
|
||||
let Some((mut key, value)) = self.parse_key_value_with_recovery()? else {
|
||||
continue;
|
||||
};
|
||||
self.insert(dst, &mut key, value, false, false);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&self,
|
||||
dst: &mut IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
keys: &mut Key,
|
||||
value: Spanned<Value>,
|
||||
modify_array_element: bool,
|
||||
append_last: bool,
|
||||
) {
|
||||
let key = keys.pop_front().unwrap();
|
||||
if keys.is_empty() {
|
||||
if let RawEntryMut::Occupied(mut old) =
|
||||
dst.raw_entry_mut_v1().from_key(key.value.as_str())
|
||||
{
|
||||
if append_last && let Value::Array(array) = &mut old.get_mut().value {
|
||||
array.push(value);
|
||||
return;
|
||||
}
|
||||
if let Value::Table(old) = &mut old.get_mut().value
|
||||
&& let Value::Table(new) = value.value
|
||||
{
|
||||
for (k, v) in new {
|
||||
let mut keys = Key::new();
|
||||
keys.push_back(k);
|
||||
self.insert(old, &mut keys, v, false, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
self.error_handler
|
||||
.redefinition(ParserError::Redefined.spanned(key.span), old.key().span);
|
||||
old.shift_remove();
|
||||
}
|
||||
let span = value.span;
|
||||
let value = match append_last {
|
||||
true => Value::Array(vec![value]).spanned(span),
|
||||
false => value,
|
||||
};
|
||||
dst.insert(key, value);
|
||||
} else {
|
||||
if let RawEntryMut::Occupied(mut o) = dst.raw_entry_mut_v1().from_key(&key) {
|
||||
match &mut o.get_mut().value {
|
||||
Value::Table(dst) => {
|
||||
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||
return;
|
||||
}
|
||||
Value::Array(array) if modify_array_element => {
|
||||
if let Some(Value::Table(dst)) =
|
||||
array.last_mut().as_mut().map(|v| &mut v.value)
|
||||
{
|
||||
self.insert(dst, keys, value, modify_array_element, append_last);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.error_handler
|
||||
.redefinition(ParserError::Redefined.spanned(key.span), o.key().span);
|
||||
o.shift_remove();
|
||||
}
|
||||
let mut map = IndexMap::new();
|
||||
let span = value.span;
|
||||
self.insert(&mut map, keys, value, modify_array_element, append_last);
|
||||
dst.insert(key, Value::Table(map).spanned(span));
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_key_value_with_recovery(
|
||||
&mut self,
|
||||
) -> Result<Option<(Key, Spanned<Value>)>, Spanned<ParserError>> {
|
||||
let pos = self.lexer.pos();
|
||||
match self.parse_key_value() {
|
||||
Ok(kv) => Ok(Some(kv)),
|
||||
Err((e, key)) => {
|
||||
if let Some(key) = key {
|
||||
let span = key.back().unwrap().span;
|
||||
self.error_handler
|
||||
.handle(ParserError::IgnoringKey.spanned(span));
|
||||
}
|
||||
if self.lexer.pos() == pos {
|
||||
Err(e)
|
||||
} else {
|
||||
self.error_handler.handle(e);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::type_complexity)]
|
||||
fn parse_key_value(
|
||||
&mut self,
|
||||
) -> Result<(Key, Spanned<Value>), (Spanned<ParserError>, Option<Key>)> {
|
||||
let key = self.parse_key();
|
||||
let eq = self.parse_exact(Token::Equals, true);
|
||||
let value = self.parse_value();
|
||||
let key = match key {
|
||||
Ok(k) => k,
|
||||
Err(e) => return Err((e, None)),
|
||||
};
|
||||
if let Err(e) = eq {
|
||||
return Err((e, Some(key)));
|
||||
}
|
||||
let value = match value {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Err((e, Some(key))),
|
||||
};
|
||||
Ok((key, value))
|
||||
}
|
||||
|
||||
fn parse_key(&mut self) -> Result<Key, Spanned<ParserError>> {
|
||||
let mut parts = Key::new();
|
||||
loop {
|
||||
if parts.len() > 0 {
|
||||
if self.parse_exact(Token::Dot, false).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let Some(token) = self.lexer.peek(false) else {
|
||||
break;
|
||||
};
|
||||
let s = match token.value {
|
||||
Token::LiteralString(s) => s.as_bstr().to_string(),
|
||||
Token::CookedString(s) => self.cook_string(s),
|
||||
Token::Literal(l) => l.as_bstr().to_string(),
|
||||
_ => break,
|
||||
};
|
||||
parts.push_back(s.spanned(token.span));
|
||||
let _ = self.next(false);
|
||||
}
|
||||
if parts.is_empty() {
|
||||
Err(ParserError::MissingKey.spanned(self.next_span()))
|
||||
} else {
|
||||
Ok(parts)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_exact(
|
||||
&mut self,
|
||||
token: Token<'a>,
|
||||
value_context: bool,
|
||||
) -> Result<Span, Spanned<ParserError>> {
|
||||
let actual = match self.peek(value_context) {
|
||||
Ok(t) if t.value == token => {
|
||||
let _ = self.next(value_context);
|
||||
return Ok(t.span);
|
||||
}
|
||||
Ok(t) => t.value.name(value_context),
|
||||
Err(_) => "end of file",
|
||||
};
|
||||
let span = self.next_span();
|
||||
Err(ParserError::Expected(token.name(value_context), actual).spanned(span))
|
||||
}
|
||||
|
||||
fn last_span(&self) -> Span {
|
||||
self.last_span.unwrap_or(Span { lo: 0, hi: 0 })
|
||||
}
|
||||
|
||||
fn next_span(&mut self) -> Span {
|
||||
self.lexer.peek(false).map(|v| v.span).unwrap_or_else(|| {
|
||||
let hi = self.last_span().hi;
|
||||
Span { lo: hi, hi }
|
||||
})
|
||||
}
|
||||
|
||||
fn skip_tree(&mut self, start: Token, end: Token) {
|
||||
let mut depth = 1;
|
||||
while let Ok(next) = self.next(false) {
|
||||
if next.value == start {
|
||||
depth += 1;
|
||||
} else if next.value == end {
|
||||
depth -= 1;
|
||||
if depth == 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
108
crates/toml-parser/src/toml_span.rs
Normal file
108
crates/toml-parser/src/toml_span.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
fmt::{Debug, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct Span {
|
||||
pub lo: usize,
|
||||
pub hi: usize,
|
||||
}
|
||||
|
||||
impl Debug for Span {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}..{}", self.lo, self.hi)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq)]
|
||||
pub struct Spanned<T> {
|
||||
pub span: Span,
|
||||
pub value: T,
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
pub fn as_ref(&self) -> Spanned<&T> {
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: &self.value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: f(self.value),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into<U>(self) -> Spanned<U>
|
||||
where
|
||||
T: Into<U>,
|
||||
{
|
||||
Spanned {
|
||||
span: self.span,
|
||||
value: self.value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Spanned<T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.value.fmt(f)?;
|
||||
write!(f, " @ {:?}", self.span)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value.eq(&other.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Spanned<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.value.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for Spanned<String> {
|
||||
fn borrow(&self) -> &str {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpannedExt: Sized {
|
||||
fn spanned(self, span: Span) -> Spanned<Self>;
|
||||
}
|
||||
|
||||
impl<T> SpannedExt for T {
|
||||
fn spanned(self, span: Span) -> Spanned<Self> {
|
||||
Spanned { span, value: self }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DespanExt: Sized {
|
||||
type T;
|
||||
|
||||
fn despan(self) -> Option<Self::T>;
|
||||
fn despan_into<U>(self) -> Option<U>
|
||||
where
|
||||
Self::T: Into<U>;
|
||||
}
|
||||
|
||||
impl<T> DespanExt for Option<Spanned<T>> {
|
||||
type T = T;
|
||||
|
||||
fn despan(self) -> Option<Self::T> {
|
||||
self.map(|v| v.value)
|
||||
}
|
||||
|
||||
fn despan_into<U>(self) -> Option<U>
|
||||
where
|
||||
Self::T: Into<U>,
|
||||
{
|
||||
self.map(|v| v.value.into())
|
||||
}
|
||||
}
|
||||
63
crates/toml-parser/src/toml_value.rs
Normal file
63
crates/toml-parser/src/toml_value.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use {
|
||||
crate::toml_span::Spanned,
|
||||
indexmap::IndexMap,
|
||||
std::{
|
||||
cmp::Ordering,
|
||||
fmt::{Debug, Formatter},
|
||||
},
|
||||
};
|
||||
|
||||
pub enum Value {
|
||||
String(String),
|
||||
Integer(i64),
|
||||
Float(f64),
|
||||
Boolean(bool),
|
||||
Array(Vec<Spanned<Value>>),
|
||||
Table(IndexMap<Spanned<String>, Spanned<Value>>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Value::String(_) => "a string",
|
||||
Value::Integer(_) => "an integer",
|
||||
Value::Float(_) => "a float",
|
||||
Value::Boolean(_) => "a boolean",
|
||||
Value::Array(_) => "an array",
|
||||
Value::Table(_) => "a table",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::String(v) => v.fmt(f),
|
||||
Value::Integer(v) => v.fmt(f),
|
||||
Value::Float(v) => v.fmt(f),
|
||||
Value::Boolean(v) => v.fmt(f),
|
||||
Value::Array(v) => v.fmt(f),
|
||||
Value::Table(v) => v.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Value::String(v1), Value::String(v2)) => v1 == v2,
|
||||
(Value::Integer(v1), Value::Integer(v2)) => v1 == v2,
|
||||
(Value::Float(v1), Value::Float(v2)) => {
|
||||
if v1.is_nan() && v2.is_nan() {
|
||||
true
|
||||
} else {
|
||||
v1.total_cmp(v2) == Ordering::Equal
|
||||
}
|
||||
}
|
||||
(Value::Boolean(v1), Value::Boolean(v2)) => v1 == v2,
|
||||
(Value::Array(v1), Value::Array(v2)) => v1 == v2,
|
||||
(Value::Table(v1), Value::Table(v2)) => v1 == v2,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
18
crates/toml-parser/src/value_ext.rs
Normal file
18
crates/toml-parser/src/value_ext.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use crate::{
|
||||
toml_span::Span,
|
||||
toml_value::Value,
|
||||
value_parser::{ParseResult, Parser},
|
||||
};
|
||||
|
||||
impl Value {
|
||||
pub fn parse<P: Parser>(&self, span: Span, parser: &mut P) -> ParseResult<P> {
|
||||
match self {
|
||||
Value::String(a) => parser.parse_string(span, a),
|
||||
Value::Integer(a) => parser.parse_integer(span, *a),
|
||||
Value::Float(a) => parser.parse_float(span, *a),
|
||||
Value::Boolean(a) => parser.parse_bool(span, *a),
|
||||
Value::Array(a) => parser.parse_array(span, a),
|
||||
Value::Table(a) => parser.parse_table(span, a),
|
||||
}
|
||||
}
|
||||
}
|
||||
118
crates/toml-parser/src/value_parser.rs
Normal file
118
crates/toml-parser/src/value_parser.rs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
use {
|
||||
crate::{
|
||||
toml_span::{Span, Spanned, SpannedExt},
|
||||
toml_value::Value,
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
std::{
|
||||
error::Error,
|
||||
fmt::{self, Display, Formatter},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum DataType {
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Boolean,
|
||||
Array,
|
||||
Table,
|
||||
}
|
||||
|
||||
impl Display for DataType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let s = match self {
|
||||
DataType::String => "a string",
|
||||
DataType::Integer => "an integer",
|
||||
DataType::Float => "a float",
|
||||
DataType::Boolean => "a bool",
|
||||
DataType::Array => "an array",
|
||||
DataType::Table => "a table",
|
||||
};
|
||||
f.write_str(s)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataTypes(&'static [DataType]);
|
||||
|
||||
impl Display for DataTypes {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let d = self.0;
|
||||
match d.len() {
|
||||
0 => Ok(()),
|
||||
1 => d[0].fmt(f),
|
||||
2 => write!(f, "{} or {}", d[0], d[1]),
|
||||
_ => {
|
||||
let mut first = true;
|
||||
#[expect(clippy::needless_range_loop)]
|
||||
for i in 0..d.len() - 1 {
|
||||
if !first {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
first = false;
|
||||
d[i].fmt(f)?;
|
||||
}
|
||||
write!(f, ", or {}", d[d.len() - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedDataType(&'static [DataType], DataType);
|
||||
|
||||
impl Display for UnexpectedDataType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Expected {} but found {}", DataTypes(self.0), self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for UnexpectedDataType {}
|
||||
|
||||
pub type ParseResult<P> = Result<<P as Parser>::Value, Spanned<<P as Parser>::Error>>;
|
||||
|
||||
pub trait Parser {
|
||||
type Value;
|
||||
type Error: From<UnexpectedDataType>;
|
||||
|
||||
const EXPECTED: &'static [DataType];
|
||||
|
||||
fn parse_string(&mut self, span: Span, string: &str) -> ParseResult<Self> {
|
||||
let _ = string;
|
||||
expected(self, span, DataType::String)
|
||||
}
|
||||
|
||||
fn parse_integer(&mut self, span: Span, integer: i64) -> ParseResult<Self> {
|
||||
let _ = integer;
|
||||
expected(self, span, DataType::Integer)
|
||||
}
|
||||
|
||||
fn parse_float(&mut self, span: Span, float: f64) -> ParseResult<Self> {
|
||||
let _ = float;
|
||||
expected(self, span, DataType::Float)
|
||||
}
|
||||
|
||||
fn parse_bool(&mut self, span: Span, bool: bool) -> ParseResult<Self> {
|
||||
let _ = bool;
|
||||
expected(self, span, DataType::Boolean)
|
||||
}
|
||||
|
||||
fn parse_array(&mut self, span: Span, array: &[Spanned<Value>]) -> ParseResult<Self> {
|
||||
let _ = array;
|
||||
expected(self, span, DataType::Array)
|
||||
}
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let _ = table;
|
||||
expected(self, span, DataType::Table)
|
||||
}
|
||||
}
|
||||
|
||||
fn expected<P: Parser + ?Sized>(_p: &P, span: Span, actual: DataType) -> ParseResult<P> {
|
||||
Err(P::Error::from(UnexpectedDataType(P::EXPECTED, actual)).spanned(span))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue