summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/ParseNodeVisitor.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/frontend/ParseNodeVisitor.h')
-rw-r--r--js/src/frontend/ParseNodeVisitor.h136
1 files changed, 136 insertions, 0 deletions
diff --git a/js/src/frontend/ParseNodeVisitor.h b/js/src/frontend/ParseNodeVisitor.h
new file mode 100644
index 0000000000..18e57d68e6
--- /dev/null
+++ b/js/src/frontend/ParseNodeVisitor.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef frontend_ParseNodeVisitor_h
+#define frontend_ParseNodeVisitor_h
+
+#include "mozilla/Assertions.h"
+
+#include "frontend/ParseNode.h"
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
+
+namespace js {
+
+class FrontendContext;
+
+namespace frontend {
+
+/**
+ * Utility class for walking a JS AST.
+ *
+ * Simple usage:
+ *
+ * class HowTrueVisitor : public ParseNodeVisitor<HowTrueVisitor> {
+ * public:
+ * bool visitTrueExpr(BooleanLiteral* pn) {
+ * std::cout << "How true.\n";
+ * return true;
+ * }
+ * bool visitClassDecl(ClassNode* pn) {
+ * // The base-class implementation of each visit method
+ * // simply visits the node's children. So the subclass
+ * // gets to decide whether to descend into a subtree
+ * // and can do things either before or after:
+ * std::cout << "How classy.\n";
+ * return ParseNodeVisitor::visitClassDecl(pn);
+ * }
+ * };
+ *
+ * HowTrueVisitor v;
+ * v.visit(programRootNode); // walks the entire tree
+ *
+ * A ParseNodeVisitor can modify nodes, but it can't replace the current node
+ * with a different one; for that, use a RewritingParseNodeVisitor.
+ *
+ * Note that the Curiously Recurring Template Pattern is used for performance,
+ * as it eliminates the need for virtual method calls. Some rough testing shows
+ * about a 12% speedup in the FoldConstants.cpp pass.
+ * https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
+ */
+template <typename Derived>
+class ParseNodeVisitor {
+ public:
+ FrontendContext* fc_;
+
+ explicit ParseNodeVisitor(FrontendContext* fc) : fc_(fc) {}
+
+ [[nodiscard]] bool visit(ParseNode* pn) {
+ AutoCheckRecursionLimit recursion(fc_);
+ if (!recursion.check(fc_)) {
+ return false;
+ }
+
+ switch (pn->getKind()) {
+#define VISIT_CASE(KIND, TYPE) \
+ case ParseNodeKind::KIND: \
+ return static_cast<Derived*>(this)->visit##KIND(&pn->as<TYPE>());
+ FOR_EACH_PARSE_NODE_KIND(VISIT_CASE)
+#undef VISIT_CASE
+ default:
+ MOZ_CRASH("invalid node kind");
+ }
+ }
+
+ // using static_cast<Derived*> here allows plain visit() to be overridden.
+#define VISIT_METHOD(KIND, TYPE) \
+ [[nodiscard]] bool visit##KIND(TYPE* pn) { /* NOLINT */ \
+ return pn->accept(*static_cast<Derived*>(this)); \
+ }
+ FOR_EACH_PARSE_NODE_KIND(VISIT_METHOD)
+#undef VISIT_METHOD
+};
+
+/*
+ * Utility class for walking a JS AST that allows for replacing nodes.
+ *
+ * The API is the same as ParseNodeVisitor, except that visit methods take an
+ * argument of type `ParseNode*&`, a reference to the field in the parent node
+ * that points down to the node being visited. Methods can replace the current
+ * node by assigning to this reference.
+ *
+ * All visit methods take a `ParseNode*&` rather than more specific types like
+ * `BinaryNode*&`, to allow replacing the current node with one of a different
+ * type. Constant folding makes use of this.
+ */
+template <typename Derived>
+class RewritingParseNodeVisitor {
+ public:
+ FrontendContext* fc_;
+
+ explicit RewritingParseNodeVisitor(FrontendContext* fc) : fc_(fc) {}
+
+ [[nodiscard]] bool visit(ParseNode*& pn) {
+ AutoCheckRecursionLimit recursion(fc_);
+ if (!recursion.check(fc_)) {
+ return false;
+ }
+
+ switch (pn->getKind()) {
+#define VISIT_CASE(KIND, _type) \
+ case ParseNodeKind::KIND: \
+ return static_cast<Derived*>(this)->visit##KIND(pn);
+ FOR_EACH_PARSE_NODE_KIND(VISIT_CASE)
+#undef VISIT_CASE
+ default:
+ MOZ_CRASH("invalid node kind");
+ }
+ }
+
+ // using static_cast<Derived*> here allows plain visit() to be overridden.
+#define VISIT_METHOD(KIND, TYPE) \
+ [[nodiscard]] bool visit##KIND(ParseNode*& pn) { \
+ MOZ_ASSERT(pn->is<TYPE>(), \
+ "Node of kind " #KIND " was not of type " #TYPE); \
+ return pn->as<TYPE>().accept(*static_cast<Derived*>(this)); \
+ }
+ FOR_EACH_PARSE_NODE_KIND(VISIT_METHOD)
+#undef VISIT_METHOD
+};
+
+} // namespace frontend
+} // namespace js
+
+#endif // frontend_ParseNodeVisitor_h