You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/10/06 13:36:38 UTC

[groovy] 13/22: GROOVY-8258: build AST for ginq and generate target method calls

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch GROOVY-8258
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 7273eed4ed4a4f0a90ce517b78748c6721b65c18
Author: Daniel Sun <su...@apache.org>
AuthorDate: Mon Oct 5 20:47:43 2020 +0800

    GROOVY-8258: build AST for ginq and generate target method calls
---
 .../apache/groovy/linq/LinqGroovyMethods.groovy    | 116 ++------------------
 .../org/apache/groovy/linq/dsl/GinqAstBuilder.java |  67 ++++++++++++
 .../org/apache/groovy/linq/dsl/GinqBuilder.groovy  | 118 +++++++++++++++++++++
 .../org/apache/groovy/linq/dsl/GinqVisitor.java    |  33 ++++++
 .../dsl/expression/AbstractGinqExpression.java     |  20 ++++
 .../linq/dsl/expression/FilterableExpression.java  |  31 ++++++
 .../groovy/linq/dsl/expression/FromExpression.java |  54 ++++++++++
 .../groovy/linq/dsl/expression/GinqExpression.java |  25 +++++
 .../groovy/linq/dsl/expression/RootExpression.java |  58 ++++++++++
 .../linq/dsl/expression/SelectExpression.java      |  46 ++++++++
 .../linq/dsl/expression/WhereExpression.java       |  39 +++++++
 11 files changed, 501 insertions(+), 106 deletions(-)

diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/LinqGroovyMethods.groovy b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/LinqGroovyMethods.groovy
index 70a9596..4d9af35 100644
--- a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/LinqGroovyMethods.groovy
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/LinqGroovyMethods.groovy
@@ -18,126 +18,30 @@
  */
 package org.apache.groovy.linq
 
-import groovy.transform.CompileDynamic
 import groovy.transform.CompileStatic
-import groovy.transform.ToString
-import org.codehaus.groovy.ast.ClassHelper
-import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.apache.groovy.linq.dsl.GinqAstBuilder
+import org.apache.groovy.linq.dsl.GinqBuilder
+import org.apache.groovy.linq.dsl.expression.RootExpression
 import org.codehaus.groovy.ast.expr.ClosureExpression
 import org.codehaus.groovy.ast.expr.Expression
 import org.codehaus.groovy.ast.expr.MethodCallExpression
-import org.codehaus.groovy.ast.expr.VariableExpression
-import org.codehaus.groovy.ast.stmt.BlockStatement
-import org.codehaus.groovy.ast.stmt.ExpressionStatement
 import org.codehaus.groovy.ast.stmt.Statement
 import org.codehaus.groovy.macro.runtime.Macro
 import org.codehaus.groovy.macro.runtime.MacroContext
 
-import static org.codehaus.groovy.ast.tools.GeneralUtils.callX
-import static org.codehaus.groovy.ast.tools.GeneralUtils.lambdaX
-import static org.codehaus.groovy.ast.tools.GeneralUtils.param
-import static org.codehaus.groovy.ast.tools.GeneralUtils.params
-import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt
-
 @CompileStatic
 class LinqGroovyMethods {
     @Macro
     static Expression GINQ(MacroContext ctx, final ClosureExpression closureExpression) {
-        BlockStatement code = (BlockStatement) closureExpression.getCode()
-        List<Statement> statementList = code.getStatements()
-
-        LinqContext linqContext = new LinqContext()
-        for (Statement statement : statementList) {
-            ExpressionStatement expressionStatement = (ExpressionStatement) statement
-            MethodCallExpression methodCallExpression = (MethodCallExpression) expressionStatement.getExpression()
-
-            String methodName = methodCallExpression.getMethodAsString()
-            switch (methodName) {
-                case 'from': {
-                    break
-                }
-                case 'of': {
-                    MethodCallExpression fromMethodCallExpression = (MethodCallExpression) methodCallExpression.getObjectExpression()
-                    VariableExpression aliasVariable = (VariableExpression) ((ArgumentListExpression) fromMethodCallExpression.getArguments()).getExpression(0)
-                    Expression dataSourceExpression = ((ArgumentListExpression) methodCallExpression.getArguments()).getExpression(0)
-                    linqContext.addFrom(aliasVariable, dataSourceExpression)
-                    break
-                }
-                case 'where': {
-                    Expression conditionExpression = ((ArgumentListExpression) methodCallExpression.getArguments()).getExpression(0)
-                    linqContext.addWhere(conditionExpression)
-                    break
-                }
-                case 'select': {
-                    Expression selectExpression = ((ArgumentListExpression) methodCallExpression.getArguments()).getExpression(0)
-                    linqContext.addSelect(selectExpression)
-                    break
-                }
-                default: {
-                    break
-                }
-            }
-        }
-
-        constructLinqMethodCalls(linqContext)
-    }
+        Statement code = closureExpression.getCode()
 
-    private static Expression constructLinqMethodCalls(LinqContext linqContext) {
-        Map.Entry<VariableExpression, Expression> fromEntry = linqContext.fromMap.entrySet().toList().get(0)
-        VariableExpression aliasVariable = fromEntry.key
+        GinqAstBuilder ginqAstBuilder = new GinqAstBuilder()
+        code.visit(ginqAstBuilder)
+        RootExpression rootExpression = ginqAstBuilder.getRootExpression()
 
-        Expression selectMethodReceiver = null
+        GinqBuilder ginqBuilder = new GinqBuilder()
+        MethodCallExpression selectMethodCallExpression = ginqBuilder.visitRootExpression(rootExpression)
 
-        MethodCallExpression from = constructFromMethodCall(fromEntry)
-
-        selectMethodReceiver = from
-
-        if (linqContext.whereList.size() > 0) {
-            MethodCallExpression where = callXWithLambda(from, "where", aliasVariable.name, linqContext.whereList[0])
-            selectMethodReceiver = where
-        }
-
-        MethodCallExpression select = callXWithLambda(selectMethodReceiver, "select", aliasVariable.name, linqContext.selectList[0])
-
-        return select
-    }
-
-    @CompileDynamic
-    private static MethodCallExpression constructFromMethodCall(fromEntry) {
-        macro {
-            org.apache.groovy.linq.provider.QueryableCollection
-                    .from($v { fromEntry.value })
-        }
-    }
-
-    private static MethodCallExpression callXWithLambda(Expression receiver, String methodName, String lambdaParamName, Expression lambdaCode) {
-        callX(
-                receiver,
-                methodName,
-                lambdaX(
-                        params(param(ClassHelper.DYNAMIC_TYPE, lambdaParamName)),
-                        stmt(lambdaCode)
-                )
-        )
-    }
-
-    @ToString(includeNames=true)
-    static class LinqContext {
-        Map<VariableExpression, Expression> fromMap = new LinkedHashMap<>()
-        List<Expression> whereList = new ArrayList<>()
-        List<Expression> selectList = new ArrayList<>()
-
-        void addFrom(VariableExpression aliasVariable, Expression dataSourceExpression) {
-            fromMap.put(aliasVariable, dataSourceExpression)
-        }
-
-        void addWhere(Expression... conditionExpressions) {
-            whereList.addAll(conditionExpressions)
-        }
-
-        void addSelect(Expression... selectExpressions) {
-            selectList.addAll(selectExpressions)
-        }
+        return selectMethodCallExpression
     }
 }
-
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqAstBuilder.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqAstBuilder.java
new file mode 100644
index 0000000..e2bec3f
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqAstBuilder.java
@@ -0,0 +1,67 @@
+package org.apache.groovy.linq.dsl;
+
+import org.apache.groovy.linq.dsl.expression.FilterableExpression;
+import org.apache.groovy.linq.dsl.expression.FromExpression;
+import org.apache.groovy.linq.dsl.expression.GinqExpression;
+import org.apache.groovy.linq.dsl.expression.RootExpression;
+import org.apache.groovy.linq.dsl.expression.SelectExpression;
+import org.apache.groovy.linq.dsl.expression.WhereExpression;
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+
+public class GinqAstBuilder extends CodeVisitorSupport {
+    private RootExpression rootExpression = new RootExpression(); // store the result
+    private GinqExpression ginqExpression; // store the return value
+
+    public RootExpression getRootExpression() {
+        return rootExpression;
+    }
+
+    @Override
+    public void visitMethodCallExpression(MethodCallExpression call) {
+        super.visitMethodCallExpression(call);
+        final String methodName = call.getMethodAsString();
+        System.out.println(methodName + " : " + call);
+
+        if ("from".equals(methodName)) {
+            return;
+        }
+
+        if ("of".equals(methodName)) {
+            MethodCallExpression fromMethodCallExpression = (MethodCallExpression) call.getObjectExpression();
+            Expression aliasExpr = ((ArgumentListExpression) fromMethodCallExpression.getArguments()).getExpression(0);
+            Expression dataSourceExpr = ((ArgumentListExpression) call.getArguments()).getExpression(0);
+
+            FromExpression fromExpression = new FromExpression(aliasExpr, dataSourceExpr);
+            rootExpression.addFromExpression(fromExpression);
+
+            ginqExpression = fromExpression;
+            return;
+        }
+
+        if ("where".equals(methodName)) {
+            Expression filterExpr = ((ArgumentListExpression) call.getArguments()).getExpression(0);
+            WhereExpression whereExpression = new WhereExpression(filterExpr);
+
+            if (ginqExpression instanceof FilterableExpression) {
+                ((FilterableExpression) ginqExpression).setWhereExpression(whereExpression);
+            } else {
+                throw new GroovyBugError("The preceding expression is not a FilterableExpression: " + ginqExpression);
+            }
+
+            return;
+        }
+
+        if ("select".equals(methodName)) {
+            SelectExpression selectExpression = new SelectExpression(call.getArguments());
+            rootExpression.setSelectExpression(selectExpression);
+
+            ginqExpression = selectExpression;
+
+            return;
+        }
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqBuilder.groovy b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqBuilder.groovy
new file mode 100644
index 0000000..8cbb86b
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqBuilder.groovy
@@ -0,0 +1,118 @@
+/*
+ *  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.groovy.linq.dsl
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import org.apache.groovy.linq.dsl.expression.FromExpression
+import org.apache.groovy.linq.dsl.expression.GinqExpression
+import org.apache.groovy.linq.dsl.expression.RootExpression
+import org.apache.groovy.linq.dsl.expression.SelectExpression
+import org.apache.groovy.linq.dsl.expression.WhereExpression
+import org.codehaus.groovy.ast.ClassHelper
+import org.codehaus.groovy.ast.expr.ArgumentListExpression
+import org.codehaus.groovy.ast.expr.Expression
+import org.codehaus.groovy.ast.expr.MethodCallExpression
+
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.lambdaX
+import static org.codehaus.groovy.ast.tools.GeneralUtils.param
+import static org.codehaus.groovy.ast.tools.GeneralUtils.params
+import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt
+
+@CompileStatic
+class GinqBuilder implements GinqVisitor<Object> {
+    @Override
+    MethodCallExpression visitRootExpression(RootExpression rootExpression) {
+        List<MethodCallExpression> fromMethodCallExpressionList = new LinkedList<>()
+        List<FromExpression> fromExpressionList = rootExpression.getFromExpressionList()
+        for (FromExpression fromExpression : (fromExpressionList)) {
+            MethodCallExpression methodCallExpression = this.visitFromExpression(fromExpression)
+            fromMethodCallExpressionList.add(methodCallExpression)
+        }
+
+        MethodCallExpression selectMethodReceiver = fromMethodCallExpressionList.getLast()
+
+        SelectExpression selectExpression = rootExpression.getSelectExpression()
+        selectExpression.putNodeMetaData(__SELECT_METHOD_RECEIVER, selectMethodReceiver)
+        selectExpression.putNodeMetaData(__ALIAS_EXPR, fromExpressionList.get(fromExpressionList.size() - 1).aliasExpr)
+        MethodCallExpression selectMethodCallExpression = this.visitSelectExpression(selectExpression)
+
+
+        return selectMethodCallExpression
+    }
+
+    @Override
+    MethodCallExpression visitFromExpression(FromExpression fromExpression) {
+        MethodCallExpression fromMethodCallExpression = constructFromMethodCallExpression(fromExpression)
+
+        WhereExpression whereExpression = fromExpression.getWhereExpression()
+        if (whereExpression) {
+            whereExpression.putNodeMetaData(__FROM_EXPRESSION, fromExpression)
+            whereExpression.putNodeMetaData(__FROM_METHOD_CALL_EXPRESSION, fromMethodCallExpression)
+
+            return visitWhereExpression(whereExpression)
+        }
+
+        return fromMethodCallExpression
+    }
+
+    @CompileDynamic
+    private MethodCallExpression constructFromMethodCallExpression(FromExpression fromExpression) {
+        macro {
+            org.apache.groovy.linq.provider.QueryableCollection
+                    .from($v { fromExpression.dataSourceExpr })
+        }
+    }
+
+    @Override
+    MethodCallExpression visitWhereExpression(WhereExpression whereExpression) {
+        FromExpression fromExpression = whereExpression.getNodeMetaData(__FROM_EXPRESSION)
+        Expression fromMethodCallExpression = whereExpression.getNodeMetaData(__FROM_METHOD_CALL_EXPRESSION)
+        return callXWithLambda(fromMethodCallExpression, "where", fromExpression.aliasExpr.text, whereExpression.getFilterExpr())
+    }
+
+    @Override
+    MethodCallExpression visitSelectExpression(SelectExpression selectExpression) {
+        Expression selectMethodReceiver = selectExpression.getNodeMetaData(__SELECT_METHOD_RECEIVER)
+        Expression aliasExpr = selectExpression.getNodeMetaData(__ALIAS_EXPR)
+        return callXWithLambda(selectMethodReceiver, "select", aliasExpr.text, ((ArgumentListExpression) selectExpression.getProjectionExpr()).getExpression(0))
+    }
+
+    @Override
+    Object visit(GinqExpression expression) {
+        return expression.accept(this)
+    }
+
+    private static MethodCallExpression callXWithLambda(Expression receiver, String methodName, String lambdaParamName, Expression lambdaCode) {
+        callX(
+                receiver,
+                methodName,
+                lambdaX(
+                        params(param(ClassHelper.DYNAMIC_TYPE, lambdaParamName)),
+                        stmt(lambdaCode)
+                )
+        )
+    }
+
+    private static final String __FROM_EXPRESSION = "__fromExpression"
+    private static final String __FROM_METHOD_CALL_EXPRESSION = "__fromMethodCallExpression"
+    private static final String __SELECT_METHOD_RECEIVER = "__selectMethodReceiver"
+    private static final String __ALIAS_EXPR = "__aliasExpr"
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqVisitor.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqVisitor.java
new file mode 100644
index 0000000..4e7c242
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/GinqVisitor.java
@@ -0,0 +1,33 @@
+/*
+ *  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.groovy.linq.dsl;
+
+import org.apache.groovy.linq.dsl.expression.FromExpression;
+import org.apache.groovy.linq.dsl.expression.GinqExpression;
+import org.apache.groovy.linq.dsl.expression.RootExpression;
+import org.apache.groovy.linq.dsl.expression.SelectExpression;
+import org.apache.groovy.linq.dsl.expression.WhereExpression;
+
+public interface GinqVisitor<R> {
+    R visitRootExpression(RootExpression rootExpression);
+    R visitFromExpression(FromExpression fromExpression);
+    R visitWhereExpression(WhereExpression whereExpression);
+    R visitSelectExpression(SelectExpression selectExpression);
+    R visit(GinqExpression expression);
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/AbstractGinqExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/AbstractGinqExpression.java
new file mode 100644
index 0000000..25e4e84
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/AbstractGinqExpression.java
@@ -0,0 +1,20 @@
+package org.apache.groovy.linq.dsl.expression;
+
+import org.codehaus.groovy.ast.NodeMetaDataHandler;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public abstract class AbstractGinqExpression implements GinqExpression, NodeMetaDataHandler {
+    private Map<?, ?> metaDataMap = new LinkedHashMap<>();
+
+    @Override
+    public Map<?, ?> getMetaDataMap() {
+        return metaDataMap;
+    }
+
+    @Override
+    public void setMetaDataMap(Map<?, ?> metaDataMap) {
+        this.metaDataMap = metaDataMap;
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FilterableExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FilterableExpression.java
new file mode 100644
index 0000000..d739e21
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FilterableExpression.java
@@ -0,0 +1,31 @@
+/*
+ *  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.groovy.linq.dsl.expression;
+
+public abstract class FilterableExpression extends AbstractGinqExpression {
+    protected WhereExpression whereExpression;
+
+    public WhereExpression getWhereExpression() {
+        return whereExpression;
+    }
+
+    public void setWhereExpression(WhereExpression whereExpression) {
+        this.whereExpression = whereExpression;
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FromExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FromExpression.java
new file mode 100644
index 0000000..4c212e4
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/FromExpression.java
@@ -0,0 +1,54 @@
+/*
+ *  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.groovy.linq.dsl.expression;
+
+import org.apache.groovy.linq.dsl.GinqVisitor;
+import org.codehaus.groovy.ast.expr.Expression;
+
+public class FromExpression extends FilterableExpression {
+    private final Expression aliasExpr;
+    private final Expression dataSourceExpr;
+
+    public FromExpression(Expression aliasExpr, Expression dataSourceExpr) {
+        this.aliasExpr = aliasExpr;
+        this.dataSourceExpr = dataSourceExpr;
+    }
+
+    @Override
+    public <R> R accept(GinqVisitor<R> visitor) {
+        return visitor.visitFromExpression(this);
+    }
+
+    public Expression getAliasExpr() {
+        return aliasExpr;
+    }
+
+    public Expression getDataSourceExpr() {
+        return dataSourceExpr;
+    }
+
+    @Override
+    public String toString() {
+        return "FromExpression{" +
+                "aliasExpr=" + aliasExpr +
+                ", dataSourceExpr=" + dataSourceExpr +
+                ", whereExpression=" + whereExpression +
+                '}';
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/GinqExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/GinqExpression.java
new file mode 100644
index 0000000..22ef3ff
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/GinqExpression.java
@@ -0,0 +1,25 @@
+/*
+ *  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.groovy.linq.dsl.expression;
+
+import org.apache.groovy.linq.dsl.GinqVisitor;
+
+public interface GinqExpression {
+    <R> R accept(GinqVisitor<R> visitor);
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/RootExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/RootExpression.java
new file mode 100644
index 0000000..d4ddf0c
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/RootExpression.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.groovy.linq.dsl.expression;
+
+import org.apache.groovy.linq.dsl.GinqVisitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RootExpression extends AbstractGinqExpression {
+    private List<FromExpression> fromExpressionList = new ArrayList<>();
+    private SelectExpression selectExpression;
+
+    @Override
+    public <R> R accept(GinqVisitor<R> visitor) {
+        return visitor.visitRootExpression(this);
+    }
+
+    public List<FromExpression> getFromExpressionList() {
+        return fromExpressionList;
+    }
+
+    public void addFromExpression(FromExpression fromExpression) {
+        this.fromExpressionList.add(fromExpression);
+    }
+
+    public SelectExpression getSelectExpression() {
+        return selectExpression;
+    }
+
+    public void setSelectExpression(SelectExpression selectExpression) {
+        this.selectExpression = selectExpression;
+    }
+
+    @Override
+    public String toString() {
+        return "RootExpression{" +
+                "fromExpressionList=" + fromExpressionList +
+                ", selectExpression=" + selectExpression +
+                '}';
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/SelectExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/SelectExpression.java
new file mode 100644
index 0000000..0cd3429
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/SelectExpression.java
@@ -0,0 +1,46 @@
+/*
+ *  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.groovy.linq.dsl.expression;
+
+import org.apache.groovy.linq.dsl.GinqVisitor;
+import org.codehaus.groovy.ast.expr.Expression;
+
+public class SelectExpression extends AbstractGinqExpression {
+    private final Expression projectionExpr;
+
+    public SelectExpression(Expression projectionExpr) {
+        this.projectionExpr = projectionExpr;
+    }
+
+    @Override
+    public <R> R accept(GinqVisitor<R> visitor) {
+        return visitor.visitSelectExpression(this);
+    }
+
+    public Expression getProjectionExpr() {
+        return projectionExpr;
+    }
+
+    @Override
+    public String toString() {
+        return "SelectExpression{" +
+                "projectionExpr=" + projectionExpr +
+                '}';
+    }
+}
diff --git a/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/WhereExpression.java b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/WhereExpression.java
new file mode 100644
index 0000000..b23ba94
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/groovy/org/apache/groovy/linq/dsl/expression/WhereExpression.java
@@ -0,0 +1,39 @@
+/*
+ *  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.groovy.linq.dsl.expression;
+
+import org.apache.groovy.linq.dsl.GinqVisitor;
+import org.codehaus.groovy.ast.expr.Expression;
+
+public class WhereExpression extends AbstractGinqExpression {
+    private final Expression filterExpr;
+
+    public WhereExpression(Expression filterExpr) {
+        this.filterExpr = filterExpr;
+    }
+
+    @Override
+    public <R> R accept(GinqVisitor<R> visitor) {
+        return visitor.visitWhereExpression(this);
+    }
+
+    public Expression getFilterExpr() {
+        return filterExpr;
+    }
+}