use crate::{
collection::{ArrayStringU8, ArrayVectorU8},
database::schema_manager::SchemaManagerError,
misc::str_split1,
};
use alloc::string::String;
use std::io::{BufRead, BufReader, Read};
pub(crate) const EXPR_ARRAY_MAX_LEN: usize = 8;
pub(crate) type ExprArrayTy = ArrayVectorU8<ExprStringTy, EXPR_ARRAY_MAX_LEN>;
pub(crate) type ExprStringTy = ArrayStringU8<128>;
pub(crate) type IdentTy = ArrayStringU8<64>;
pub(crate) type RootParamsTy = ArrayVectorU8<(IdentTy, Expr), 2>;
#[expect(clippy::large_enum_variant, reason = "work in progress")]
#[derive(Debug, PartialEq)]
pub(crate) enum Expr {
Array(ExprArrayTy),
String(ExprStringTy),
}
pub(crate) fn toml<R>(read: R) -> crate::Result<RootParamsTy>
where
R: Read,
{
let mut br = BufReader::new(read);
let mut is_in_array_context = None;
let mut buffer = String::new();
let mut root_params = ArrayVectorU8::new();
macro_rules! clear_and_continue {
() => {
buffer.clear();
continue;
};
}
loop {
if br.read_line(&mut buffer)? == 0 {
break;
}
let buffer_ref = buffer.trim();
if buffer_ref.is_empty() || buffer_ref.starts_with('#') {
clear_and_continue!();
}
if let Some(ident) = is_in_array_context.as_ref() {
if buffer_ref.ends_with(']') {
let inner = buffer_ref.get(0..buffer_ref.len().saturating_sub(1)).unwrap_or_default();
parse_and_push_toml_expr_array(inner, *ident, &mut root_params)?;
is_in_array_context = None;
buffer.clear();
}
continue;
}
let mut root_param_iter = buffer_ref.split('=');
let ident = if let Some(el) = root_param_iter.next() {
el.trim().try_into().map_err(|_err| SchemaManagerError::TomlValueIsTooLarge)?
} else {
clear_and_continue!();
};
let expr_raw = if let Some(el) = root_param_iter.next() {
el.trim()
} else {
clear_and_continue!();
};
if expr_raw.starts_with('[') {
if expr_raw.ends_with(']') {
let inner = expr_raw.get(1..expr_raw.len().saturating_sub(1)).unwrap_or_default();
parse_and_push_toml_expr_array(inner, ident, &mut root_params)?;
} else {
is_in_array_context = Some(ident);
}
} else {
parse_and_push_toml_expr_string(expr_raw, ident, &mut root_params)?;
}
buffer.clear();
}
Ok(root_params)
}
fn parse_and_push_toml_expr_array(
s: &str,
ident: IdentTy,
root_params: &mut RootParamsTy,
) -> crate::Result<()> {
let expr_array = parse_expr_array(s)?;
root_params
.push((ident, Expr::Array(expr_array)))
.map_err(|_err| SchemaManagerError::TomlValueIsTooLarge)?;
Ok(())
}
fn parse_and_push_toml_expr_string(
s: &str,
ident: IdentTy,
root_params: &mut RootParamsTy,
) -> crate::Result<()> {
let expr_string = parse_expr_string(s)?;
root_params
.push((ident, Expr::String(expr_string)))
.map_err(|_err| SchemaManagerError::TomlValueIsTooLarge)?;
Ok(())
}
fn parse_expr_array(s: &str) -> crate::Result<ExprArrayTy> {
let mut array = ArrayVectorU8::new();
if s.is_empty() {
return Ok(array);
}
for elem in str_split1(s, b',') {
let expr_string = parse_expr_string(elem.trim())?;
array.push(expr_string).map_err(|_err| SchemaManagerError::TomlValueIsTooLarge)?;
}
Ok(array)
}
fn parse_expr_string(s: &str) -> crate::Result<ExprStringTy> {
let mut iter = str_split1(s, b'"');
let _ = iter.next().ok_or(SchemaManagerError::TomlParserOnlySupportsStringsAndArraysOfStrings)?;
let value =
iter.next().ok_or(SchemaManagerError::TomlParserOnlySupportsStringsAndArraysOfStrings)?;
let _ = iter.next().ok_or(SchemaManagerError::TomlParserOnlySupportsStringsAndArraysOfStrings)?;
if iter.next().is_some() {
return Err(SchemaManagerError::TomlParserOnlySupportsStringsAndArraysOfStrings.into());
}
Ok(value.trim().try_into().map_err(|_err| SchemaManagerError::TomlValueIsTooLarge)?)
}
#[cfg(test)]
mod tests {
use crate::{
collection::ArrayVectorU8,
database::schema_manager::toml_parser::{Expr, ExprArrayTy, toml},
};
#[test]
fn toml_parses_root_parameter_array_in_a_single_line() {
let array = toml(
&br#"
foo = ["1", "2"]
bar=[]
"#[..],
)
.unwrap();
assert_eq!(
array[0],
(
"foo".try_into().unwrap(),
Expr::Array({
let mut elems = ArrayVectorU8::new();
elems.push("1".try_into().unwrap()).unwrap();
elems.push("2".try_into().unwrap()).unwrap();
elems
})
)
);
assert_eq!(array[1], ("bar".try_into().unwrap(), Expr::Array(ExprArrayTy::default())));
}
#[test]
fn toml_parses_root_parameter_array_in_multiple_lines() {
let array = toml(
&br#"
foo=[
"1",
"2",
"3"
]
"#[..],
)
.unwrap();
assert_eq!(
array[0],
(
"foo".try_into().unwrap(),
Expr::Array({
let mut elems = ArrayVectorU8::new();
elems.push("1".try_into().unwrap()).unwrap();
elems.push("2".try_into().unwrap()).unwrap();
elems.push("3".try_into().unwrap()).unwrap();
elems
})
)
);
}
#[test]
fn toml_parses_root_parameter_string() {
let array = toml(&br#"foo="bar""#[..]).unwrap();
assert_eq!(array[0], ("foo".try_into().unwrap(), Expr::String("bar".try_into().unwrap())));
}
#[test]
fn toml_ignores_comments() {
let array = toml(
&br#"
# Foo
foo="bar"
"#[..],
)
.unwrap();
assert_eq!(array[0], ("foo".try_into().unwrap(), Expr::String("bar".try_into().unwrap())));
}
}