use super::super::reader::Reader;
use crate::runtime::Value;
use crate::runtime::function::{LocVar, Proto, UpvalDesc};
use crate::runtime::heap::{Gc, GcHeader, Heap, ObjTag};
use crate::vm::isa::{Inst, Op};
const SIGNATURE: &[u8; 4] = b"\x1bLua";
const LUAC_VERSION_53: u8 = 0x53;
const LUAC_FORMAT: u8 = 0x00;
const LUAC_DATA: &[u8; 6] = b"\x19\x93\r\n\x1a\n";
const LUAC_INT: i64 = 0x5678;
const LUAC_NUM: f64 = 370.5;
const PUC53_SIZE_OP: u32 = 6;
const PUC53_SIZE_A: u32 = 8;
const PUC53_SIZE_B: u32 = 9;
const PUC53_SIZE_C: u32 = 9;
const PUC53_POS_A: u32 = PUC53_SIZE_OP;
const PUC53_POS_C: u32 = PUC53_POS_A + PUC53_SIZE_A;
const PUC53_POS_B: u32 = PUC53_POS_C + PUC53_SIZE_C;
const PUC53_MAX_ARG_BX: u32 = (1 << (PUC53_SIZE_B + PUC53_SIZE_C)) - 1;
const PUC53_MAX_ARG_SBX: i32 = (PUC53_MAX_ARG_BX >> 1) as i32;
const PUC53_BITRK: u32 = 1 << (PUC53_SIZE_B - 1);
const OP_MOVE: u8 = 0;
const OP_LOADK: u8 = 1;
const OP_LOADKX: u8 = 2;
const OP_LOADBOOL: u8 = 3;
const OP_LOADNIL: u8 = 4;
const OP_GETUPVAL: u8 = 5;
const OP_GETTABUP: u8 = 6;
const OP_GETTABLE: u8 = 7;
const OP_SETTABUP: u8 = 8;
const OP_SETUPVAL: u8 = 9;
const OP_SETTABLE: u8 = 10;
const OP_NEWTABLE: u8 = 11;
const OP_SELF: u8 = 12;
const OP_ADD: u8 = 13;
const OP_SUB: u8 = 14;
const OP_MUL: u8 = 15;
const OP_MOD: u8 = 16;
const OP_POW: u8 = 17;
const OP_DIV: u8 = 18;
const OP_IDIV: u8 = 19;
const OP_BAND: u8 = 20;
const OP_BOR: u8 = 21;
const OP_BXOR: u8 = 22;
const OP_SHL: u8 = 23;
const OP_SHR: u8 = 24;
const OP_UNM: u8 = 25;
const OP_BNOT: u8 = 26;
const OP_NOT: u8 = 27;
const OP_LEN: u8 = 28;
const OP_CONCAT: u8 = 29;
const OP_JMP: u8 = 30;
const OP_EQ: u8 = 31;
const OP_LT: u8 = 32;
const OP_LE: u8 = 33;
const OP_TEST: u8 = 34;
const OP_TESTSET: u8 = 35;
const OP_CALL: u8 = 36;
const OP_TAILCALL: u8 = 37;
const OP_RETURN: u8 = 38;
const OP_FORLOOP: u8 = 39;
const OP_FORPREP: u8 = 40;
const OP_TFORCALL: u8 = 41;
const OP_TFORLOOP: u8 = 42;
const OP_SETLIST: u8 = 43;
const OP_CLOSURE: u8 = 44;
const OP_VARARG: u8 = 45;
const OP_EXTRAARG: u8 = 46;
const NUM_PUC53_OPS: u8 = OP_EXTRAARG + 1;
const LUA_TNIL: u8 = 0;
const LUA_TBOOLEAN: u8 = 1;
const LUA_TNUMFLT: u8 = 3; const LUA_TNUMINT: u8 = 3 | (1 << 4); const LUA_TSHRSTR: u8 = 4; const LUA_TLNGSTR: u8 = 4 | (1 << 4);
#[derive(Clone, Copy, Debug)]
struct Puc53Inst {
op: u8,
a: u32,
b: u32,
c: u32,
}
impl Puc53Inst {
fn bx(self) -> u32 {
(self.b << PUC53_SIZE_C) | self.c
}
fn sbx(self) -> i32 {
self.bx() as i32 - PUC53_MAX_ARG_SBX
}
}
fn decode_53(word: u32) -> Puc53Inst {
let op = (word & ((1 << PUC53_SIZE_OP) - 1)) as u8;
let a = (word >> PUC53_POS_A) & ((1 << PUC53_SIZE_A) - 1);
let c = (word >> PUC53_POS_C) & ((1 << PUC53_SIZE_C) - 1);
let b = (word >> PUC53_POS_B) & ((1 << PUC53_SIZE_B) - 1);
Puc53Inst { op, a, b, c }
}
fn r_int(r: &mut Reader) -> Result<i32, String> {
Ok(i32::from_le_bytes(r.take(4)?.try_into().unwrap()))
}
fn r_size(r: &mut Reader) -> Result<u64, String> {
Ok(u64::from_le_bytes(r.take(8)?.try_into().unwrap()))
}
fn r_integer(r: &mut Reader) -> Result<i64, String> {
Ok(i64::from_le_bytes(r.take(8)?.try_into().unwrap()))
}
fn r_number(r: &mut Reader) -> Result<f64, String> {
Ok(f64::from_bits(u64::from_le_bytes(
r.take(8)?.try_into().unwrap(),
)))
}
fn r_string<'a>(r: &mut Reader<'a>) -> Result<Option<&'a [u8]>, String> {
let b = r.u8()?;
let size: u64 = if b == 0xFF { r_size(r)? } else { b as u64 };
if size == 0 {
return Ok(None);
}
let len = size
.checked_sub(1)
.ok_or_else(|| "bad 5.3 string size".to_string())?;
let slice = r.take(len as usize)?;
Ok(Some(slice))
}
fn check_header(r: &mut Reader) -> Result<(), String> {
let sig = r.take(4)?;
if sig != SIGNATURE {
return Err("bad PUC 5.3 signature".to_string());
}
if r.u8()? != LUAC_VERSION_53 {
return Err("PUC 5.3 translator: version mismatch".to_string());
}
if r.u8()? != LUAC_FORMAT {
return Err("PUC 5.3 translator: unsupported format byte (only 0x00)".to_string());
}
let data = r.take(6)?;
if data != LUAC_DATA {
return Err("PUC 5.3 translator: corrupted LUAC_DATA literal".to_string());
}
let sz_int = r.u8()?;
if sz_int != 4 {
return Err(format!(
"PUC 5.3 translator: expected sizeof(int)=4, got {sz_int}"
));
}
let sz_size = r.u8()?;
if sz_size != 8 {
return Err(format!(
"PUC 5.3 translator: expected sizeof(size_t)=8, got {sz_size}"
));
}
let sz_inst = r.u8()?;
if sz_inst != 4 {
return Err(format!(
"PUC 5.3 translator: expected sizeof(Instruction)=4, got {sz_inst}"
));
}
let sz_int_ty = r.u8()?;
if sz_int_ty != 8 {
return Err(format!(
"PUC 5.3 translator: expected sizeof(lua_Integer)=8, got {sz_int_ty}"
));
}
let sz_num = r.u8()?;
if sz_num != 8 {
return Err(format!(
"PUC 5.3 translator: expected sizeof(lua_Number)=8, got {sz_num}"
));
}
let int_check = r_integer(r)?;
if int_check != LUAC_INT {
return Err(format!(
"PUC 5.3 translator: endianness mismatch (LUAC_INT expected 0x5678, got 0x{int_check:x})"
));
}
let num_check = r_number(r)?;
if num_check != LUAC_NUM {
return Err(format!(
"PUC 5.3 translator: float format mismatch (LUAC_NUM expected 370.5, got {num_check})"
));
}
Ok(())
}
fn translate_inst(word: u32) -> Result<Inst, String> {
let i = decode_53(word);
if i.op >= NUM_PUC53_OPS {
return Err(format!(
"PUC 5.3 translator: unknown opcode {} (max {})",
i.op,
NUM_PUC53_OPS - 1
));
}
let b_is_k = i.b & PUC53_BITRK != 0;
let c_is_k = i.c & PUC53_BITRK != 0;
let b_idx = i.b & (PUC53_BITRK - 1);
let c_idx = i.c & (PUC53_BITRK - 1);
fn fit_b(v: u32, name: &str) -> Result<u32, String> {
if v > 0xFF {
Err(format!(
"PUC 5.3 translator: {name} operand {v} > 255 — out of luna's 8-bit field"
))
} else {
Ok(v)
}
}
fn reject_b_k(opname: &str, b_is_k: bool) -> Result<(), String> {
if b_is_k {
Err(format!(
"PUC 5.3 translator: {opname} with RK on B not yet supported \
(baseline accepts only RK on C; emit a `LoadK tmp; OP A tmp B` \
sequence is the planned polish)"
))
} else {
Ok(())
}
}
Ok(match i.op {
OP_MOVE => Inst::iabc(Op::Move, i.a, fit_b(b_idx, "MOVE.B")?, 0, false),
OP_LOADNIL => {
Inst::iabc(Op::LoadNil, i.a, fit_b(b_idx, "LOADNIL.B")?, 0, false)
}
OP_GETUPVAL => Inst::iabc(Op::GetUpval, i.a, fit_b(b_idx, "GETUPVAL.B")?, 0, false),
OP_SETUPVAL => Inst::iabc(Op::SetUpval, i.a, fit_b(b_idx, "SETUPVAL.B")?, 0, false),
OP_NEWTABLE => {
Inst::iabc(
Op::NewTable,
i.a,
fit_b(b_idx, "NEWTABLE.B")?,
fit_b(c_idx, "NEWTABLE.C")?,
false,
)
}
OP_NOT => Inst::iabc(Op::Not, i.a, fit_b(b_idx, "NOT.B")?, 0, false),
OP_LEN => Inst::iabc(Op::Len, i.a, fit_b(b_idx, "LEN.B")?, 0, false),
OP_UNM => Inst::iabc(Op::Unm, i.a, fit_b(b_idx, "UNM.B")?, 0, false),
OP_BNOT => Inst::iabc(Op::BNot, i.a, fit_b(b_idx, "BNOT.B")?, 0, false),
OP_LOADK => Inst::iabx(Op::LoadK, i.a, i.bx()),
OP_LOADKX => Inst::iabx(Op::LoadKx, i.a, 0),
OP_LOADBOOL => match (b_idx, c_idx) {
(0, 0) => Inst::iabc(Op::LoadFalse, i.a, 0, 0, false),
(0, _) => Inst::iabc(Op::LFalseSkip, i.a, 0, 0, false),
(_, 0) => Inst::iabc(Op::LoadTrue, i.a, 0, 0, false),
(_, _) => {
return Err("PUC 5.3 translator: LOADBOOL true+skip not yet supported \
(luna has no LTrueSkip; planned polish is to emit \
`LoadTrue; Jmp +1` with PC remap)"
.to_string());
}
},
OP_GETTABUP => {
if !c_is_k {
return Err(
"PUC 5.3 translator: GETTABUP with register key — not yet supported \
(only constant-string keys land in baseline)"
.to_string(),
);
}
Inst::iabc(
Op::GetTabUp,
i.a,
fit_b(b_idx, "GETTABUP.B")?,
fit_b(c_idx, "GETTABUP.C")?,
false,
)
}
OP_GETTABLE => {
if c_is_k {
Inst::iabc(
Op::GetField,
i.a,
fit_b(b_idx, "GETTABLE.B")?,
fit_b(c_idx, "GETTABLE.C")?,
false,
)
} else {
Inst::iabc(
Op::GetTable,
i.a,
fit_b(b_idx, "GETTABLE.B")?,
fit_b(c_idx, "GETTABLE.C")?,
false,
)
}
}
OP_SETTABUP => {
if !b_is_k {
return Err(
"PUC 5.3 translator: SETTABUP with register key — not yet supported"
.to_string(),
);
}
Inst::iabc(
Op::SetTabUp,
i.a,
fit_b(b_idx, "SETTABUP.B")?,
fit_b(c_idx, "SETTABUP.C")?,
c_is_k,
)
}
OP_SETTABLE => {
if b_is_k {
Inst::iabc(
Op::SetField,
i.a,
fit_b(b_idx, "SETTABLE.B")?,
fit_b(c_idx, "SETTABLE.C")?,
c_is_k,
)
} else {
Inst::iabc(
Op::SetTable,
i.a,
fit_b(b_idx, "SETTABLE.B")?,
fit_b(c_idx, "SETTABLE.C")?,
c_is_k,
)
}
}
OP_SELF => {
if !c_is_k {
return Err(
"PUC 5.3 translator: SELF with register key — not yet supported".to_string(),
);
}
Inst::iabc(
Op::SelfOp,
i.a,
fit_b(b_idx, "SELF.B")?,
fit_b(c_idx, "SELF.C")?,
false,
)
}
OP_ADD => arith(
Op::Add,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"ADD",
reject_b_k,
)?,
OP_SUB => arith(
Op::Sub,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"SUB",
reject_b_k,
)?,
OP_MUL => arith(
Op::Mul,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"MUL",
reject_b_k,
)?,
OP_MOD => arith(
Op::Mod,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"MOD",
reject_b_k,
)?,
OP_POW => arith(
Op::Pow,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"POW",
reject_b_k,
)?,
OP_DIV => arith(
Op::Div,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"DIV",
reject_b_k,
)?,
OP_IDIV => arith(
Op::IDiv,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"IDIV",
reject_b_k,
)?,
OP_BAND => arith(
Op::BAnd,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"BAND",
reject_b_k,
)?,
OP_BOR => arith(
Op::BOr,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"BOR",
reject_b_k,
)?,
OP_BXOR => arith(
Op::BXor,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"BXOR",
reject_b_k,
)?,
OP_SHL => arith(
Op::Shl,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"SHL",
reject_b_k,
)?,
OP_SHR => arith(
Op::Shr,
i.a,
b_idx,
b_is_k,
c_idx,
c_is_k,
"SHR",
reject_b_k,
)?,
OP_CONCAT => {
if b_idx != i.a {
return Err("PUC 5.3 translator: CONCAT with B != A not yet supported \
(would require a MOVE pre-pass)"
.to_string());
}
let len = c_idx
.checked_sub(b_idx)
.ok_or("PUC 5.3 translator: CONCAT C < B")?
+ 1;
Inst::iabc(Op::Concat, i.a, fit_b(len, "CONCAT len")?, 0, false)
}
OP_JMP => {
if i.a != 0 {
return Err(
"PUC 5.3 translator: OP_JMP with close-upvalues hint (A != 0) not yet \
supported (planned: emit `Close (A-1); Jmp sBx` with PC remap)"
.to_string(),
);
}
Inst::isj(Op::Jmp, i.sbx())
}
OP_EQ => cmp(Op::Eq, i.a, b_idx, b_is_k, c_idx, c_is_k, "EQ")?,
OP_LT => cmp(Op::Lt, i.a, b_idx, b_is_k, c_idx, c_is_k, "LT")?,
OP_LE => cmp(Op::Le, i.a, b_idx, b_is_k, c_idx, c_is_k, "LE")?,
OP_TEST => {
Inst::iabc(Op::Test, i.a, 0, 0, c_idx != 0)
}
OP_TESTSET => Inst::iabc(Op::TestSet, i.a, fit_b(b_idx, "TESTSET.B")?, 0, c_idx != 0),
OP_CALL => Inst::iabc(
Op::Call,
i.a,
fit_b(b_idx, "CALL.B")?,
fit_b(c_idx, "CALL.C")?,
false,
),
OP_TAILCALL => Inst::iabc(
Op::TailCall,
i.a,
fit_b(b_idx, "TAILCALL.B")?,
fit_b(c_idx, "TAILCALL.C")?,
false,
),
OP_RETURN => Inst::iabc(Op::Return, i.a, fit_b(b_idx, "RETURN.B")?, 0, false),
OP_FORPREP => {
let sbx = i.sbx();
if !(0..=crate::vm::isa::MAX_BX as i32).contains(&sbx) {
return Err(format!(
"PUC 5.3 translator: FORPREP sBx {sbx} out of luna Bx range"
));
}
Inst::iabx(Op::ForPrep, i.a, sbx as u32)
}
OP_FORLOOP => {
let sbx = i.sbx();
if sbx > 0 {
return Err(format!(
"PUC 5.3 translator: FORLOOP sBx {sbx} > 0 (expected backward jump)"
));
}
let back = (-sbx) as u32;
if back > crate::vm::isa::MAX_BX {
return Err(format!(
"PUC 5.3 translator: FORLOOP back-distance {back} out of luna Bx range"
));
}
Inst::iabx(Op::ForLoop, i.a, back)
}
OP_TFORCALL | OP_TFORLOOP => {
return Err(
"PUC 5.3 translator: generic-for (TFORCALL / TFORLOOP) not yet supported \
(5.3 lacks OP_TFORPREP; translator must synthesise tbc marker via \
luna Op::TForPrep — planned for next polish pass)"
.to_string(),
);
}
OP_SETLIST => {
if c_idx == 0 {
return Err(
"PUC 5.3 translator: SETLIST with C == 0 (EXTRAARG block-index) \
not yet supported"
.to_string(),
);
}
Inst::iabc(
Op::SetList,
i.a,
fit_b(b_idx, "SETLIST.B")?,
fit_b(c_idx, "SETLIST.C")?,
false,
)
}
OP_CLOSURE => Inst::iabx(Op::Closure, i.a, i.bx()),
OP_VARARG => Inst::iabc(Op::Vararg, i.a, fit_b(b_idx, "VARARG.B")?, 0, false),
OP_EXTRAARG => Inst::iax(Op::ExtraArg, i.bx()),
_ => unreachable!("opcode-range guard above ruled this out"),
})
}
#[allow(clippy::too_many_arguments)]
fn arith(
op: Op,
a: u32,
b: u32,
b_is_k: bool,
c: u32,
c_is_k: bool,
name: &str,
reject_b_k: fn(&str, bool) -> Result<(), String>,
) -> Result<Inst, String> {
reject_b_k(name, b_is_k)?;
if b > 0xFF {
return Err(format!("PUC 5.3 translator: {name}.B operand {b} > 255"));
}
if c > 0xFF {
return Err(format!("PUC 5.3 translator: {name}.C operand {c} > 255"));
}
Ok(Inst::iabc(op, a, b, c, c_is_k))
}
fn cmp(
op: Op,
a: u32,
b: u32,
b_is_k: bool,
c: u32,
c_is_k: bool,
name: &str,
) -> Result<Inst, String> {
if b_is_k || c_is_k {
return Err(format!(
"PUC 5.3 translator: {name} with RK operand not yet supported \
(baseline accepts only register operands)"
));
}
if b > 0xFF || c > 0xFF {
return Err(format!(
"PUC 5.3 translator: {name} B/C operand out of luna 8-bit range"
));
}
let k = a != 0;
Ok(Inst::iabc(op, b, c, 0, k))
}
fn r_const(r: &mut Reader, heap: &mut Heap) -> Result<Value, String> {
Ok(match r.u8()? {
LUA_TNIL => Value::Nil,
LUA_TBOOLEAN => Value::Bool(r.u8()? != 0),
LUA_TNUMFLT => Value::Float(r_number(r)?),
LUA_TNUMINT => Value::Int(r_integer(r)?),
LUA_TSHRSTR | LUA_TLNGSTR => {
match r_string(r)? {
Some(b) => Value::Str(heap.intern(b)),
None => return Err("PUC 5.3 translator: NULL string in const pool".to_string()),
}
}
t => return Err(format!("PUC 5.3 translator: bad constant tag {t}")),
})
}
fn r_proto(
r: &mut Reader,
heap: &mut Heap,
parent_source: Option<Gc<crate::runtime::LuaStr>>,
) -> Result<Gc<Proto>, String> {
let source = match r_string(r)? {
Some(b) => heap.intern(b),
None => parent_source.unwrap_or_else(|| heap.intern(b"")),
};
let line_defined = r_int(r)? as u32;
let last_line_defined = r_int(r)? as u32;
let num_params = r.u8()?;
let is_vararg = r.u8()? != 0;
let max_stack = r.u8()?;
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative code length".to_string());
}
let n = n as usize;
let mut code = Vec::with_capacity(n);
for pc in 0..n {
let word = u32::from_le_bytes(r.take(4)?.try_into().unwrap());
code.push(translate_inst(word).map_err(|e| format!("{e} (pc={pc})"))?);
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative const count".to_string());
}
let mut consts = Vec::with_capacity(n as usize);
for _ in 0..n {
consts.push(r_const(r, heap)?);
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative upval count".to_string());
}
let mut upvals: Vec<UpvalDesc> = Vec::with_capacity(n as usize);
for _ in 0..n {
let in_stack = r.u8()? != 0;
let index = r.u8()?;
upvals.push(UpvalDesc {
in_stack,
index,
name: Box::from(""),
read_only: false,
});
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative proto count".to_string());
}
let mut protos = Vec::with_capacity(n as usize);
for _ in 0..n {
protos.push(r_proto(r, heap, Some(source))?);
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative lineinfo count".to_string());
}
let mut lines = Vec::with_capacity(n as usize);
for _ in 0..n {
let ln = r_int(r)?;
lines.push(if ln < 0 { 0 } else { ln as u32 });
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative locvar count".to_string());
}
let mut locvars = Vec::with_capacity(n as usize);
for _ in 0..n {
let name = match r_string(r)? {
Some(b) => String::from_utf8_lossy(b).into_owned().into(),
None => Box::from(""),
};
let start_pc = r_int(r)? as u32;
let end_pc = r_int(r)? as u32;
locvars.push(LocVar {
name,
reg: u32::MAX,
start_pc,
end_pc,
});
}
let n = r_int(r)?;
if n < 0 {
return Err("PUC 5.3 translator: negative upval-name count".to_string());
}
let n = n as usize;
if n > upvals.len() {
return Err("PUC 5.3 translator: more upval names than upvals".to_string());
}
for i in 0..n {
let name = match r_string(r)? {
Some(b) => String::from_utf8_lossy(b).into_owned().into(),
None => Box::from(""),
};
upvals[i].name = name;
}
let env_upval_idx = upvals
.iter()
.take(u8::MAX as usize)
.position(|u| &*u.name == "_ENV")
.map_or(u8::MAX, |i| i as u8);
Ok(heap.adopt_proto(Proto {
hdr: GcHeader::new(ObjTag::Proto),
code: code.into_boxed_slice(),
consts: consts.into_boxed_slice(),
protos: protos.into_boxed_slice(),
upvals: upvals.into_boxed_slice(),
num_params,
is_vararg,
has_vararg_table_pseudo: false,
has_compat_vararg_arg: false,
max_stack,
lines: lines.into_boxed_slice(),
source,
line_defined,
last_line_defined,
locvars: locvars.into_boxed_slice(),
cache: std::cell::Cell::new(None),
jit: std::cell::Cell::new(crate::runtime::function::JitProtoState::Untried),
env_upval_idx,
trace_hot_count: std::cell::Cell::new(0),
call_hot_count: std::cell::Cell::new(0),
trace_discard_count: std::cell::Cell::new(0),
trace_gave_up: std::cell::Cell::new(false),
traces: std::cell::RefCell::new(Vec::new()),
}))
}
pub(super) fn undump_puc_53(bytes: &[u8], heap: &mut Heap) -> Result<Gc<Proto>, String> {
let mut r = Reader::at(bytes, 0);
check_header(&mut r)?;
let _main_nupvals = r.u8()?;
let proto = r_proto(&mut r, heap, None)?;
if r.pos() != bytes.len() {
return Err(format!(
"PUC 5.3 translator: {} trailing bytes after main proto",
bytes.len() - r.pos()
));
}
Ok(proto)
}
#[cfg(test)]
#[allow(clippy::identity_op, clippy::erasing_op)]
mod tests {
use super::*;
#[test]
fn decode_53_layout_roundtrip() {
let word = 0u32 | (3 << 6) | (7 << 14) | (5 << 23);
let i = decode_53(word);
assert_eq!(i.op, 0);
assert_eq!(i.a, 3);
assert_eq!(i.b, 5);
assert_eq!(i.c, 7);
}
#[test]
fn decode_53_sbx_sign() {
let bx: u32 = (PUC53_MAX_ARG_SBX - 3) as u32;
let word = 39u32
| 0
| ((bx & ((1 << PUC53_SIZE_C) - 1)) << PUC53_POS_C)
| (((bx >> PUC53_SIZE_C) & ((1 << PUC53_SIZE_B) - 1)) << PUC53_POS_B);
let i = decode_53(word);
assert_eq!(i.op, 39);
assert_eq!(i.sbx(), -3);
}
#[test]
fn translate_move() {
let word = 0u32 | (3 << 6) | (0 << 14) | (5 << 23);
let inst = translate_inst(word).unwrap();
assert_eq!(inst.op(), Op::Move);
assert_eq!(inst.a(), 3);
assert_eq!(inst.b(), 5);
}
#[test]
fn translate_loadbool_false_skip() {
let word = 3u32 | (2 << 6) | (1 << 14) | (0 << 23);
let inst = translate_inst(word).unwrap();
assert_eq!(inst.op(), Op::LFalseSkip);
assert_eq!(inst.a(), 2);
}
#[test]
fn translate_loadbool_true_skip_rejected() {
let word = 3u32 | (2 << 6) | (1 << 14) | (1 << 23);
assert!(translate_inst(word).unwrap_err().contains("LOADBOOL"));
}
#[test]
fn translate_add_rk_c() {
let c_field = PUC53_BITRK | 7;
let word = 13u32 | (4 << 6) | (c_field << 14) | (5 << 23);
let inst = translate_inst(word).unwrap();
assert_eq!(inst.op(), Op::Add);
assert_eq!(inst.a(), 4);
assert_eq!(inst.b(), 5);
assert_eq!(inst.c(), 7);
assert!(inst.k());
}
#[test]
fn translate_add_rk_b_rejected() {
let b_field = PUC53_BITRK | 5;
let word = 13u32 | (4 << 6) | (7 << 14) | (b_field << 23);
assert!(translate_inst(word).unwrap_err().contains("RK on B"));
}
#[test]
fn translate_jmp_close_rejected() {
let bx: u32 = (PUC53_MAX_ARG_SBX + 10) as u32;
let word = 30u32 | (3 << 6) | ((bx & 0x1FF) << 14) | (((bx >> 9) & 0x1FF) << 23);
assert!(translate_inst(word).unwrap_err().contains("close-upvalues"));
}
}