use crate::state::LuaState;
#[allow(unused_imports)] use crate::prelude::*;
use lua_types::{LuaValue, GcRef, LuaString, StackIdx};
use lua_types::error::LuaError;
use lua_types::arith::ArithOp;
const MAX_SIG_DIG: usize = 30;
pub const MAX_NUMBER_2_STR: usize = 44;
pub const UTF8_BUF_SZ: usize = 8;
pub const LUA_ID_SIZE: usize = 60;
const BUF_VFS: usize = LUA_ID_SIZE + MAX_NUMBER_2_STR + 95;
const RETS: &[u8] = b"...";
const PRE: &[u8] = b"[string \"";
const POS: &[u8] = b"\"]";
pub fn ceil_log2(x: u32) -> i32 {
static LOG_2: [u8; 256] = [
0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
];
let mut l: i32 = 0;
let mut x = x.wrapping_sub(1);
while x >= 256 {
l += 8;
x >>= 8;
}
l + LOG_2[x as usize] as i32
}
fn int_arith(state: &mut LuaState, op: ArithOp, v1: i64, v2: i64) -> Result<i64, LuaError> {
match op {
ArithOp::Add => Ok((v1 as u64).wrapping_add(v2 as u64) as i64),
ArithOp::Sub => Ok((v1 as u64).wrapping_sub(v2 as u64) as i64),
ArithOp::Mul => Ok((v1 as u64).wrapping_mul(v2 as u64) as i64),
ArithOp::Mod => crate::vm::int_floor_mod(state, v1, v2),
ArithOp::Idiv => crate::vm::int_floor_div(state, v1, v2),
ArithOp::Band => Ok(v1 & v2),
ArithOp::Bor => Ok(v1 | v2),
ArithOp::Bxor => Ok(v1 ^ v2),
ArithOp::Shl => Ok(crate::vm::shiftl(v1, v2)),
ArithOp::Shr => Ok(crate::vm::shiftl(v1, -v2)),
ArithOp::Unm => Ok((0u64).wrapping_sub(v1 as u64) as i64),
ArithOp::Bnot => Ok((!0u64 ^ v1 as u64) as i64),
_ => {
debug_assert!(false, "int_arith called with non-integer op");
Ok(0)
}
}
}
fn float_arith(state: &mut LuaState, op: ArithOp, v1: f64, v2: f64) -> Result<f64, LuaError> {
match op {
ArithOp::Add => Ok(v1 + v2),
ArithOp::Sub => Ok(v1 - v2),
ArithOp::Mul => Ok(v1 * v2),
ArithOp::Div => Ok(v1 / v2),
ArithOp::Pow => Ok(if v2 == 2.0 { v1 * v1 } else { v1.powf(v2) }),
ArithOp::Idiv => Ok((v1 / v2).floor()),
ArithOp::Unm => Ok(-v1),
ArithOp::Mod => crate::vm::float_floor_mod(state, v1, v2),
_ => {
debug_assert!(false, "float_arith called with non-float op");
Ok(0.0)
}
}
}
pub fn raw_arith(
state: &mut LuaState,
op: ArithOp,
p1: &LuaValue,
p2: &LuaValue,
res: &mut LuaValue,
) -> Result<bool, LuaError> {
match op {
ArithOp::Band | ArithOp::Bor | ArithOp::Bxor
| ArithOp::Shl | ArithOp::Shr | ArithOp::Bnot => {
if let (Some(i1), Some(i2)) = (
p1.to_integer_no_strconv(),
p2.to_integer_no_strconv(),
) {
*res = LuaValue::Int(int_arith(state, op, i1, i2)?);
Ok(true)
} else {
Ok(false)
}
}
ArithOp::Div | ArithOp::Pow => {
if let (Some(n1), Some(n2)) = (
p1.to_number_no_strconv(),
p2.to_number_no_strconv(),
) {
*res = LuaValue::Float(float_arith(state, op, n1, n2)?);
Ok(true)
} else {
Ok(false)
}
}
_ => {
if let (LuaValue::Int(i1), LuaValue::Int(i2)) = (p1, p2) {
*res = LuaValue::Int(int_arith(state, op, *i1, *i2)?);
return Ok(true);
}
if let (Some(n1), Some(n2)) = (
p1.to_number_no_strconv(),
p2.to_number_no_strconv(),
) {
*res = LuaValue::Float(float_arith(state, op, n1, n2)?);
Ok(true)
} else {
Ok(false)
}
}
}
}
pub fn arith(
state: &mut LuaState,
op: ArithOp,
p1: &LuaValue,
p2: &LuaValue,
res: StackIdx,
) -> Result<(), LuaError> {
let mut temp = LuaValue::Nil;
if raw_arith(state, op, p1, p2, &mut temp)? {
state.set_at(res, temp);
} else {
let _ = (p1, p2);
return Err(LuaError::runtime(format_args!(
"arithmetic metamethod dispatch not yet implemented for opcode {:?}", op
)));
}
Ok(())
}
pub fn hex_value(c: u8) -> u8 {
if c.is_ascii_digit() {
c - b'0'
} else {
c.to_ascii_lowercase() - b'a' + 10
}
}
fn is_neg(s: &[u8], idx: &mut usize) -> bool {
if *idx < s.len() && s[*idx] == b'-' {
*idx += 1;
return true;
}
if *idx < s.len() && s[*idx] == b'+' {
*idx += 1;
}
false
}
fn str_x2number(s: &[u8]) -> Option<(f64, usize)> {
let mut idx = 0;
while idx < s.len() && s[idx].is_ascii_whitespace() {
idx += 1;
}
let neg = is_neg(s, &mut idx);
if idx + 1 >= s.len() || s[idx] != b'0' || (s[idx + 1] != b'x' && s[idx + 1] != b'X') {
return None;
}
idx += 2;
let mut r: f64 = 0.0;
let mut sigdig: usize = 0;
let mut nosigdig: usize = 0;
let mut e: i32 = 0;
let mut hasdot = false;
let dot = b'.';
loop {
if idx >= s.len() {
break;
}
let ch = s[idx];
if ch == dot {
if hasdot {
break;
}
hasdot = true;
} else if ch.is_ascii_hexdigit() {
if sigdig == 0 && ch == b'0' {
nosigdig += 1;
} else if {
sigdig += 1;
sigdig <= MAX_SIG_DIG
} {
r = r * 16.0 + hex_value(ch) as f64;
} else {
e += 1;
}
if hasdot {
e -= 1;
}
} else {
break;
}
idx += 1;
}
if nosigdig + sigdig == 0 {
return None;
}
e *= 4;
if idx < s.len() && (s[idx] == b'p' || s[idx] == b'P') {
idx += 1;
let neg1 = is_neg(s, &mut idx);
if idx >= s.len() || !s[idx].is_ascii_digit() {
return None;
}
let mut exp1: i32 = 0;
while idx < s.len() && s[idx].is_ascii_digit() {
exp1 = exp1 * 10 + (s[idx] - b'0') as i32;
idx += 1;
}
if neg1 {
exp1 = -exp1;
}
e += exp1;
}
let result = if neg { -r } else { r };
Some((result * (2.0f64).powi(e), idx))
}
fn str2dloc(s: &[u8], mode: u8) -> Option<(f64, usize)> {
let (result, end) = if mode == b'x' {
str_x2number(s)?
} else {
let text = core::str::from_utf8(s).ok()?;
let trimmed = text.trim();
let lower = trimmed.to_ascii_lowercase();
if lower.starts_with("inf") || lower.starts_with("nan") {
return None;
}
let f: f64 = trimmed.parse().ok()?;
(f, s.len()) };
if end == 0 {
return None;
}
let mut end2 = end;
while end2 < s.len() && s[end2].is_ascii_whitespace() {
end2 += 1;
}
if end2 == s.len() {
Some((result, end2))
} else {
None
}
}
fn str2d(s: &[u8]) -> Option<(f64, usize)> {
let pmode = s.iter().position(|&b| {
b == b'.' || b == b'x' || b == b'X' || b == b'n' || b == b'N'
});
let mode = pmode.map(|i| s[i].to_ascii_lowercase()).unwrap_or(0);
if mode == b'n' {
return None;
}
if let Some(result) = str2dloc(s, mode) {
return Some(result);
}
None
}
fn str2int(s: &[u8]) -> Option<i64> {
let mut idx = 0;
while idx < s.len() && s[idx].is_ascii_whitespace() {
idx += 1;
}
let neg = is_neg(s, &mut idx);
let mut a: u64 = 0;
let mut empty = true;
if idx + 1 < s.len() && s[idx] == b'0' && (s[idx + 1] == b'x' || s[idx + 1] == b'X') {
idx += 2;
while idx < s.len() && s[idx].is_ascii_hexdigit() {
a = a.wrapping_mul(16).wrapping_add(hex_value(s[idx]) as u64);
empty = false;
idx += 1;
}
} else {
const MAX_BY10: u64 = (i64::MAX / 10) as u64;
const MAX_LAST_D: u64 = (i64::MAX % 10) as u64;
while idx < s.len() && s[idx].is_ascii_digit() {
let d = (s[idx] - b'0') as u64;
if a >= MAX_BY10 && (a > MAX_BY10 || d > MAX_LAST_D + if neg { 1 } else { 0 }) {
return None; }
a = a.wrapping_mul(10).wrapping_add(d);
empty = false;
idx += 1;
}
}
while idx < s.len() && s[idx].is_ascii_whitespace() {
idx += 1;
}
if empty || idx != s.len() {
return None;
}
let result = if neg { (0u64).wrapping_sub(a) as i64 } else { a as i64 };
Some(result)
}
pub fn str2num(s: &[u8], o: &mut LuaValue) -> usize {
if let Some(i) = str2int(s) {
*o = LuaValue::Int(i);
return s.len() + 1; }
if let Some((n, end)) = str2d(s) {
*o = LuaValue::Float(n);
return end + 1;
}
0
}
pub fn utf8_esc(buff: &mut [u8; UTF8_BUF_SZ], x: u32) -> usize {
debug_assert!(x <= 0x7FFF_FFFF, "codepoint out of range");
let mut n: usize = 1;
if x < 0x80 {
buff[UTF8_BUF_SZ - 1] = x as u8;
} else {
let mut mfb: u32 = 0x3f;
let mut x = x;
loop {
buff[UTF8_BUF_SZ - n] = 0x80 | (x & 0x3f) as u8;
n += 1;
x >>= 6;
mfb >>= 1;
if x <= mfb {
break;
}
}
buff[UTF8_BUF_SZ - n] = ((!mfb << 1) | x) as u8;
}
n
}
fn fmt_g14(f: f64) -> Vec<u8> {
if f.is_nan() {
return b"nan".to_vec();
}
if f.is_infinite() {
return if f > 0.0 { b"inf".to_vec() } else { b"-inf".to_vec() };
}
if f == 0.0 {
return if f.is_sign_negative() { b"-0".to_vec() } else { b"0".to_vec() };
}
let precision: i32 = 14;
let abs = f.abs();
let exp = abs.log10().floor() as i32;
let s = if exp < -4 || exp >= precision {
let mantissa_decimals = (precision - 1) as usize;
let raw = format!("{:.*e}", mantissa_decimals, f);
let e_idx = raw.find('e').expect("Rust scientific format always contains 'e'");
let mantissa = strip_fixed_trailing_zeros(&raw[..e_idx]);
let exp_num: i32 = raw[e_idx + 1..].parse().expect("Rust formats integer exponents");
let sign = if exp_num < 0 { '-' } else { '+' };
let abs_exp = exp_num.abs();
if abs_exp < 10 {
format!("{}e{}0{}", mantissa, sign, abs_exp)
} else {
format!("{}e{}{}", mantissa, sign, abs_exp)
}
} else {
let decimals = (precision - 1 - exp).max(0) as usize;
let raw = format!("{:.*}", decimals, f);
strip_fixed_trailing_zeros(&raw)
};
s.into_bytes()
}
fn strip_fixed_trailing_zeros(s: &str) -> String {
if !s.contains('.') {
return s.to_string();
}
let mut out = s.to_string();
while out.ends_with('0') {
out.pop();
}
if out.ends_with('.') {
out.pop();
}
out
}
fn number_to_str_buf(val: &LuaValue) -> Vec<u8> {
debug_assert!(
matches!(val, LuaValue::Int(_) | LuaValue::Float(_)),
"number_to_str_buf: value is not a number"
);
match val {
LuaValue::Int(i) => {
let s = format!("{}", i);
s.into_bytes()
}
LuaValue::Float(f) => {
let mut bytes = fmt_g14(*f);
let looks_like_int = bytes.iter().all(|&b| b == b'-' || b.is_ascii_digit());
if looks_like_int {
bytes.push(b'.');
bytes.push(b'0');
}
bytes
}
_ => Vec::new(),
}
}
pub fn num_to_string(state: &mut LuaState, val: &LuaValue) -> Result<GcRef<LuaString>, LuaError> {
let bytes = number_to_str_buf(val);
state.intern_str(&bytes)
}
pub enum FmtArg<'a> {
Str(&'a [u8]),
Char(u8),
Int(i32),
LuaInt(i64),
Float(f64),
Utf8Codepoint(u32),
}
struct BufFs {
pushed: bool,
space: Vec<u8>,
}
impl BufFs {
fn new() -> Self {
BufFs {
pushed: false,
space: Vec::with_capacity(BUF_VFS),
}
}
}
fn pushstr(buf: &mut BufFs, state: &mut LuaState, str_bytes: &[u8]) -> Result<(), LuaError> {
let s = state.intern_str(str_bytes)?;
state.push(LuaValue::Str(s));
if !buf.pushed {
buf.pushed = true;
} else {
crate::vm::concat(state, 2)?;
}
Ok(())
}
fn clearbuff(buf: &mut BufFs, state: &mut LuaState) -> Result<(), LuaError> {
let bytes: Vec<u8> = buf.space.drain(..).collect();
pushstr(buf, state, &bytes)
}
fn addstr2buff(buf: &mut BufFs, state: &mut LuaState, str_bytes: &[u8]) -> Result<(), LuaError> {
if str_bytes.len() <= BUF_VFS {
if str_bytes.len() > BUF_VFS - buf.space.len() {
clearbuff(buf, state)?;
}
buf.space.extend_from_slice(str_bytes);
} else {
clearbuff(buf, state)?;
pushstr(buf, state, str_bytes)?;
}
Ok(())
}
fn addnum2buff(buf: &mut BufFs, state: &mut LuaState, num: &LuaValue) -> Result<(), LuaError> {
let bytes = number_to_str_buf(num);
addstr2buff(buf, state, &bytes)
}
pub fn push_vfstring<'a>(
state: &mut LuaState,
fmt: &[u8],
args: &[FmtArg<'a>],
) -> Result<GcRef<LuaString>, LuaError> {
let mut buf = BufFs::new();
let mut arg_idx = 0usize;
let mut pos = 0usize;
while let Some(rel) = fmt[pos..].iter().position(|&b| b == b'%') {
let e = pos + rel;
addstr2buff(&mut buf, state, &fmt[pos..e])?;
let spec = if e + 1 < fmt.len() { fmt[e + 1] } else { 0 };
match spec {
b's' => {
let s = match args.get(arg_idx) {
Some(FmtArg::Str(b)) => *b,
None => b"(null)",
_ => b"(null)",
};
arg_idx += 1;
addstr2buff(&mut buf, state, s)?;
}
b'c' => {
let c = match args.get(arg_idx) {
Some(FmtArg::Char(b)) => *b,
_ => b'?',
};
arg_idx += 1;
addstr2buff(&mut buf, state, &[c])?;
}
b'd' => {
let n = match args.get(arg_idx) {
Some(FmtArg::Int(i)) => *i as i64,
_ => 0,
};
arg_idx += 1;
addnum2buff(&mut buf, state, &LuaValue::Int(n))?;
}
b'I' => {
let n = match args.get(arg_idx) {
Some(FmtArg::LuaInt(i)) => *i,
_ => 0,
};
arg_idx += 1;
addnum2buff(&mut buf, state, &LuaValue::Int(n))?;
}
b'f' => {
let f = match args.get(arg_idx) {
Some(FmtArg::Float(f)) => *f,
_ => 0.0,
};
arg_idx += 1;
addnum2buff(&mut buf, state, &LuaValue::Float(f))?;
}
b'p' => {
arg_idx += 1; addstr2buff(&mut buf, state, b"<ptr>")?;
}
b'U' => {
let cp = match args.get(arg_idx) {
Some(FmtArg::Utf8Codepoint(u)) => *u,
_ => b'?' as u32,
};
arg_idx += 1;
let mut bf = [0u8; UTF8_BUF_SZ];
let n = utf8_esc(&mut bf, cp);
addstr2buff(&mut buf, state, &bf[UTF8_BUF_SZ - n..])?;
}
b'%' => {
addstr2buff(&mut buf, state, b"%")?;
}
other => {
return Err(LuaError::runtime(format_args!(
"invalid option '%%{}' to 'lua_pushfstring'",
other as char
)));
}
}
pos = e + 2;
}
addstr2buff(&mut buf, state, &fmt[pos..])?;
clearbuff(&mut buf, state)?;
debug_assert!(buf.pushed, "push_vfstring: no string was pushed");
Ok(state.peek_string_at_top())
}
pub fn push_fstring<'a>(
state: &mut LuaState,
fmt: &[u8],
args: &[FmtArg<'a>],
) -> Result<GcRef<LuaString>, LuaError> {
push_vfstring(state, fmt, args)
}
pub fn chunk_id(out: &mut [u8], source: &[u8]) -> usize {
let bufflen = LUA_ID_SIZE;
let mut written = 0usize;
let write_bytes = |out: &mut [u8], written: &mut usize, bytes: &[u8]| {
let avail = out.len().saturating_sub(*written);
let n = bytes.len().min(avail);
out[*written..*written + n].copy_from_slice(&bytes[..n]);
*written += n;
};
let first = source.first().copied();
let srclen = source.len();
match first {
Some(b'=') => {
let body = &source[1..];
if srclen <= bufflen {
write_bytes(out, &mut written, body);
} else {
write_bytes(out, &mut written, &body[..bufflen - 1]);
if written < out.len() {
out[written] = 0;
}
}
}
Some(b'@') => {
let body = &source[1..];
if srclen <= bufflen {
write_bytes(out, &mut written, body);
} else {
write_bytes(out, &mut written, RETS);
let tail_len = bufflen - RETS.len() - 1;
let tail_start = body.len() - tail_len;
write_bytes(out, &mut written, &body[tail_start..tail_start + tail_len]);
}
}
_ => {
let nl_pos = source.iter().position(|&b| b == b'\n');
write_bytes(out, &mut written, PRE);
let reserved = PRE.len() + RETS.len() + POS.len() + 1;
let inner_limit = bufflen.saturating_sub(reserved);
if srclen < inner_limit && nl_pos.is_none() {
write_bytes(out, &mut written, source);
} else {
let take = nl_pos.unwrap_or(srclen).min(inner_limit);
write_bytes(out, &mut written, &source[..take]);
write_bytes(out, &mut written, RETS);
}
write_bytes(out, &mut written, POS);
}
}
written
}