lunify 1.1.0

A crate for converting Lua byte code to different versions and formats
Documentation
use super::OperandLayout;
use crate::{lua50, lua51, LunifyError, Settings};

pub(crate) trait ModeGet<T> {
    fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self;
}

pub(crate) trait ModePut {
    fn put(&self, settings: &Settings, layout: &OperandLayout) -> Result<u64, LunifyError>;
}

pub(crate) trait ModeOffset {
    fn offset(&mut self, _stack_start: u64, _offset: i64) {}
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Unused;

impl<T> ModeGet<T> for Unused {
    fn get(_value: u64, _settings: &Settings, _layout: &OperandLayout) -> Self {
        Unused
    }
}

impl ModePut for Unused {
    fn put(&self, _settings: &Settings, _layout: &OperandLayout) -> Result<u64, LunifyError> {
        Ok(0)
    }
}

impl ModeOffset for Unused {}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Generic(pub u64);

impl<T> ModeGet<T> for Generic {
    fn get(value: u64, _settings: &Settings, layout: &OperandLayout) -> Self {
        Generic(layout.get(value))
    }
}

impl ModePut for Generic {
    fn put(&self, _settings: &Settings, layout: &OperandLayout) -> Result<u64, LunifyError> {
        layout.put(self.0)
    }
}

impl ModeOffset for Generic {}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Register(pub u64);

impl<T> ModeGet<T> for Register {
    fn get(value: u64, _settings: &Settings, layout: &OperandLayout) -> Self {
        Register(layout.get(value))
    }
}

impl ModePut for Register {
    fn put(&self, _settings: &Settings, layout: &OperandLayout) -> Result<u64, LunifyError> {
        layout.put(self.0)
    }
}

impl ModeOffset for Register {
    fn offset(&mut self, stack_start: u64, offset: i64) {
        if self.0 >= stack_start {
            self.0 = (self.0 as i64 + offset) as u64;
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ConstantRegister(pub u64, pub bool);

impl ModeGet<lua50::Instruction> for ConstantRegister {
    fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self {
        let mut value = layout.get(value);
        let is_constant = value >= settings.lua50.stack_limit;

        if is_constant {
            value -= settings.lua50.stack_limit;
        }

        ConstantRegister(value, is_constant)
    }
}

impl ModeGet<lua51::Instruction> for ConstantRegister {
    fn get(value: u64, settings: &Settings, layout: &OperandLayout) -> Self {
        let mut value = layout.get(value);
        let constant_bit = settings.lua51.get_constant_bit();
        let is_constant = value & constant_bit != 0;

        if is_constant {
            value ^= constant_bit;
        }

        ConstantRegister(value, is_constant)
    }
}

impl ModePut for ConstantRegister {
    fn put(&self, settings: &Settings, layout: &OperandLayout) -> Result<u64, LunifyError> {
        if self.0 > settings.output.get_maximum_constant_index() {
            return Err(LunifyError::ValueTooBigForOperand);
        }

        let value = match self.1 {
            true => self.0 | settings.output.get_constant_bit(),
            false => self.0,
        };

        layout.put(value)
    }
}

impl ModeOffset for ConstantRegister {
    fn offset(&mut self, stack_start: u64, offset: i64) {
        if self.0 >= stack_start && !self.1 {
            self.0 = (self.0 as i64 + offset) as u64;
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{ConstantRegister, Generic, ModeGet, ModeOffset, Register, Unused};
    use crate::function::instruction::operand::mode::ModePut;
    use crate::{lua50, lua51, LunifyError, Settings};

    fn mode_test_get<T, L>(value: u64, expected: T)
    where
        T: ModeGet<L> + Eq + std::fmt::Debug,
    {
        let settings = Settings::default();
        let result: T = ModeGet::<L>::get(value << 6, &settings, &settings.lua50.layout.c);
        assert_eq!(result, expected);
    }

    fn mode_test_put<T>(value: T, expected: Result<u64, LunifyError>)
    where
        T: ModePut + Eq + std::fmt::Debug,
    {
        let settings = Settings::default();
        let result = value.put(&settings, &settings.lua50.layout.c);
        assert_eq!(result.map(|value| value >> 6), expected);
    }

    fn mode_test_offset<T>(mut value: T, base: u64, offset: i64, expected: T)
    where
        T: ModeOffset + Eq + std::fmt::Debug,
    {
        value.offset(base, offset);
        assert_eq!(value, expected);
    }

    #[test]
    fn unused_get() {
        mode_test_get::<_, ()>(0, Unused);
    }

    #[test]
    fn unused_put() {
        mode_test_put(Unused, Ok(0));
    }

    #[test]
    fn generic_get() {
        mode_test_get::<_, ()>(1, Generic(1));
    }

    #[test]
    fn generic_put() {
        mode_test_put(Generic(1), Ok(1));
    }

    #[test]
    fn generic_offset() {
        mode_test_offset(Generic(4), 0, 5, Generic(4));
    }

    #[test]
    fn register_get() {
        mode_test_get::<_, ()>(1, Register(1));
    }

    #[test]
    fn register_put() {
        mode_test_put(Register(1), Ok(1));
    }

    #[test]
    fn register_offset() {
        mode_test_offset(Register(4), 0, 5, Register(9));
    }

    #[test]
    fn register_offset_below() {
        mode_test_offset(Register(4), 5, 5, Register(4));
    }

    #[test]
    fn constant_register_get() {
        mode_test_get::<_, lua50::Instruction>(1, ConstantRegister(1, false));
    }

    #[test]
    fn constant_register_get_constant_lua_50() {
        let settings = Settings::default();
        mode_test_get::<_, lua50::Instruction>(1 + settings.lua50.stack_limit, ConstantRegister(1, true));
    }

    #[test]
    fn constant_register_get_constant_lua_51() {
        let settings = Settings::default();
        mode_test_get::<_, lua51::Instruction>(1 | settings.lua51.get_constant_bit(), ConstantRegister(1, true));
    }

    #[test]
    fn constant_register_put() {
        mode_test_put(ConstantRegister(1, false), Ok(1));
    }

    #[test]
    fn constant_register_put_value_too_big() {
        let settings = Settings::default();
        mode_test_put(
            ConstantRegister(1 + settings.output.get_maximum_constant_index(), false),
            Err(LunifyError::ValueTooBigForOperand),
        );
    }

    #[test]
    fn constant_register_offset() {
        mode_test_offset(ConstantRegister(4, false), 0, 5, ConstantRegister(9, false));
    }

    #[test]
    fn constant_register_offset_below() {
        mode_test_offset(ConstantRegister(4, false), 5, 5, ConstantRegister(4, false));
    }

    #[test]
    fn constant_register_offset_constant() {
        mode_test_offset(ConstantRegister(4, true), 0, 5, ConstantRegister(4, true));
    }
}