luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use crate::ast::{Ass, Attr, Var};
use crate::compiler::Compiler;
use crate::error::{LuaError, Result};
use crate::vm::{OpCode, VarType};

use super::Variable;

impl Compiler {
    pub(super) fn compile_ass(&mut self, ass: Ass) -> Result<()> {
        // Delay all assignments until end, save here
        let mut end = Vec::new();
        let mut to_free = Vec::new();

        // Resolve assignment names first
        for (var, _) in ass.vars.iter() {
            if let Var::Name(name) = var {
                self.scopes.get(name);
            }
        }
        let exp_regs: Vec<usize> = ass
            .exps
            .into_iter()
            .map(|exp| self.compile_exp(exp))
            .collect::<Result<_>>()?;

        for (ind, (var, pos)) in ass.vars.into_iter().enumerate() {
            let src_reg = if ind + 1 < exp_regs.len() {
                exp_regs[ind]
            } else {
                let dst_reg = self.scopes.reg_reserve();
                self.code.emit(
                    OpCode::MovMult {
                        src_reg: *exp_regs.last().unwrap(),
                        ind: ind + 1 - exp_regs.len(),
                        dst_reg,
                    },
                    self.code.get_pos_highest(),
                );
                to_free.push(dst_reg);
                dst_reg
            };

            match var {
                Var::Name(name) => match self.scopes.get(&name) {
                    Variable::Direct(dst_var, attr) => {
                        if matches!(attr, Some(Attr::Const | Attr::Close)) {
                            return err!(self.source, pos, LuaError::AssignToConst(name));
                        }
                        match dst_var {
                            VarType::Local(dst_loc) => {
                                end.push(OpCode::LocalSet { src_reg, dst_loc })
                            }
                            VarType::Up(dst_up) => end.push(OpCode::UpSet { src_reg, dst_up }),
                        }
                    }
                    Variable::Global(env) => end.push(OpCode::GlobalSet { src_reg, name, env }),
                },
                Var::Index(prefix, exp) => {
                    let tbl_reg = self.compile_prefix_exp(prefix)?;
                    let ind_reg = self.compile_exp(exp)?;
                    end.push(OpCode::TableSet {
                        src_reg,
                        tbl_reg,
                        ind_reg,
                    });
                    to_free.push(tbl_reg);
                    to_free.push(ind_reg);
                }
            };
        }

        self.code.emit_all(end, self.code.get_pos_last());
        for reg in exp_regs {
            self.scopes.reg_free(reg);
        }
        for reg in to_free {
            self.scopes.reg_free(reg);
        }

        Ok(())
    }
}