summaryrefslogtreecommitdiffstats
path: root/js/src/frontend/ParseNodeVisitor.h
blob: 18e57d68e61c8c24ce0efa32e7dbc1c843fccfc8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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