locrian 0.2.2

A simple embeddable functional programming language.
Documentation
use pest::Parser;
use pest::error::Error;
use pest_derive::Parser;

use crate::eval::EvalContext;

#[derive(Parser)]
#[grammar = "grammar.pest"]
struct ExprParser;

#[derive(Debug, PartialEq, Clone)]
pub enum ExprValue<'a> {
    Object(Vec<(String, ExprValue<'a>)>),
    Array(Vec<ExprValue<'a>>),
    String(String),
    Number(f64),
    Boolean(bool),
    Ident(&'a str),
    FnCall(&'a str, Box<ExprValue<'a>>),
    Quote(Box<ExprValue<'a>>),
    QuoteWithCtx(Box<ExprValue<'a>>, EvalContext<'a>),
    Null,
}

pub fn parse_expr(expr: &str) -> Result<ExprValue, Error<Rule>> {
    let parsed = ExprParser::parse(Rule::expr, expr)?.next().unwrap();

    use pest::iterators::Pair;

    fn parse_value(pair: Pair<Rule>) -> ExprValue {
        match pair.as_rule() {
            Rule::object => ExprValue::Object({
                pair.into_inner()
                    .map(|pair| {
                        let mut inner_rules = pair.into_inner();
                        let name = inner_rules
                            .next()
                            .unwrap()
                            .into_inner()
                            .next()
                            .unwrap()
                            .as_span()
                            .as_str()
                            .to_string();
                        let value = parse_value(inner_rules.next().unwrap());
                        (name, value)
                    })
                    .collect()
            }),
            Rule::array => ExprValue::Array(pair.into_inner().map(parse_value).collect()),
            Rule::string => ExprValue::String(
                pair.into_inner()
                    .next()
                    .unwrap()
                    .as_span()
                    .as_str()
                    .to_string(),
            ),
            Rule::number => ExprValue::Number(pair.as_str().parse().unwrap()),
            Rule::boolean => ExprValue::Boolean(pair.as_str().parse().unwrap()),
            Rule::ident => ExprValue::Ident(pair.as_str()),
            Rule::fn_call => {
                let mut iter = pair.into_inner();
                let name = iter.next().unwrap();
                let params = iter.next().unwrap();
                ExprValue::FnCall(
                    name.as_str(),
                    match params.as_rule() {
                        Rule::parameters => Box::new(ExprValue::Array(
                            params.into_inner().map(parse_value).collect(),
                        )),
                        Rule::splat => Box::new(parse_value(params.into_inner().next().unwrap())),
                        _ => unreachable!(),
                    },
                )
            }
            Rule::quote => {
                ExprValue::Quote(Box::new(parse_value(pair.into_inner().next().unwrap())))
            }
            Rule::null => ExprValue::Null,
            Rule::expr
            | Rule::EOI
            | Rule::pair
            | Rule::value
            | Rule::inner
            | Rule::char
            | Rule::parameters
            | Rule::splat
            | Rule::COMMENT
            | Rule::WHITESPACE => {
                panic!("unreachable token: {pair:?}");
            }
        }
    }

    Ok(parse_value(parsed))
}

#[cfg(test)]
mod tests {
    use crate::parser::{ExprValue, parse_expr};

    #[test]
    fn parse_fn_call() {
        assert_eq!(
            parse_expr("test(\"hi\", world, 64.5, false, {\"my\": [value]}) /* this comment should be ignored */").unwrap(),
            ExprValue::FnCall(
                "test",
                Box::new(ExprValue::Array(vec![
                    ExprValue::String("hi".into()),
                    ExprValue::Ident("world"),
                    ExprValue::Number(64.5),
                    ExprValue::Boolean(false),
                    ExprValue::Object(vec![(
                        "my".to_string(),
                        ExprValue::Array(vec![ExprValue::Ident("value")])
                    )])
                ]))
            )
        )
    }
}