nadi_core 0.8.0

Core library for Nadi systems, for use by plugins
Documentation
use crate::parser::{
    components::*,
    errors::{ParseError, ParseErrorType},
    tokenizer::{RawToken, Token},
};
use nadi_core::network::StrPath;
use nom::{branch::alt, combinator::map, sequence::separated_pair, Finish};

pub fn node_name<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, String> {
    err_ctx(
        &ParseErrorType::ValueError("Invalid node name"),
        alt((
            // keywords are valid because they are like variables, but
            // can only be used as string in tasks
            map(alt((variable, integer, float, boolean, keyword)), |v| {
                v.content.to_string()
            }),
            string_val,
        )),
    )(inp)
}

pub fn str_path<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, StrPath> {
    let (rest, (start, end)) = separated_pair(
        node_name,
        err_ctx(&ParseErrorType::ExpectedPath, maybe_space(path_sep)),
        err_ctx(&ParseErrorType::IncompletePath, maybe_space(node_name)),
    )(inp)?;
    Ok((rest, StrPath::new(start.into(), end.into())))
}

pub fn network<'a, 'b>(inp: &'a [Token<'b>]) -> MatchRes<'a, 'b, Vec<StrPath>> {
    trailing_newlines(newline_separated(str_path))(inp)
}

pub fn parse(tokens: Vec<RawToken>) -> Result<Vec<StrPath>, ParseError> {
    let tokens = Token::validate(tokens)?;
    match network(&tokens).finish() {
        Ok((rest, paths)) => {
            if rest.is_empty() {
                Ok(paths)
            } else {
                match maybe_newline(str_path)(rest).finish() {
                    Ok((rest, _)) => {
                        Err(ParseError::new(&tokens, rest, ParseErrorType::SyntaxError))
                    }
                    Err(err) => Err(ParseError::new(&tokens, err.internal.input, err.ty)),
                }
            }
        }
        Err(e) => Err(ParseError::new(&tokens, e.internal.input, e.ty)),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::parser::tokenizer::get_tokens;
    use rstest::rstest;

    #[rstest]
    #[case("12.23")]
    #[case("12")]
    #[case("012")]
    #[case("name")]
    #[case("node_name")]
    #[should_panic]
    #[case("0_node_name")]
    #[should_panic]
    #[case("node-name")]
    pub fn node_name_test(#[case] txt: &str) {
        let tokens = Token::validate(get_tokens(txt)).unwrap();
        let (rest, name) = node_name(&tokens).unwrap();
        assert!(rest.is_empty());
        assert_eq!(name, txt);
    }

    #[rstest]
    #[case("12.23->name", ("12.23", "name"))]
    #[case("12 -> \"12\"", ("12", "12"))]
    #[case("012-> xyz_is_12", ("012", "xyz_is_12"))]
    #[case("node_name -> name", ("node_name", "name"))]
    #[should_panic]
    #[case("0_node_name -> name", ("0_node_name", "name"))]
    #[should_panic]
    #[case("node-name -> another", ("node-name", "another"))]
    pub fn str_path_test(#[case] txt: &str, #[case] path: (&str, &str)) {
        let tokens = Token::validate(get_tokens(txt)).unwrap();
        let (rest, p) = str_path(&tokens).unwrap();
        let path2 = (p.start.as_str(), p.end.as_str());
        assert!(rest.is_empty());
        assert_eq!(path2, path);
    }

    #[rstest]
    #[case("12.23->name", vec![("12.23", "name")])]
    #[case("12 -> \"12\"", vec![("12", "12")])]
    #[case("012-> xyz_is_12", vec![("012", "xyz_is_12")])]
    #[case("valid -> edge \nnode_name -> another", vec![("valid", "edge"), ("node_name", "another")])]
    #[case("# test this \nnode_name -> another", vec![("node_name", "another")])]
    pub fn parse_test(#[case] txt: &str, #[case] paths: Vec<(&str, &str)>) {
        let tokens = get_tokens(txt);
        let edges = parse(tokens).unwrap();
        let paths2: Vec<_> = edges
            .iter()
            .map(|p| (p.start.as_str(), p.end.as_str()))
            .collect();
        assert_eq!(paths2, paths);
    }

    #[rstest]
    #[case("0_node_name -> name", 1)]
    #[case("valid -> edge \nnode-name -> another", 2)]
    #[case("# test this \nnode-name -> another", 2)]
    #[should_panic]
    #[case("012-> xyz_is_12", 1)]
    pub fn parse_error_test(#[case] txt: &str, #[case] line: usize) {
        let tokens = get_tokens(txt);
        let err = parse(tokens).err().unwrap();
        assert_eq!(err.line, line);
    }
}