litto 0.1.0

Building blocks for DSL scripting language interpreters that interact with native Rust code.
Documentation
//! Parser support for `tinylang`.

use crate::Interpreter;
use pest::Parser;
use pest_derive::Parser;

type Expr = <super::TinyLang as Interpreter>::Expr;
type Pair<'a> = pest::iterators::Pair<'a, Rule>;

#[derive(Parser)]
#[grammar_inline = r###"
WHITESPACE = _{" " | "\t" | NEWLINE}
COMMENT = _{ ";" ~ (!NEWLINE ~ ANY)* }

list = { "(" ~ listbody ~ ")" }
listbody = _{ (symbol | list)* }

symbol = @{('a'..'z' | '0'..'9' | 'A'..'Z' | "<" | ">" | "!" | "+" | "-" | "*" | "/" | "=" | "?" | "λ")+}

file = {SOI ~ listbody ~ EOI}
"###]
struct TinyLangParser;

/// Parse code into a `TinyLang` expression.
pub fn parse(code: &str) -> Result<Expr, String> {
    let pairs = TinyLangParser::parse(Rule::file, code).map_err(|e| e.to_string())?;

    fn handle_pair(pair: Pair) -> Result<Expr, String> {
        Ok(match pair.as_rule() {
            Rule::file => {
                // Wrap lists in a "begin" call
                let mut vec = vec![Expr::Symbol("begin".into())];
                for pair in pair.into_inner() {
                    let rule = pair.as_rule();
                    if rule != Rule::EOI {
                        vec.push(handle_pair(pair)?);
                    }
                }
                Expr::Compound(vec)
            }
            Rule::list => {
                let mut vec = vec![];
                for pair in pair.into_inner() {
                    vec.push(handle_pair(pair)?);
                }
                Expr::Compound(vec)
            }
            Rule::symbol => Expr::Symbol(pair.as_str().to_string().into()),
            Rule::EOI | Rule::listbody | Rule::WHITESPACE | Rule::COMMENT => unreachable!(),
        })
    };

    for pair in pairs {
        return handle_pair(pair);
    }

    Err("empty input".to_string())
}