The accept function that act as a nodes accept method in the Visitor pattern.
Helper to dispatch the node if the method exist otherwise to a fallback.
Copyright (c) 2017, Joakim Brännström. All rights reserved.
Joakim Brännström (joakim.brannstrom@gmx.com)
This module contains the basic building blocks for an AST.
TODO consider if disabling postblit for visitors is a good recommendation.
## Enable different tree visit algorithms The intension of the design is to separate the algorithm for _how_ to visit the tree from the structural requirements _of_ the tree.
This is achieve by the _accept_ function in this module. It correspond with the _accept_ method in the Visitor pattern. It dispatch the call to the composite visitors implAccept. _How_ to visit the specific part of the tree is implemented in implAccept.
## Generic Structural Design It is desired to avoid boilerplate code and dynamic dispatch. This is achieved via compile time introspection in the maybeCallVisit template.
A pro side effect of this is that the runtime performance is _probably_ improved. A con side effect is that the compile time may be adversely affected.
## Potential Problem The recommended implementation of using mixins for the implAccept makes it so that when a new node type is added to a subtree all the parents to that part of the tree must update their visitors.
This is deemed OK because adding new nodes is assumed to be rare. This design problem do not affect the use override of visit functions.
# Usage This is a minimal example using the building blocks of this module.
Separate the implAccept in mixin templates to allow reuse of them in a supertree to allow visiting a full tree.
mixin template NodeA_Accept(VisitorT, UserT) { // to avoid problems of missing imports when using this mixin import my.subtree; void implAccept(ref NodeA n) { static void fallback(ref VisitorT!UserT self, ref UserT user, ref NodeA node) { auto parent_node = cast(Node) node; maybeCallVisit(self, user, parent_node); } foreach (child; n.children) { maybeCallVisit(this, user, child, &fallback); } } }
Make a subtree visitor for NodeA. Add a convenient visit function for the user to use if the top node isn't of interest in the user implementation. But it is important to still allow an override to be handled correctly.
struct NodeAVisitor(UserT) { UserT user; void visit(ref NodeA n) { import llvm_hiwrap.ast.tree; static void fallback(ref this self, ref UserT user, ref NodeA node) { accept(n, self); } maybeCallVisit(this, user, n); } mixin NodeA_Accept!(NodeAVisitor, UserT); }
Make a supertree visitor. Assume that there exist a NodeB with the same implementation as NodeA.
mixin template TopNode_Accept(VisitorT, UserT) { import my.supertree; void implAccept(ref TopNode n) { static void fallback(T)(ref VisitorT!UserT self, ref UserT user, ref T node) { auto parent_node = cast(Node) node; maybeCallVisit(self, user, parent_node); } foreach (child; n.childrenA) { maybeCallVisit(this, user, child, &fallback!NodeA); } foreach (child; n.childrenB) { maybeCallVisit(this, user, child, &fallback!NodeB); } } } struct SuperVisitor(UserT) { UserT user; void visit(ref TopNode n) { import llvm_hiwrap.ast.tree; static void fallback(ref this self, ref UserT user, ref TopNode node) { accept(n, self); } maybeCallVisit(this, user, n); } mixin NodeA_Accept!(NodeAVisitor, UserT); mixin NodeB_Accept!(NodeAVisitor, UserT); mixin TopNode_Accept!(NodeAVisitor, UserT); }
Page generated by adrdox