diff --git a/src/main/java/com/laytonsmith/core/functions/Regex.java b/src/main/java/com/laytonsmith/core/functions/Regex.java index 8aa3cdc8e..04d73fa60 100644 --- a/src/main/java/com/laytonsmith/core/functions/Regex.java +++ b/src/main/java/com/laytonsmith/core/functions/Regex.java @@ -297,19 +297,21 @@ public ParseTree optimizeDynamic(Target t, Environment env, Set> envs, List children, FileOptions fileOptions) throws ConfigCompileException, ConfigRuntimeException { - ParseTree data = children.get(0); - if(!Construct.IsDynamicHelper(data.getData())) { - String pattern = data.getData().val(); - if(isLiteralRegex(pattern)) { + ParseTree patternArg = children.get(0); + ParseTree replacementArg = children.get(1); + if(!Construct.IsDynamicHelper(patternArg.getData()) && !Construct.IsDynamicHelper(replacementArg.getData())) { + String pattern = patternArg.getData().val(); + String replacement = replacementArg.getData().val(); + if(isLiteralRegex(pattern) && !isBackreference(replacement)) { //We want to replace this with replace() //Note the alternative order of arguments - ParseTree replaceNode = new ParseTree(new CFunction(replace.NAME, t), data.getFileOptions()); + ParseTree replaceNode = new ParseTree(new CFunction(replace.NAME, t), patternArg.getFileOptions()); replaceNode.addChildAt(0, children.get(2)); //subject -> main replaceNode.addChildAt(1, new ParseTree(new CString(getLiteralRegex(pattern), t), replaceNode.getFileOptions())); //pattern -> what replaceNode.addChildAt(2, children.get(1)); //replacement -> that return replaceNode; } else { - getPattern(data.getData(), t); + getPattern(patternArg.getData(), t); } } return null; @@ -619,6 +621,10 @@ private static Pattern getPattern(Mixed c, Target t) throws ConfigRuntimeExcepti } } + private static boolean isBackreference(String replacement) { + return replacement.length() > 0 && replacement.charAt(0) == '$'; + } + private static boolean isLiteralRegex(String regex) { //These are the special characters in a regex. If a regex does not contain any of these //characters, we can use a faster method in many cases, though the extra overhead of doing diff --git a/src/main/java/com/laytonsmith/core/functions/StringHandling.java b/src/main/java/com/laytonsmith/core/functions/StringHandling.java index 84208c34e..f8b9592a1 100644 --- a/src/main/java/com/laytonsmith/core/functions/StringHandling.java +++ b/src/main/java/com/laytonsmith/core/functions/StringHandling.java @@ -13,7 +13,6 @@ import com.laytonsmith.annotations.seealso; import com.laytonsmith.core.ArgumentValidation; import com.laytonsmith.core.MSVersion; -import com.laytonsmith.core.ObjectGenerator; import com.laytonsmith.core.Optimizable; import com.laytonsmith.core.ParseTree; import com.laytonsmith.core.Static; @@ -49,7 +48,6 @@ import com.laytonsmith.core.exceptions.CancelCommandException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigRuntimeException; -import com.laytonsmith.core.natives.interfaces.Callable; import com.laytonsmith.core.natives.interfaces.Mixed; import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; @@ -66,8 +64,6 @@ import com.laytonsmith.core.natives.interfaces.Sizeable; import java.lang.reflect.InaccessibleObjectException; import java.util.UUID; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; /** * @@ -306,35 +302,15 @@ public Integer[] numArgs() { @Override public Mixed exec(Target t, Environment env, Mixed... args) throws CancelCommandException, ConfigRuntimeException { - String subject = args[0].val(); - String search = args[1].val(); - Mixed replacement = args[2]; - String ret = ""; - - if(replacement instanceof Callable replacer) { - try { - Pattern pattern = Pattern.compile(search); - ret = pattern.matcher(subject).replaceAll(mr -> ArgumentValidation.getStringObject( - replacer.executeCallable(env, t, ObjectGenerator.GetGenerator().regMatchValue(mr, t)), t)); - } catch (PatternSyntaxException e) { - throw new CREFormatException(e.getMessage(), args[0].getTarget()); - } catch (IndexOutOfBoundsException e) { - throw new CREFormatException("Expecting a regex group at parameter 2 of replace", t); - } catch (IllegalArgumentException e) { - throw new CREFormatException(e.getMessage(), t); - } - } else { - ret = subject.replace(search, replacement.val()); - } - - return new CString(ret, t); + String thing = args[0].val(); + String what = args[1].val(); + String that = args[2].val(); + return new CString(thing.replace(what, that), t); } @Override public String docs() { - return "string {subject, search, replacement} Replaces all instances of 'search' with 'replacement' in 'subject'." - + " 'replacement' can be a closure that necessarily returns a string value as a replacement" - + " and search will be read as a regular expression."; + return "string {subject, search, replacement} Replaces all instances of 'search' with 'replacement' in 'subject'"; } @Override @@ -361,9 +337,7 @@ public Boolean runAsync() { public ExampleScript[] examples() throws ConfigCompileException { return new ExampleScript[]{ new ExampleScript("", "replace('Where in the world is Carmen Sandiego?', 'Carmen Sandiego', 'Waldo')"), - new ExampleScript("No match found", "replace('The same thing', 'not found', '404')"), - new ExampleScript("Using closure as replacement function", - "replace('I love dogs', 'dogs', closure() {return 'cats'},", "I love cats")}; + new ExampleScript("No match found", "replace('The same thing', 'not found', '404')")}; } @Override diff --git a/src/test/java/com/laytonsmith/core/OptimizationTest.java b/src/test/java/com/laytonsmith/core/OptimizationTest.java index b5c1325c5..4017d41cf 100644 --- a/src/test/java/com/laytonsmith/core/OptimizationTest.java +++ b/src/test/java/com/laytonsmith/core/OptimizationTest.java @@ -216,7 +216,7 @@ public void testRegSplitOptimization2() throws Exception { @Test public void testRegReplaceOptimization1() throws Exception { - assertEquals("replace('this is a thing','thing',dyn('hi'))", optimize("reg_replace('thing', dyn('hi'), 'this is a thing')")); + assertEquals("replace('this is a thing','thing','hi')", optimize("reg_replace('thing', 'hi', 'this is a thing')")); } @Test diff --git a/src/test/java/com/laytonsmith/core/functions/RegexTest.java b/src/test/java/com/laytonsmith/core/functions/RegexTest.java index 0e4adcedc..9006f2465 100644 --- a/src/test/java/com/laytonsmith/core/functions/RegexTest.java +++ b/src/test/java/com/laytonsmith/core/functions/RegexTest.java @@ -76,8 +76,7 @@ public void testRegReplace() throws Exception { reg_replace('plate', closure(@match) {return 'mug'}, 'Lucy pushed the plate down.') """, null)); assertThrows(CRECastException.class, () -> SRun(""" - @subject = '' // for disable optimization - reg_replace(@subject, closure() {}, 'This is fine') + reg_replace('', closure() {}, 'This is fine') """, null)); } diff --git a/src/test/java/com/laytonsmith/core/functions/StringHandlingTest.java b/src/test/java/com/laytonsmith/core/functions/StringHandlingTest.java index 1cafdda59..cfc106127 100644 --- a/src/test/java/com/laytonsmith/core/functions/StringHandlingTest.java +++ b/src/test/java/com/laytonsmith/core/functions/StringHandlingTest.java @@ -2,7 +2,6 @@ import com.laytonsmith.abstraction.MCPlayer; import com.laytonsmith.core.constructs.Target; -import com.laytonsmith.core.exceptions.CRE.CRECastException; import com.laytonsmith.core.exceptions.CRE.CREFormatException; import com.laytonsmith.core.exceptions.ConfigCompileException; import com.laytonsmith.core.exceptions.ConfigCompileGroupException; @@ -14,7 +13,6 @@ import org.junit.After; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import org.junit.Before; import org.junit.BeforeClass; @@ -81,13 +79,11 @@ public void testRead() { } @Test(timeout = 10000) - public void testReplace() throws Exception { + public void testReplace() { StringHandling.replace a = new StringHandling.replace(); assertCEquals(C.onstruct("yay"), a.exec(Target.UNKNOWN, null, C.onstruct("yayathing"), C.onstruct("athing"), C.onstruct(""))); assertCEquals(C.onstruct("yaymonkey"), a.exec(Target.UNKNOWN, null, C.onstruct("yayathing"), C.onstruct("athing"), C.onstruct("monkey"))); assertCEquals(C.onstruct("yayathing"), a.exec(Target.UNKNOWN, null, C.onstruct("yayathing"), C.onstruct("wut"), C.onstruct("chicken"))); - assertEquals("ya ya oh no wow", SRun("replace('ya ya oh no', 'oh no', closure(@match) {return @match[0].' wow'})", null)); - assertThrows(CRECastException.class, () -> SRun("replace('ya ya oh no', 'oh no', closure() {})", null)); } @Test(timeout = 10000)