summaryrefslogtreecommitdiffstats
path: root/tools/cr/cr/visitor.py
blob: 8e01c70b5614e5078fefbfd7bc0e0e23c0cea341 (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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

""".

"""
import collections

# HIDDEN is a marker used to suppress a value, making it as if it were not set
# in that object. This causes the search to continue through the tree.
# This is most useful as a return value of dynamic values that want to find
# the value they are shadowing.
HIDDEN = object()


class VisitComplete(Exception):
  """Indicates a vist traversal has finished early."""


class Visitor(object):
  """The base class for anything that wants to "visit" all variables.

  The two main uses of visitor are search and export. They differ in that export
  is trying to find all variables, whereas search is just looking for one.
  """

  def __init__(self):
    self.stack = []

  def VisitNode(self, node):
    """Called for every node in the tree."""
    if not node.enabled:
      return self
    try:
      try:
        self.stack.append(node)
        self.StartNode()
        # Visit all the values first
        for key in self.KeysOf(node.values):
          self.Visit(key, node.values[key])
        # And now recurse into all the children
        for child in  node.children:
          self.VisitNode(child)
      finally:
        self.EndNode()
        self.stack.pop()
    except VisitComplete:
      if self.stack:
        # propagate back up the stack
        raise
    return self

  def Visit(self, key, value):
    """Visit is called for every variable in each node."""

  def StartNode(self):
    """StartNode is called once for each node before traversal."""

  def EndNode(self):
    """Visit is called for every node after traversal."""

  @property
  def root_node(self):
    """Returns the variable at the root of the current traversal."""
    return self.stack[0]

  @property
  def current_node(self):
    """Returns the node currently being scanned."""
    return self.stack[-1]

  def Resolve(self, key, value):
    """Returns a fully substituted value.

    This asks the root node to do the actual work.
    Args:
      key: The key being visited.
      value: The unresolved value associated with the key.
    Returns:
      the fully resolved value.
    """
    return self.root_node.Resolve(self, key, value)

  def Where(self):
    """Returns the current traversal stack as a string."""
    return '/'.join([entry.name for entry in self.stack])


class SearchVisitor(Visitor):
  """A Visitor that finds a single matching key."""

  def __init__(self, key):
    super(SearchVisitor, self).__init__()
    self.key = key
    self.found = False
    self.error = None

  def KeysOf(self, store):
    if self.key in store:
      yield self.key

  def Visit(self, key, value):
    value, error = self.Resolve(key, value)
    if value is not HIDDEN:
      self.found = True
      self.value = value
      self.error = error
      raise VisitComplete()


class WhereVisitor(SearchVisitor):
  """A SearchVisitor that returns the path to the matching key."""

  def Visit(self, key, value):
    self.where = self.Where()
    super(WhereVisitor, self).Visit(key, value)


class ExportVisitor(Visitor):
  """A visitor that builds a fully resolved map of all variables."""

  def __init__(self, store):
    super(ExportVisitor, self).__init__()
    self.store = store

  def KeysOf(self, store):
    if self.current_node.export is False:
      # not exporting from this config
      return
    for key in store.keys():
      if key in self.store:
        # duplicate
        continue
      if (self.current_node.export is None) and key.startswith('_'):
        # non exported name
        continue
      yield key

  def Visit(self, key, value):
    value, _ = self.Resolve(key, value)
    if value is not HIDDEN:
      self.store[key] = value


class Node(object):
  """The base class for objects in a visitable node tree."""

  def __init__(self, name='--', enabled=True, export=True):
    self._name = name
    self._children = collections.deque()
    self._values = {}
    self._viewers = []
    self.trail = []
    self._enabled = enabled
    self._export = export
    self._export_cache = None

  @property
  def name(self):
    return self._name

  @name.setter
  def name(self, value):
    self._name = value

  @property
  def enabled(self):
    return self._enabled

  @enabled.setter
  def enabled(self, value):
    if self._enabled == value:
      return
    self._enabled = value
    self.NotifyChanged()

  @property
  def export(self):
    return self._export

  @property
  def exported(self):
    if self._export_cache is None:
      self._export_cache = ExportVisitor({}).VisitNode(self).store
    return self._export_cache

  @property
  def values(self):
    return self._values

  @property
  def children(self):
    return self._children

  def RegisterViewer(self, viewer):
    self._viewers.append(viewer)

  def UnregisterViewer(self, viewer):
    self._viewers.remove(viewer)

  def OnChanged(self, child):
    _ = child
    self.NotifyChanged()

  def NotifyChanged(self):
    self._export_cache = None
    for viewers in self._viewers:
      viewers.OnChanged(self)

  def _AddChild(self, child):
    if child and child != self and child not in self._children:
      self._children.appendleft(child)
      child.RegisterViewer(self)

  def AddChild(self, child):
    self._AddChild(child)
    self.NotifyChanged()
    return self

  def AddChildren(self, *children):
    for child in children:
      self._AddChild(child)
    self.NotifyChanged()
    return self

  def Find(self, key):
    search = SearchVisitor(key).VisitNode(self)
    if not search.found:
      return None
    return search.value

  def WhereIs(self, key):
    search = WhereVisitor(key).VisitNode(self)
    if not search.found:
      return None
    return search.where

  def Get(self, key, raise_errors=False):
    search = SearchVisitor(key).VisitNode(self)
    if not search.found:
      self.Missing(key)
    if search.error and raise_errors:
      raise search.error  # bad type inference pylint: disable=raising-bad-type
    return search.value

  def Missing(self, key):
    raise KeyError(key)

  def Resolve(self, visitor, key, value):
    _ = visitor, key
    return value

  def Wipe(self):
    for child in self._children:
      child.UnregisterViewer(self)
    self._children = collections.deque()
    self._values = {}
    self.NotifyChanged()