This is about the simplest example I can come up with (and actually not a far cry from the actual use case) that I anticipate people might not tell me to simply change the regex itself, but here goes.
Say I have these lovely nonsensical input words: sent seimk semek t͡ʃeno eint͡ɬi em t͡ʃeʃeimp t͡ɬent͡ɬien keinsen
And I want to search replace all instances of ei OR e with a before n or m, which (ei|e)(?:n|m) matches, UNLESS
it is directly preceded (i.e. no negative lookbehinds, since they'll cause a false negative) by
t͡ɬ,t͡ʃ, ord͡ʒ, so i.e.(?:t͡ɬ|t͡ʃ|d͡ʒ)(ei|e), orif the n/m is directly succeeded (i.e. no negative lookaheads, for the same reason) by
p,t, ork, i.e. if(ei|e)(?:n|m)(?:p|t|k)matches
The desired output is therefore sent seimk samek t͡ʃeno ant͡ɬi am t͡ʃeʃeimp t͡ɬent͡ɬian kansan.
So if the "find and replace" function in JS is String.replace(RegExp pattern, String replacement), then then you would have to compress all 3 regexes into 1 for the first argument, which 1) I don't think is possible? It would require negative non-capturing groups which... aren't a thing, right? and 2) in the actual use case, the regex patterns are generated by a text parser, not by hand, and I don't have enough faith in my ability to write a parser smart enough to optimize.
The other way I thought about doing this was to simply cache all the matches to the first regex in a dictionary, and then remove everything that should be ruled out by the other two, but when you look at the output:
let sEnv = "(ei|e)(?:n|m)";
let reEnv = new RegExp(sEnv, "g");
let sExc1 = "(?:t͡ɬ|t͡ʃ|d͡ʒ)(ei|e)";
let reExc1 = new RegExp(sExc1, "g");
let sExc2 = "(ei|e)(?:n|m)(?:p|t|k)";
let reExc2 = new RegExp(sExc2, "g");
let sWords = "sent seimk semek t͡ʃeno eint͡ɬi em t͡ʃeʃeimp t͡ɬent͡ɬien keinsen";
let tWords = sWords.split(" ");
for (i = 0; i < tWords.length; i++){
let sCurrentWord = tWords[i];
let result;
let tMatches = {}
// first cache all the matches (ignoring the exceptions)
while (result = reEnv.exec(sCurrentWord)) {
tMatches[result.index] = result[0].length;
}
// then remove the exceptions
while (result = reExc1.exec(sCurrentWord)) {
delete tMatches[result.index];
}
while (result = reExc2.exec(sCurrentWord)) {
delete tMatches[result.index];
}
// then apply all remaining matches
let sOutput = sCurrentWord;
for (var index in tMatches){
console.log(sCurrentWord+": starting at "+index+", "+tMatches[index]+" chars long");
}
}
It's... a bit confused. Not only is it apparently capturing the n despite being in a non-capturing group (result length printed keeps being 2 when the thing I want to capture is only 1 char long), but it also filtered out eint͡ɬi but kept t͡ʃeno as matches, which is the opposite of what it's supposed to do - and as the length of the input list grows this method must get quite slow.
How else can I go about this find-and-replace?