luallaby 0.1.0

**Work in progress** A pure-Rust Lua interpreter/compiler
Documentation
use crate::ast::{Exp, PrefixExp, TableField};
use crate::compiler::{offset, Compiler};
use crate::error::Result;
use crate::lexer::Pos;
use crate::vm::{BinOp, Literal, OpCode, Oper};

impl Compiler {
    pub(super) fn compile_exp(&mut self, exp: Exp) -> Result<usize> {
        let (oper, pos) = self.convert_exp(exp)?;
        Ok(match oper {
            Oper::Local(src_loc) => {
                let dst_reg = self.scopes.reg_reserve();
                self.code.emit(OpCode::LocalGet { src_loc, dst_reg }, pos);
                dst_reg
            }
            Oper::Up(src_up) => {
                let dst_reg = self.scopes.reg_reserve();
                self.code.emit(OpCode::UpGet { src_up, dst_reg }, pos);
                dst_reg
            }
            Oper::Reg(r) => r,
            Oper::Raw(..) => unreachable!(), // Only to be used in stdlib
        })
    }

    fn convert_exp(&mut self, exp: Exp) -> Result<(Oper, Pos)> {
        Ok(match exp {
            Exp::BinOp(lhs, op, rhs, pos) => match op {
                // Short circuit logical operators
                BinOp::And | BinOp::Or => {
                    let lhs_reg = self.compile_exp(*lhs)?;

                    let jump_pc = self.code.current_pc();
                    self.code.emit(OpCode::Jump { off: 0 }, pos); // Placeholder

                    let rhs_reg = self.compile_exp(*rhs)?;
                    self.code.emit(OpCode::Single { reg: rhs_reg }, pos);
                    self.code.emit(
                        OpCode::Mov {
                            src_reg: rhs_reg,
                            dst_reg: lhs_reg,
                        },
                        pos,
                    );
                    self.scopes.reg_free(rhs_reg);

                    let off = offset(jump_pc, self.code.current_pc());
                    self.code.set_op(
                        jump_pc,
                        match op {
                            BinOp::And => OpCode::JumpIfNot {
                                cmp_reg: lhs_reg,
                                off,
                            },
                            BinOp::Or => OpCode::JumpIf {
                                cmp_reg: lhs_reg,
                                off,
                            },
                            _ => unreachable!(),
                        },
                    );

                    (Oper::Reg(lhs_reg), pos)
                }
                op => {
                    let start = self.code.current_pc();
                    let (lhs, _) = self.convert_exp(*lhs)?;
                    self.code.set_pos_from(start, pos); // TODO: Workaround for illogical Lua trace behavior
                    let (rhs, _) = self.convert_exp(*rhs)?;
                    let dst_reg = self.scopes.reg_reserve();

                    self.free_oper(&lhs);
                    self.free_oper(&rhs);
                    self.code.emit(
                        OpCode::BinOp {
                            lhs,
                            rhs,
                            op,
                            dst_reg,
                        },
                        pos,
                    );

                    (Oper::Reg(dst_reg), pos)
                }
            },
            Exp::UnOp(op, exp, pos) => {
                let (lhs, _) = self.convert_exp(*exp)?;
                let dst_reg = self.scopes.reg_reserve();

                self.free_oper(&lhs);
                self.code.emit(OpCode::UnOp { lhs, op, dst_reg }, pos);

                (Oper::Reg(dst_reg), pos)
            }
            Exp::Lit(val, pos) => {
                let dst_reg = self.scopes.reg_reserve();
                self.code.emit(OpCode::Lit { val, dst_reg }, pos);
                (Oper::Reg(dst_reg), pos)
            }
            Exp::Prefix(exp) => self.compile_prefix_exp_to_oper(exp)?,
            Exp::Table(fields, pos) => {
                let tbl_reg = self.scopes.reg_reserve();
                self.code.emit(OpCode::TableEmpty { dst_reg: tbl_reg }, pos);

                let mut index: usize = 1;
                // TODO: Nil and NaN cannot be keys
                let mut fields = fields.into_iter().peekable();
                while let Some(field) = fields.next() {
                    match field {
                        TableField::Index(key, val) => {
                            let ind_reg = self.compile_exp(key)?;
                            let src_reg = self.compile_exp(val)?;

                            self.code.emit(
                                OpCode::TableSet {
                                    src_reg,
                                    tbl_reg,
                                    ind_reg,
                                },
                                pos,
                            );

                            self.scopes.reg_free(ind_reg);
                            self.scopes.reg_free(src_reg);
                        }
                        TableField::List(val) => {
                            if fields.peek().is_none()
                                && matches!(
                                    val,
                                    Exp::Prefix(PrefixExp::Call(..) | PrefixExp::CallMethod(..))
                                        | Exp::Varargs(..)
                                )
                            {
                                // If last element is function call or varargs, extend table
                                let src_reg = self.compile_exp(val)?;

                                self.code.emit(
                                    OpCode::TableExtend {
                                        src_reg,
                                        tbl_reg,
                                        ind_from: index,
                                    },
                                    pos,
                                );

                                self.scopes.reg_free(src_reg);
                            } else {
                                let src_reg = self.compile_exp(val)?;
                                let ind_reg = self.scopes.reg_reserve();
                                self.code.emit(
                                    OpCode::Lit {
                                        val: Literal::Int(index as i64),
                                        dst_reg: ind_reg,
                                    },
                                    pos,
                                );
                                index += 1;

                                self.code.emit(
                                    OpCode::TableSet {
                                        src_reg,
                                        tbl_reg,
                                        ind_reg,
                                    },
                                    pos,
                                );

                                self.scopes.reg_free(ind_reg);
                                self.scopes.reg_free(src_reg);
                            }
                        }
                    }
                }

                (Oper::Reg(tbl_reg), pos)
            }
            Exp::Func(func) => {
                let pos_start = func.pos_start;
                let reg = self.compile_func(func)?;
                (Oper::Reg(reg), pos_start)
            }
            Exp::Varargs(pos) => {
                let dst_reg = self.scopes.reg_reserve();
                self.code.emit(OpCode::Varargs { dst_reg }, pos);
                (Oper::Reg(dst_reg), pos)
            }
        })
    }

    fn free_oper(&mut self, oper: &Oper) {
        match oper {
            Oper::Reg(reg) => {
                self.scopes.reg_free(*reg);
            }
            _ => {}
        }
    }
}