Node.ceylon

import ceylon.collection {
    HashMap
}
"""Abstract superclass of all AST nodes.
   
   Note that nodes are not [[Identifiable]]: as they are immutable,
   the identity of a particular instance is meaningless.
   
   ### Additional information
   
   You can attach additional information to individual AST nodes
   using the [[put]] and [[get]] methods. Usage example:
   ~~~
   // toplevel
   shared Key<Token[]> tokensKey
       = ScopedKey<Token[]>(`module my.parser`, "tokens");
   
   // in the parser
   node.put(tokensKey, tokens);
   
   // somewhere else
   assert (exists tokens = node.get(tokensKey));
   ~~~"""
shared abstract class Node()
        of ExpressionIsh | Statement | Declaration | Annotation | Annotations | Parameter | TypeParameter | TypeParameters | CaseTypes | SatisfiedTypes | TypeConstraint | PackageDescriptor | ModuleImport | ModuleBody | ModuleDescriptor | ImportAlias | ImportWildcard | ImportElement | ImportElements | Import | AnyCompilationUnit | Condition | Conditions | IfClause | ElseClause | ClassInstantiation | ExtendedType | ClassSpecifier | TypeSpecifier | Variable | ForIterator | ForClause | FailClause | ComprehensionClause | FinallyClause | CatchClause | Resource | Resources | TryClause | CaseItem | ElseCaseClause | CaseClause | SwitchCases | SwitchClause
        extends Object() {
    
    "The child nodes of this node."
    shared formal Node[] children;
    
    /*
     TODO
     [Performance tests][gist] seem to indicate that
     the a HashMap with string keys performs worse
     than a HashMap with keys that wrap a string (here: Key).
     This is probably because the string is often wrapped
     and unwrapped between java.lang.String and
     ceylon.language.String.
     
     This requires more investigation. If the String (un)wrapping
     really is the cause of the worsened performance, the
     compiler probably needs to learn to optimize that,
     as a HashMap keyed by strings is probably not uncommon.
     
     [gist]: https://gist.github.com/lucaswerkmeister/b95470259328dea550ad
     */
    HashMap<String,Object> extraInfo = HashMap<String,Object>();
    
    "Returns the additional information attached to this node
     using the given [[key]], if any."
    see (`function put`)
    shared Type? get<out Type>(Key<out Type> key)
            given Type satisfies Object {
        if (exists ret = extraInfo[key.id]) {
            assert (is Type ret);
            return ret;
        } else {
            return null;
        }
    }
    
    "Attaches the given [[additional information|item]]
     to this node using the given [[key]].
     If other information was attached
     with the same key previously, it is returned.
     
     If you don’t care about the previously attached information,
     you might want to use [[set]] instead."
    see (`function set`, `function get`, `function remove`)
    shared Type? put<Type>(Key<Type> key, Type item)
            given Type satisfies Object {
        assert (is Type? ret = extraInfo.put(key.id, item));
        return ret;
    }
    
    "Attaches the given [[additional information|item]]
     to this node using the given [[key]].
     
     Unlike [[put]], this method discards any previously attached information;
     this makes it possible to declare the type parameter of [[key]]
     contravariant, enabling users to abstract over keys of different types."
    see (`function put`, `function get`, `function remove`)
    shared void set<in Type>(Key<in Type> key, Type item)
            given Type satisfies Object
            => extraInfo.put(key.id, item);
    
    "Removes the additional information attached to
     this node using the given [[key]] from this node,
     returning it."
    see (`function put`)
    shared Type? remove<Type>(Key<out Type> key)
            given Type satisfies Object {
        assert (is Type? ret = extraInfo.remove(key.id));
        return ret;
    }
    
    "Copies this node’s additional information to the [[other]] node."
    see (`function put`, `function get`)
    shared void copyExtraInfoTo(Node other) {
        other.extraInfo.clear();
        other.extraInfo.putAll(extraInfo);
    }
    
    "Transform this node with the given [[transformer]] by calling the appropriate
     `transformX` method on the transformer.
     
     If you have a `Node node` that’s actually an [[LIdentifier]] instance, then the
     runtime will call `LIdentifier.transform`; therefore, this method is by nature *narrowing*.
     This means that if [[transformer]] is a [[NarrowingTransformer]], calling
     `node.transform(transformer)` is equivalent to calling `transformer.transformNode(node)`.
     On the other hand, if [[transformer]] is a [[WideningTransformer]], then the two
     operations are very different."
    see (`interface NarrowingTransformer`, `interface WideningTransformer`)
    shared formal Result transform<out Result>(Transformer<Result> transformer);
    "Transform all [[child nodes|children]] by calling their [[transform]] methods."
    shared Result[] transformChildren<out Result>(Transformer<Result> transformer) => [
        for (child in children)
            child.transform(transformer)
    ];
    
    "Visit this node with the given [[visitor]].
     Calls the appropriate `visitX` method on the visitor."
    shared void visit(Visitor visitor) => transform(visitor);
    "Visit the children of this node with the given [[visitor]].
     Calls [[visit]] on each [[child node|children]]."
    shared void visitChildren(Visitor visitor) => transformChildren(visitor);
    
    "A developer-friendly string representing the instance.
     
     At the moment, this is a Ceylon expression (created by [[CeylonExpressionTransformer]]), but this may change in the future;
     for anything other than diagnostic information, use [[CeylonExpressionTransformer]] or one of the other attributes directly."
    shared actual String string {
        // TODO investigate performance for big nodes. Should we cache the result?
        // (not all that useful since CET won’t use the cached result for sub-nodes.)
        return transform(CeylonExpressionTransformer());
        // (using the same CET every time might also improve performance)
        // (but not across multiple nodes! it carries state in its indentation)
    }
}