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)
+        }
+    }
 }