idl_c_proto.py revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 sys
11
12from idl_log import ErrOut, InfoOut, WarnOut
13from idl_node import IDLNode
14from idl_ast import IDLAst
15from idl_option import GetOption, Option, ParseOptions
16from idl_parser import ParseFiles
17
18Option('cgen_debug', 'Debug generate.')
19
20class CGenError(Exception):
21  def __init__(self, msg):
22    self.value = msg
23
24  def __str__(self):
25    return repr(self.value)
26
27
28def CommentLines(lines, tabs=0):
29  # Generate a C style comment block by prepending the block with '<tab>/*'
30  # and adding a '<tab> *' per line.
31  tab = '  ' * tabs
32
33  out = '%s/*' % tab + ('\n%s *' % tab).join(lines)
34
35  # Add a terminating ' */' unless the last line is blank which would mean it
36  # already has ' *'
37  if not lines[-1]:
38    out += '/\n'
39  else:
40    out += ' */\n'
41  return out
42
43def Comment(node, prefix=None, tabs=0):
44  # Generate a comment block from the provided Comment node.
45  comment = node.GetName()
46  lines = comment.split('\n')
47
48  # If an option prefix is provided, then prepend that to the comment
49  # for this node.
50  if prefix:
51    prefix_lines = prefix.split('\n')
52    # If both the prefix and comment start with a blank line ('*') remove
53    # the extra one.
54    if prefix_lines[0] == '*' and lines[0] == '*':
55      lines = prefix_lines + lines[1:]
56    else:
57      lines = prefix_lines + lines;
58  return CommentLines(lines, tabs)
59
60def GetNodeComments(node, tabs=0):
61  # Generate a comment block joining all comment nodes which are children of
62  # the provided node.
63  comment_txt = ''
64  for doc in node.GetListOf('Comment'):
65    comment_txt += Comment(doc, tabs=tabs)
66  return comment_txt
67
68
69class CGen(object):
70  # TypeMap
71  #
72  # TypeMap modifies how an object is stored or passed, for example pointers
73  # are passed as 'const' if they are 'in' parameters, and structures are
74  # preceeded by the keyword 'struct' as well as using a pointer.
75  #
76  TypeMap = {
77    'Array': {
78      'in': 'const %s',
79      'inout': '%s',
80      'out': '%s*',
81      'store': '%s',
82      'return': '%s'
83    },
84    'Callspec': {
85      'in': '%s',
86      'inout': '%s',
87      'out': '%s',
88      'store': '%s',
89      'return': '%s'
90    },
91    'Enum': {
92      'in': '%s',
93      'inout': '%s*',
94      'out': '%s*',
95      'store': '%s',
96      'return': '%s'
97    },
98    'Interface': {
99      'in': 'const %s*',
100      'inout': '%s*',
101      'out': '%s**',
102      'return': '%s*',
103      'store': '%s*'
104    },
105    'Struct': {
106      'in': 'const %s*',
107      'inout': '%s*',
108      'out': '%s*',
109      'return': ' %s*',
110      'store': '%s'
111    },
112    'blob_t': {
113      'in': 'const %s',
114      'inout': '%s',
115      'out': '%s',
116      'return': '%s',
117      'store': '%s'
118    },
119    'mem_t': {
120      'in': 'const %s',
121      'inout': '%s',
122      'out': '%s',
123      'return': '%s',
124      'store': '%s'
125    },
126    'str_t': {
127      'in': 'const %s',
128      'inout': '%s',
129      'out': '%s',
130      'return': 'const %s',
131      'store': '%s'
132    },
133    'cstr_t': {
134      'in': '%s',
135      'inout': '%s*',
136      'out': '%s*',
137      'return': '%s',
138      'store': '%s'
139    },
140    'TypeValue': {
141      'in': '%s',
142      'inout': '%s*',
143      'out': '%s*',
144      'return': '%s',
145      'store': '%s'
146    },
147  }
148
149
150  #
151  # RemapName
152  #
153  # A diction array of PPAPI types that are converted to language specific
154  # types before being returned by by the C generator
155  #
156  RemapName = {
157  'blob_t': 'void**',
158  'float_t': 'float',
159  'double_t': 'double',
160  'handle_t': 'int',
161  'mem_t': 'void*',
162  'str_t': 'char*',
163  'cstr_t': 'const char*',
164  'interface_t' : 'const void*'
165  }
166
167  def __init__(self):
168    self.dbg_depth = 0
169
170  #
171  # Debug Logging functions
172  #
173  def Log(self, txt):
174    if not GetOption('cgen_debug'): return
175    tabs = '  ' * self.dbg_depth
176    print '%s%s' % (tabs, txt)
177
178  def LogEnter(self, txt):
179    if txt: self.Log(txt)
180    self.dbg_depth += 1
181
182  def LogExit(self, txt):
183    self.dbg_depth -= 1
184    if txt: self.Log(txt)
185
186
187  def GetDefine(self, name, value):
188    out = '#define %s %s' % (name, value)
189    if len(out) > 80:
190      out = '#define %s \\\n    %s' % (name, value)
191    return '%s\n' % out
192
193  #
194  # Interface strings
195  #
196  def GetMacroHelper(self, node):
197    macro = node.GetProperty('macro')
198    if macro: return macro
199    name = node.GetName()
200    name = name.upper()
201    return "%s_INTERFACE" % name
202
203  def GetInterfaceMacro(self, node, version = None):
204    name = self.GetMacroHelper(node)
205    if version is None:
206      return name
207    return '%s_%s' % (name, str(version).replace('.', '_'))
208
209  def GetInterfaceString(self, node, version = None):
210    # If an interface name is specified, use that
211    name = node.GetProperty('iname')
212    if not name:
213      # Otherwise, the interface name is the object's name
214      # With '_Dev' replaced by '(Dev)' if it's a Dev interface.
215      name = node.GetName()
216      if name.endswith('_Dev'):
217        name = '%s(Dev)' % name[:-4]
218    if version is None:
219      return name
220    return "%s;%s" % (name, version)
221
222
223  #
224  # Return the array specification of the object.
225  #
226  def GetArraySpec(self, node):
227    assert(node.cls == 'Array')
228    fixed = node.GetProperty('FIXED')
229    if fixed:
230      return '[%s]' % fixed
231    else:
232      return '[]'
233
234  #
235  # GetTypeName
236  #
237  # For any valid 'typed' object such as Member or Typedef
238  # the typenode object contains the typename
239  #
240  # For a given node return the type name by passing mode.
241  #
242  def GetTypeName(self, node, release, prefix=''):
243    self.LogEnter('GetTypeName of %s rel=%s' % (node, release))
244
245    # For Members, Params, and Typedefs get the type it refers to otherwise
246    # the node in question is it's own type (struct, union etc...)
247    if node.IsA('Member', 'Param', 'Typedef'):
248      typeref = node.GetType(release)
249    else:
250      typeref = node
251
252    if typeref is None:
253      node.Error('No type at release %s.' % release)
254      raise CGenError('No type for %s' % node)
255
256    # If the type is a (BuiltIn) Type then return it's name
257    # remapping as needed
258    if typeref.IsA('Type'):
259      name = CGen.RemapName.get(typeref.GetName(), None)
260      if name is None: name = typeref.GetName()
261      name = '%s%s' % (prefix, name)
262
263    # For Interfaces, use the name + version
264    elif typeref.IsA('Interface'):
265      rel = typeref.first_release[release]
266      name = 'struct %s%s' % (prefix, self.GetStructName(typeref, rel, True))
267
268    # For structures, preceed with 'struct' or 'union' as appropriate
269    elif typeref.IsA('Struct'):
270      if typeref.GetProperty('union'):
271        name = 'union %s%s' % (prefix, typeref.GetName())
272      else:
273        name = 'struct %s%s' % (prefix, typeref.GetName())
274
275    # If it's an enum, or typedef then return the Enum's name
276    elif typeref.IsA('Enum', 'Typedef'):
277      # The enum may have skipped having a typedef, we need prefix with 'enum'.
278      if typeref.GetProperty('notypedef'):
279        name = 'enum %s%s' % (prefix, typeref.GetName())
280      else:
281        name = '%s%s' % (prefix, typeref.GetName())
282
283    else:
284      raise RuntimeError('Getting name of non-type %s.' % node)
285    self.LogExit('GetTypeName %s is %s' % (node, name))
286    return name
287
288
289  #
290  # GetRootType
291  #
292  # For a given node return basic type of that object.  This is
293  # either a 'Type', 'Callspec', or 'Array'
294  #
295  def GetRootTypeMode(self, node, release, mode):
296    self.LogEnter('GetRootType of %s' % node)
297    # If it has an array spec, then treat it as an array regardless of type
298    if node.GetOneOf('Array'):
299      rootType = 'Array'
300    # Or if it has a callspec, treat it as a function
301    elif node.GetOneOf('Callspec'):
302      rootType, mode = self.GetRootTypeMode(node.GetType(release), release,
303                                            'return')
304
305    # If it's a plain typedef, try that object's root type
306    elif node.IsA('Member', 'Param', 'Typedef'):
307      rootType, mode = self.GetRootTypeMode(node.GetType(release),
308                                            release, mode)
309
310    # If it's an Enum, then it's normal passing rules
311    elif node.IsA('Enum'):
312      rootType = node.cls
313
314    # If it's an Interface or Struct, we may be passing by value
315    elif node.IsA('Interface', 'Struct'):
316      if mode == 'return':
317        if node.GetProperty('returnByValue'):
318          rootType = 'TypeValue'
319        else:
320          rootType = node.cls
321      else:
322        if node.GetProperty('passByValue'):
323          rootType = 'TypeValue'
324        else:
325          rootType = node.cls
326
327    # If it's an Basic Type, check if it's a special type
328    elif node.IsA('Type'):
329      if node.GetName() in CGen.TypeMap:
330        rootType = node.GetName()
331      else:
332        rootType = 'TypeValue'
333    else:
334      raise RuntimeError('Getting root type of non-type %s.' % node)
335    self.LogExit('RootType is "%s"' % rootType)
336    return rootType, mode
337
338
339  def GetTypeByMode(self, node, release, mode):
340    self.LogEnter('GetTypeByMode of %s mode=%s release=%s' %
341                  (node, mode, release))
342    name = self.GetTypeName(node, release)
343    ntype, mode = self.GetRootTypeMode(node, release, mode)
344    out = CGen.TypeMap[ntype][mode] % name
345    self.LogExit('GetTypeByMode %s = %s' % (node, out))
346    return out
347
348
349  # Get the passing mode of the object (in, out, inout).
350  def GetParamMode(self, node):
351    self.Log('GetParamMode for %s' % node)
352    if node.GetProperty('in'): return 'in'
353    if node.GetProperty('out'): return 'out'
354    if node.GetProperty('inout'): return 'inout'
355    return 'return'
356
357  #
358  # GetComponents
359  #
360  # Returns the signature components of an object as a tuple of
361  # (rtype, name, arrays, callspec) where:
362  #   rtype - The store or return type of the object.
363  #   name - The name of the object.
364  #   arrays - A list of array dimensions as [] or [<fixed_num>].
365  #   args -  None if not a function, otherwise a list of parameters.
366  #
367  def GetComponents(self, node, release, mode):
368    self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release))
369
370    # Generate passing type by modifying root type
371    rtype = self.GetTypeByMode(node, release, mode)
372    if node.IsA('Enum', 'Interface', 'Struct'):
373      rname = node.GetName()
374    else:
375      rname = node.GetType(release).GetName()
376
377    if rname in CGen.RemapName:
378      rname = CGen.RemapName[rname]
379    if '%' in rtype:
380      rtype = rtype % rname
381    name = node.GetName()
382    arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')]
383    callnode = node.GetOneOf('Callspec')
384    if callnode:
385      callspec = []
386      for param in callnode.GetListOf('Param'):
387        mode = self.GetParamMode(param)
388        ptype, pname, parray, pspec = self.GetComponents(param, release, mode)
389        callspec.append((ptype, pname, parray, pspec))
390    else:
391      callspec = None
392
393    self.LogExit('GetComponents: %s, %s, %s, %s' %
394                 (rtype, name, arrayspec, callspec))
395    return (rtype, name, arrayspec, callspec)
396
397
398  def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr,
399              ptr_prefix, include_name):
400    self.LogEnter('Compose: %s %s' % (rtype, name))
401    arrayspec = ''.join(arrayspec)
402    if not include_name:
403      name = prefix + arrayspec
404    else:
405      name = prefix + name + arrayspec
406    if callspec is None:
407      out = '%s %s' % (rtype, name)
408    else:
409      params = []
410      for ptype, pname, parray, pspec in callspec:
411        params.append(self.Compose(ptype, pname, parray, pspec, '', True,
412                                   ptr_prefix='', include_name=True))
413      if func_as_ptr:
414        name = '(%s*%s)' % (ptr_prefix, name)
415      out = '%s %s(%s)' % (rtype, name, ', '.join(params))
416    self.LogExit('Exit Compose: %s' % out)
417    return out
418
419  #
420  # GetSignature
421  #
422  # Returns the 'C' style signature of the object
423  #  prefix - A prefix for the object's name
424  #  func_as_ptr - Formats a function as a function pointer
425  #  ptr_prefix - A prefix that goes before the "*" for a function pointer
426  #  include_name - If true, include member name in the signature.
427  #                 If false, leave it out. In any case, prefix and ptr_prefix
428  #                 are always included.
429  #
430  def GetSignature(self, node, release, mode, prefix='', func_as_ptr=True,
431                   ptr_prefix='', include_name=True):
432    self.LogEnter('GetSignature %s %s as func=%s' %
433                  (node, mode, func_as_ptr))
434    rtype, name, arrayspec, callspec = self.GetComponents(node, release, mode)
435    out = self.Compose(rtype, name, arrayspec, callspec, prefix,
436                       func_as_ptr, ptr_prefix, include_name)
437    self.LogExit('Exit GetSignature: %s' % out)
438    return out
439
440  # Define a Typedef.
441  def DefineTypedef(self, node, releases, prefix='', comment=False):
442    __pychecker__ = 'unusednames=comment'
443    build_list = node.GetUniqueReleases(releases)
444
445    # TODO(noelallen) : Bug 157017 finish multiversion support
446    if len(build_list) != 1:
447      node.Error('Can not support multiple versions of node.')
448    assert len(build_list) == 1
449
450    out = 'typedef %s;\n' % self.GetSignature(node, build_list[0], 'return',
451                                              prefix, True)
452    self.Log('DefineTypedef: %s' % out)
453    return out
454
455  # Define an Enum.
456  def DefineEnum(self, node, releases, prefix='', comment=False):
457    __pychecker__ = 'unusednames=comment,releases'
458    self.LogEnter('DefineEnum %s' % node)
459    name = '%s%s' % (prefix, node.GetName())
460    notypedef = node.GetProperty('notypedef')
461    unnamed = node.GetProperty('unnamed')
462
463    if unnamed:
464      out = 'enum {'
465    elif notypedef:
466      out = 'enum %s {' % name
467    else:
468      out = 'typedef enum {'
469    enumlist = []
470    for child in node.GetListOf('EnumItem'):
471      value = child.GetProperty('VALUE')
472      comment_txt = GetNodeComments(child, tabs=1)
473      if value:
474        item_txt = '%s%s = %s' % (prefix, child.GetName(), value)
475      else:
476        item_txt = '%s%s' % (prefix, child.GetName())
477      enumlist.append('%s  %s' % (comment_txt, item_txt))
478    self.LogExit('Exit DefineEnum')
479
480    if unnamed or notypedef:
481      out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist))
482    else:
483      out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name)
484    return out
485
486  def DefineMember(self, node, releases, prefix='', comment=False):
487    __pychecker__ = 'unusednames=prefix,comment'
488    release = releases[0]
489    self.LogEnter('DefineMember %s' % node)
490    out = '%s;' % self.GetSignature(node, release, 'store', '', True)
491    self.LogExit('Exit DefineMember')
492    return out
493
494  def GetStructName(self, node, release, include_version=False):
495    suffix = ''
496    if include_version:
497      ver_num = node.GetVersion(release)
498      suffix = ('_%s' % ver_num).replace('.', '_')
499    return node.GetName() + suffix
500
501  def DefineStructInternals(self, node, release,
502                            include_version=False, comment=True):
503    out = ''
504    if node.GetProperty('union'):
505      out += 'union %s {\n' % (
506          self.GetStructName(node, release, include_version))
507    else:
508      out += 'struct %s {\n' % (
509          self.GetStructName(node, release, include_version))
510
511    # Generate Member Functions
512    members = []
513    for child in node.GetListOf('Member'):
514      member = self.Define(child, [release], tabs=1, comment=comment)
515      if not member:
516        continue
517      members.append(member)
518    out += '%s\n};\n' % '\n'.join(members)
519    return out
520
521
522  def DefineStruct(self, node, releases, prefix='', comment=False):
523    __pychecker__ = 'unusednames=comment,prefix'
524    self.LogEnter('DefineStruct %s' % node)
525    out = ''
526    build_list = node.GetUniqueReleases(releases)
527
528    # TODO(noelallen) : Bug 157017 finish multiversion support
529    if node.IsA('Struct'):
530      if len(build_list) != 1:
531        node.Error('Can not support multiple versions of node.')
532      assert len(build_list) == 1
533
534
535    if node.IsA('Interface'):
536      # Build the most recent one versioned, with comments
537      out = self.DefineStructInternals(node, build_list[-1],
538                                       include_version=True, comment=True)
539
540      # Define an unversioned typedef for the most recent version
541      out += '\ntypedef struct %s %s;\n' % (
542          self.GetStructName(node, build_list[-1], include_version=True),
543          self.GetStructName(node, build_list[-1], include_version=False))
544    else:
545      # Build the most recent one versioned, with comments
546      out = self.DefineStructInternals(node, build_list[-1],
547                                       include_version=False, comment=True)
548
549
550    # Build the rest without comments and with the version number appended
551    for rel in build_list[0:-1]:
552      out += '\n' + self.DefineStructInternals(node, rel,
553                                               include_version=True,
554                                               comment=False)
555
556    self.LogExit('Exit DefineStruct')
557    return out
558
559
560  #
561  # Copyright and Comment
562  #
563  # Generate a comment or copyright block
564  #
565  def Copyright(self, node, tabs=0):
566    lines = node.GetName().split('\n')
567    return CommentLines(lines, tabs)
568
569
570  # Define a top level object.
571  def Define(self, node, releases, tabs=0, prefix='', comment=False):
572    # If this request does not match unique release, or if the release is not
573    # available (possibly deprecated) then skip.
574    unique = node.GetUniqueReleases(releases)
575    if not unique or not node.InReleases(releases):
576      return ''
577
578    self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix))
579    declmap = dict({
580      'Enum': CGen.DefineEnum,
581      'Function': CGen.DefineMember,
582      'Interface': CGen.DefineStruct,
583      'Member': CGen.DefineMember,
584      'Struct': CGen.DefineStruct,
585      'Typedef': CGen.DefineTypedef
586    })
587
588    out = ''
589    func = declmap.get(node.cls, None)
590    if not func:
591      ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName()))
592    define_txt = func(self, node, releases, prefix=prefix, comment=comment)
593
594    comment_txt = GetNodeComments(node, tabs=0)
595    if comment_txt and comment:
596      out += comment_txt
597    out += define_txt
598
599    tab = '  ' * tabs
600    lines = []
601    for line in out.split('\n'):
602      # Add indentation
603      line = tab + line
604      if len(line) > 80:
605        left = line.rfind('(') + 1
606        args = line[left:].split(',')
607        line_max = 0
608        for arg in args:
609          if len(arg) > line_max: line_max = len(arg)
610
611        if left + line_max >= 80:
612          space = '%s    ' % tab
613          args =  (',\n%s' % space).join([arg.strip() for arg in args])
614          lines.append('%s\n%s%s' % (line[:left], space, args))
615        else:
616          space = ' ' * (left - 1)
617          args =  (',\n%s' % space).join(args)
618          lines.append('%s%s' % (line[:left], args))
619      else:
620        lines.append(line.rstrip())
621    self.LogExit('Exit Define')
622    return '\n'.join(lines)
623
624# Clean a string representing an object definition and return then string
625# as a single space delimited set of tokens.
626def CleanString(instr):
627  instr = instr.strip()
628  instr = instr.split()
629  return ' '.join(instr)
630
631
632# Test a file, by comparing all it's objects, with their comments.
633def TestFile(filenode):
634  cgen = CGen()
635
636  errors = 0
637  for node in filenode.GetChildren()[2:]:
638    instr = node.GetOneOf('Comment')
639    if not instr: continue
640    instr.Dump()
641    instr = CleanString(instr.GetName())
642
643    outstr = cgen.Define(node, releases=['M14'])
644    if GetOption('verbose'):
645      print outstr + '\n'
646    outstr = CleanString(outstr)
647
648    if instr != outstr:
649      ErrOut.Log('Failed match of\n>>%s<<\n>>%s<<\nto:' % (instr, outstr))
650      node.Dump(1, comments=True)
651      errors += 1
652  return errors
653
654
655# Build and resolve the AST and compare each file individual.
656def TestFiles(filenames):
657  if not filenames:
658    idldir = os.path.split(sys.argv[0])[0]
659    idldir = os.path.join(idldir, 'test_cgen', '*.idl')
660    filenames = glob.glob(idldir)
661
662  filenames = sorted(filenames)
663  ast = ParseFiles(filenames)
664
665  total_errs = 0
666  for filenode in ast.GetListOf('File'):
667    errs = TestFile(filenode)
668    if errs:
669      ErrOut.Log('%s test failed with %d error(s).' %
670                 (filenode.GetName(), errs))
671      total_errs += errs
672
673  if total_errs:
674    ErrOut.Log('Failed generator test.')
675  else:
676    InfoOut.Log('Passed generator test.')
677  return total_errs
678
679def Main(args):
680  filenames = ParseOptions(args)
681  if GetOption('test'):
682    return TestFiles(filenames)
683  ast = ParseFiles(filenames)
684  cgen = CGen()
685  for f in ast.GetListOf('File'):
686    if f.GetProperty('ERRORS') > 0:
687      print 'Skipping %s' % f.GetName()
688      continue
689    for node in f.GetChildren()[2:]:
690      print cgen.Define(node, comment=True, prefix='tst_')
691
692
693if __name__ == '__main__':
694  sys.exit(Main(sys.argv[1:]))
695
696