1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6""" Generator for C style prototypes and definitions """
7
8import glob
9import os
10import re
11import sys
12
13from idl_log import ErrOut, InfoOut, WarnOut
14from idl_node import IDLAttribute, IDLNode
15from idl_ast import IDLAst
16from idl_option import GetOption, Option, ParseOptions
17from idl_outfile import IDLOutFile
18from idl_parser import ParseFiles
19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment
20from idl_generator import Generator, GeneratorByFile
21from idl_visitor import IDLVisitor
22
23Option('dstroot', 'Base directory of output', default=os.path.join('..', 'c'))
24Option('guard', 'Include guard prefix', default=os.path.join('ppapi', 'c'))
25
26
27#
28# PrototypeResolver
29#
30# A specialized visitor which traverses the AST, building a mapping of
31# Release names to Versions numbers and calculating a min version.
32# The mapping is applied to the File nodes within the AST.
33#
34class ProtoResolver(IDLVisitor):
35  def __init__(self):
36    IDLVisitor.__init__(self)
37    self.struct_map = {}
38    self.interface_map = {}
39
40  def Arrive(self, node, ignore):
41    if node.IsA('Member') and node.GetProperty('ref'):
42      typeref = node.typelist.GetReleases()[0]
43      if typeref.IsA('Struct'):
44        nodelist = self.struct_map.get(typeref.GetName(), [])
45        nodelist.append(node)
46        self.struct_map[typeref.GetName()] = nodelist
47
48    if node.IsA('Param'):
49      typeref = node.typelist.GetReleases()[0]
50      if typeref.IsA('Interface'):
51        nodelist = self.struct_map.get(typeref.GetName(), [])
52        nodelist.append(node)
53        self.interface_map[typeref.GetName()] = nodelist
54
55    return None
56
57
58def GetPathFromNode(filenode, relpath=None, ext=None):
59  path, name = os.path.split(filenode.GetProperty('NAME'))
60  if ext: name = os.path.splitext(name)[0] + ext
61  if path: name = os.path.join(path, name)
62  if relpath: name = os.path.join(relpath, name)
63  name = os.path.normpath(name)
64  return name
65
66
67def GetHeaderFromNode(filenode, relpath=None):
68  return GetPathFromNode(filenode, relpath, ext='.h')
69
70
71def WriteGroupMarker(out, node, last_group):
72  # If we are part of a group comment marker...
73  if last_group and last_group != node.cls:
74    pre = CommentLines(['*',' @}', '']) + '\n'
75  else:
76    pre = '\n'
77
78  if node.cls in ['Typedef', 'Interface', 'Struct', 'Enum']:
79    if last_group != node.cls:
80      pre += CommentLines(['*',' @addtogroup %ss' % node.cls, ' @{', ''])
81    last_group = node.cls
82  else:
83    last_group = None
84  out.Write(pre)
85  return last_group
86
87
88def GenerateHeader(out, filenode, releases):
89  cgen = CGen()
90  pref = ''
91  do_comments = True
92
93  # Generate definitions.
94  last_group = None
95  top_types = ['Typedef', 'Interface', 'Struct', 'Enum', 'Inline']
96  for node in filenode.GetListOf(*top_types):
97    # Skip if this node is not in this release
98    if not node.InReleases(releases):
99      print "Skiping %s" % node
100      continue
101
102    # End/Start group marker
103    if do_comments:
104      last_group = WriteGroupMarker(out, node, last_group)
105
106    if node.IsA('Inline'):
107      item = node.GetProperty('VALUE')
108      # If 'C++' use __cplusplus wrapper
109      if node.GetName() == 'cc':
110        item = '#ifdef __cplusplus\n%s\n#endif  /* __cplusplus */\n\n' % item
111      # If not C++ or C, then skip it
112      elif not node.GetName() == 'c':
113        continue
114      if item: out.Write(item)
115      continue
116
117    #
118    # Otherwise we are defining a file level object, so generate the
119    # correct document notation.
120    #
121    item = cgen.Define(node, releases, prefix=pref, comment=True)
122    if not item: continue
123    asize = node.GetProperty('assert_size()')
124    if asize:
125      name = '%s%s' % (pref, node.GetName())
126      if node.IsA('Struct'):
127        form = 'PP_COMPILE_ASSERT_STRUCT_SIZE_IN_BYTES(%s, %s);\n'
128      elif node.IsA('Enum'):
129        if node.GetProperty('notypedef'):
130          form = 'PP_COMPILE_ASSERT_ENUM_SIZE_IN_BYTES(%s, %s);\n'
131        else:
132          form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n'
133      else:
134        form = 'PP_COMPILE_ASSERT_SIZE_IN_BYTES(%s, %s);\n'
135      item += form % (name, asize[0])
136
137    if item: out.Write(item)
138  if last_group:
139    out.Write(CommentLines(['*',' @}', '']) + '\n')
140
141
142def CheckTypedefs(filenode, releases):
143  """Checks that typedefs don't specify callbacks that take some structs.
144
145  See http://crbug.com/233439 for details.
146  """
147  cgen = CGen()
148  for node in filenode.GetListOf('Typedef'):
149    build_list = node.GetUniqueReleases(releases)
150    callnode = node.GetOneOf('Callspec')
151    if callnode:
152      for param in callnode.GetListOf('Param'):
153        if param.GetListOf('Array'):
154          continue
155        if cgen.GetParamMode(param) != 'in':
156          continue
157        t = param.GetType(build_list[0])
158        while t.IsA('Typedef'):
159          t = t.GetType(build_list[0])
160        if t.IsA('Struct') and t.GetProperty('passByValue'):
161          raise Exception('%s is a struct in callback %s. '
162                          'See http://crbug.com/233439' %
163                          (t.GetName(), node.GetName()))
164
165
166def CheckPassByValue(filenode, releases):
167  """Checks that new pass-by-value structs are not introduced.
168
169  See http://crbug.com/233439 for details.
170  """
171  cgen = CGen()
172  # DO NOT add any more entries to this whitelist.
173  # http://crbug.com/233439
174  type_whitelist = ['PP_ArrayOutput', 'PP_CompletionCallback',
175                    'PP_Ext_EventListener', 'PP_FloatPoint',
176                    'PP_Point', 'PP_TouchPoint', 'PP_Var']
177  nodes_to_check = filenode.GetListOf('Struct')
178  nodes_to_check.extend(filenode.GetListOf('Union'))
179  for node in nodes_to_check:
180    if node.GetName() in type_whitelist:
181      continue
182    build_list = node.GetUniqueReleases(releases)
183    if node.GetProperty('passByValue'):
184      raise Exception('%s is a new passByValue struct or union. '
185                      'See http://crbug.com/233439' % node.GetName())
186    if node.GetProperty('returnByValue'):
187      raise Exception('%s is a new returnByValue struct or union. '
188                      'See http://crbug.com/233439' % node.GetName())
189
190
191class HGen(GeneratorByFile):
192  def __init__(self):
193    Generator.__init__(self, 'C Header', 'cgen', 'Generate the C headers.')
194
195  def GenerateFile(self, filenode, releases, options):
196    CheckTypedefs(filenode, releases)
197    CheckPassByValue(filenode, releases)
198    savename = GetHeaderFromNode(filenode, GetOption('dstroot'))
199    my_min, my_max = filenode.GetMinMax(releases)
200    if my_min > releases[-1] or my_max < releases[0]:
201      if os.path.isfile(savename):
202        print "Removing stale %s for this range." % filenode.GetName()
203        os.remove(os.path.realpath(savename))
204      return False
205
206    out = IDLOutFile(savename)
207    self.GenerateHead(out, filenode, releases, options)
208    self.GenerateBody(out, filenode, releases, options)
209    self.GenerateTail(out, filenode, releases, options)
210    return out.Close()
211
212  def GenerateHead(self, out, filenode, releases, options):
213    __pychecker__ = 'unusednames=options'
214
215    proto = ProtoResolver()
216    proto.Visit(filenode, None)
217
218    cgen = CGen()
219    gpath = GetOption('guard')
220    def_guard = GetHeaderFromNode(filenode, relpath=gpath)
221    def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_'
222
223    cright_node = filenode.GetChildren()[0]
224    assert(cright_node.IsA('Copyright'))
225    fileinfo = filenode.GetChildren()[1]
226    assert(fileinfo.IsA('Comment'))
227
228    out.Write('%s\n' % cgen.Copyright(cright_node))
229
230    # Wrap the From ... modified ... comment if it would be >80 characters.
231    from_text = 'From %s' % GetPathFromNode(filenode).replace(os.sep, '/')
232    modified_text = 'modified %s.' % (
233        filenode.GetProperty('DATETIME'))
234    if len(from_text) + len(modified_text) < 74:
235      out.Write('/* %s %s */\n\n' % (from_text, modified_text))
236    else:
237      out.Write('/* %s,\n *   %s\n */\n\n' % (from_text, modified_text))
238
239    out.Write('#ifndef %s\n#define %s\n\n' % (def_guard, def_guard))
240    # Generate set of includes
241
242    deps = set()
243    for release in releases:
244      deps |= filenode.GetDeps(release)
245
246    includes = set([])
247    for dep in deps:
248      depfile = dep.GetProperty('FILE')
249      if depfile:
250        includes.add(depfile)
251    includes = [GetHeaderFromNode(
252        include, relpath=gpath).replace(os.sep, '/') for include in includes]
253    includes.append('ppapi/c/pp_macros.h')
254
255    # Assume we need stdint if we "include" C or C++ code
256    if filenode.GetListOf('Include'):
257      includes.append('ppapi/c/pp_stdint.h')
258
259    includes = sorted(set(includes))
260    cur_include = GetHeaderFromNode(filenode,
261                                    relpath=gpath).replace(os.sep, '/')
262    for include in includes:
263      if include == cur_include: continue
264      out.Write('#include "%s"\n' % include)
265
266    # Generate Prototypes
267    if proto.struct_map:
268      out.Write('\n/* Struct prototypes */\n')
269      for struct in proto.struct_map:
270        out.Write('struct %s;\n' % struct)
271
272    # Create a macro for the highest available release number.
273    if filenode.GetProperty('NAME').endswith('pp_macros.idl'):
274      releasestr = ' '.join(releases)
275      if releasestr:
276        release_numbers = re.findall('[\d\_]+', releasestr)
277        release = re.findall('\d+', release_numbers[-1])[0]
278        if release:
279          out.Write('\n#define PPAPI_RELEASE %s\n' % release)
280
281    # Generate all interface defines
282    out.Write('\n')
283    for node in filenode.GetListOf('Interface'):
284      idefs = ''
285      macro = cgen.GetInterfaceMacro(node)
286      unique = node.GetUniqueReleases(releases)
287
288      # Skip this interface if there are no matching versions
289      if not unique: continue
290
291      # Skip this interface if it should have no interface string.
292      if node.GetProperty('no_interface_string'): continue
293
294      last_stable_ver = None
295      last_dev_rel = None
296      for rel in unique:
297        channel = node.GetProperty('FILE').release_map.GetChannel(rel)
298        if channel == 'dev':
299          last_dev_rel = rel
300
301      for rel in unique:
302        version = node.GetVersion(rel)
303        name = cgen.GetInterfaceString(node, version)
304        strver = str(version).replace('.', '_')
305        channel = node.GetProperty('FILE').release_map.GetChannel(rel)
306        if channel == 'dev':
307          # Skip dev channel interface versions that are
308          #   Not the newest version, and
309          #   Don't have an equivalent stable version.
310          if rel != last_dev_rel and not node.DevInterfaceMatchesStable(rel):
311            continue
312          value_string = '"%s" /* dev */' % name
313        else:
314          value_string = '"%s"' % name
315          last_stable_ver = strver
316        idefs += cgen.GetDefine('%s_%s' % (macro, strver), value_string)
317      if last_stable_ver:
318        idefs += cgen.GetDefine(macro, '%s_%s' % (macro, last_stable_ver))
319        idefs += '\n'
320
321      out.Write(idefs)
322
323    # Generate the @file comment
324    out.Write('%s\n' % Comment(fileinfo, prefix='*\n @file'))
325
326  def GenerateBody(self, out, filenode, releases, options):
327    __pychecker__ = 'unusednames=options'
328    GenerateHeader(out, filenode, releases)
329
330  def GenerateTail(self, out, filenode, releases, options):
331    __pychecker__ = 'unusednames=options,releases'
332    gpath = GetOption('guard')
333    def_guard = GetPathFromNode(filenode, relpath=gpath, ext='.h')
334    def_guard = def_guard.replace(os.sep,'_').replace('.','_').upper() + '_'
335    out.Write('#endif  /* %s */\n\n' % def_guard)
336
337
338hgen = HGen()
339
340def main(args):
341  # Default invocation will verify the golden files are unchanged.
342  failed = 0
343  if not args:
344    args = ['--wnone', '--diff', '--test', '--dstroot=.']
345
346  ParseOptions(args)
347
348  idldir = os.path.split(sys.argv[0])[0]
349  idldir = os.path.join(idldir, 'test_cgen', '*.idl')
350  filenames = glob.glob(idldir)
351  ast = ParseFiles(filenames)
352  if hgen.GenerateRelease(ast, 'M14', {}):
353    print "Golden file for M14 failed."
354    failed = 1
355  else:
356    print "Golden file for M14 passed."
357
358
359  idldir = os.path.split(sys.argv[0])[0]
360  idldir = os.path.join(idldir, 'test_cgen_range', '*.idl')
361  filenames = glob.glob(idldir)
362
363  ast = ParseFiles(filenames)
364  if hgen.GenerateRange(ast, ['M13', 'M14', 'M15', 'M16', 'M17'], {}):
365    print "Golden file for M13-M17 failed."
366    failed =1
367  else:
368    print "Golden file for M13-M17 passed."
369
370  return failed
371
372if __name__ == '__main__':
373  sys.exit(main(sys.argv[1:]))
374
375