This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-175 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 732f933cec0f0d0ee442702aefc00a50959a7f59 Author: Dmitri Blinov <[email protected]> AuthorDate: Mon Aug 20 12:00:36 2018 +0300 Introduce regex Pattern literal ~// --- .../apache/commons/jexl3/internal/Debugger.java | 7 +++ .../apache/commons/jexl3/internal/Interpreter.java | 8 +++ .../commons/jexl3/internal/ScriptVisitor.java | 6 +++ .../commons/jexl3/parser/ASTRegexLiteral.java | 58 ++++++++++++++++++++++ .../org/apache/commons/jexl3/parser/Parser.jjt | 19 +++++++ .../apache/commons/jexl3/parser/ParserVisitor.java | 2 + .../apache/commons/jexl3/parser/StringParser.java | 30 +++++++++++ src/site/xdoc/reference/syntax.xml | 8 +++ .../commons/jexl3/ArithmeticOperatorTest.java | 10 ++++ 9 files changed, 148 insertions(+) diff --git a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java index db75d81..5a57fc0 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Debugger.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Debugger.java @@ -70,6 +70,7 @@ import org.apache.commons.jexl3.parser.ASTOrNode; import org.apache.commons.jexl3.parser.ASTRangeNode; import org.apache.commons.jexl3.parser.ASTReference; import org.apache.commons.jexl3.parser.ASTReferenceExpression; +import org.apache.commons.jexl3.parser.ASTRegexLiteral; import org.apache.commons.jexl3.parser.ASTReturnStatement; import org.apache.commons.jexl3.parser.ASTSWNode; import org.apache.commons.jexl3.parser.ASTSetAddNode; @@ -906,6 +907,12 @@ public class Debugger extends ParserVisitor implements JexlInfo.Detail { } @Override + protected Object visit(ASTRegexLiteral node, Object data) { + String img = node.getLiteral().replace("/", "\\/"); + return check(node, "~/" + img + "/", data); + } + + @Override protected Object visit(ASTTernaryNode node, Object data) { accept(node.jjtGetChild(0), data); if (node.jjtGetNumChildren() > 2) { diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index 0ff4796..5098b9c 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -84,6 +84,7 @@ import org.apache.commons.jexl3.parser.ASTOrNode; import org.apache.commons.jexl3.parser.ASTRangeNode; import org.apache.commons.jexl3.parser.ASTReference; import org.apache.commons.jexl3.parser.ASTReferenceExpression; +import org.apache.commons.jexl3.parser.ASTRegexLiteral; import org.apache.commons.jexl3.parser.ASTReturnStatement; import org.apache.commons.jexl3.parser.ASTSWNode; import org.apache.commons.jexl3.parser.ASTSetAddNode; @@ -111,7 +112,9 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import java.util.concurrent.Callable; + import org.apache.commons.jexl3.JxltEngine; @@ -788,6 +791,11 @@ public class Interpreter extends InterpreterBase { } @Override + protected Object visit(ASTRegexLiteral node, Object data) { + return Pattern.compile(node.getLiteral()); + } + + @Override protected Object visit(ASTArrayLiteral node, Object data) { int childCount = node.jjtGetNumChildren(); JexlArithmetic.ArrayBuilder ab = arithmetic.arrayBuilder(childCount); diff --git a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java index 8f72800..1f5015a 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java +++ b/src/main/java/org/apache/commons/jexl3/internal/ScriptVisitor.java @@ -70,6 +70,7 @@ import org.apache.commons.jexl3.parser.ASTOrNode; import org.apache.commons.jexl3.parser.ASTRangeNode; import org.apache.commons.jexl3.parser.ASTReference; import org.apache.commons.jexl3.parser.ASTReferenceExpression; +import org.apache.commons.jexl3.parser.ASTRegexLiteral; import org.apache.commons.jexl3.parser.ASTReturnStatement; import org.apache.commons.jexl3.parser.ASTSWNode; import org.apache.commons.jexl3.parser.ASTSetAddNode; @@ -355,6 +356,11 @@ public class ScriptVisitor extends ParserVisitor { } @Override + protected Object visit(ASTRegexLiteral node, Object data) { + return visitNode(node, data); + } + + @Override protected Object visit(ASTSetLiteral node, Object data) { return visitNode(node, data); } diff --git a/src/main/java/org/apache/commons/jexl3/parser/ASTRegexLiteral.java b/src/main/java/org/apache/commons/jexl3/parser/ASTRegexLiteral.java new file mode 100644 index 0000000..30d4dc8 --- /dev/null +++ b/src/main/java/org/apache/commons/jexl3/parser/ASTRegexLiteral.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.commons.jexl3.parser; + +public final class ASTRegexLiteral extends JexlNode implements JexlNode.Constant<String> { + /** The actual literal value; the inherited 'value' member may host a cached getter. */ + private String literal = null; + + ASTRegexLiteral(int id) { + super(id); + } + + ASTRegexLiteral(Parser p, int id) { + super(p, id); + } + + @Override + public String toString() { + return this.literal; + } + + /** + * Gets the literal value. + * @return the string literal + */ + @Override + public String getLiteral() { + return this.literal; + } + + @Override + protected boolean isConstant(boolean literal) { + return true; + } + + void setLiteral(String literal) { + this.literal = literal; + } + + @Override + public Object jjtAccept(ParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} diff --git a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt index 6bcaae4..0574099 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt +++ b/src/main/java/org/apache/commons/jexl3/parser/Parser.jjt @@ -265,6 +265,13 @@ TOKEN_MGR_DECLS : { > { popDot(); } /* Revert state to default if was DOT_ID. */ } +<*> TOKEN : +{ + < REGEX_LITERAL: + "~" "/" (~["/","\n","\r","\u2028","\u2029"] | "\\" "/" )* "/" + > { popDot(); } /* Revert state to default if was DOT_ID. */ +} + /*************************************** * Statements ***************************************/ @@ -624,6 +631,8 @@ void Literal() #void : | StringLiteral() | + RegexLiteral() +| NullLiteral() | NaNLiteral() @@ -686,6 +695,16 @@ void JxltLiteral() #JxltLiteral : { jjtThis.setLiteral(Parser.buildString(t.image, true)); } } +void RegexLiteral() : +{ + Token t; +} +{ + t=<REGEX_LITERAL> + { jjtThis.setLiteral(Parser.buildRegex(t.image)); } +} + + void ExtendedLiteral() #ExtendedLiteral() : {} { <ELIPSIS> diff --git a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java index 0304738..e20f670 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java +++ b/src/main/java/org/apache/commons/jexl3/parser/ParserVisitor.java @@ -128,6 +128,8 @@ public abstract class ParserVisitor { protected abstract Object visit(ASTStringLiteral node, Object data); + protected abstract Object visit(ASTRegexLiteral node, Object data); + protected abstract Object visit(ASTSetLiteral node, Object data); protected abstract Object visit(ASTExtendedLiteral node, Object data); diff --git a/src/main/java/org/apache/commons/jexl3/parser/StringParser.java b/src/main/java/org/apache/commons/jexl3/parser/StringParser.java index f58288d..6fa6479 100644 --- a/src/main/java/org/apache/commons/jexl3/parser/StringParser.java +++ b/src/main/java/org/apache/commons/jexl3/parser/StringParser.java @@ -54,6 +54,36 @@ public class StringParser { } /** + * Builds a regex pattern string, handles escaping '/' through '\/' syntax. + * @param str the string to build from + * @return the built string + */ + public static String buildRegex(CharSequence str) { + + int length = str.length(); + + StringBuilder strb = new StringBuilder(length); + + int start = 2; + int end = length - 1; + + for (int i = start; i < end; ++i) { + char c = str.charAt(i); + if (c == '\\') { + if (i+1 < end && str.charAt(i+1) == '/') { + strb.append("/"); + i++; + } else { + strb.append(c); + } + } else { + strb.append(c); + } + } + return strb.toString(); + } + + /** * Read the remainder of a string till a given separator, * handles escaping through '\' syntax. * @param strb the destination buffer to copy characters into diff --git a/src/site/xdoc/reference/syntax.xml b/src/site/xdoc/reference/syntax.xml index e0ac82d..1c63bd0 100644 --- a/src/site/xdoc/reference/syntax.xml +++ b/src/site/xdoc/reference/syntax.xml @@ -346,6 +346,14 @@ </td> </tr> <tr> + <td>Regex literals</td> + <td> + Start with <code>~/</code> and ends with <code>/</code> delimiters, e.g. + <code>~/ABC.*/</code> + <p>The escape character is <code>\</code> (backslash); it only escapes the string delimiter <code>\</code> (slash)</p> + </td> + </tr> + <tr> <td>Boolean literals</td> <td> The literals <code>true</code> and <code>false</code> can be used, e.g. diff --git a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java index 0a03a0b..c7c051b 100644 --- a/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java +++ b/src/test/java/org/apache/commons/jexl3/ArithmeticOperatorTest.java @@ -91,6 +91,16 @@ public class ArithmeticOperatorTest extends JexlTestCase { } @Test + public void testRegexp2() throws Exception { + asserter.setVariable("str", "abc456"); + asserter.assertExpression("str =~ ~/.*456/", Boolean.TRUE); + asserter.assertExpression("str !~ ~/ABC.*/", Boolean.TRUE); + asserter.assertExpression("str =~ ~/abc\\d{3}/", Boolean.TRUE); + asserter.setVariable("str", "4/6"); + asserter.assertExpression("str =~ ~/\\d\\/\\d/", Boolean.TRUE); + } + + @Test public void testStartsEndsWithString() throws Exception { asserter.setVariable("x", "foobar"); asserter.assertExpression("x =^ 'foo'", Boolean.TRUE);
