idl_c_proto.py revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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      if not typeref.LastRelease(release):
280        first = node.first_release[release]
281        ver = '_' + node.GetVersion(first).replace('.','_')
282      else:
283        ver = ''
284      # The enum may have skipped having a typedef, we need prefix with 'enum'.
285      if typeref.GetProperty('notypedef'):
286        name = 'enum %s%s%s' % (prefix, typeref.GetName(), ver)
287      else:
288        name = '%s%s%s' % (prefix, typeref.GetName(), ver)
289
290    else:
291      raise RuntimeError('Getting name of non-type %s.' % node)
292    self.LogExit('GetTypeName %s is %s' % (node, name))
293    return name
294
295
296  #
297  # GetRootType
298  #
299  # For a given node return basic type of that object.  This is
300  # either a 'Type', 'Callspec', or 'Array'
301  #
302  def GetRootTypeMode(self, node, release, mode):
303    self.LogEnter('GetRootType of %s' % node)
304    # If it has an array spec, then treat it as an array regardless of type
305    if node.GetOneOf('Array'):
306      rootType = 'Array'
307    # Or if it has a callspec, treat it as a function
308    elif node.GetOneOf('Callspec'):
309      rootType, mode = self.GetRootTypeMode(node.GetType(release), release,
310                                            'return')
311
312    # If it's a plain typedef, try that object's root type
313    elif node.IsA('Member', 'Param', 'Typedef'):
314      rootType, mode = self.GetRootTypeMode(node.GetType(release),
315                                            release, mode)
316
317    # If it's an Enum, then it's normal passing rules
318    elif node.IsA('Enum'):
319      rootType = node.cls
320
321    # If it's an Interface or Struct, we may be passing by value
322    elif node.IsA('Interface', 'Struct'):
323      if mode == 'return':
324        if node.GetProperty('returnByValue'):
325          rootType = 'TypeValue'
326        else:
327          rootType = node.cls
328      else:
329        if node.GetProperty('passByValue'):
330          rootType = 'TypeValue'
331        else:
332          rootType = node.cls
333
334    # If it's an Basic Type, check if it's a special type
335    elif node.IsA('Type'):
336      if node.GetName() in CGen.TypeMap:
337        rootType = node.GetName()
338      else:
339        rootType = 'TypeValue'
340    else:
341      raise RuntimeError('Getting root type of non-type %s.' % node)
342    self.LogExit('RootType is "%s"' % rootType)
343    return rootType, mode
344
345
346  def GetTypeByMode(self, node, release, mode):
347    self.LogEnter('GetTypeByMode of %s mode=%s release=%s' %
348                  (node, mode, release))
349    name = self.GetTypeName(node, release)
350    ntype, mode = self.GetRootTypeMode(node, release, mode)
351    out = CGen.TypeMap[ntype][mode] % name
352    self.LogExit('GetTypeByMode %s = %s' % (node, out))
353    return out
354
355
356  # Get the passing mode of the object (in, out, inout).
357  def GetParamMode(self, node):
358    self.Log('GetParamMode for %s' % node)
359    if node.GetProperty('in'): return 'in'
360    if node.GetProperty('out'): return 'out'
361    if node.GetProperty('inout'): return 'inout'
362    return 'return'
363
364  #
365  # GetComponents
366  #
367  # Returns the signature components of an object as a tuple of
368  # (rtype, name, arrays, callspec) where:
369  #   rtype - The store or return type of the object.
370  #   name - The name of the object.
371  #   arrays - A list of array dimensions as [] or [<fixed_num>].
372  #   args -  None if not a function, otherwise a list of parameters.
373  #
374  def GetComponents(self, node, release, mode):
375    self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release))
376
377    # Generate passing type by modifying root type
378    rtype = self.GetTypeByMode(node, release, mode)
379    if node.IsA('Enum', 'Interface', 'Struct'):
380      rname = node.GetName()
381    else:
382      rname = node.GetType(release).GetName()
383
384    if rname in CGen.RemapName:
385      rname = CGen.RemapName[rname]
386    if '%' in rtype:
387      rtype = rtype % rname
388    name = node.GetName()
389    arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')]
390    callnode = node.GetOneOf('Callspec')
391    if callnode:
392      callspec = []
393      for param in callnode.GetListOf('Param'):
394        if not param.IsRelease(release):
395          continue
396        mode = self.GetParamMode(param)
397        ptype, pname, parray, pspec = self.GetComponents(param, release, mode)
398        callspec.append((ptype, pname, parray, pspec))
399    else:
400      callspec = None
401
402    self.LogExit('GetComponents: %s, %s, %s, %s' %
403                 (rtype, name, arrayspec, callspec))
404    return (rtype, name, arrayspec, callspec)
405
406
407  def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr,
408              include_name, unsized_as_ptr):
409    self.LogEnter('Compose: %s %s' % (rtype, name))
410    arrayspec = ''.join(arrayspec)
411
412    # Switch unsized array to a ptr. NOTE: Only last element can be unsized.
413    if unsized_as_ptr and arrayspec[-2:] == '[]':
414      prefix +=  '*'
415      arrayspec=arrayspec[:-2]
416
417    if not include_name:
418      name = prefix + arrayspec
419    else:
420      name = prefix + name + arrayspec
421    if callspec is None:
422      out = '%s %s' % (rtype, name)
423    else:
424      params = []
425      for ptype, pname, parray, pspec in callspec:
426        params.append(self.Compose(ptype, pname, parray, pspec, '', True,
427                                   include_name=True,
428                                   unsized_as_ptr=unsized_as_ptr))
429      if func_as_ptr:
430        name = '(*%s)' % name
431      if not params:
432        params = ['void']
433      out = '%s %s(%s)' % (rtype, name, ', '.join(params))
434    self.LogExit('Exit Compose: %s' % out)
435    return out
436
437  #
438  # GetSignature
439  #
440  # Returns the 'C' style signature of the object
441  #  prefix - A prefix for the object's name
442  #  func_as_ptr - Formats a function as a function pointer
443  #  include_name - If true, include member name in the signature.
444  #                 If false, leave it out. In any case, prefix is always
445  #                 included.
446  #  include_version - if True, include version in the member name
447  #
448  def GetSignature(self, node, release, mode, prefix='', func_as_ptr=True,
449                   include_name=True, include_version=False):
450    self.LogEnter('GetSignature %s %s as func=%s' %
451                  (node, mode, func_as_ptr))
452    rtype, name, arrayspec, callspec = self.GetComponents(node, release, mode)
453    if include_version:
454      name = self.GetStructName(node, release, True)
455
456    # If not a callspec (such as a struct) use a ptr instead of []
457    unsized_as_ptr = not callspec
458
459    out = self.Compose(rtype, name, arrayspec, callspec, prefix,
460                       func_as_ptr, include_name, unsized_as_ptr)
461
462    self.LogExit('Exit GetSignature: %s' % out)
463    return out
464
465  # Define a Typedef.
466  def DefineTypedef(self, node, releases, prefix='', comment=False):
467    __pychecker__ = 'unusednames=comment'
468    build_list = node.GetUniqueReleases(releases)
469
470    out = 'typedef %s;\n' % self.GetSignature(node, build_list[-1], 'return',
471                                              prefix, True,
472                                              include_version=False)
473    # Version mangle any other versions
474    for index, rel in enumerate(build_list[:-1]):
475      out += '\n'
476      out += 'typedef %s;\n' % self.GetSignature(node, rel, 'return',
477                                                 prefix, True,
478                                                 include_version=True)
479    self.Log('DefineTypedef: %s' % out)
480    return out
481
482  # Define an Enum.
483  def DefineEnum(self, node, releases, prefix='', comment=False):
484    __pychecker__ = 'unusednames=comment,releases'
485    self.LogEnter('DefineEnum %s' % node)
486    name = '%s%s' % (prefix, node.GetName())
487    notypedef = node.GetProperty('notypedef')
488    unnamed = node.GetProperty('unnamed')
489
490    if unnamed:
491      out = 'enum {'
492    elif notypedef:
493      out = 'enum %s {' % name
494    else:
495      out = 'typedef enum {'
496    enumlist = []
497    for child in node.GetListOf('EnumItem'):
498      value = child.GetProperty('VALUE')
499      comment_txt = GetNodeComments(child, tabs=1)
500      if value:
501        item_txt = '%s%s = %s' % (prefix, child.GetName(), value)
502      else:
503        item_txt = '%s%s' % (prefix, child.GetName())
504      enumlist.append('%s  %s' % (comment_txt, item_txt))
505    self.LogExit('Exit DefineEnum')
506
507    if unnamed or notypedef:
508      out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist))
509    else:
510      out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name)
511    return out
512
513  def DefineMember(self, node, releases, prefix='', comment=False):
514    __pychecker__ = 'unusednames=prefix,comment'
515    release = releases[0]
516    self.LogEnter('DefineMember %s' % node)
517    if node.GetProperty('ref'):
518      out = '%s;' % self.GetSignature(node, release, 'ref', '', True)
519    else:
520      out = '%s;' % self.GetSignature(node, release, 'store', '', True)
521    self.LogExit('Exit DefineMember')
522    return out
523
524  def GetStructName(self, node, release, include_version=False):
525    suffix = ''
526    if include_version:
527      ver_num = node.GetVersion(release)
528      suffix = ('_%s' % ver_num).replace('.', '_')
529    return node.GetName() + suffix
530
531  def DefineStructInternals(self, node, release,
532                            include_version=False, comment=True):
533    out = ''
534    if node.GetProperty('union'):
535      out += 'union %s {\n' % (
536          self.GetStructName(node, release, include_version))
537    else:
538      out += 'struct %s {\n' % (
539          self.GetStructName(node, release, include_version))
540
541    # Generate Member Functions
542    members = []
543    for child in node.GetListOf('Member'):
544      member = self.Define(child, [release], tabs=1, comment=comment)
545      if not member:
546        continue
547      members.append(member)
548    out += '%s\n};\n' % '\n'.join(members)
549    return out
550
551
552  def DefineStruct(self, node, releases, prefix='', comment=False):
553    __pychecker__ = 'unusednames=comment,prefix'
554    self.LogEnter('DefineStruct %s' % node)
555    out = ''
556    build_list = node.GetUniqueReleases(releases)
557
558    # TODO(noelallen) : Bug 157017 finish multiversion support
559    if node.IsA('Struct'):
560      if len(build_list) != 1:
561        node.Error('Can not support multiple versions of node.')
562      assert len(build_list) == 1
563
564
565    if node.IsA('Interface'):
566      # Build the most recent one versioned, with comments
567      out = self.DefineStructInternals(node, build_list[-1],
568                                       include_version=True, comment=True)
569
570      # Define an unversioned typedef for the most recent version
571      out += '\ntypedef struct %s %s;\n' % (
572          self.GetStructName(node, build_list[-1], include_version=True),
573          self.GetStructName(node, build_list[-1], include_version=False))
574    else:
575      # Build the most recent one versioned, with comments
576      out = self.DefineStructInternals(node, build_list[-1],
577                                       include_version=False, comment=True)
578
579
580    # Build the rest without comments and with the version number appended
581    for rel in build_list[0:-1]:
582      out += '\n' + self.DefineStructInternals(node, rel,
583                                               include_version=True,
584                                               comment=False)
585
586    self.LogExit('Exit DefineStruct')
587    return out
588
589
590  #
591  # Copyright and Comment
592  #
593  # Generate a comment or copyright block
594  #
595  def Copyright(self, node, cpp_style=False):
596    lines = node.GetName().split('\n')
597    if cpp_style:
598      return '//' + '\n//'.join(filter(lambda f: f != '', lines)) + '\n'
599    return CommentLines(lines)
600
601
602  def Indent(self, data, tabs=0):
603    """Handles indentation and 80-column line wrapping."""
604    tab = '  ' * tabs
605    lines = []
606    for line in data.split('\n'):
607      # Add indentation
608      line = tab + line
609      if len(line) <= 80:
610        lines.append(line.rstrip())
611      else:
612        left = line.rfind('(') + 1
613        args = line[left:].split(',')
614        orig_args = args
615        orig_left = left
616        # Try to split on '(arg1)' or '(arg1, arg2)', not '()'
617        while args[0][0] == ')':
618          left = line.rfind('(', 0, left - 1) + 1
619          if left == 0:  # No more parens, take the original option
620            args = orig_args
621            left = orig_left
622            break
623          args = line[left:].split(',')
624
625        line_max = 0
626        for arg in args:
627          if len(arg) > line_max: line_max = len(arg)
628
629        if left + line_max >= 80:
630          indent = '%s    ' % tab
631          args =  (',\n%s' % indent).join([arg.strip() for arg in args])
632          lines.append('%s\n%s%s' % (line[:left], indent, args))
633        else:
634          indent = ' ' * (left - 1)
635          args =  (',\n%s' % indent).join(args)
636          lines.append('%s%s' % (line[:left], args))
637    return '\n'.join(lines)
638
639
640  # Define a top level object.
641  def Define(self, node, releases, tabs=0, prefix='', comment=False):
642    # If this request does not match unique release, or if the release is not
643    # available (possibly deprecated) then skip.
644    unique = node.GetUniqueReleases(releases)
645    if not unique or not node.InReleases(releases):
646      return ''
647
648    self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix))
649    declmap = dict({
650      'Enum': CGen.DefineEnum,
651      'Function': CGen.DefineMember,
652      'Interface': CGen.DefineStruct,
653      'Member': CGen.DefineMember,
654      'Struct': CGen.DefineStruct,
655      'Typedef': CGen.DefineTypedef
656    })
657
658    out = ''
659    func = declmap.get(node.cls, None)
660    if not func:
661      ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName()))
662    define_txt = func(self, node, releases, prefix=prefix, comment=comment)
663
664    comment_txt = GetNodeComments(node, tabs=0)
665    if comment_txt and comment:
666      out += comment_txt
667    out += define_txt
668
669    indented_out = self.Indent(out, tabs)
670    self.LogExit('Exit Define')
671    return indented_out
672
673
674# Clean a string representing an object definition and return then string
675# as a single space delimited set of tokens.
676def CleanString(instr):
677  instr = instr.strip()
678  instr = instr.split()
679  return ' '.join(instr)
680
681
682# Test a file, by comparing all it's objects, with their comments.
683def TestFile(filenode):
684  cgen = CGen()
685
686  errors = 0
687  for node in filenode.GetChildren()[2:]:
688    instr = node.GetOneOf('Comment')
689    if not instr: continue
690    instr.Dump()
691    instr = CleanString(instr.GetName())
692
693    outstr = cgen.Define(node, releases=['M14'])
694    if GetOption('verbose'):
695      print outstr + '\n'
696    outstr = CleanString(outstr)
697
698    if instr != outstr:
699      ErrOut.Log('Failed match of\n>>%s<<\nto:\n>>%s<<\nFor:\n' %
700                 (instr, outstr))
701      node.Dump(1, comments=True)
702      errors += 1
703  return errors
704
705
706# Build and resolve the AST and compare each file individual.
707def TestFiles(filenames):
708  if not filenames:
709    idldir = os.path.split(sys.argv[0])[0]
710    idldir = os.path.join(idldir, 'test_cgen', '*.idl')
711    filenames = glob.glob(idldir)
712
713  filenames = sorted(filenames)
714  ast = ParseFiles(filenames)
715
716  total_errs = 0
717  for filenode in ast.GetListOf('File'):
718    errs = TestFile(filenode)
719    if errs:
720      ErrOut.Log('%s test failed with %d error(s).' %
721                 (filenode.GetName(), errs))
722      total_errs += errs
723
724  if total_errs:
725    ErrOut.Log('Failed generator test.')
726  else:
727    InfoOut.Log('Passed generator test.')
728  return total_errs
729
730def main(args):
731  filenames = ParseOptions(args)
732  if GetOption('test'):
733    return TestFiles(filenames)
734  ast = ParseFiles(filenames)
735  cgen = CGen()
736  for f in ast.GetListOf('File'):
737    if f.GetProperty('ERRORS') > 0:
738      print 'Skipping %s' % f.GetName()
739      continue
740    for node in f.GetChildren()[2:]:
741      print cgen.Define(node, comment=True, prefix='tst_')
742
743
744if __name__ == '__main__':
745  sys.exit(main(sys.argv[1:]))
746
747