@@ -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>
271272fn 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)
570573pub 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> {
675714pub 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+ }
0 commit comments