From 83593f2315c8d26a9740a7b1b6dd68e28eede5b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jesper=20=C3=96qvist?= <jesper.oqvist@cs.lth.se>
Date: Mon, 18 Mar 2019 11:24:55 +0100
Subject: [PATCH] Generalize handling of implicit collection roots

Allow implicit root type for collection attributes when the grammar has
multiple root types.

If there are multiple roots in the grammar, the collection root type is set to
ASTNode.

Factored out root finding in collection attribute templates.

fixes #294 (bitbucket)
---
 ChangeLog                                 |  5 +++
 src/jastadd/ast/ASTErrors.jrag            |  9 ------
 src/jastadd/ast/AttributeProblems.jrag    | 16 ----------
 src/jastadd/ast/CollectionAttributes.jrag | 37 ++++++++++++++++++++---
 src/template/ast/Collections.tt           | 34 ++++++++++++---------
 5 files changed, 57 insertions(+), 44 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3ce0d883..ac56606a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2019-03-18  Jesper Öqvist <jesper.oqvist@cs.lth.se>
+
+    * Allow implicit root type for collection attributes in grammars with
+    multiple root types.
+
 2018-06-14  Jesper Öqvist <jesper.oqvist@cs.lth.se>
 
     * Removed generation of the is$Equal method. The method was unused.
diff --git a/src/jastadd/ast/ASTErrors.jrag b/src/jastadd/ast/ASTErrors.jrag
index a2109f3d..96a8f448 100644
--- a/src/jastadd/ast/ASTErrors.jrag
+++ b/src/jastadd/ast/ASTErrors.jrag
@@ -43,15 +43,6 @@ aspect ASTErrors {
       [new LinkedList<Problem>()]
       root Grammar;
 
-  syn boolean TypeDecl.isRootNode() = false;
-
-  eq ASTDecl.isRootNode() = isPotentialRootNode() && parents().isEmpty();
-
-  syn boolean TypeDecl.isPotentialRootNode() = false;
-
-  eq ASTDecl.isPotentialRootNode() =
-      !hasAbstract() && !isASTNodeDecl() && !isOptSubtype() && !isListSubtype();
-
   Grammar contributes Problem.builder()
           .message("there is no root node in the grammar!")
           .buildWarning()
diff --git a/src/jastadd/ast/AttributeProblems.jrag b/src/jastadd/ast/AttributeProblems.jrag
index de7fa1df..aa5e9ae6 100644
--- a/src/jastadd/ast/AttributeProblems.jrag
+++ b/src/jastadd/ast/AttributeProblems.jrag
@@ -461,22 +461,6 @@ aspect AttributeProblems {
     return error(msg);
   }
 
-  CollDecl contributes multipleRootsProblem()
-      when hasMultipleRootsProblem()
-      to TypeDecl.attributeProblems()
-      for hostClass();
-
-  syn boolean CollDecl.hasMultipleRootsProblem() = root == null && grammar().roots().size() > 1;
-
-  syn Problem CollDecl.multipleRootsProblem() {
-    StringBuilder buf = new StringBuilder();
-    buf.append("multiple tree roots to search for contributions. Please explicitly select one of");
-    for (ASTDecl decl : grammar().roots()) {
-      buf.append(" " + decl.name());
-    }
-    return error(buf.toString());
-  }
-
   CollDecl contributes error("No tree roots to search for contributions. "
           + "Please declare an explicit root node.")
       when hasNoRootProblem()
diff --git a/src/jastadd/ast/CollectionAttributes.jrag b/src/jastadd/ast/CollectionAttributes.jrag
index c4ca0f82..28ba15e9 100644
--- a/src/jastadd/ast/CollectionAttributes.jrag
+++ b/src/jastadd/ast/CollectionAttributes.jrag
@@ -1,4 +1,4 @@
-/* Copyright (c) 2005-2016, The JastAdd Team
+/* Copyright (c) 2005-2019, The JastAdd Team
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -39,6 +39,9 @@ aspect CollectionAttributes {
   /** @return the type name of the collection root node */
   syn String CollDecl.rootType() = root().name();
 
+  /** Returns {@code true} if the declaration does not have an explicit root type. */
+  syn boolean CollDecl.implicitRoot() = root == null;
+
   /** @return the type name of the collection root node */
   syn String CollEq.rootType() = decl().rootType();
 
@@ -134,9 +137,18 @@ aspect CollectionAttributes {
     }
   }
 
-  syn TypeDecl AttrDecl.root() = grammar().root();
-
-  eq CollDecl.root() = root != null ? grammar().lookup(root) : super.root();
+  syn TypeDecl CollDecl.root() {
+    if (root != null) {
+      return grammar().lookup(root);
+    } else {
+      Collection<ASTDecl> roots = grammar().roots();
+      if (roots.size() == 1) {
+        return roots.iterator().next();
+      } else {
+        return grammar().lookup(config().astNodeType());
+      }
+    }
+  }
 
   // Only used by circular collection attributes.
   public void AttrDecl.emitCircularCollectionEval(PrintStream out) {
@@ -428,6 +440,10 @@ aspect CollectionAttributes {
     return s.toString();
   }
 
+  /**
+   * Finds all roots in the grammar.
+   * A root is not a child of any other node.
+   */
   syn lazy Collection<ASTDecl> Grammar.roots() {
     Collection<ASTDecl> roots = new HashSet<ASTDecl>();
     for (TypeDecl decl : getTypeDeclList()) {
@@ -438,7 +454,18 @@ aspect CollectionAttributes {
     return roots;
   }
 
-  syn ASTDecl Grammar.root() = roots().isEmpty() ? null : roots().iterator().next();
+  /**
+   * Returns {@code true} if this type is a root in the abstract grammar.
+   * A root is not abstract and not a child of any other type in the grammar.
+   */
+  syn boolean TypeDecl.isRootNode() = false;
+
+  eq ASTDecl.isRootNode() = isPotentialRootNode() && parents().isEmpty();
+
+  syn boolean TypeDecl.isPotentialRootNode() = false;
+
+  eq ASTDecl.isPotentialRootNode() =
+      !hasAbstract() && !isASTNodeDecl() && !isOptSubtype() && !isListSubtype();
 
   eq ASTDecl.getCollDecl().hostClass() = this;
 
diff --git a/src/template/ast/Collections.tt b/src/template/ast/Collections.tt
index 660efece..d5c16669 100644
--- a/src/template/ast/Collections.tt
+++ b/src/template/ast/Collections.tt
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2015, The JastAdd Team
+# Copyright (c) 2013-2019, The JastAdd Team
 # All rights reserved.
 #
 # Redistribution and use in source and binary forms, with or without
@@ -25,6 +25,22 @@
 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 # POSSIBILITY OF SUCH DAMAGE.
 
+CollDecl.findRoot [[
+$if(#implicitRoot)
+$ASTNode node = this;
+while (node.getParent() != null) {
+  node = node.getParent();
+}
+$else
+$ASTNode node = this;
+while (node != null && !(node instanceof #rootType)) {
+  node = node.getParent();
+}
+$endif
+$include(CollDecl.collDebugCheck)
+#rootType root = (#rootType) node;
+]]
+
 CollDecl.collDebugCheck [[
 $if(DebugMode)
 if (node == null) {
@@ -37,12 +53,7 @@ $endif
 CollDecl.computeMethod:onePhase [[
   /** @apilevel internal */
   private #getType #(name)_compute() {
-    $ASTNode node = this;
-    while (node != null && !(node instanceof #rootType)) {
-      node = node.getParent();
-    }
-    $include(CollDecl.collDebugCheck)
-    #rootType root = (#rootType) node;
+    $include(CollDecl.findRoot)
     root.survey_#collectionId();
     if (#(signature)_value == null) {
       #(signature)_value = $BottomValue;
@@ -54,12 +65,7 @@ CollDecl.computeMethod:onePhase [[
 CollDecl.computeMethod:twoPhase [[
   /** @apilevel internal */
   private #getType #(name)_compute() {
-    $ASTNode node = this;
-    while (node != null && !(node instanceof #rootType)) {
-      node = node.getParent();
-    }
-    $include(CollDecl.collDebugCheck)
-    #rootType root = (#rootType) node;
+    $include(CollDecl.findRoot)
     root.survey_#collectionId();
     #getType _computedValue = $BottomValue;
     $include(CollDecl.collectContributions)
@@ -69,7 +75,7 @@ CollDecl.computeMethod:twoPhase [[
 
 CollDecl.collectContributions [[
 if (root.contributorMap_#collectionId.containsKey(this)) {
-  for ($ASTNode contributor : root.contributorMap_#collectionId.get(this)) {
+  for ($ASTNode contributor : (java.util.Set<$ASTNode>) root.contributorMap_#collectionId.get(this)) {
     contributor.contributeTo_#(signature)(_computedValue);
   }
 }
-- 
GitLab