use crate::attrs::{AttrMap, Attribute, HasAttributes};
use crate::parser::{
components::*,
errors::{ParseError, ParseErrorType},
tokenizer::{RawToken, Token},
};
use nom::{branch::alt, combinator::map, sequence::delimited, Finish};
pub fn attr_group<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, Vec<String>> {
delimited(
bracket_start,
maybe_space(dot_variable),
maybe_space(bracket_end),
)(inp)
}
pub enum Line {
Group(Vec<String>),
KeyVal((Vec<String>, Attribute)),
}
pub fn attr_file_line<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, Line> {
alt((map(attr_group, Line::Group), map(key_val_dot, Line::KeyVal)))(inp)
}
pub fn attr_file<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, Vec<Line>> {
trailing_newlines(newline_separated(attr_file_line))(inp)
}
pub fn parse(tokens: Vec<RawToken>) -> Result<AttrMap, ParseError> {
let tokens = Token::validate(tokens)?;
let lines = match attr_file(&tokens).finish() {
Ok((rest, lines)) => {
if rest.is_empty() {
lines
} else {
return match maybe_newline(attr_file_line)(rest).finish() {
Ok((rest, _)) => {
Err(ParseError::new(&tokens, rest, ParseErrorType::SyntaxError))
}
Err(err) => Err(ParseError::new(&tokens, err.internal.input, err.ty)),
};
}
}
Err(e) => return Err(ParseError::new(&tokens, e.internal.input, e.ty)),
};
let mut attrmap = AttrMap::new();
let mut curr_var = &mut attrmap;
for line in lines {
match line {
Line::Group(grp) => {
curr_var = move_in(&grp, curr_var)?;
}
Line::KeyVal((keys, val)) => {
let old = match keys.as_slice() {
[] => return Err(ParseError::custom("Empty attribute group".into())),
[name] => curr_var.set_attr(name, val.clone()),
[pre @ .., name] => {
let map = move_in(pre, curr_var)?;
map.set_attr(name, val.clone())
}
};
if let Some(oval) = old {
return Err(ParseError::custom(format!(
"Key {} already set to value {} (new: {})",
keys.join("."),
val,
oval
)));
}
}
};
}
Ok(attrmap)
}
fn move_in<'a>(keys: &[String], table: &'a mut AttrMap) -> Result<&'a mut AttrMap, ParseError> {
let mut map = table;
for k in keys {
map = match map
.entry(k.to_string().into())
.or_insert(Attribute::Table(AttrMap::new()))
{
Attribute::Table(ref mut mp) => mp,
val => {
return Err(ParseError::custom(format!(
"Key {k} in {} is not a table (value: {})",
keys.join("."),
val,
)));
}
};
}
Ok(map)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::attr_map;
use crate::parser::tokenizer::get_tokens;
use rstest::rstest;
#[rstest]
#[case("val = 12", attr_map!(val => 12))]
#[case("val = true\nval2 = \"sth\"", attr_map!(val => true, val2 => "sth"))]
#[case("val = true\n[grp]\nval2 = \"sth\"", attr_map!(val => true, grp => attr_map!(val2 => "sth")))]
#[case("val.sth = 12", attr_map!(val => attr_map!(sth => 12)))]
#[case("[zzz]\nval.sth = 12", attr_map!(zzz => attr_map!(val => attr_map!(sth => 12))))]
fn attr_test(#[case] txt: &str, #[case] attrs: AttrMap) {
let tokens = get_tokens(txt);
let am = parse(tokens).unwrap();
assert_eq!(am, attrs);
}
}