Skip to content

Commit bc3f5cc

Browse files
committed
Merge branch 'main' of https://github.com/CppCXY/lua-rs
2 parents 76dc862 + 92dd53e commit bc3f5cc

File tree

10 files changed

+1218
-796
lines changed

10 files changed

+1218
-796
lines changed

crates/luars/src/compiler/expr.rs

Lines changed: 261 additions & 376 deletions
Large diffs are not rendered by default.

crates/luars/src/compiler/helpers.rs

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ pub fn add_local_with_attrs(
199199
register,
200200
is_const,
201201
is_to_be_closed,
202+
needs_close: false,
202203
};
203204
c.scope_chain.borrow_mut().locals.push(local);
204205

@@ -271,9 +272,11 @@ pub fn resolve_upvalue_from_chain(c: &mut Compiler, name: &str) -> Option<usize>
271272
fn resolve_in_parent_scope(scope: &Rc<RefCell<ScopeChain>>, name: &str) -> Option<(bool, u32)> {
272273
// First, search in this scope's locals
273274
{
274-
let scope_ref = scope.borrow();
275-
if let Some(local) = scope_ref.locals.iter().rev().find(|l| l.name == name) {
275+
let mut scope_ref = scope.borrow_mut();
276+
if let Some(local) = scope_ref.locals.iter_mut().rev().find(|l| l.name == name) {
276277
let register = local.register;
278+
// Mark this local as captured by a closure - needs CLOSE on scope exit
279+
local.needs_close = true;
277280
// Found as local - return (true, register)
278281
return Some((true, register));
279282
}
@@ -568,8 +571,15 @@ pub fn try_expr_as_constant(c: &mut Compiler, expr: &emmylua_parser::LuaExpr) ->
568571

569572
/// Begin a new loop (for break statement support)
570573
pub fn begin_loop(c: &mut Compiler) {
574+
begin_loop_with_register(c, c.freereg);
575+
}
576+
577+
/// Begin a new loop with a specific first register for CLOSE
578+
pub fn begin_loop_with_register(c: &mut Compiler, first_reg: u32) {
571579
c.loop_stack.push(super::LoopInfo {
572580
break_jumps: Vec::new(),
581+
scope_depth: c.scope_depth,
582+
first_local_register: first_reg,
573583
});
574584
}
575585

@@ -589,6 +599,35 @@ pub fn emit_break(c: &mut Compiler) -> Result<(), String> {
589599
return Err("break statement outside loop".to_string());
590600
}
591601

602+
// Before breaking, check if we need to close any captured upvalues
603+
// This is needed when break jumps past the CLOSE instruction that would
604+
// normally be executed at the end of each iteration
605+
let loop_info = c.loop_stack.last().unwrap();
606+
let loop_scope_depth = loop_info.scope_depth;
607+
let first_reg = loop_info.first_local_register;
608+
609+
// Find minimum register of captured locals in the loop scope
610+
let mut min_close_reg: Option<u32> = None;
611+
{
612+
let scope = c.scope_chain.borrow();
613+
for local in scope.locals.iter().rev() {
614+
if local.depth < loop_scope_depth {
615+
break; // Only check loop scope and nested scopes
616+
}
617+
if local.needs_close && local.register >= first_reg {
618+
min_close_reg = Some(match min_close_reg {
619+
None => local.register,
620+
Some(min_reg) => min_reg.min(local.register),
621+
});
622+
}
623+
}
624+
}
625+
626+
// Emit CLOSE if needed
627+
if let Some(reg) = min_close_reg {
628+
emit(c, Instruction::encode_abc(OpCode::Close, reg, 0, 0));
629+
}
630+
592631
let jump_pos = emit_jump(c, OpCode::Jmp);
593632
c.loop_stack.last_mut().unwrap().break_jumps.push(jump_pos);
594633
Ok(())
@@ -675,3 +714,77 @@ pub fn check_unresolved_gotos(c: &Compiler) -> Result<(), String> {
675714
pub fn clear_scope_labels(c: &mut Compiler) {
676715
c.labels.retain(|l| l.scope_depth < c.scope_depth);
677716
}
717+
718+
/// Check if an expression is a vararg (...) literal
719+
pub fn is_vararg_expr(expr: &LuaExpr) -> bool {
720+
if let LuaExpr::LiteralExpr(lit) = expr {
721+
matches!(lit.get_literal(), Some(LuaLiteralToken::Dots(_)))
722+
} else {
723+
false
724+
}
725+
}
726+
727+
/// Result of parsing a Lua number literal
728+
#[derive(Debug, Clone, Copy)]
729+
pub enum ParsedNumber {
730+
/// Successfully parsed as integer
731+
Int(i64),
732+
/// Number is too large for i64, use float instead
733+
Float(f64),
734+
}
735+
736+
/// Parse a Lua integer literal from text, handling hex numbers that overflow i64
737+
/// Lua treats 0xFFFFFFFFFFFFFFFF as -1 (two's complement interpretation)
738+
/// For decimal numbers that overflow i64 range, returns Float instead
739+
pub fn parse_lua_int(text: &str) -> ParsedNumber {
740+
let text = text.trim();
741+
if text.starts_with("0x") || text.starts_with("0X") {
742+
// Hex number - parse as u64 first, then reinterpret as i64
743+
// This handles the case like 0xFFFFFFFFFFFFFFFF which should be -1
744+
let hex_part = &text[2..];
745+
// Remove any trailing decimal part (e.g., 0xFF.0)
746+
let hex_part = hex_part.split('.').next().unwrap_or(hex_part);
747+
if let Ok(val) = u64::from_str_radix(hex_part, 16) {
748+
return ParsedNumber::Int(val as i64); // Reinterpret bits as signed
749+
}
750+
}
751+
// Decimal case: parse as i64 only, if overflow use float
752+
// (Unlike hex, decimal numbers should NOT be reinterpreted as two's complement)
753+
if let Ok(val) = text.parse::<i64>() {
754+
return ParsedNumber::Int(val);
755+
}
756+
// Decimal number is too large for i64, parse as float
757+
if let Ok(val) = text.parse::<f64>() {
758+
return ParsedNumber::Float(val);
759+
}
760+
// Default fallback
761+
ParsedNumber::Int(0)
762+
}
763+
764+
/// Lua left shift: x << n (returns 0 if |n| >= 64)
765+
/// Negative n means right shift
766+
#[inline(always)]
767+
pub fn lua_shl(l: i64, r: i64) -> i64 {
768+
if r >= 64 || r <= -64 {
769+
0
770+
} else if r >= 0 {
771+
(l as u64).wrapping_shl(r as u32) as i64
772+
} else {
773+
// Negative shift means right shift (logical)
774+
(l as u64).wrapping_shr((-r) as u32) as i64
775+
}
776+
}
777+
778+
/// Lua right shift: x >> n (logical shift, returns 0 if |n| >= 64)
779+
/// Negative n means left shift
780+
#[inline(always)]
781+
pub fn lua_shr(l: i64, r: i64) -> i64 {
782+
if r >= 64 || r <= -64 {
783+
0
784+
} else if r >= 0 {
785+
(l as u64).wrapping_shr(r as u32) as i64
786+
} else {
787+
// Negative shift means left shift
788+
(l as u64).wrapping_shl((-r) as u32) as i64
789+
}
790+
}

crates/luars/src/compiler/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,14 @@ pub(crate) struct Local {
8383
pub register: u32,
8484
pub is_const: bool, // <const> attribute
8585
pub is_to_be_closed: bool, // <close> attribute
86+
pub needs_close: bool, // True if captured by a closure (needs CLOSE on scope exit)
8687
}
8788

8889
/// Loop information for break statements
8990
pub(crate) struct LoopInfo {
90-
pub break_jumps: Vec<usize>, // Positions of break statements to patch
91+
pub break_jumps: Vec<usize>, // Positions of break statements to patch
92+
pub scope_depth: usize, // Scope depth at loop start
93+
pub first_local_register: u32, // First register of loop-local variables (for CLOSE on break)
9194
}
9295

9396
/// Label definition

0 commit comments

Comments
 (0)