mssh 0.0.0

Mssh Simple SHell. Bash interpreter/compiler. Will not support all the functionalities.
use crate::compiler::ir::*;
use crate::compiler::program::Program;
use crate::shell;

use crate::shell::cmd::CmdResult;

type Locals = Vec<String>;

pub struct Evaluator {
    pub functions: Vec<UDF>,
    pub consts: Vec<String>,
    pub globs: Vec<String>,
    pub expr: Vec<Expr>,
    pub name_to_var: NameToIndex,
    pub name_to_func: NameToIndex,
    pub name_to_const: NameToIndex,
    pub entrypoint_idx: u16,
}

impl Evaluator {
    // need a macro to sync program and evaluator
    pub fn new(p: Program) -> Self {
        let functions = p.functions;
        let consts = p.consts;
        let expr = p.expr;
        let entrypoint_idx = p.entrypoint_idx;
        let name_to_var = p.name_to_var;
        let name_to_func = p.name_to_func;
        let name_to_const = p.name_to_const;

        Self {
            globs: Vec::new(),
            name_to_const,
            name_to_var,
            name_to_func,
            functions,
            consts,
            expr,
            entrypoint_idx,
        }
    }

    pub fn eval(&mut self) -> CmdResult {
        let args: Vec<String> = Vec::new();
        // assign global vars, cannot fail as it is built by the compiler
        let _ = self.eval_udf(0, &args);
        self.eval_udf(self.entrypoint_idx, &args)?;
        Ok(())
    }

    fn eval_index(&self, index: &Index, loc: &Locals, args: &[String]) -> String {
        match index {
            Index::Loc(idx) => loc[*idx as usize].to_owned(),
            Index::Arg(idx) => args[*idx as usize - 1].to_owned(),
            Index::Const(idx) => self.consts[*idx as usize].to_owned(),
            Index::Expr(idx) => self.eval_expr_by_index(*idx, loc),
            _ => todo!("eval for index {:?}", index),
        }
    }

    fn eval_udf(&mut self, udf_idx: u16, args: &[String]) -> CmdResult {
        let mut loc = Locals::new();
        let func = self.functions[udf_idx as usize].clone();
        for ir in func.instructions.iter().clone() {
            self.eval_ir(&ir, &mut loc, args)?;
        }
        Ok(())
        //
    }

    fn eval_ir(&mut self, ir: &IR, loc: &mut Locals, args: &[String]) -> CmdResult {
        match ir {
            IR::GlobalAssignement(dst_idx, src_idx) => {
                let val = self.eval_index(src_idx, &loc, args);
                //self.expr[*dst_idx as usize].set(val);
                if *dst_idx < self.globs.len() as u16 {
                    self.globs[*dst_idx as usize] = val;
                } else {
                    self.globs.push(val);
                }
            }

            IR::LocalAssignement(dst_idx, src_idx) => {
                let val = self.eval_index(src_idx, &loc, args);
                if (loc.len() as u8) > *dst_idx {
                    loc[*dst_idx as usize] = val;
                } else {
                    loc.push(val);
                }
            }
            IR::UdfCall(idx, arg_idxs) => {
                let mut sub_args: Vec<String> = Vec::with_capacity(arg_idxs.len());
                for arg_idx in arg_idxs.iter() {
                    sub_args.push(self.eval_index(arg_idx, &loc, args))
                }
                self.eval_udf(*idx, &sub_args)?;
            }
            IR::CmdCall(idx, arg_idxs) => {
                let mut sub_args: Vec<String> = Vec::with_capacity(arg_idxs.len());
                for arg_idx in arg_idxs.iter() {
                    sub_args.push(self.eval_index(arg_idx, &loc, args))
                }
                let cmd = &self.consts[*idx as usize];
                let mut cmd_args: Vec<String> = Vec::new();
                for idx in arg_idxs.iter() {
                    let arg = self.eval_index(idx, loc, &args);
                    cmd_args.push(arg);
                }
                shell::cmd::run_no_redirect(cmd, &cmd_args)?;
            }
            _ => todo!("eval for IRs"),
        };
        Ok(())
    }

    fn eval_expr_by_index(&self, idx: u16, loc: &Locals) -> String {
        let expr = &self.expr[idx as usize];
        self.eval_expr(expr, loc)
    }

    fn eval_expr(&self, expr: &Expr, loc: &Locals) -> String {
        // cannot fail because compilation ensured that the expr
        // is evaluable
        let mut res = String::with_capacity(2 * expr.data.len());
        let mut idx = 0;
        let var = &expr.data;
        let len = var.len();
        while idx < len {
            let ch = var[idx];
            if ch == GLOB_VAR_INDIC {
                // build u16 from 2 u8
                let msb = var[idx + 1] as u16;
                let lsb = var[idx + 2] as u16;
                let var_idx = (msb << 8) | lsb;
                //println!("{}")
                let val = &self.globs[var_idx as usize];
                res.push_str(&val);
                idx += 3;
            } else if ch == LOCL_VAR_INDIC {
                let loc_idx = var[idx + 1] as usize;
                let val = &loc[loc_idx];
                res.push_str(&val);
                idx += 2;
            } else {
                res.push(ch as char);
                idx += 1;
            }
        }
        return res;
    }

    pub fn print(&self) {
        println!("{}", self.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::runtime::tests::snap;
    const DESC_1: &'static str = "
VAR_1 (at position 0) must be hello before the program starts
VAR_4 resolves to 'hello from sbash' after evaluating the program
when update_var2 is called VAR_4 becomes 'hello from bash' without the s
";

    fn validate_glob_eval(p: Program) -> String {
        let mut evaluator = Evaluator::new(p);
        let locals: Vec<String> = Vec::new();

        let mut res = String::with_capacity(120);
        evaluator.eval_udf(0, &locals); // compute initial value of global vars
                                        // check value of first global var
        let hello = &evaluator.globs[0];
        res.push_str(hello);
        res.push('\n');
        //run main, it should in turn call update_var
        evaluator.eval();
        // check new value of VAR_4
        let var_4_new = &evaluator.globs[4];
        res.push_str(&var_4_new);
        res.push('\n');

        // simulate a call to update_var2- to update VAR_4 again
        let args: Vec<String> = Vec::new();
        evaluator.eval_udf(2, &args);
        // check last value of VAR_4
        let var_4_updated = &evaluator.globs[4];
        res.push_str(&var_4_updated);

        res
    }

    snap!(basic_eval, DESC_1, validate_glob_eval);
}