AutoHotkey caches the last 100 regex patterns, so callouts and function objects are inherently unsafe to use in this manner at present. Even if the reference counting is corrected to prevent the crash, the callout would not be updated unless the pattern happens to have been pushed out of the cache (by compiling 100 other patterns). This would mean that generally only the first call to your function would work, because every other call would refer to the wrong callout. You can verify this by adding
ObjPtrAddRef(Callout).
Correcting the reference counting would mean not only calling AddRef, but also releasing the callout when the compiled pattern is deleted. The callout is currently baked directly into the compiled bytecode, so this wouldn't be trivial.
Another issue is that cached patterns are not scoped to the function, so if there are multiple functions in the script named X, a callout such as
(?CX) may call the wrong function, depending on which function caused the pattern to be cached. Since we work with variables now and not purely functions, there can be similar issues when the same pattern is used repeatedly within the one function, after assigning different values to the variable X.
The simplest solution is probably to resolve callouts each time they are called. This would reduce performance, but I think callouts are rarely the most efficient solution to a problem, unless they are the only solution.
As for iterating over matches, using RegExMatch in a loop is both cleaner and more efficient than using RegExReplace with a callout.
Code: Select all
RegExReplaceF(Haystack, NeedleRegEx, CallBack, Limit := -1, StartingPosition := 1) {
Ret := ''
while RegExMatch(Haystack, NeedleRegEx, &Match, StartPos := IsSet(Match) ? Match.Pos + Match.Len : 1)
Ret .= SubStr(Haystack, StartPos, Match.Pos - StartPos) CallBack(Match)
return Ret SubStr(HayStack, StartPos)
}
I tested against two implementations based on yours:
Code: Select all
; Use static variables to prevent the nested function from becoming a closure.
RegExReplaceEx(Haystack, NeedleRegEx, CallBack, Limit := -1, StartingPosition := 1) {
static LastFoundPos, Ret, sCallBack
LastFoundPos := 1, Ret := '', sCallBack := CallBack
RegExReplace(Haystack, NeedleRegEx '(?CCallout)', '', , Limit, StartingPosition)
return Ret SubStr(Haystack, LastFoundPos)
Callout(Match, CalloutNumber, FoundPos, Haystack, *) {
Ret .= SubStr(Haystack, LastFoundPos, FoundPos - LastFoundPos) sCallBack(Match)
LastFoundPos := FoundPos + Match.Len
}
}
Code: Select all
; Use pcre_callout instead of an explicitly named callout.
RegExReplaceEx(Haystack, NeedleRegEx, CallBack, Limit := -1, StartingPosition := 1) {
local LastFoundPos := 1, Ret := ''
RegExReplace(Haystack, NeedleRegEx '(?C)', '', , Limit, StartingPosition)
return Ret SubStr(Haystack, LastFoundPos)
pcre_callout(Match, CalloutNumber, FoundPos, *) => (Ret .= SubStr(Haystack, LastFoundPos, FoundPos - LastFoundPos) CallBack(Match), LastFoundPos := FoundPos + StrLen(Match[0]), 0)
}