summaryrefslogtreecommitdiffstats
path: root/ppapi/generators/idl_node.py
blob: 93b6b4acc12ffc144fca0b001c57833bfe0696e2 (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
#!/usr/bin/python
#
# Copyright (c) 2011 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.

""" Nodes for PPAPI IDL AST """

#
# IDL Node
#
# IDL Node defines the IDLAttribute and IDLNode objects which are constructed
# by the parser as it processes the various 'productions'.  The IDLAttribute
# objects are assigned to the IDLNode's property dictionary instead of being
# applied as children of The IDLNodes, so they do not exist in the final tree.
# The AST of IDLNodes is the output from the parsing state and will be used
# as the source data by the various generators.
#

import re
import sys
import hashlib

from idl_log import ErrOut, InfoOut, WarnOut

#
# IDLAttribute
#
# A temporary object used by the parsing process to hold an Extended Attribute
# which will be passed as a child to a standard IDLNode.
#
class IDLAttribute(object):
  def __init__(self, name, value):
    self.cls = 'ExtAttribute'
    self.name = name
    self.value = value

# Set of object IDLNode types which have a name and belong in the namespace.
NamedSet = set(['Enum', 'EnumItem', 'Function', 'Interface', 'Member', 'Param',
               'Struct', 'Type', 'Typedef'])

#
# IDLNode
#
# A node in the AST.  The Node is composed of children, implemented as a
# dictionary of lists of child types and a dictionary of local properties,
# a.k.a. ExtendedAttributes.
#
class IDLNode(object):
  # Regular expression to parse property keys in a string such that a string
  #  "My string #NAME#" will find the key "NAME".
  regex_var = re.compile("(?P<src>[^\\$]+)|(?P<key>\\$\\w+\\$)")

  def __init__(self, cls, name, filename, lineno, pos, children):
    self.cls = cls
    self.name = name
    self.lineno = lineno
    self.pos = pos
    self.filename = filename

    # Dictionary of type to in order child list
    self.children = {}

    # Dictionary of child name to child object
    self.namespace = {}

    # Dictionary of properties (ExtAttributes)
    self.properties = { 'NAME' : name }

    self.hash = None
    self.typeref = None
    self.parent = None
    self.childlist = children

    if children:
      for child in children:
        #
        # Copy children into local dictionary such that children of the
        # same class are added in order to the per key list.  All
        # ExtAttributes are filtered and applied to a property dictionary
        # instead of becoming children of IDLNode
        #
        if child.cls == 'ExtAttribute':
          self.properties[child.name] =  child.value
        else:
          if child.cls in self.children:
            self.children[child.cls].append(child)
          else:
            self.children[child.cls] = [child]

  # Return a string representation of this node
  def __str__(self):
    return "%s(%s)" % (self.cls, self.name)

  # Log an error for this object
  def Error(self, msg):
    ErrOut.LogLine(self.filename, self.lineno, 0, " %s %s" %
                   (str(self), msg))

  # Log a warning for this object
  def Warning(self, msg):
    WarnOut.LogLine(self.filename, self.lineno, 0, " %s %s" %
                    (str(self), msg))

  # Get a list of objects for this key
  def GetListOf(self, key):
    return self.children.get(key, [])

  def GetOneOf(self, key):
    children = self.children.get(key, None)
    if children:
      assert(len(children) == 1)
      return children[0]
    return None

  # Get a list of all objects
  def Children(self):
    out = []
    for key in sorted(self.children.keys()):
      out.extend(self.children[key])
    return out

  # Dump this object and its children
  def Dump(self, depth=0, comments=False, out=sys.stdout):
    if self.cls == 'Comment' or self.cls == 'Copyright':
      is_comment = True
    else:
      is_comment = False

    # Skip this node if it's a comment, and we are not printing comments
    if not comments and is_comment: return

    tab = ""
    for t in range(depth):
      tab += '    '

    if is_comment:
      for line in self.name.split('\n'):
        out.write("%s%s\n" % (tab, line))
    else:
      out.write("%s%s\n" % (tab, self))

    if self.properties:
      out.write("%s  Properties\n" % tab)
      for p in self.properties:
        out.write("%s    %s : %s\n" % (tab, p, self.properties[p]))

    for cls in sorted(self.children.keys()):
      # Skip comments
      if (cls == 'Comment' or cls == 'Copyright') and not comments: continue

      out.write("%s  %ss\n" % (tab, cls))
      for c in self.children[cls]:
        c.Dump(depth + 1, comments=comments, out=out)

  # Link the parents and add the object to the parent's namespace
  def BuildTree(self, parent):
    self.parent = parent
    for child in self.Children():
      child.BuildTree(self)
      name = child.name

      # Add this child to the local namespace if it's a 'named' type
      if child.cls in NamedSet:
        if name in self.namespace:
          other = self.namespace[name]
          child.Error('Attempting to add %s to namespace of %s when already '
                      'declared in %s' % (name, str(self), str(other)))
        self.namespace[name] = child

  def Resolve(self):
    errs = 0
    typename = self.properties.get('TYPEREF', None)

    if typename:
      if typename in self.namespace:
        self.Error('Type collision in local namespace')
        errs += 1

      typeinfo = self.parent.Find(typename)
      if not typeinfo:
        self.Error('Unable to resolve typename %s.' % typename)
        errs += 1
        sys.exit(-1)
      self.typeinfo = typeinfo
    else:
      self.typeinfo = None

    for child in self.Children():
      errs += child.Resolve()

    return errs

  def Find(self, name):
    if name in self.namespace: return self.namespace[name]
    if self.parent: return self.parent.Find(name)
    return None

  def Hash(self):
    hash = hashlib.sha1()
    for key, val in self.properties.iteritems():
      hash.update('%s=%s' % (key, str(val)))
    for child in self.Children():
      hash.update(child.Hash())
    if self.typeref: hash.update(self.typeref.hash)
    self.hash = hash.hexdigest()
    return self.hash

  def GetProperty(self, name):
    return self.properties.get(name, None)

  # Recursively expands text keys in the form of $KEY$ with the value
  # of the property of the same name.  Since this is done recursively
  # one property can be defined in tems of another.
  def Replace(self, text):
    itr = IDLNode.regex_var.finditer(text)
    out = ""
    for m in itr:
      (min,max) = m.span()
      if m.lastgroup == "src":
        out += text[min:max]
      if m.lastgroup == "key":
        key = text[min+1:max-1]
        val = self.properties.get(key, None)
        if not val:
          self.Error("No property '%s'" % key)
        out += self.Replace(str(val))
    return out
#
# IDL Predefined types
#
BuiltIn = set(['int8_t', 'int16_t', 'int32_t', 'int64_t', 'uint8_t', 'uint16_t',
              'uint32_t', 'uint64_t', 'double_t', 'float_t', 'handle_t',
              'mem_t', 'str_t', 'void', 'enum', 'struct', 'bool'])

#
# IDLAst
#
# A specialized version of the IDLNode for containing the whole of the
# AST.  The specialized BuildTree function pulls the per file namespaces
# into the global AST namespace and checks for collisions.
#
class IDLAst(IDLNode):
  def __init__(self, children):
    objs = [IDLNode('Type', name, 'BuiltIn', 1, 0, None) for name in BuiltIn]
    IDLNode.__init__(self, 'AST', 'PPAPI', 'BuiltIn', 1, 0, objs + children)

  def BuildTree(self, parent):
    IDLNode.BuildTree(self, parent)
    for fileobj in self.GetListOf('File'):
      for name, val in fileobj.namespace.iteritems():
        if name in self.namespace:
          other = self.namespace[name]
          val.Error('Attempting to add %s to namespace of %s when already '
                    'declared in %s' % (name, str(self), str(other)))
        self.namespace[name] = val