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