diff --git a/ChangeLog b/ChangeLog index 798df6c9829196ab917282b88c9d77c47a8571dc..f8539cc26c44d9285fd706375ea50d7475c02c97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -7,6 +7,13 @@ evaluated. The next time the attribute is accessed it checks if it can reuse the last cached value based on the current cycle state and the last cached cycle state. + * Added mechanism for custom control over the collection attribute survey + phase. This allows altering the tree traversal of collection attributes + to, e.g., traverse certain nonterminal nodes, or to exclude certain + subtrees. This feature works similar to an ordinary contribution + statement, but instead of a contribution expression the statement contains + a code block which is inserted directly into the generated collection + attribute tree traversal code. 2016-03-16 Jesper Öqvist <jesper.oqvist@cs.lth.se> diff --git a/doc/reference-manual.md b/doc/reference-manual.md index 3c35810ce4f36b8c5c3be83e612756d144585ac1..0042d0d0b8aa6981636b70e3fb4ec371cebd1e1a 100644 --- a/doc/reference-manual.md +++ b/doc/reference-manual.md @@ -1460,6 +1460,28 @@ For example, if the collection attribute is declared as `coll LinkedList<String> ...` then `value-exp` should have the type `Iterable<String>`. +#### Custom Collection Survey + +It is possible to customize the tree traversal used to search for contributions +for a collection attribute. This can be done using an alternative form of the +`contributes` statement, where the expression part is replaced by a code block: + + N1 contributes { + getA().collectContributions(); + super.collectContributions(); + } to N2.a(); + + +The meaning of the above code is that the `N1` node type should search its `A` +child while searching contributions for the `N2.a()` collection attribute. The +call to `super.collectContributions()` is needed to ensure that all regular +children of `N1` are also searched for contributions. + +Multiple custom collection survey blocks like this can be used, but only one of +them needs to call `super.collectContributions()`. It is possible to use +attributes inside the code blocks to decide when a particular subtree should be +searched for contributions. + ## <a id="Rewrites"></a>Rewrites JastAdd has a mechanism for replacing AST nodes by a rewritten version of the diff --git a/src/jastadd/ast/CollectionAttributes.jrag b/src/jastadd/ast/CollectionAttributes.jrag index 545f871930fb0cfc56b8335ccfd680babd59b2c9..09f600ef2d737b1d3c4d18ac56855b571b490650 100644 --- a/src/jastadd/ast/CollectionAttributes.jrag +++ b/src/jastadd/ast/CollectionAttributes.jrag @@ -159,8 +159,7 @@ aspect CollectionAttributes { return false; } - syn lazy String CollEq.contributionSignature() = - ((CollDecl) decl()).getTarget() + "_" + signature(); + syn lazy String CollEq.contributionSignature() = decl().getTarget() + "_" + signature(); /** * Lazy condition means that the condition is evaluated during the @@ -201,56 +200,110 @@ aspect CollectionAttributes { syn boolean CollEq.hasCondition() = !getCondition().isEmpty(); + interface SurveyContribution { + void emitSurveyCode(CollDecl delc, PrintStream out); + } + + class EqSurveyContribution implements SurveyContribution { + private final CollEq eq; + public EqSurveyContribution(CollEq eq) { + this.eq = eq; + } + + @Override + public void emitSurveyCode(CollDecl decl, PrintStream out) { + TemplateContext tt = eq.templateContext(); + tt.bind("BottomValue", decl.getBottomValue()); + tt.bind("CombOp", decl.getCombOp()); + if (decl.onePhase()) { + tt.bind("HasCondition", eq.hasCondition()); + tt.expand("CollEq.collectContributors:onePhase", out); + } else { + tt.bind("HasCondition", eq.hasCondition() && !decl.lazyCondition()); + tt.expand("CollEq.collectContributors:twoPhase", out); + } + } + } + + class BlockSurveyContribution implements SurveyContribution { + private final CustomSurveyBlock block; + public BlockSurveyContribution(CustomSurveyBlock block) { + this.block = block; + } + + @Override + public void emitSurveyCode(CollDecl decl, PrintStream out) { + out.println(block.comment); + String replacement; + if (decl.onePhase()) { + replacement = String.format(".collect_contributors_%s(_root);", decl.collectionId()); + } else { + replacement = String.format(".collect_contributors_%s(_root, _map);", decl.collectionId()); + } + out.println(block.surveyCode.replaceAll("\\.collectContributions\\(\\);", replacement)); + } + } + + /** + * Generates the survey method for each type with a collection contribution. + */ private void ASTDecl.collectContributors(PrintStream out) { // Mapping collection declaration to collection equations. - HashMap<CollDecl, Collection<CollEq>> map = new LinkedHashMap<CollDecl, Collection<CollEq>>(); + HashMap<CollDecl, Collection<SurveyContribution>> map = + new LinkedHashMap<CollDecl, Collection<SurveyContribution>>(); - for (int i = 0; i < getNumCollEq(); i++) { - CollEq attr = getCollEq(i); - CollDecl decl = (CollDecl) attr.decl(); - Collection<CollEq> equations = map.get(decl); + for (CollEq attr : getCollEqList()) { + CollDecl decl = attr.decl(); + Collection<SurveyContribution> equations = map.get(decl); if (equations == null) { - equations = new ArrayList<CollEq>(); + equations = new ArrayList<SurveyContribution>(); map.put(decl, equations); } - equations.add(attr); + equations.add(new EqSurveyContribution(attr)); } - for (Map.Entry<CollDecl, Collection<CollEq>> entry : map.entrySet()) { - CollDecl decl = entry.getKey(); - Collection<CollEq> equations = entry.getValue(); + for (CustomSurveyBlock block : customSurveyBlocks) { + CollDecl decl = grammar().lookupCollDecl(block.collHost, block.collName); + if (decl == null) { + throw new Error(String.format( + "%s:%d: Can not add custom survey code for unknown collection attribute: %s.%s()", + block.fileName, block.startLine, block.collHost, block.collName)); + } + Collection<SurveyContribution> equations = map.get(decl); + if (equations == null) { + equations = new ArrayList<SurveyContribution>(); + map.put(decl, equations); + } + equations.add(new BlockSurveyContribution(block)); + } + for (Map.Entry<CollDecl, Collection<SurveyContribution>> entry : map.entrySet()) { + CollDecl decl = entry.getKey(); + Collection<SurveyContribution> equations = entry.getValue(); decl.templateContext().expand("CollDecl.collectContributors:header", out); - - String bottomValue = decl.getBottomValue(); - - for (CollEq equation : equations) { - TemplateContext tt = equation.templateContext(); - tt.bind("BottomValue", bottomValue); - tt.bind("CombOp", decl.getCombOp()); - if (decl.onePhase()) { - tt.bind("HasCondition", equation.hasCondition()); - tt.expand("CollEq.collectContributors:onePhase", out); - } else { - tt.bind("HasCondition", equation.hasCondition() && !decl.lazyCondition()); - tt.expand("CollEq.collectContributors:twoPhase", out); - } + boolean skipSuperCall = false; + for (SurveyContribution contribution : equations) { + contribution.emitSurveyCode(decl, out); + skipSuperCall |= contribution instanceof BlockSurveyContribution; } - if (isASTNodeDecl()) { - decl.templateContext().expand("CollDecl.collectContributors:default", out); + if (skipSuperCall) { + out.println(config().indent + "}"); } else { - decl.templateContext().expand("CollDecl.collectContributors:end", out); + if (isASTNodeDecl()) { + decl.templateContext().expand("CollDecl.collectContributors:default", out); + } else { + decl.templateContext().expand("CollDecl.collectContributors:end", out); + } } } } private void ASTDecl.contributeTo(PrintStream out) { - HashMap<CollDecl, ArrayList<CollEq>> map = new LinkedHashMap<CollDecl, ArrayList<CollEq>>(); - for (int i = 0; i < getNumCollEq(); i++) { - CollEq attr = getCollEq(i); + HashMap<CollDecl, Collection<CollEq>> map = new LinkedHashMap<CollDecl, Collection<CollEq>>(); + for (CollEq attr : getCollEqList()) { if (!attr.onePhase()) { - CollDecl decl = (CollDecl) attr.decl(); - ArrayList<CollEq> equations = map.get(decl); + CollDecl decl = attr.decl(); + Collection<CollEq> equations = map.get(decl); if (equations == null) { equations = new ArrayList<CollEq>(); map.put(decl, equations); @@ -259,9 +312,9 @@ aspect CollectionAttributes { } } - for (Map.Entry<CollDecl, ArrayList<CollEq>> entry : map.entrySet()) { + for (Map.Entry<CollDecl, Collection<CollEq>> entry : map.entrySet()) { CollDecl decl = entry.getKey(); - ArrayList<CollEq> equations = entry.getValue(); + Collection<CollEq> equations = entry.getValue(); decl.templateContext().bind("IsAstNode", isASTNodeDecl()); decl.templateContext().expand("CollDecl.contributeTo:header", out); @@ -392,13 +445,14 @@ aspect CollectionAttributes { * @return the collection attribute declaration, or {@code null} if no * declaration was found. */ - syn CollDecl CollEq.decl() { - TypeDecl typeDecl = grammar().lookup(getTargetName()); + syn CollDecl CollEq.decl() = grammar().lookupCollDecl(getTargetName(), getTargetAttributeName()); + + syn CollDecl Grammar.lookupCollDecl(String hostName, String collName) { + TypeDecl typeDecl = lookup(hostName); if (typeDecl != null) { - TypeDecl astDecl = (TypeDecl) typeDecl; - for (int i = 0; i < astDecl.getNumCollDecl(); i++) { - if (astDecl.getCollDecl(i).getName().equals(getTargetAttributeName())) { - return astDecl.getCollDecl(i); + for (CollDecl decl : typeDecl.getCollDeclList()) { + if (decl.getName().equals(collName)) { + return decl; } } } @@ -462,26 +516,88 @@ aspect CollectionAttributes { return null; } - public CollEq Grammar.addCollEq(String targetName, String targetAttributeName, - String attributeType, String reference, String fileName, + public class CustomSurveyBlock { + public final String collName; + public final String collHost; + public final String surveyCode; + public final String fileName; + public final int startLine; + public final int endLine; + public final String comment; + public final String aspectName; + + public CustomSurveyBlock(String collName, String collHost, String surveyCode, + String fileName, int startLine, int endLine, String comment, + String aspectName) { + this.collName = collName; + this.collHost = collHost; + this.surveyCode = surveyCode; + this.fileName = fileName; + this.startLine = startLine; + this.endLine = endLine; + this.comment = comment; + this.aspectName = aspectName; + } + } + + public final Collection<CustomSurveyBlock> ASTDecl.customSurveyBlocks = + new LinkedList<CustomSurveyBlock>(); + + /** + * Adds code to the survey method for a particular collection attribute in the + * given node type. + */ + public void Grammar.addCustomSurveyBlock(String collHost, + String collName, + String nodeType, + String codeBlock, + String fileName, + int startLine, + int endLine, + org.jastadd.jrag.AST.SimpleNode commentNode, + String aspectName, + ArrayList<String> annotations) { + TypeDecl type = lookup(nodeType); + if (type != null && type instanceof ASTDecl) { + ((ASTDecl) type).customSurveyBlocks.add(new CustomSurveyBlock( + collName, + collHost, + codeBlock, + fileName, + startLine, + endLine, + Unparser.unparseComment(commentNode), + aspectName)); + for (String annotation : annotations) { + errorf("annotation %s not allowed for custom survey blocks.", annotation); + } + } else { + errorf("Can not add custom collection survey code to unknown class %s in %s at line %d", + nodeType, fileName, startLine); + } + } + + public CollEq Grammar.addCollEq(String collHost, + String collName, + String nodeType, String reference, String fileName, int startLine, int endLine, boolean iterableValue, boolean iterableTarget, - org.jastadd.jrag.AST.SimpleNode node, String aspectName, + org.jastadd.jrag.AST.SimpleNode commentNode, String aspectName, ArrayList<String> annotations) { - TypeDecl c = lookup(attributeType); - if (c != null && c instanceof ASTDecl) { + TypeDecl type = lookup(nodeType); + if (type != null && type instanceof ASTDecl) { CollEq equ = new CollEq( new List(), new List(), - targetName, + collHost, fileName, startLine, endLine, - Unparser.unparseComment(node), + Unparser.unparseComment(commentNode), aspectName, "", "", - targetName, - targetAttributeName, + collHost, + collName, reference); equ.setIterableValue(iterableValue); equ.setIterableTarget(iterableTarget); @@ -489,11 +605,11 @@ aspect CollectionAttributes { equ.addAnnotation(new Annotation(annotation)); } // TODO(joqvist): defer collection attribute weaving. - ((ASTDecl) c).addCollEq(equ); + ((ASTDecl) type).addCollEq(equ); return equ; } else { errorf("Can not add collection contribution to unknown class %s in %s at line %d", - attributeType, fileName, startLine); + nodeType, fileName, startLine); // TODO(joqvist): defer weaving so we can return non-null. return null; } diff --git a/src/javacc/jrag/Jrag.jjt b/src/javacc/jrag/Jrag.jjt index 9bbcd42428a3c6e644f20c5dca99ed81e48fcbde..bd9a97408f0d87e68a75dba480ca25c40e24b26f 100644 --- a/src/javacc/jrag/Jrag.jjt +++ b/src/javacc/jrag/Jrag.jjt @@ -1371,11 +1371,13 @@ void CollectionContribution(): { Token t; Token first, last; - SimpleNode value; + SimpleNode block; + SimpleNode value = null; SimpleNode condition = null; String targetName; String targetAttributeName; String attributeType; + String surveyBlock = null; SimpleNode reference = null; org.jastadd.ast.AST.List contributionList = new org.jastadd.ast.AST.List(); boolean iterableValue = false; @@ -1387,30 +1389,47 @@ void CollectionContribution(): ( annotation = Annotation() { annotations.add(Unparser.unparseComment(annotation)); } )* t = <IDENTIFIER> { first = token; attributeType = t.image; } "contributes" - [ LOOKAHEAD("each") "each" { iterableValue = true; } ] - value = Expression() - [ "when" condition = Expression() ] - "to" t = <IDENTIFIER> "." { targetName = t.image; } t = AttributeName() { targetAttributeName = t.image; } "(" ")" - [ "for" [ LOOKAHEAD("each") "each" { iterableTarget = true; } ] reference = Expression() ] + ( + [ LOOKAHEAD("each") "each" { iterableValue = true; } ] + value = Expression() + [ "when" condition = Expression() ] + "to" t = <IDENTIFIER> "." { targetName = t.image; } t = AttributeName() { targetAttributeName = t.image; } "(" ")" + [ "for" [ LOOKAHEAD("each") "each" { iterableTarget = true; } ] reference = Expression() ] + | block = Block() { surveyBlock = Unparser.unparse(block); } + "to" t = <IDENTIFIER> "." { targetName = t.image; } t = AttributeName() { targetAttributeName = t.image; } "(" ")" + ) ";" { last = token; } { - org.jastadd.ast.AST.CollEq equ = root.addCollEq(targetName, - targetAttributeName, - attributeType, - reference == null ? "" : Unparser.unparse(reference), - fileName, - first.beginLine, - last.endLine, - iterableValue, - iterableTarget, - jjtThis, - enclosingAspect, - annotations); - // TODO(joqvist): make equ non-null. - if (equ != null) { - equ.setValue(Unparser.unparse(value)); - if (condition != null) { - equ.setCondition(Unparser.unparse(condition)); + if (surveyBlock != null) { + root.addCustomSurveyBlock(targetName, + targetAttributeName, + attributeType, + surveyBlock, + fileName, + first.beginLine, + last.endLine, + jjtThis, + enclosingAspect, + annotations); + } else { + org.jastadd.ast.AST.CollEq equ = root.addCollEq(targetName, + targetAttributeName, + attributeType, + reference == null ? "" : Unparser.unparse(reference), + fileName, + first.beginLine, + last.endLine, + iterableValue, + iterableTarget, + jjtThis, + enclosingAspect, + annotations); + // TODO(joqvist): make equ non-null. + if (equ != null) { + equ.setValue(Unparser.unparse(value)); + if (condition != null) { + equ.setCondition(Unparser.unparse(condition)); + } } } }