@@ -522,20 +522,14 @@ CancellationToken cancellationToken
522522 MatchState matchState = new ( context . State , s , pattern ) ;
523523 var captures = matchState . Captures ;
524524
525- // Check for anchor at start
526- var anchor = pattern . Length > 0 && pattern [ 0 ] == '^' ;
527- var pIdx = anchor ? 1 : 0 ;
528-
529525 // Include the end position so patterns can match an empty string there.
530526 for ( var sIdx = start ; sIdx <= s . Length ; sIdx ++ )
531527 {
532528 // Reset match state for each attempt
533529 matchState . Level = 0 ;
534530 matchState . MatchDepth = MatchState . MaxCalls ;
535- // Clear captures to avoid stale data
536- Array . Clear ( captures , 0 , captures . Length ) ;
537531
538- var res = matchState . Match ( sIdx , pIdx ) ;
532+ var res = matchState . Match ( sIdx , 0 ) ;
539533
540534 if ( res >= 0 )
541535 {
@@ -551,31 +545,17 @@ CancellationToken cancellationToken
551545 var buffer = context . GetReturnBuffer ( resultLength ) ;
552546 for ( var i = 0 ; i < matchState . Level ; i ++ )
553547 {
554- var capture = captures [ i ] ;
555- if ( capture . IsPosition )
556- {
557- buffer [ i ] = capture . Init + 1 ; // 1-based position
558- }
559- else
560- {
561- buffer [ i ] = s . AsSpan ( capture . Init , capture . Len ) . ToString ( ) ;
562- }
548+ buffer [ i ] = matchState . GetCapture ( i ) ;
563549 }
564550
565551 // Update start index for next iteration
566552 // Handle empty matches by advancing at least 1 position
567553 upValues [ 2 ] = res > sIdx ? res : sIdx + 1 ;
568554 return new ( resultLength ) ;
569555 }
570-
571- // For anchored patterns, only try once
572- if ( anchor )
573- {
574- break ;
575- }
576556 }
577557
578- return new ( context . Return ( LuaValue . Nil ) ) ;
558+ return new ( context . Return ( ) ) ;
579559 }
580560 )
581561 )
@@ -654,18 +634,36 @@ CancellationToken cancellationToken
654634 var repl = context . GetArgument ( 2 ) ;
655635 var n_arg = context . HasArgument ( 3 ) ? context . GetArgument < double > ( 3 ) : s . Length + 1 ;
656636
657- LuaRuntimeException . ThrowBadArgumentIfNumberIsNotInteger ( context . State , 4 , n_arg ) ;
658-
659- var n = ( int ) n_arg ;
637+ n_arg = Math . Truncate ( n_arg ) ;
638+ if ( ! MathEx . IsFinite ( n_arg ) )
639+ LuaRuntimeException . BadArgumentNumberIsNotInteger ( context . State , 4 ) ;
640+ if (
641+ repl . Type
642+ is not (
643+ LuaValueType . String
644+ or LuaValueType . Number
645+ or LuaValueType . Table
646+ or LuaValueType . Function
647+ )
648+ )
649+ LuaRuntimeException . BadArgument ( context . State , 3 , "string/function/table expected" ) ;
650+ // Lua 5.2 stores the limit as size_t; a negative integer means no practical limit.
651+ var n = n_arg < 0 || n_arg > int . MaxValue ? int . MaxValue : ( int ) n_arg ;
652+ if ( n == 0 )
653+ return context . Return ( s , 0 ) ;
660654
661- // Use MatchState instead of regex
662655 MatchState matchState = new ( context . State , s , pattern ) ;
663656 var captures = matchState . Captures ;
664657
665658 StringBuilder builder = new ( ) ;
659+ var replacementText =
660+ repl . Type == LuaValueType . String ? repl . UnsafeReadString ( )
661+ : repl . Type == LuaValueType . Number
662+ ? repl . UnsafeReadDouble ( ) . ToString ( default , CultureInfo . InvariantCulture )
663+ : null ;
666664 var replacedBuilder =
667- repl . Type == LuaValueType . String
668- ? new StringBuilder ( repl . UnsafeReadString ( ) . Length )
665+ replacementText != null && replacementText . IndexOf ( '%' ) >= 0
666+ ? new StringBuilder ( replacementText . Length )
669667 : null ;
670668 var lastIndex = 0 ;
671669 var replaceCount = 0 ;
@@ -680,11 +678,6 @@ CancellationToken cancellationToken
680678 // Reset match state for each attempt
681679 matchState . Level = 0 ;
682680 Debug . Assert ( matchState . MatchDepth == MatchState . MaxCalls ) ;
683- // Clear captures array to avoid stale data
684- for ( var i = 0 ; i < captures . Length ; i ++ )
685- {
686- captures [ i ] = default ;
687- }
688681
689682 // Always start pattern from beginning (0 or 1 if anchored)
690683 var pIdx = anchor ? 1 : 0 ;
@@ -704,34 +697,30 @@ CancellationToken cancellationToken
704697 }
705698
706699 LuaValue result ;
707- if ( repl . TryRead < string > ( out var str ) )
700+ if ( replacementText != null )
708701 {
709- if ( ! str . Contains ( "%" ) )
702+ if ( replacedBuilder == null )
710703 {
711- result = str ; // No special characters, use as is
704+ result = replacementText ;
712705 }
713706 else
714707 {
715708 // String replacement
716- replacedBuilder ! . Clear ( ) ;
717- AppendReplacement ( replacedBuilder , str , s , sIdx , res , matchState ) ;
709+ replacedBuilder . Clear ( ) ;
710+ AppendReplacement (
711+ replacedBuilder ,
712+ replacementText ,
713+ s ,
714+ sIdx ,
715+ res ,
716+ matchState
717+ ) ;
718718 result = replacedBuilder . ToString ( ) ;
719719 }
720720 }
721721 else if ( repl . TryRead < LuaTable > ( out var table ) )
722722 {
723- // Table lookup - use first capture or whole match
724- LuaValue key ;
725- if ( matchState . Level > 0 )
726- {
727- key = captures [ 0 ] . IsPosition
728- ? captures [ 0 ] . Init + 1
729- : s . AsSpan ( captures [ 0 ] . Init , captures [ 0 ] . Len ) . ToString ( ) ;
730- }
731- else
732- {
733- key = s . AsSpan ( sIdx , res - sIdx ) . ToString ( ) ;
734- }
723+ var key = matchState . GetCapture ( 0 ) ;
735724
736725 if ( ! table . TryGetValue ( key , out result ) && table . Metatable != null )
737726 {
@@ -743,60 +732,32 @@ CancellationToken cancellationToken
743732 ) ;
744733 }
745734 }
746- else if ( repl . TryRead < LuaFunction > ( out var func ) )
735+ else
747736 {
737+ var func = repl . UnsafeRead < LuaFunction > ( ) ;
748738 // Function call with captures as arguments
749739 var stack = context . State . Stack ;
750740
751- if ( matchState . Level == 0 )
741+ for ( var k = 0 ; k < matchState . Level ; k ++ )
752742 {
753- // No captures, pass whole match
754- stack . Push ( s . AsSpan ( sIdx , res - sIdx ) . ToString ( ) ) ;
755- var retCount = await context . State . RunAsync ( func , 1 , cancellationToken ) ;
756- using var results = context . State . ReadStack ( retCount ) ;
757- result = results . Count > 0 ? results [ 0 ] : LuaValue . Nil ;
743+ stack . Push ( matchState . GetCapture ( k ) ) ;
758744 }
759- else
760- {
761- // Pass all captures
762- for ( var k = 0 ; k < matchState . Level ; k ++ )
763- {
764- var capture = captures [ k ] ;
765- if ( capture . IsPosition )
766- {
767- stack . Push ( capture . Init + 1 ) ; // 1-based position
768- }
769- else
770- {
771- stack . Push ( s . AsSpan ( capture . Init , capture . Len ) . ToString ( ) ) ;
772- }
773- }
774-
775- var retCount = await context . State . RunAsync (
776- func ,
777- matchState . Level ,
778- cancellationToken
779- ) ;
780- using var results = context . State . ReadStack ( retCount ) ;
781- result = results . Count > 0 ? results [ 0 ] : LuaValue . Nil ;
782- }
783- }
784- else
785- {
786- throw new LuaRuntimeException (
787- context . State ,
788- "bad argument #3 to 'gsub' (string/function/table expected)"
745+ var retCount = await context . State . RunAsync (
746+ func ,
747+ matchState . Level ,
748+ cancellationToken
789749 ) ;
750+ using var results = context . State . ReadStack ( retCount ) ;
751+ result = results . Count > 0 ? results [ 0 ] : LuaValue . Nil ;
790752 }
791-
792753 // Handle replacement result
793754 if ( result . TryRead < string > ( out var rs ) )
794755 {
795756 builder . Append ( rs ) ;
796757 }
797758 else if ( result . TryRead < double > ( out var rd ) )
798759 {
799- builder . Append ( rd ) ;
760+ AppendNumber ( builder , rd ) ;
800761 }
801762 else if ( ! result . ToBoolean ( ) )
802763 {
@@ -807,7 +768,7 @@ CancellationToken cancellationToken
807768 {
808769 throw new LuaRuntimeException (
809770 context . State ,
810- $ "invalid replacement value (a { result . Type } )"
771+ $ "invalid replacement value (a { result . TypeToString ( ) } )"
811772 ) ;
812773 }
813774
@@ -894,33 +855,22 @@ public ValueTask<int> FindAux(LuaFunctionExecutionContext context, bool find)
894855 {
895856 var s = context . GetArgument < string > ( 0 ) ;
896857 var pattern = context . GetArgument < string > ( 1 ) ;
897- var init = context . HasArgument ( 2 ) ? context . GetArgument < int > ( 2 ) : 1 ;
898-
899- LuaRuntimeException . ThrowBadArgumentIfNumberIsNotInteger ( context . State , 3 , init ) ;
900-
901- // Convert to 0-based index
902- if ( init < 0 )
903- {
904- init = s . Length + init + 1 ;
905- }
906-
907- init -- ; // Convert from 1-based to 0-based
858+ var init = context . HasArgument ( 2 ) ? ReadStringIndex ( context , 2 ) : 1 ;
859+ var offset = Math . Max ( 1 , StringHelper . RelativePosition ( init , s . Length ) ) - 1 ;
908860
909861 // Check if init is beyond string bounds
910- if ( init > s . Length )
862+ if ( offset > s . Length )
911863 {
912864 return new ( context . Return ( LuaValue . Nil ) ) ;
913865 }
914866
915- init = Math . Max ( 0 , init ) ; // Clamp to 0 if negative
916-
917867 // Check for plain search mode (4th parameter = true) or if pattern has no special characters
918868 if ( find && ( context . GetArgumentOrDefault ( 3 ) . ToBoolean ( ) || MatchState . NoSpecials ( pattern ) ) )
919869 {
920- return PlainSearch ( context , s , pattern , init ) ;
870+ return PlainSearch ( context , s , pattern , offset ) ;
921871 }
922872
923- return PatternSearch ( context , s , pattern , init , find ) ;
873+ return PatternSearch ( context , s , pattern , offset , find ) ;
924874 }
925875
926876 static ValueTask < int > PlainSearch (
@@ -961,7 +911,6 @@ bool find
961911 // Reset match state for each attempt
962912 matchState . Level = 0 ;
963913 matchState . MatchDepth = MatchState . MaxCalls ;
964- Array . Clear ( captures , 0 , captures . Length ) ;
965914
966915 var res = matchState . Match ( sIdx , pIdx ) ;
967916
@@ -989,15 +938,7 @@ bool find
989938 // Return captures
990939 for ( var i = 0 ; i < matchState . Level ; i ++ )
991940 {
992- var capture = captures [ i ] ;
993- if ( capture . IsPosition )
994- {
995- buffer [ i ] = capture . Init + 1 ; // 1-based position
996- }
997- else
998- {
999- buffer [ i ] = s . AsSpan ( capture . Init , capture . Len ) . ToString ( ) ;
1000- }
941+ buffer [ i ] = matchState . GetCapture ( i ) ;
1001942 }
1002943
1003944 return new ( resultLength ) ;
@@ -1140,4 +1081,11 @@ static int ReadStringIndex(in LuaFunctionExecutionContext context, int index)
11401081 return ( int ) Math . Min ( int . MaxValue , Math . Max ( int . MinValue , number ) ) ;
11411082#endif
11421083 }
1084+
1085+ static void AppendNumber ( StringBuilder builder , double value )
1086+ {
1087+ Span < char > buffer = stackalloc char [ 32 ] ;
1088+ value . TryFormat ( buffer , out var written , default , CultureInfo . InvariantCulture ) ;
1089+ builder . Append ( buffer [ .. written ] ) ;
1090+ }
11431091}
0 commit comments