Remove Side Effects in Aspect Weaving

Issue #268 new
Jesper Öqvist created an issue

Aspect introductions are woven into the AST (JastAdd's internal AST, not the generated AST) by directly modifying the AST. These modifications are mixed with attribute evaluations, meaning that the attributes compute values based on a partially woven AST. For most attributes this is OK in practice, but it might break stuff we don't know about yet because some attributes are producing wrong results. For example: the parents of an AST class depend on synthesized NTAs. The test case inh/nta_04p in jastadd-test demonstrates this:

// grammar: { A; B; }
aspect Test {
  syn nta B A.b() = new B();
  inh A C.a();
  eq A.b().a() = this;
}

The class A is a parent of B, however you wouldn't know this just from looking at the abstract grammar. The synthesized NTA A.b() makes A a parent of B, thus the parent information depends on attribute weaving. The parentMap() attribute is used to figure out the parents of AST classes, and so this attribute depends on attribute weaving.

It gets more complicated since attributes can be introduced into an AST class not only by direct attribute declarations, but also through interfaces. For example (syn/interface01):

aspect Test {
        interface I { }
        Node implements I;
        syn boolean I.attr();
        eq Node.attr() = false;
}

Interface weaving is done after normal attribute weaving and it depends on refinement processing.

A large downside to the current side-effect driven attribute weaving is that the parentMap() attribute can not be memoized, due to it producing incorrect results in the first example above. However, parentMap() can take a long time to evaluate and it gets exponentially worse for larger JastAdd projects.

Possible Solution

One possible solution is to add NTAs to compute attributes of different types for an AST class. Instead of iterating over attribute declarations and copying them into the target AST class, the AST class could query a global map of attributes to find attributes that should be woven into that class.

A sketch to this approach:

syn lazy Map<String, Collection<SynDecl>> Grammar.synDeclMap(String name) {
  Map<String, Collection<SynDecl>> map = new HashMap<>();
  for (TypeDecl type : getTypeDeclList()) {
    map.put(type.name(), new ArrayList<>());
  }
  for (SynDecl decl : grammar.synDecls) {
    map.get(decl.hostName).add(decl);
  }
  return map;
}

syn nta List<SynDecl> ASTDecl.synDecls() {
  List<SynDecl> decls = new List<>();
  for (SynDecl decl : grammar().synDeclMap().get(name())) {
    decls.add(decl);
  }
  return decls;
}

Comments (6)

  1. Jesper Öqvist reporter

    Attribute refinements are difficult to process in a declarative attribute-driven way because they insert new body declarations into existing AST classes. If refinements are handled via attributes, then mostly all other weaving needs to be converted at once.

  2. Jesper Öqvist reporter

    Declarative synthesized attribute weaving

    This replaces the imperative weaving transformation for synthesized attribute declarations by a nonterminal attribute that computes all existing synthesized attribute declarations.

    see #268

    → <<cset 164477ab8d3c>>

  3. Jesper Öqvist reporter

    Declarative synthesized NTA lookup

    Replaced the SynthesizedNta list child of ASTDecl by a synthesized attribute ASTDecl.synNtaDecls() in order to avoid a weaving side effect adding SynthesizedNta nodes to ASTDecl.

    see #268

    → <<cset 0fcf3e9d3b6a>>

  4. Log in to comment