Skip to content

Commit

Permalink
Strict reg_replace optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
Anatoliy057 committed Apr 1, 2024
1 parent acce5f6 commit 606e6d2
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 46 deletions.
18 changes: 12 additions & 6 deletions src/main/java/com/laytonsmith/core/functions/Regex.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,19 +297,21 @@ public ParseTree optimizeDynamic(Target t, Environment env,
Set<Class<? extends Environment.EnvironmentImpl>> envs,
List<ParseTree> 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;
Expand Down Expand Up @@ -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
Expand Down
38 changes: 6 additions & 32 deletions src/main/java/com/laytonsmith/core/functions/StringHandling.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;

/**
*
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/com/laytonsmith/core/OptimizationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions src/test/java/com/laytonsmith/core/functions/RegexTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 606e6d2

Please sign in to comment.