You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2015/10/07 21:26:50 UTC
[30/37] incubator-groovy git commit: ASTMatcher: Add constraints to
pattern matching * work in progress: add a MatchingConstraints class
corresponding to the constraints being applied to the pattern * constraints
are defined using a DSL called thro
ASTMatcher: Add constraints to pattern matching
* work in progress: add a MatchingConstraints class corresponding to the constraints being applied to the pattern
* constraints are defined using a DSL called through the ASTMatcher.withConstraints method
* rename some parameters in ASTMatcher to improve readability of algorithms
Project: http://git-wip-us.apache.org/repos/asf/incubator-groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-groovy/commit/bcd68a75
Tree: http://git-wip-us.apache.org/repos/asf/incubator-groovy/tree/bcd68a75
Diff: http://git-wip-us.apache.org/repos/asf/incubator-groovy/diff/bcd68a75
Branch: refs/heads/master
Commit: bcd68a75c6909820faf2f7a19b8068613f3e93de
Parents: cd39e26
Author: Cedric Champeau <ce...@gmail.com>
Authored: Sat Oct 18 10:40:54 2014 +0200
Committer: Sergei Egorov <bs...@gmail.com>
Committed: Mon Sep 28 14:33:12 2015 +0300
----------------------------------------------------------------------
.../groovy/macro/matcher/ASTMatcher.groovy | 101 +++++++++++++++----
.../matcher/ContextualClassCodeVisitor.java | 3 +-
.../macro/matcher/MatchingConstraints.groovy | 56 ++++++++++
.../groovy/macro/matcher/TreeContext.java | 23 ++++-
.../groovy/macro/matcher/ASTMatcherTest.groovy | 38 +++++++
5 files changed, 199 insertions(+), 22 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/bcd68a75/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ASTMatcher.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ASTMatcher.groovy b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ASTMatcher.groovy
index 5b5dc8e..727f39e 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ASTMatcher.groovy
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ASTMatcher.groovy
@@ -29,7 +29,7 @@ import org.codehaus.groovy.classgen.BytecodeExpression
import org.codehaus.groovy.control.SourceUnit
@CompileStatic
-class ASTMatcher extends ClassCodeVisitorSupport {
+class ASTMatcher extends ContextualClassCodeVisitor {
public static final String WILDCARD = "_";
@@ -67,8 +67,8 @@ class ASTMatcher extends ClassCodeVisitorSupport {
match = match && value
}
- private static boolean matchByName(String potentialWildcard, String node) {
- return node.equals(potentialWildcard) || WILDCARD.equals(potentialWildcard);
+ private static boolean matchByName(String patternText, String nodeText) {
+ return nodeText.equals(patternText) || WILDCARD.equals(patternText);
}
private static boolean isWildcardExpression(Object exp) {
@@ -91,21 +91,63 @@ class ASTMatcher extends ClassCodeVisitorSupport {
finder.matches
}
- private void doWithNode(Object search, Object next, Closure cl) {
- Class expectedClass = search?search.class:Object
+ private void storeContraints(ASTNode src) {
+ def constraints = src.getNodeMetaData(MatchingConstraints)
+ if (constraints) {
+ treeContext.putUserdata(MatchingConstraints, constraints)
+ }
+ }
+
+ private String findPlaceholder(Object exp) {
+ def constraints = (List<MatchingConstraints>) treeContext.getUserdata(MatchingConstraints, true)
+ if (constraints) {
+ def placeholders = constraints[0].placeholders
+ if ((exp instanceof VariableExpression && placeholders.contains(exp.name))) {
+ return exp.name
+ }
+ if ((exp instanceof ConstantExpression && placeholders.contains(exp.value))) {
+ return exp.value
+ }
+ }
+ null
+ }
+
+ private void doWithNode(Object patternNode, Object foundNode, Closure cl) {
+ Class expectedClass = patternNode?patternNode.class:Object
if (expectedClass == null) {
expectedClass = Object
}
- if (isWildcardExpression(search)) {
- return
+
+ boolean doPush = treeContext.node!=foundNode && foundNode instanceof ASTNode
+ if (doPush) {
+ if (patternNode instanceof ASTNode) {
+ storeContraints(patternNode)
+ }
+ pushContext((ASTNode)foundNode)
}
- if (match && (next == null || expectedClass.isAssignableFrom(next.class))) {
- Object old = current
- current = next
- cl()
- current = old
- } else {
- failIfNot(false)
+
+ if (!isWildcardExpression(patternNode)) {
+ String placeholder = findPlaceholder(patternNode)
+ if (placeholder) {
+ def alreadySeenAST = treeContext.getUserdata("placeholder_$placeholder", true)
+ if (!alreadySeenAST) {
+ treeContext.parent.putUserdata("placeholder_$placeholder", foundNode)
+ } else {
+ // during the tree inspection, placeholder already found
+ // so we need to check that they are identical
+ failIfNot(matches((ASTNode)alreadySeenAST[0], (ASTNode)foundNode))
+ }
+ } else if (match && (foundNode == null || expectedClass.isAssignableFrom(foundNode.class))) {
+ Object old = current
+ current = foundNode
+ cl()
+ current = old
+ } else {
+ failIfNot(false)
+ }
+ }
+ if (doPush) {
+ popContext()
}
}
@@ -461,10 +503,12 @@ class ASTMatcher extends ClassCodeVisitorSupport {
@Override
public void visitConstructorCallExpression(final ConstructorCallExpression call) {
- def cur = (ConstructorCallExpression) current
- doWithNode(call.arguments, cur.arguments) {
- call.arguments.visit(this)
- failIfNot(call.type == cur.type)
+ doWithNode(call, current) {
+ def cur = (ConstructorCallExpression) current
+ doWithNode(call.arguments, cur.arguments) {
+ call.arguments.visit(this)
+ failIfNot(call.type == cur.type)
+ }
}
}
@@ -913,4 +957,25 @@ class ASTMatcher extends ClassCodeVisitorSupport {
}
}
}
+
+ /**
+ * // todo: experimental!
+ *
+ * Annotates an AST node with matching contraints. This method should be called
+ * on an AST intended to be used as a pattern only. It will put node metadata on
+ * the AST node allowing customized behavior in pattern matching.
+ *
+ * @param pattern a pattern AST
+ * @param constraintsSpec a closure specification of matching constraints
+ * @return the same pattern, annotated with constraints
+ */
+ public static ASTNode withConstraints(
+ final ASTNode pattern,
+ final @DelegatesTo(value=MatchingConstraints.Builder, strategy=Closure.DELEGATE_ONLY) Closure constraintsSpec) {
+ def builder = new MatchingConstraints.Builder()
+ def constraints = builder.build(constraintsSpec)
+ pattern.putNodeMetaData(MatchingConstraints, constraints)
+
+ pattern
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/bcd68a75/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ContextualClassCodeVisitor.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ContextualClassCodeVisitor.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ContextualClassCodeVisitor.java
index babd3e3..a8f0e40 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ContextualClassCodeVisitor.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/ContextualClassCodeVisitor.java
@@ -107,7 +107,8 @@ public abstract class ContextualClassCodeVisitor extends ClassCodeVisitorSupport
return super.transform(exp);
}
};
- parentNode.visit(trn);
+ // todo: reliable way to call the transformer
+ //parentNode.visit(trn);
}
return treeContext;
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/bcd68a75/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/MatchingConstraints.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/MatchingConstraints.groovy b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/MatchingConstraints.groovy
new file mode 100644
index 0000000..82306c0
--- /dev/null
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/MatchingConstraints.groovy
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2003-2014 the original author or authors.
+ *
+ * Licensed 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.codehaus.groovy.macro.matcher
+
+import groovy.transform.CompileStatic
+import groovy.transform.PackageScope
+
+/**
+ * Represents constraints in AST pattern matching.
+ *
+ * @author Cedric Champeau
+ * @since 2.4.0
+ */
+@CompileStatic
+class MatchingConstraints {
+ final Set<String> placeholders = new LinkedHashSet<>()
+
+ @PackageScope static class Builder {
+ private final MatchingConstraints constraints = new MatchingConstraints()
+
+ MatchingConstraints build(@DelegatesTo(value=Builder, strategy=Closure.DELEGATE_ONLY) Closure spec) {
+ def clone = (Closure) spec.clone()
+ clone.delegate = this
+ clone.resolveStrategy = Closure.DELEGATE_ONLY
+ clone()
+
+ constraints
+ }
+
+ def getProperty(String name) {
+ if ('constraints'==name) {
+ return constraints
+ }
+ name
+ }
+
+ Builder placeholder(String... names) {
+ names.each { String it -> constraints.placeholders.add(it) }
+ this
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/bcd68a75/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/TreeContext.java
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/TreeContext.java b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/TreeContext.java
index 6020592..4644555 100644
--- a/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/TreeContext.java
+++ b/subprojects/groovy-macro/src/main/groovy/org/codehaus/groovy/macro/matcher/TreeContext.java
@@ -53,10 +53,23 @@ public class TreeContext {
}
}
- public Map<?, ?> getUserdata() {
+ public Map<?, List<?>> getUserdata() {
return userdata;
}
+ public void putUserdata(Object key, Object value) {
+ ((List)userdata.get(key)).add(value);
+ }
+
+ public List<?> getUserdata(Object key, boolean searchParent) {
+ if (userdata.containsKey(key)) {
+ return userdata.get(key);
+ } else if (parent!=null && searchParent) {
+ return parent.getUserdata(key, true);
+ }
+ return null;
+ }
+
public TreeContext getParent() { return parent; }
public ASTNode getNode() { return node; }
@@ -104,13 +117,13 @@ public class TreeContext {
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("TreeContext{");
- sb.append("node=").append(node!=null?node.getClass().getSimpleName():"undefined");
+ sb.append("node=").append(dumpNode());
TreeContext p = parent;
if (p!=null) {
sb.append(", path=");
}
while (p!=null) {
- sb.append(p.node!=null?p.node.getClass().getSimpleName():"undefined");
+ sb.append(p.dumpNode());
sb.append("<-");
p = p.parent;
}
@@ -118,4 +131,8 @@ public class TreeContext {
sb.append('}');
return sb.toString();
}
+
+ private String dumpNode() {
+ return node!=null?node.getClass().getSimpleName():"undefined";
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-groovy/blob/bcd68a75/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy
----------------------------------------------------------------------
diff --git a/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy b/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy
index 63d4a9a..9802a2a 100644
--- a/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy
+++ b/subprojects/groovy-macro/src/test/groovy/org/codehaus/groovy/macro/matcher/ASTMatcherTest.groovy
@@ -616,4 +616,42 @@ class ASTMatcherTest extends GroovyTestCase {
assert !ASTMatcher.matches(ast2, ast1)
assert !ASTMatcher.matches(ast1, ast3)
}
+
+ void testConstrainedMatcher() {
+ def ast = macro { a+foo(b) }
+ def pattern = macro {
+ a+b
+ }
+ ASTMatcher.withConstraints(pattern) {
+ placeholder a,b
+ }
+ assert ASTMatcher.matches(ast, pattern)
+ }
+
+ void testPlaceholdersMustMatch() {
+ def ast1 = macro { foo(a)+foo(a) }
+ def ast2 = macro { foo(a)+foo(b) }
+ def pattern = macro {
+ x+x
+ }
+ ASTMatcher.withConstraints(pattern) {
+ placeholder x
+ }
+ assert ASTMatcher.matches(ast1, pattern)
+ assert !ASTMatcher.matches(ast2, pattern)
+ }
+
+ void testPlaceholdersMustMatch2() {
+ use(ASTMatcher) {
+ def ast1 = macro { foo(a) + foo(a) }
+ def ast2 = macro { foo(a) + foo(foo(a)) }
+ def pattern = macro {
+ x + foo(x)
+ }.withConstraints {
+ placeholder x
+ }
+ assert !ast1.matches(pattern)
+ assert ast2.matches(pattern)
+ }
+ }
}