mini-lang 0.1.2

A minimal, experimental language for lazy evaluation.
Documentation
use crate::{parser, MiniError, MiniResult};
use std::collections::HashMap;

pub use parser::Operator;

/// List of define functions, variables, and expressions to print.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
    pub funcs: Vec<Expr>,
    pub vars: Vec<Expr>,
    pub prints: Vec<Expr>,
}

/// The expression tree.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Expr {
    /// The literal value.
    Value(i32),
    /// The variable's scope depth (specified by `expr.circulate`), and index (in `program.vars`).
    Variable(usize, usize),
    /// The operator, and left side value, and right side value.
    Operation(Operator, Box<Expr>, Box<Expr>),
    /// The function index (in `program.funcs`) and list of arguments (number integrity is already verified.)
    FuncCall(usize, Vec<Expr>),
    /// The condition, the expression evaluated if condition is true, and false.
    If(Box<Expr>, Box<Expr>, Box<Expr>),
}

impl Expr {
    fn from_ast(
        e: parser::Expr,
        ns_vars: &HashMap<String, usize>,
        ns_funcs: &HashMap<String, (usize, usize)>,
    ) -> MiniResult<Self> {
        Ok(match e {
            parser::Expr::Value(v) => Self::Value(v),
            parser::Expr::Variable(s) => {
                Self::Variable(0, *ns_vars.get(&s).ok_or("Using undefined variable.")?)
            }
            parser::Expr::Operation(op, lhs, rhs) => Self::Operation(
                op,
                Box::new(Self::from_ast(*lhs, ns_vars, ns_funcs)?),
                Box::new(Self::from_ast(*rhs, ns_vars, ns_funcs)?),
            ),
            parser::Expr::FuncCall(s, e) => {
                let (id, args) = *ns_funcs.get(&s).ok_or("Using undefined function.")?;
                if e.len() != args {
                    return Err(MiniError::from("Illegal arguments."));
                }
                Self::FuncCall(
                    id,
                    e.into_iter()
                        .map(|e| Self::from_ast(e, ns_vars, ns_funcs))
                        .collect::<Result<Vec<_>, _>>()?,
                )
            }
            parser::Expr::If(c, t, f) => Self::If(
                Box::new(Self::from_ast(*c, ns_vars, ns_funcs)?),
                Box::new(Self::from_ast(*t, ns_vars, ns_funcs)?),
                Box::new(Self::from_ast(*f, ns_vars, ns_funcs)?),
            ),
        })
    }

    /// Circulate the variable's scope depth recursively.
    pub fn circulate(self, depth: usize) -> Self {
        match self {
            Self::Value(v) => Self::Value(v),
            Self::Variable(_, id) => Self::Variable(depth, id),
            Self::Operation(op, lhs, rhs) => Self::Operation(
                op,
                Box::new((*lhs).circulate(depth)),
                Box::new((*rhs).circulate(depth)),
            ),
            Self::FuncCall(id, args) => {
                Self::FuncCall(id, args.into_iter().map(|e| e.circulate(depth)).collect())
            }
            Self::If(c, t, f) => Self::If(
                Box::new((*c).circulate(depth)),
                Box::new((*t).circulate(depth)),
                Box::new((*f).circulate(depth)),
            ),
        }
    }
}

pub fn compile(ast: parser::Ast) -> MiniResult<Program> {
    let mut vars = Vec::new();
    let mut ns_vars = HashMap::new();
    let mut funcs = Vec::new();
    let mut ns_funcs = HashMap::new();
    let mut prints = Vec::new();
    for stmt in ast {
        match stmt {
            parser::Stmt::Binding(v, e) => {
                let id = vars.len();
                vars.push(Expr::from_ast(e, &ns_vars, &ns_funcs)?);
                ns_vars.insert(v, id);
            }
            parser::Stmt::Print(e) => {
                prints.push(Expr::from_ast(e, &ns_vars, &ns_funcs)?);
            }
            parser::Stmt::Define(f, a, e) => {
                let id = vars.len();
                let args = a.len();
                ns_funcs.insert(f, (id, args));
                let local_vars = a.into_iter().enumerate().map(|(i, s)| (s, i)).collect();
                funcs.push(Expr::from_ast(e, &local_vars, &ns_funcs)?);
            }
        }
    }

    Ok(Program {
        vars,
        funcs,
        prints,
    })
}