apiel 0.2.0

A subset of the APL programming language implemented in Rust. Exports a macro for evaluating APL expressions from Rust code, providing a way to solve some problems in a very concise manner.
Documentation
pub mod eval;
pub mod val;

use cfgrammar::Span;
use lrlex::{DefaultLexerTypes, lrlex_mod};
use lrpar::{Lexeme, Lexer, NonStreamingLexer, lrpar_mod};

lrlex_mod!("apiel.l");
lrpar_mod!("apiel.y");

pub use eval::Env;
use val::{Scalar, Val};

pub fn parse_and_evaluate(line: &str) -> Result<Vec<f64>, String> {
    let mut env = Env::new();
    parse_and_evaluate_with_env(line, &mut env)
}

pub fn parse_and_evaluate_with_env(line: &str, env: &mut Env) -> Result<Vec<f64>, String> {
    eval_to_val(line, env).map(|val| val.data.into_iter().map(f64::from).collect())
}

pub fn eval_to_val(line: &str, env: &mut Env) -> Result<Val, String> {
    let lexerdef = apiel_l::lexerdef();
    let lexer = lexerdef.lexer(line);

    {
        let mut tokens = String::new();
        for token in lexer.iter() {
            match token {
                Ok(token) => tokens.push_str(&format!("{} ", token.tok_id())),
                Err(e) => {
                    tracing::warn!("Failed to parse a token: {e}");
                    tokens.push_str("UNKNOWN");
                }
            }
        }
        tracing::debug!(tokens, "Tokens:");
    }

    let (res, errs) = apiel_y::parse(&lexer);

    if !errs.is_empty() {
        return Err(format!("Parse error: {:?}", errs));
    }

    if let Some(Ok(r)) = res {
        eval::eval(&lexer, r, env).map_err(|(span, msg)| {
            let ((line, col), _) = lexer.line_col(span);
            format!(
                "Evaluation error at line {} column {}: '{}', {}.",
                line,
                col,
                lexer.span_str(span),
                msg
            )
        })
    } else {
        Err("Failed to evaluate expression".to_string())
    }
}

pub fn format_val(val: &Val) -> String {
    if val.data.iter().all(|s| matches!(s, Scalar::Char(_))) {
        // All chars: display as string
        val.data
            .iter()
            .map(|s| match s {
                Scalar::Char(c) => *c,
                _ => ' ',
            })
            .collect()
    } else {
        val.data
            .iter()
            .map(|v| match v {
                Scalar::Integer(i) => format!("{i}"),
                Scalar::Float(f) if f.fract() == 0.0 => format!("{}", *f as i64),
                Scalar::Float(f) => format!("{f}"),
                Scalar::Char(c) => format!("{c}"),
                Scalar::Nested(v) => format!("({})", format_val(v)),
            })
            .collect::<Vec<_>>()
            .join(" ")
    }
}