Skip to content
Snippets Groups Projects
Commit 63de7884 authored by Jesper's avatar Jesper
Browse files

Add custom collection tree traversal feature

Added a new collection contribution construct that can be used to control the
collection survey phase. The reference manual has been updated to document this
new feature.

fixes #256 (bitbucket)
parent 62d28e6f
Branches
No related tags found
No related merge requests found
......@@ -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>
......
......@@ -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
......
......@@ -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,41 +200,95 @@ 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 (skipSuperCall) {
out.println(config().indent + "}");
} else {
if (isASTNodeDecl()) {
decl.templateContext().expand("CollDecl.collectContributors:default", out);
} else {
......@@ -243,14 +296,14 @@ aspect CollectionAttributes {
}
}
}
}
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;
}
......
......@@ -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,13 +1389,29 @@ 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() ]
| block = Block() { surveyBlock = Unparser.unparse(block); }
"to" t = <IDENTIFIER> "." { targetName = t.image; } t = AttributeName() { targetAttributeName = t.image; } "(" ")"
)
";" { last = token; }
{
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,
......@@ -1415,6 +1433,7 @@ void CollectionContribution():
}
}
}
}
void AspectAddInterface() :
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment