use std::mem::size_of;
#[allow(unused_imports)] use crate::prelude::*;
use crate::state::LuaState;
use lua_types::{GcRef, LuaError, LuaString, LuaValue};
use lua_types::proto::LuaProto;
const LUA_SIGNATURE: &[u8] = b"\x1bLua";
const LUA_VERSION_NUM_DUMP: i32 = 504;
const LUAC_VERSION: u8 =
((LUA_VERSION_NUM_DUMP / 100) * 16 + LUA_VERSION_NUM_DUMP % 100) as u8;
const LUAC_FORMAT: u8 = 0;
const LUAC_DATA: &[u8] = b"\x19\x93\r\n\x1a\n";
const LUAC_INT: i64 = 0x5678;
const LUAC_NUM: f64 = 370.5;
const INSTRUCTION_SIZE: u8 = size_of::<u32>() as u8;
const LUA_INTEGER_SIZE: u8 = size_of::<i64>() as u8;
const LUA_NUMBER_SIZE: u8 = size_of::<f64>() as u8;
struct DumpState<'a> {
writer: &'a mut dyn FnMut(&[u8]) -> Result<(), LuaError>,
strip: bool,
}
impl<'a> DumpState<'a> {
fn dump_block(&mut self, data: &[u8]) -> Result<(), LuaError> {
if !data.is_empty() {
(self.writer)(data)?;
}
Ok(())
}
fn dump_byte(&mut self, y: u8) -> Result<(), LuaError> {
self.dump_block(&[y])
}
fn dump_size(&mut self, mut x: usize) -> Result<(), LuaError> {
const DIBS: usize = (usize::BITS as usize + 6) / 7;
let mut buff = [0u8; DIBS];
let mut n: usize = 0;
loop {
n += 1;
buff[DIBS - n] = (x & 0x7f) as u8; x >>= 7;
if x == 0 {
break;
}
}
buff[DIBS - 1] |= 0x80;
self.dump_block(&buff[DIBS - n..])
}
fn dump_int(&mut self, x: i32) -> Result<(), LuaError> {
debug_assert!(
x >= 0,
"dump_int: negative value {} cast to usize would wrap",
x
);
self.dump_size(x as usize)
}
fn dump_number(&mut self, x: f64) -> Result<(), LuaError> {
self.dump_block(&x.to_ne_bytes())
}
fn dump_integer(&mut self, x: i64) -> Result<(), LuaError> {
self.dump_block(&x.to_ne_bytes())
}
fn dump_string(&mut self, s: Option<&GcRef<LuaString>>) -> Result<(), LuaError> {
match s {
None => self.dump_size(0),
Some(s) => {
let bytes = s.as_bytes(); self.dump_size(bytes.len() + 1)?;
self.dump_block(bytes)
}
}
}
fn dump_code(&mut self, proto: &LuaProto) -> Result<(), LuaError> {
self.dump_int(proto.code.len() as i32)?;
for instr in &proto.code {
self.dump_block(&instr.0.to_ne_bytes())?;
}
Ok(())
}
fn dump_constants(&mut self, proto: &LuaProto) -> Result<(), LuaError> {
let n = proto.k.len();
self.dump_int(n as i32)?;
for constant in &proto.k {
let tag = constant.full_type_tag();
self.dump_byte(tag)?;
match constant {
LuaValue::Float(f) => {
self.dump_number(*f)?;
}
LuaValue::Int(i) => {
self.dump_integer(*i)?;
}
LuaValue::Str(s) => {
self.dump_string(Some(s))?;
}
LuaValue::Nil | LuaValue::Bool(_) => {
debug_assert!(
matches!(constant, LuaValue::Nil | LuaValue::Bool(_)),
"dump_constants: default branch reached for unexpected variant"
);
}
_ => {
debug_assert!(false, "dump_constants: unexpected LuaValue variant in constant pool");
}
}
}
Ok(())
}
fn dump_protos(&mut self, proto: &LuaProto) -> Result<(), LuaError> {
let n = proto.p.len();
self.dump_int(n as i32)?;
for sub in &proto.p {
self.dump_function(sub, proto.source.as_ref())?;
}
Ok(())
}
fn dump_upvalues(&mut self, proto: &LuaProto) -> Result<(), LuaError> {
let n = proto.upvalues.len();
self.dump_int(n as i32)?;
for upval in &proto.upvalues {
self.dump_byte(upval.instack as u8)?;
self.dump_byte(upval.idx)?;
self.dump_byte(upval.kind)?;
}
Ok(())
}
fn dump_debug(&mut self, proto: &LuaProto) -> Result<(), LuaError> {
let n_lineinfo = if self.strip { 0 } else { proto.lineinfo.len() };
self.dump_int(n_lineinfo as i32)?;
let lineinfo_bytes: Vec<u8> = proto.lineinfo[..n_lineinfo]
.iter()
.map(|&b| b as u8)
.collect();
self.dump_block(&lineinfo_bytes)?;
let n_absline = if self.strip { 0 } else { proto.abslineinfo.len() };
self.dump_int(n_absline as i32)?;
for abs in proto.abslineinfo.iter().take(n_absline) {
self.dump_int(abs.pc)?;
self.dump_int(abs.line)?;
}
let n_locvars = if self.strip { 0 } else { proto.locvars.len() };
self.dump_int(n_locvars as i32)?;
for locvar in proto.locvars.iter().take(n_locvars) {
self.dump_string(Some(&locvar.varname))?;
self.dump_int(locvar.startpc)?;
self.dump_int(locvar.endpc)?;
}
let n_upval_names = if self.strip { 0 } else { proto.upvalues.len() };
self.dump_int(n_upval_names as i32)?;
for upval in proto.upvalues.iter().take(n_upval_names) {
self.dump_string(upval.name.as_ref())?;
}
Ok(())
}
fn dump_function(
&mut self,
proto: &LuaProto,
psource: Option<&GcRef<LuaString>>,
) -> Result<(), LuaError> {
let same_source = match (psource, proto.source.as_ref()) {
(Some(ps), Some(src)) => GcRef::ptr_eq(src, ps),
_ => false,
};
if self.strip || same_source {
self.dump_string(None)?;
} else {
self.dump_string(proto.source.as_ref())?;
}
self.dump_int(proto.linedefined)?;
self.dump_int(proto.lastlinedefined)?;
self.dump_byte(proto.numparams)?;
self.dump_byte(proto.is_vararg as u8)?;
self.dump_byte(proto.maxstacksize)?;
self.dump_code(proto)?;
self.dump_constants(proto)?;
self.dump_upvalues(proto)?;
self.dump_protos(proto)?;
self.dump_debug(proto)?;
Ok(())
}
fn dump_header(&mut self) -> Result<(), LuaError> {
self.dump_block(LUA_SIGNATURE)?;
self.dump_byte(LUAC_VERSION)?;
self.dump_byte(LUAC_FORMAT)?;
self.dump_block(LUAC_DATA)?;
self.dump_byte(INSTRUCTION_SIZE)?;
self.dump_byte(LUA_INTEGER_SIZE)?;
self.dump_byte(LUA_NUMBER_SIZE)?;
self.dump_integer(LUAC_INT)?;
self.dump_number(LUAC_NUM)?;
Ok(())
}
}
pub(crate) fn dump(
_state: &LuaState,
proto: &GcRef<LuaProto>,
writer: &mut dyn FnMut(&[u8]) -> Result<(), LuaError>,
strip: bool,
) -> Result<(), LuaError> {
let mut d = DumpState {
writer,
strip,
};
d.dump_header()?;
d.dump_byte(proto.upvalues.len() as u8)?;
d.dump_function(proto, None)?;
Ok(())
}