xref: /aosp_15_r20/external/libchrome/third_party/jinja2/idtracking.py (revision 635a864187cb8b6c713ff48b7e790a6b21769273)
1*635a8641SAndroid Build Coastguard Workerfrom jinja2.visitor import NodeVisitor
2*635a8641SAndroid Build Coastguard Workerfrom jinja2._compat import iteritems
3*635a8641SAndroid Build Coastguard Worker
4*635a8641SAndroid Build Coastguard Worker
5*635a8641SAndroid Build Coastguard WorkerVAR_LOAD_PARAMETER = 'param'
6*635a8641SAndroid Build Coastguard WorkerVAR_LOAD_RESOLVE = 'resolve'
7*635a8641SAndroid Build Coastguard WorkerVAR_LOAD_ALIAS = 'alias'
8*635a8641SAndroid Build Coastguard WorkerVAR_LOAD_UNDEFINED = 'undefined'
9*635a8641SAndroid Build Coastguard Worker
10*635a8641SAndroid Build Coastguard Worker
11*635a8641SAndroid Build Coastguard Workerdef find_symbols(nodes, parent_symbols=None):
12*635a8641SAndroid Build Coastguard Worker    sym = Symbols(parent=parent_symbols)
13*635a8641SAndroid Build Coastguard Worker    visitor = FrameSymbolVisitor(sym)
14*635a8641SAndroid Build Coastguard Worker    for node in nodes:
15*635a8641SAndroid Build Coastguard Worker        visitor.visit(node)
16*635a8641SAndroid Build Coastguard Worker    return sym
17*635a8641SAndroid Build Coastguard Worker
18*635a8641SAndroid Build Coastguard Worker
19*635a8641SAndroid Build Coastguard Workerdef symbols_for_node(node, parent_symbols=None):
20*635a8641SAndroid Build Coastguard Worker    sym = Symbols(parent=parent_symbols)
21*635a8641SAndroid Build Coastguard Worker    sym.analyze_node(node)
22*635a8641SAndroid Build Coastguard Worker    return sym
23*635a8641SAndroid Build Coastguard Worker
24*635a8641SAndroid Build Coastguard Worker
25*635a8641SAndroid Build Coastguard Workerclass Symbols(object):
26*635a8641SAndroid Build Coastguard Worker
27*635a8641SAndroid Build Coastguard Worker    def __init__(self, parent=None, level=None):
28*635a8641SAndroid Build Coastguard Worker        if level is None:
29*635a8641SAndroid Build Coastguard Worker            if parent is None:
30*635a8641SAndroid Build Coastguard Worker                level = 0
31*635a8641SAndroid Build Coastguard Worker            else:
32*635a8641SAndroid Build Coastguard Worker                level = parent.level + 1
33*635a8641SAndroid Build Coastguard Worker        self.level = level
34*635a8641SAndroid Build Coastguard Worker        self.parent = parent
35*635a8641SAndroid Build Coastguard Worker        self.refs = {}
36*635a8641SAndroid Build Coastguard Worker        self.loads = {}
37*635a8641SAndroid Build Coastguard Worker        self.stores = set()
38*635a8641SAndroid Build Coastguard Worker
39*635a8641SAndroid Build Coastguard Worker    def analyze_node(self, node, **kwargs):
40*635a8641SAndroid Build Coastguard Worker        visitor = RootVisitor(self)
41*635a8641SAndroid Build Coastguard Worker        visitor.visit(node, **kwargs)
42*635a8641SAndroid Build Coastguard Worker
43*635a8641SAndroid Build Coastguard Worker    def _define_ref(self, name, load=None):
44*635a8641SAndroid Build Coastguard Worker        ident = 'l_%d_%s' % (self.level, name)
45*635a8641SAndroid Build Coastguard Worker        self.refs[name] = ident
46*635a8641SAndroid Build Coastguard Worker        if load is not None:
47*635a8641SAndroid Build Coastguard Worker            self.loads[ident] = load
48*635a8641SAndroid Build Coastguard Worker        return ident
49*635a8641SAndroid Build Coastguard Worker
50*635a8641SAndroid Build Coastguard Worker    def find_load(self, target):
51*635a8641SAndroid Build Coastguard Worker        if target in self.loads:
52*635a8641SAndroid Build Coastguard Worker            return self.loads[target]
53*635a8641SAndroid Build Coastguard Worker        if self.parent is not None:
54*635a8641SAndroid Build Coastguard Worker            return self.parent.find_load(target)
55*635a8641SAndroid Build Coastguard Worker
56*635a8641SAndroid Build Coastguard Worker    def find_ref(self, name):
57*635a8641SAndroid Build Coastguard Worker        if name in self.refs:
58*635a8641SAndroid Build Coastguard Worker            return self.refs[name]
59*635a8641SAndroid Build Coastguard Worker        if self.parent is not None:
60*635a8641SAndroid Build Coastguard Worker            return self.parent.find_ref(name)
61*635a8641SAndroid Build Coastguard Worker
62*635a8641SAndroid Build Coastguard Worker    def ref(self, name):
63*635a8641SAndroid Build Coastguard Worker        rv = self.find_ref(name)
64*635a8641SAndroid Build Coastguard Worker        if rv is None:
65*635a8641SAndroid Build Coastguard Worker            raise AssertionError('Tried to resolve a name to a reference that '
66*635a8641SAndroid Build Coastguard Worker                                 'was unknown to the frame (%r)' % name)
67*635a8641SAndroid Build Coastguard Worker        return rv
68*635a8641SAndroid Build Coastguard Worker
69*635a8641SAndroid Build Coastguard Worker    def copy(self):
70*635a8641SAndroid Build Coastguard Worker        rv = object.__new__(self.__class__)
71*635a8641SAndroid Build Coastguard Worker        rv.__dict__.update(self.__dict__)
72*635a8641SAndroid Build Coastguard Worker        rv.refs = self.refs.copy()
73*635a8641SAndroid Build Coastguard Worker        rv.loads = self.loads.copy()
74*635a8641SAndroid Build Coastguard Worker        rv.stores = self.stores.copy()
75*635a8641SAndroid Build Coastguard Worker        return rv
76*635a8641SAndroid Build Coastguard Worker
77*635a8641SAndroid Build Coastguard Worker    def store(self, name):
78*635a8641SAndroid Build Coastguard Worker        self.stores.add(name)
79*635a8641SAndroid Build Coastguard Worker
80*635a8641SAndroid Build Coastguard Worker        # If we have not see the name referenced yet, we need to figure
81*635a8641SAndroid Build Coastguard Worker        # out what to set it to.
82*635a8641SAndroid Build Coastguard Worker        if name not in self.refs:
83*635a8641SAndroid Build Coastguard Worker            # If there is a parent scope we check if the name has a
84*635a8641SAndroid Build Coastguard Worker            # reference there.  If it does it means we might have to alias
85*635a8641SAndroid Build Coastguard Worker            # to a variable there.
86*635a8641SAndroid Build Coastguard Worker            if self.parent is not None:
87*635a8641SAndroid Build Coastguard Worker                outer_ref = self.parent.find_ref(name)
88*635a8641SAndroid Build Coastguard Worker                if outer_ref is not None:
89*635a8641SAndroid Build Coastguard Worker                    self._define_ref(name, load=(VAR_LOAD_ALIAS, outer_ref))
90*635a8641SAndroid Build Coastguard Worker                    return
91*635a8641SAndroid Build Coastguard Worker
92*635a8641SAndroid Build Coastguard Worker            # Otherwise we can just set it to undefined.
93*635a8641SAndroid Build Coastguard Worker            self._define_ref(name, load=(VAR_LOAD_UNDEFINED, None))
94*635a8641SAndroid Build Coastguard Worker
95*635a8641SAndroid Build Coastguard Worker    def declare_parameter(self, name):
96*635a8641SAndroid Build Coastguard Worker        self.stores.add(name)
97*635a8641SAndroid Build Coastguard Worker        return self._define_ref(name, load=(VAR_LOAD_PARAMETER, None))
98*635a8641SAndroid Build Coastguard Worker
99*635a8641SAndroid Build Coastguard Worker    def load(self, name):
100*635a8641SAndroid Build Coastguard Worker        target = self.find_ref(name)
101*635a8641SAndroid Build Coastguard Worker        if target is None:
102*635a8641SAndroid Build Coastguard Worker            self._define_ref(name, load=(VAR_LOAD_RESOLVE, name))
103*635a8641SAndroid Build Coastguard Worker
104*635a8641SAndroid Build Coastguard Worker    def branch_update(self, branch_symbols):
105*635a8641SAndroid Build Coastguard Worker        stores = {}
106*635a8641SAndroid Build Coastguard Worker        for branch in branch_symbols:
107*635a8641SAndroid Build Coastguard Worker            for target in branch.stores:
108*635a8641SAndroid Build Coastguard Worker                if target in self.stores:
109*635a8641SAndroid Build Coastguard Worker                    continue
110*635a8641SAndroid Build Coastguard Worker                stores[target] = stores.get(target, 0) + 1
111*635a8641SAndroid Build Coastguard Worker
112*635a8641SAndroid Build Coastguard Worker        for sym in branch_symbols:
113*635a8641SAndroid Build Coastguard Worker            self.refs.update(sym.refs)
114*635a8641SAndroid Build Coastguard Worker            self.loads.update(sym.loads)
115*635a8641SAndroid Build Coastguard Worker            self.stores.update(sym.stores)
116*635a8641SAndroid Build Coastguard Worker
117*635a8641SAndroid Build Coastguard Worker        for name, branch_count in iteritems(stores):
118*635a8641SAndroid Build Coastguard Worker            if branch_count == len(branch_symbols):
119*635a8641SAndroid Build Coastguard Worker                continue
120*635a8641SAndroid Build Coastguard Worker            target = self.find_ref(name)
121*635a8641SAndroid Build Coastguard Worker            assert target is not None, 'should not happen'
122*635a8641SAndroid Build Coastguard Worker
123*635a8641SAndroid Build Coastguard Worker            if self.parent is not None:
124*635a8641SAndroid Build Coastguard Worker                outer_target = self.parent.find_ref(name)
125*635a8641SAndroid Build Coastguard Worker                if outer_target is not None:
126*635a8641SAndroid Build Coastguard Worker                    self.loads[target] = (VAR_LOAD_ALIAS, outer_target)
127*635a8641SAndroid Build Coastguard Worker                    continue
128*635a8641SAndroid Build Coastguard Worker            self.loads[target] = (VAR_LOAD_RESOLVE, name)
129*635a8641SAndroid Build Coastguard Worker
130*635a8641SAndroid Build Coastguard Worker    def dump_stores(self):
131*635a8641SAndroid Build Coastguard Worker        rv = {}
132*635a8641SAndroid Build Coastguard Worker        node = self
133*635a8641SAndroid Build Coastguard Worker        while node is not None:
134*635a8641SAndroid Build Coastguard Worker            for name in node.stores:
135*635a8641SAndroid Build Coastguard Worker                if name not in rv:
136*635a8641SAndroid Build Coastguard Worker                    rv[name] = self.find_ref(name)
137*635a8641SAndroid Build Coastguard Worker            node = node.parent
138*635a8641SAndroid Build Coastguard Worker        return rv
139*635a8641SAndroid Build Coastguard Worker
140*635a8641SAndroid Build Coastguard Worker    def dump_param_targets(self):
141*635a8641SAndroid Build Coastguard Worker        rv = set()
142*635a8641SAndroid Build Coastguard Worker        node = self
143*635a8641SAndroid Build Coastguard Worker        while node is not None:
144*635a8641SAndroid Build Coastguard Worker            for target, (instr, _) in iteritems(self.loads):
145*635a8641SAndroid Build Coastguard Worker                if instr == VAR_LOAD_PARAMETER:
146*635a8641SAndroid Build Coastguard Worker                    rv.add(target)
147*635a8641SAndroid Build Coastguard Worker            node = node.parent
148*635a8641SAndroid Build Coastguard Worker        return rv
149*635a8641SAndroid Build Coastguard Worker
150*635a8641SAndroid Build Coastguard Worker
151*635a8641SAndroid Build Coastguard Workerclass RootVisitor(NodeVisitor):
152*635a8641SAndroid Build Coastguard Worker
153*635a8641SAndroid Build Coastguard Worker    def __init__(self, symbols):
154*635a8641SAndroid Build Coastguard Worker        self.sym_visitor = FrameSymbolVisitor(symbols)
155*635a8641SAndroid Build Coastguard Worker
156*635a8641SAndroid Build Coastguard Worker    def _simple_visit(self, node, **kwargs):
157*635a8641SAndroid Build Coastguard Worker        for child in node.iter_child_nodes():
158*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(child)
159*635a8641SAndroid Build Coastguard Worker
160*635a8641SAndroid Build Coastguard Worker    visit_Template = visit_Block = visit_Macro = visit_FilterBlock = \
161*635a8641SAndroid Build Coastguard Worker        visit_Scope = visit_If = visit_ScopedEvalContextModifier = \
162*635a8641SAndroid Build Coastguard Worker        _simple_visit
163*635a8641SAndroid Build Coastguard Worker
164*635a8641SAndroid Build Coastguard Worker    def visit_AssignBlock(self, node, **kwargs):
165*635a8641SAndroid Build Coastguard Worker        for child in node.body:
166*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(child)
167*635a8641SAndroid Build Coastguard Worker
168*635a8641SAndroid Build Coastguard Worker    def visit_CallBlock(self, node, **kwargs):
169*635a8641SAndroid Build Coastguard Worker        for child in node.iter_child_nodes(exclude=('call',)):
170*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(child)
171*635a8641SAndroid Build Coastguard Worker
172*635a8641SAndroid Build Coastguard Worker    def visit_OverlayScope(self, node, **kwargs):
173*635a8641SAndroid Build Coastguard Worker        for child in node.body:
174*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(child)
175*635a8641SAndroid Build Coastguard Worker
176*635a8641SAndroid Build Coastguard Worker    def visit_For(self, node, for_branch='body', **kwargs):
177*635a8641SAndroid Build Coastguard Worker        if for_branch == 'body':
178*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(node.target, store_as_param=True)
179*635a8641SAndroid Build Coastguard Worker            branch = node.body
180*635a8641SAndroid Build Coastguard Worker        elif for_branch == 'else':
181*635a8641SAndroid Build Coastguard Worker            branch = node.else_
182*635a8641SAndroid Build Coastguard Worker        elif for_branch == 'test':
183*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(node.target, store_as_param=True)
184*635a8641SAndroid Build Coastguard Worker            if node.test is not None:
185*635a8641SAndroid Build Coastguard Worker                self.sym_visitor.visit(node.test)
186*635a8641SAndroid Build Coastguard Worker            return
187*635a8641SAndroid Build Coastguard Worker        else:
188*635a8641SAndroid Build Coastguard Worker            raise RuntimeError('Unknown for branch')
189*635a8641SAndroid Build Coastguard Worker        for item in branch or ():
190*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(item)
191*635a8641SAndroid Build Coastguard Worker
192*635a8641SAndroid Build Coastguard Worker    def visit_With(self, node, **kwargs):
193*635a8641SAndroid Build Coastguard Worker        for target in node.targets:
194*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(target)
195*635a8641SAndroid Build Coastguard Worker        for child in node.body:
196*635a8641SAndroid Build Coastguard Worker            self.sym_visitor.visit(child)
197*635a8641SAndroid Build Coastguard Worker
198*635a8641SAndroid Build Coastguard Worker    def generic_visit(self, node, *args, **kwargs):
199*635a8641SAndroid Build Coastguard Worker        raise NotImplementedError('Cannot find symbols for %r' %
200*635a8641SAndroid Build Coastguard Worker                                  node.__class__.__name__)
201*635a8641SAndroid Build Coastguard Worker
202*635a8641SAndroid Build Coastguard Worker
203*635a8641SAndroid Build Coastguard Workerclass FrameSymbolVisitor(NodeVisitor):
204*635a8641SAndroid Build Coastguard Worker    """A visitor for `Frame.inspect`."""
205*635a8641SAndroid Build Coastguard Worker
206*635a8641SAndroid Build Coastguard Worker    def __init__(self, symbols):
207*635a8641SAndroid Build Coastguard Worker        self.symbols = symbols
208*635a8641SAndroid Build Coastguard Worker
209*635a8641SAndroid Build Coastguard Worker    def visit_Name(self, node, store_as_param=False, **kwargs):
210*635a8641SAndroid Build Coastguard Worker        """All assignments to names go through this function."""
211*635a8641SAndroid Build Coastguard Worker        if store_as_param or node.ctx == 'param':
212*635a8641SAndroid Build Coastguard Worker            self.symbols.declare_parameter(node.name)
213*635a8641SAndroid Build Coastguard Worker        elif node.ctx == 'store':
214*635a8641SAndroid Build Coastguard Worker            self.symbols.store(node.name)
215*635a8641SAndroid Build Coastguard Worker        elif node.ctx == 'load':
216*635a8641SAndroid Build Coastguard Worker            self.symbols.load(node.name)
217*635a8641SAndroid Build Coastguard Worker
218*635a8641SAndroid Build Coastguard Worker    def visit_NSRef(self, node, **kwargs):
219*635a8641SAndroid Build Coastguard Worker        self.symbols.load(node.name)
220*635a8641SAndroid Build Coastguard Worker
221*635a8641SAndroid Build Coastguard Worker    def visit_If(self, node, **kwargs):
222*635a8641SAndroid Build Coastguard Worker        self.visit(node.test, **kwargs)
223*635a8641SAndroid Build Coastguard Worker
224*635a8641SAndroid Build Coastguard Worker        original_symbols = self.symbols
225*635a8641SAndroid Build Coastguard Worker
226*635a8641SAndroid Build Coastguard Worker        def inner_visit(nodes):
227*635a8641SAndroid Build Coastguard Worker            self.symbols = rv = original_symbols.copy()
228*635a8641SAndroid Build Coastguard Worker            for subnode in nodes:
229*635a8641SAndroid Build Coastguard Worker                self.visit(subnode, **kwargs)
230*635a8641SAndroid Build Coastguard Worker            self.symbols = original_symbols
231*635a8641SAndroid Build Coastguard Worker            return rv
232*635a8641SAndroid Build Coastguard Worker
233*635a8641SAndroid Build Coastguard Worker        body_symbols = inner_visit(node.body)
234*635a8641SAndroid Build Coastguard Worker        elif_symbols = inner_visit(node.elif_)
235*635a8641SAndroid Build Coastguard Worker        else_symbols = inner_visit(node.else_ or ())
236*635a8641SAndroid Build Coastguard Worker
237*635a8641SAndroid Build Coastguard Worker        self.symbols.branch_update([body_symbols, elif_symbols, else_symbols])
238*635a8641SAndroid Build Coastguard Worker
239*635a8641SAndroid Build Coastguard Worker    def visit_Macro(self, node, **kwargs):
240*635a8641SAndroid Build Coastguard Worker        self.symbols.store(node.name)
241*635a8641SAndroid Build Coastguard Worker
242*635a8641SAndroid Build Coastguard Worker    def visit_Import(self, node, **kwargs):
243*635a8641SAndroid Build Coastguard Worker        self.generic_visit(node, **kwargs)
244*635a8641SAndroid Build Coastguard Worker        self.symbols.store(node.target)
245*635a8641SAndroid Build Coastguard Worker
246*635a8641SAndroid Build Coastguard Worker    def visit_FromImport(self, node, **kwargs):
247*635a8641SAndroid Build Coastguard Worker        self.generic_visit(node, **kwargs)
248*635a8641SAndroid Build Coastguard Worker        for name in node.names:
249*635a8641SAndroid Build Coastguard Worker            if isinstance(name, tuple):
250*635a8641SAndroid Build Coastguard Worker                self.symbols.store(name[1])
251*635a8641SAndroid Build Coastguard Worker            else:
252*635a8641SAndroid Build Coastguard Worker                self.symbols.store(name)
253*635a8641SAndroid Build Coastguard Worker
254*635a8641SAndroid Build Coastguard Worker    def visit_Assign(self, node, **kwargs):
255*635a8641SAndroid Build Coastguard Worker        """Visit assignments in the correct order."""
256*635a8641SAndroid Build Coastguard Worker        self.visit(node.node, **kwargs)
257*635a8641SAndroid Build Coastguard Worker        self.visit(node.target, **kwargs)
258*635a8641SAndroid Build Coastguard Worker
259*635a8641SAndroid Build Coastguard Worker    def visit_For(self, node, **kwargs):
260*635a8641SAndroid Build Coastguard Worker        """Visiting stops at for blocks.  However the block sequence
261*635a8641SAndroid Build Coastguard Worker        is visited as part of the outer scope.
262*635a8641SAndroid Build Coastguard Worker        """
263*635a8641SAndroid Build Coastguard Worker        self.visit(node.iter, **kwargs)
264*635a8641SAndroid Build Coastguard Worker
265*635a8641SAndroid Build Coastguard Worker    def visit_CallBlock(self, node, **kwargs):
266*635a8641SAndroid Build Coastguard Worker        self.visit(node.call, **kwargs)
267*635a8641SAndroid Build Coastguard Worker
268*635a8641SAndroid Build Coastguard Worker    def visit_FilterBlock(self, node, **kwargs):
269*635a8641SAndroid Build Coastguard Worker        self.visit(node.filter, **kwargs)
270*635a8641SAndroid Build Coastguard Worker
271*635a8641SAndroid Build Coastguard Worker    def visit_With(self, node, **kwargs):
272*635a8641SAndroid Build Coastguard Worker        for target in node.values:
273*635a8641SAndroid Build Coastguard Worker            self.visit(target)
274*635a8641SAndroid Build Coastguard Worker
275*635a8641SAndroid Build Coastguard Worker    def visit_AssignBlock(self, node, **kwargs):
276*635a8641SAndroid Build Coastguard Worker        """Stop visiting at block assigns."""
277*635a8641SAndroid Build Coastguard Worker        self.visit(node.target, **kwargs)
278*635a8641SAndroid Build Coastguard Worker
279*635a8641SAndroid Build Coastguard Worker    def visit_Scope(self, node, **kwargs):
280*635a8641SAndroid Build Coastguard Worker        """Stop visiting at scopes."""
281*635a8641SAndroid Build Coastguard Worker
282*635a8641SAndroid Build Coastguard Worker    def visit_Block(self, node, **kwargs):
283*635a8641SAndroid Build Coastguard Worker        """Stop visiting at blocks."""
284*635a8641SAndroid Build Coastguard Worker
285*635a8641SAndroid Build Coastguard Worker    def visit_OverlayScope(self, node, **kwargs):
286*635a8641SAndroid Build Coastguard Worker        """Do not visit into overlay scopes."""
287