A first very crude experiment on a tree builder for javac. jlahoda-tree-builder
authorjlahoda
Fri, 15 Mar 2019 14:21:44 +0100
branchjlahoda-tree-builder
changeset 57267 97aaf02ed830
parent 57266 d6ddb48bb34a
child 57295 5497ee9d40f4
A first very crude experiment on a tree builder for javac. Contributed-by: maurizio.cimadamore@oracle.com, jan.lahoda@oracle.com
src/jdk.compiler/share/classes/com/sun/source/util/TreeBuilder.java
src/jdk.compiler/share/classes/com/sun/source/util/Trees.java
src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java
src/jdk.compiler/share/classes/com/sun/tools/javac/api/TreeBuilderImpl.java
test/langtools/tools/javac/api/ast/ASTBuilder.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/TreeBuilder.java	Fri Mar 15 14:21:44 2019 +0100
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.source.util;
+
+import java.util.function.Consumer;
+
+import javax.lang.model.element.Modifier;
+
+import com.sun.source.doctree.DocTree;
+import com.sun.source.tree.CompilationUnitTree;
+
+/**
+ *  Note: a crude experiment to explore a builder for javac ASTs.
+ */
+public interface TreeBuilder {
+
+    CompilationUnitTree createCompilationUnitTree(Consumer<CompilationUnit> unit); //TODO: should be in Trees?
+
+    //mixins:
+    interface WithModifiers<T extends WithModifiers<T>> {
+        T modifiers(Consumer<Modifiers> modifiers);
+    }
+
+    interface WithJavadoc<T extends WithJavadoc<T>> {
+        T javadoc(DocTree doc);
+        T javadoc(String doc);
+    }
+
+    //builders:
+    interface CompilationUnit {
+        CompilationUnit _package(String... qname);
+        CompilationUnit _class(String name, Consumer<Class> clazz);
+    }
+
+    interface Class extends WithModifiers<Class>, WithJavadoc<Class> {
+        //setters:
+        Class superclass(Consumer<Expression> sup);
+        Class superinterface(Consumer<Expression> sup);
+        //type parameters?
+
+        //adders:
+        default Class field(String name, Consumer<Type> type) {
+            //TODO: has this overload a meaning
+            return field(name, type, V -> {});
+        }
+
+        Class field(String name, Consumer<Type> type, Consumer<Variable> field);
+        Class method(String name, Consumer<Type> restype, Consumer<Method> method);
+        Class _class(String name, Consumer<Class> clazz);
+        
+        //TODO:initializer block
+    }
+
+    interface Variable extends WithModifiers<Variable>, WithJavadoc<Variable> {
+        //setters:
+        Variable init(Consumer<Expression> init);
+    }
+
+    interface Parameter extends WithModifiers<Parameter> {
+        Parameter modifiers(Consumer<Modifiers> modifiers); //TODO: limit to final only?
+
+        Parameter name(String name);
+    }
+
+    interface Method extends WithModifiers<Method>, WithJavadoc<Method> {
+        default Method parameter(Consumer<Type> type) {
+            return parameter(type, P -> {});
+        }
+
+        Method parameter(Consumer<Type> type, Consumer<Parameter> parameter);
+
+        Method body(Consumer<Statements> statements);
+        //throws, default value
+    }
+
+    interface Modifiers {
+        Modifiers modifier(Modifier modifier);
+        Modifiers annotation(Consumer<Annotation> annotation);
+    }
+
+    interface Annotation {
+        //TODO...
+    }
+
+    interface Type {
+        void _class(String simpleName); //TODO: type parameters - declaredtype???
+//        void _class(String simpleName, Consumer<TypeArgs> targs);
+        void _int();
+        void _float();
+        void _void();
+    }
+
+//    interface TypeArgs { //???
+//        TypeArgs type(Consumer<Type> t);
+//        TypeArgs _extends(Consumer<Type> t);
+//        TypeArgs _super(Consumer<Type> t);
+//        TypeArgs unbound();
+//    }
+
+    interface Expression {
+        void minusminus(Consumer<Expression> expr);
+        void plus(Consumer<Expression> lhs, Consumer<Expression> rhs);
+        void cond(Consumer<Expression> cond, Consumer<Expression> truePart, Consumer<Expression> falsePart);
+        void ident(String... qnames);
+    }
+
+    interface Statements {
+        Statements _if(Consumer<Expression> cond, Consumer<Statements> ifPart, Consumer<Statements> elsePart);
+        Statements expr(Consumer<Expression> expr);
+        Statements skip();
+    }
+
+    static void test(TreeBuilder builder) {
+        builder.createCompilationUnitTree(
+         U -> U._class("Foo", C -> C.field("x", Type::_int)
+                                    .method("foo",
+                                            Type::_void,
+                                            M -> M.parameter(T -> T._class("Foo"))
+                                                  .parameter(T -> T._float(), P -> P.name("whatever"))
+                                                  .body(B -> B._if(E -> E.minusminus(V -> V.ident("foo", "bar")),
+                                                                   Statements::skip,
+                                                                   Statements::skip
+                                                                  )
+                                                       )
+                                            )));
+    }
+
+    //annotations?
+    //positions?
+}
--- a/src/jdk.compiler/share/classes/com/sun/source/util/Trees.java	Fri Mar 15 14:19:05 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/source/util/Trees.java	Fri Mar 15 14:21:44 2019 +0100
@@ -255,4 +255,6 @@
      * @return The lub of the exception parameter
      */
     public abstract TypeMirror getLub(CatchTree tree);
+
+    public abstract TreeBuilder getTreeBuilder();
 }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Fri Mar 15 14:19:05 2019 +0100
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java	Fri Mar 15 14:21:44 2019 +0100
@@ -72,6 +72,7 @@
 import com.sun.source.util.DocTrees;
 import com.sun.source.util.JavacTask;
 import com.sun.source.util.SimpleDocTreeVisitor;
+import com.sun.source.util.TreeBuilder;
 import com.sun.source.util.TreePath;
 import com.sun.tools.javac.code.Flags;
 import com.sun.tools.javac.code.Scope.NamedImportScope;
@@ -1242,4 +1243,8 @@
         jcCompilationUnit.toplevelScope = WriteableScope.create(psym);
         return new TreePath(jcCompilationUnit);
     }
+
+    public TreeBuilder getTreeBuilder() {
+        return new TreeBuilderImpl(treeMaker, names);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/TreeBuilderImpl.java	Fri Mar 15 14:21:44 2019 +0100
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */package com.sun.tools.javac.api;
+
+import java.util.function.Consumer;
+
+import com.sun.source.doctree.DocTree;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.util.TreeBuilder;
+import com.sun.tools.javac.code.TypeTag;
+import com.sun.tools.javac.tree.JCTree.JCClassDecl;
+import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
+import com.sun.tools.javac.tree.JCTree.JCExpression;
+import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
+import com.sun.tools.javac.tree.JCTree.Tag;
+
+import com.sun.tools.javac.tree.TreeMaker;
+import com.sun.tools.javac.util.List;
+import com.sun.tools.javac.util.Names;
+
+/**
+ * Implementation for TreeBuilder.
+ * Note: this is only a crude experiment.
+ */
+public class TreeBuilderImpl implements TreeBuilder {
+
+    private final TreeMaker make;
+    private final Names names;
+
+    public TreeBuilderImpl(TreeMaker make, Names names) {
+        this.make = make;
+        this.names = names;
+    }
+
+    @Override
+    public CompilationUnitTree createCompilationUnitTree(Consumer<CompilationUnit> unit) {
+        CompilationUnitImpl cui = new CompilationUnitImpl();
+        unit.accept(cui);
+        return cui.result;
+    }
+    
+    private final class CompilationUnitImpl implements CompilationUnit {
+        
+        private final JCCompilationUnit result;
+
+        public CompilationUnitImpl() {
+            this.result = make.TopLevel(List.nil());
+        }
+
+        @Override
+        public CompilationUnit _package(String... qname) {
+            JCExpression qualIdent = make.Ident(names.fromString(qname[0])); //XXX: should check qname.length > 0!
+            for (int i = 1; i < qname.length; i++) {
+                qualIdent = make.Select(qualIdent, names.fromString(qname[i]));
+            }
+            result.defs = result.defs.stream().filter(t -> !t.hasTag(Tag.PACKAGEDEF)).collect(List.collector()) //XXX: what should be the behavior if already filled?
+                          .prepend(make.PackageDecl(List.nil(), qualIdent));
+            return this;
+        }
+
+        @Override
+        public CompilationUnit _class(String name, Consumer<Class> clazz) {
+            ClassImpl ci = new ClassImpl(name);
+            clazz.accept(ci);
+            result.defs = result.defs.append(ci.result);
+            return this;
+        }
+
+    }
+    
+    private final class ClassImpl implements Class {
+
+        private final JCClassDecl result;
+
+        public ClassImpl(String name) {
+            this.result = make.ClassDef(make.Modifiers(0), names.fromString(name), List.nil(), null, List.nil(), List.nil());
+        }
+        
+        @Override
+        public Class superclass(Consumer<Expression> sup) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class superinterface(Consumer<Expression> sup) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class field(String name, Consumer<Type> type, Consumer<Variable> field) {
+            TypeImpl ti = new TypeImpl();
+            type.accept(ti);
+            if (ti.type == null) {
+                throw new IllegalStateException("Type not provided!");
+            }
+            VariableImpl vi = new VariableImpl(ti.type, name);
+            field.accept(vi);
+            result.defs = result.defs.prepend(vi.result);
+            return this;
+        }
+
+        @Override
+        public Class method(String name, Consumer<Type> restype, Consumer<Method> method) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class _class(String name, Consumer<Class> clazz) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class modifiers(Consumer<Modifiers> modifiers) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class javadoc(DocTree doc) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Class javadoc(String doc) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+        
+    }
+    
+    private final class TypeImpl implements Type {
+
+        private JCExpression type;
+
+        @Override
+        public void _class(String simpleName) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public void _int() {
+            //XXX: check empty!
+            type = make.TypeIdent(TypeTag.INT);
+        }
+
+        @Override
+        public void _float() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public void _void() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+    }
+    
+    private final class VariableImpl implements Variable {
+
+        private final JCVariableDecl result;
+        
+        public VariableImpl(JCExpression type, String name) {
+            result = make.VarDef(make.Modifiers(0), names.fromString(name), type, null);
+        }
+
+        @Override
+        public Variable init(Consumer<Expression> init) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Variable modifiers(Consumer<Modifiers> modifiers) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Variable javadoc(DocTree doc) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public Variable javadoc(String doc) {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/langtools/tools/javac/api/ast/ASTBuilder.java	Fri Mar 15 14:21:44 2019 +0100
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 9999999
+ * @summary XXX
+ * @modules java.compiler
+ *          jdk.compiler
+ */
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.ToolProvider;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TreeBuilder;
+import com.sun.source.util.TreeBuilder.*;
+import com.sun.source.util.Trees;
+import com.sun.source.util.TreeScanner;
+
+public class ASTBuilder {
+    
+    public static void main(String[] args) throws Exception {
+        runTest("class Test {" +
+                "    int x;" +
+                "}",
+                U -> U._class("Test", C -> C.field("x", Type::_int)));
+    }
+
+    private static void runTest(String expectedCode, Consumer<CompilationUnit> actualBuilder) throws Exception {
+        final JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
+        assert tool != null;
+
+        JavacTask expecteTask = (JavacTask) tool.getTask(null, null, null,
+            null, null, Arrays.asList(new MyFileObject(expectedCode)));
+        String expectedDump = dumpTree(expecteTask.parse().iterator().next());
+
+        JavacTask ct = (JavacTask) tool.getTask(null, null, null,
+            null, null, Arrays.asList(new MyFileObject("")));
+        ct.parse(); //init javac
+        Trees t = Trees.instance(ct);
+        
+        TreeBuilder builder = t.getTreeBuilder();
+
+        CompilationUnitTree cut = builder.createCompilationUnitTree(actualBuilder);
+        
+        String actualDump = dumpTree(cut);
+        
+        if (!actualDump.equals(expectedDump)) {
+            throw new AssertionError("Expected and actual dump differ. Expected: " + expectedDump + "; actual: " + actualDump);
+        }
+    }
+
+    static String dumpTree(Tree t) {
+        StringBuilder result = new StringBuilder();
+        new TreeScanner<Void, Void>() {
+            String sep = "";
+            @Override
+            public Void scan(Tree tree, Void p) {
+                result.append(sep);
+                sep = " ";
+                if (tree == null) {
+                    result.append("null");
+                    return null;
+                }
+                result.append("(");
+                result.append(tree.getKind().name());
+                super.scan(tree, p);
+                result.append(")");
+                return null;
+            }
+            @Override
+            public Void scan(Iterable<? extends Tree> nodes, Void p) {
+                result.append(sep);
+                sep = " ";
+                if (nodes == null) {
+                    result.append("null");
+                    return null;
+                }
+                result.append("(");
+                super.scan(nodes, p);
+                result.append(")");
+                return null;
+            }
+        }.scan(t, null);
+        return result.toString();
+    }
+
+    static class MyFileObject extends SimpleJavaFileObject {
+        private String text;
+
+        public MyFileObject(String text) {
+            super(URI.create("myfo:/Test.java"), JavaFileObject.Kind.SOURCE);
+            this.text = text;
+        }
+
+        @Override
+        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+            return text;
+        }
+    }
+}
\ No newline at end of file