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.
6 """ Generator for C style prototypes and definitions """
12 from idl_log import ErrOut, InfoOut, WarnOut
13 from idl_node import IDLNode
14 from idl_ast import IDLAst
15 from idl_option import GetOption, Option, ParseOptions
16 from idl_parser import ParseFiles
18 Option('cgen_debug', 'Debug generate.')
20 class CGenError(Exception):
21 def __init__(self, msg):
25 return repr(self.value)
28 def CommentLines(lines, tabs=0):
29 # Generate a C style comment block by prepending the block with '<tab>/*'
30 # and adding a '<tab> *' per line.
33 out = '%s/*' % tab + ('\n%s *' % tab).join(lines)
35 # Add a terminating ' */' unless the last line is blank which would mean it
43 def 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')
48 # If an option prefix is provided, then prepend that to the comment
51 prefix_lines = prefix.split('\n')
52 # If both the prefix and comment start with a blank line ('*') remove
54 if prefix_lines[0] == '*' and lines[0] == '*':
55 lines = prefix_lines + lines[1:]
57 lines = prefix_lines + lines;
58 return CommentLines(lines, tabs)
60 def GetNodeComments(node, tabs=0):
61 # Generate a comment block joining all comment nodes which are children of
64 for doc in node.GetListOf('Comment'):
65 comment_txt += Comment(doc, tabs=tabs)
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.
139 'return': 'const %s',
162 # A diction array of PPAPI types that are converted to language specific
163 # types before being returned by by the C generator
168 'double_t': 'double',
171 'mem_ptr_t': 'void**',
173 'cstr_t': 'const char*',
174 'interface_t' : 'const void*'
181 # Debug Logging functions
184 if not GetOption('cgen_debug'): return
185 tabs = ' ' * self.dbg_depth
186 print '%s%s' % (tabs, txt)
188 def LogEnter(self, txt):
189 if txt: self.Log(txt)
192 def LogExit(self, txt):
194 if txt: self.Log(txt)
197 def GetDefine(self, name, value):
198 out = '#define %s %s' % (name, value)
200 out = '#define %s \\\n %s' % (name, value)
206 def GetMacroHelper(self, node):
207 macro = node.GetProperty('macro')
208 if macro: return macro
209 name = node.GetName()
211 return "%s_INTERFACE" % name
213 def GetInterfaceMacro(self, node, version = None):
214 name = self.GetMacroHelper(node)
217 return '%s_%s' % (name, str(version).replace('.', '_'))
219 def GetInterfaceString(self, node, version = None):
220 # If an interface name is specified, use that
221 name = node.GetProperty('iname')
223 # Otherwise, the interface name is the object's name
224 # With '_Dev' replaced by '(Dev)' if it's a Dev interface.
225 name = node.GetName()
226 if name.endswith('_Dev'):
227 name = '%s(Dev)' % name[:-4]
230 return "%s;%s" % (name, version)
234 # Return the array specification of the object.
236 def GetArraySpec(self, node):
237 assert(node.cls == 'Array')
238 fixed = node.GetProperty('FIXED')
240 return '[%s]' % fixed
247 # For any valid 'typed' object such as Member or Typedef
248 # the typenode object contains the typename
250 # For a given node return the type name by passing mode.
252 def GetTypeName(self, node, release, prefix=''):
253 self.LogEnter('GetTypeName of %s rel=%s' % (node, release))
255 # For Members, Params, and Typedefs get the type it refers to otherwise
256 # the node in question is it's own type (struct, union etc...)
257 if node.IsA('Member', 'Param', 'Typedef'):
258 typeref = node.GetType(release)
263 node.Error('No type at release %s.' % release)
264 raise CGenError('No type for %s' % node)
266 # If the type is a (BuiltIn) Type then return it's name
267 # remapping as needed
268 if typeref.IsA('Type'):
269 name = CGen.RemapName.get(typeref.GetName(), None)
270 if name is None: name = typeref.GetName()
271 name = '%s%s' % (prefix, name)
273 # For Interfaces, use the name + version
274 elif typeref.IsA('Interface'):
275 rel = typeref.first_release[release]
276 name = 'struct %s%s' % (prefix, self.GetStructName(typeref, rel, True))
278 # For structures, preceed with 'struct' or 'union' as appropriate
279 elif typeref.IsA('Struct'):
280 if typeref.GetProperty('union'):
281 name = 'union %s%s' % (prefix, typeref.GetName())
283 name = 'struct %s%s' % (prefix, typeref.GetName())
285 # If it's an enum, or typedef then return the Enum's name
286 elif typeref.IsA('Enum', 'Typedef'):
287 if not typeref.LastRelease(release):
288 first = node.first_release[release]
289 ver = '_' + node.GetVersion(first).replace('.','_')
292 # The enum may have skipped having a typedef, we need prefix with 'enum'.
293 if typeref.GetProperty('notypedef'):
294 name = 'enum %s%s%s' % (prefix, typeref.GetName(), ver)
296 name = '%s%s%s' % (prefix, typeref.GetName(), ver)
299 raise RuntimeError('Getting name of non-type %s.' % node)
300 self.LogExit('GetTypeName %s is %s' % (node, name))
307 # For a given node return basic type of that object. This is
308 # either a 'Type', 'Callspec', or 'Array'
310 def GetRootTypeMode(self, node, release, mode):
311 self.LogEnter('GetRootType of %s' % node)
312 # If it has an array spec, then treat it as an array regardless of type
313 if node.GetOneOf('Array'):
315 # Or if it has a callspec, treat it as a function
316 elif node.GetOneOf('Callspec'):
317 rootType, mode = self.GetRootTypeMode(node.GetType(release), release,
320 # If it's a plain typedef, try that object's root type
321 elif node.IsA('Member', 'Param', 'Typedef'):
322 rootType, mode = self.GetRootTypeMode(node.GetType(release),
325 # If it's an Enum, then it's normal passing rules
326 elif node.IsA('Enum'):
329 # If it's an Interface or Struct, we may be passing by value
330 elif node.IsA('Interface', 'Struct'):
332 if node.GetProperty('returnByValue'):
333 rootType = 'TypeValue'
337 if node.GetProperty('passByValue'):
338 rootType = 'TypeValue'
342 # If it's an Basic Type, check if it's a special type
343 elif node.IsA('Type'):
344 if node.GetName() in CGen.TypeMap:
345 rootType = node.GetName()
347 rootType = 'TypeValue'
349 raise RuntimeError('Getting root type of non-type %s.' % node)
350 self.LogExit('RootType is "%s"' % rootType)
351 return rootType, mode
354 def GetTypeByMode(self, node, release, mode):
355 self.LogEnter('GetTypeByMode of %s mode=%s release=%s' %
356 (node, mode, release))
357 name = self.GetTypeName(node, release)
358 ntype, mode = self.GetRootTypeMode(node, release, mode)
359 out = CGen.TypeMap[ntype][mode] % name
360 self.LogExit('GetTypeByMode %s = %s' % (node, out))
364 # Get the passing mode of the object (in, out, inout).
365 def GetParamMode(self, node):
366 self.Log('GetParamMode for %s' % node)
367 if node.GetProperty('in'): return 'in'
368 if node.GetProperty('out'): return 'out'
369 if node.GetProperty('inout'): return 'inout'
375 # Returns the signature components of an object as a tuple of
376 # (rtype, name, arrays, callspec) where:
377 # rtype - The store or return type of the object.
378 # name - The name of the object.
379 # arrays - A list of array dimensions as [] or [<fixed_num>].
380 # args - None if not a function, otherwise a list of parameters.
382 def GetComponents(self, node, release, mode):
383 self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release))
385 # Generate passing type by modifying root type
386 rtype = self.GetTypeByMode(node, release, mode)
387 if node.IsA('Enum', 'Interface', 'Struct'):
388 rname = node.GetName()
390 rname = node.GetType(release).GetName()
392 if rname in CGen.RemapName:
393 rname = CGen.RemapName[rname]
395 rtype = rtype % rname
396 name = node.GetName()
397 arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')]
398 callnode = node.GetOneOf('Callspec')
401 for param in callnode.GetListOf('Param'):
402 if not param.IsRelease(release):
404 mode = self.GetParamMode(param)
405 ptype, pname, parray, pspec = self.GetComponents(param, release, mode)
406 callspec.append((ptype, pname, parray, pspec))
410 self.LogExit('GetComponents: %s, %s, %s, %s' %
411 (rtype, name, arrayspec, callspec))
412 return (rtype, name, arrayspec, callspec)
415 def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr,
416 include_name, unsized_as_ptr):
417 self.LogEnter('Compose: %s %s' % (rtype, name))
418 arrayspec = ''.join(arrayspec)
420 # Switch unsized array to a ptr. NOTE: Only last element can be unsized.
421 if unsized_as_ptr and arrayspec[-2:] == '[]':
423 arrayspec=arrayspec[:-2]
426 name = prefix + arrayspec
428 name = prefix + name + arrayspec
430 out = '%s %s' % (rtype, name)
433 for ptype, pname, parray, pspec in callspec:
434 params.append(self.Compose(ptype, pname, parray, pspec, '', True,
436 unsized_as_ptr=unsized_as_ptr))
438 name = '(*%s)' % name
441 out = '%s %s(%s)' % (rtype, name, ', '.join(params))
442 self.LogExit('Exit Compose: %s' % out)
448 # Returns the 'C' style signature of the object
449 # prefix - A prefix for the object's name
450 # func_as_ptr - Formats a function as a function pointer
451 # include_name - If true, include member name in the signature.
452 # If false, leave it out. In any case, prefix is always
454 # include_version - if True, include version in the member name
456 def GetSignature(self, node, release, mode, prefix='', func_as_ptr=True,
457 include_name=True, include_version=False):
458 self.LogEnter('GetSignature %s %s as func=%s' %
459 (node, mode, func_as_ptr))
460 rtype, name, arrayspec, callspec = self.GetComponents(node, release, mode)
462 name = self.GetStructName(node, release, True)
464 # If not a callspec (such as a struct) use a ptr instead of []
465 unsized_as_ptr = not callspec
467 out = self.Compose(rtype, name, arrayspec, callspec, prefix,
468 func_as_ptr, include_name, unsized_as_ptr)
470 self.LogExit('Exit GetSignature: %s' % out)
474 def DefineTypedef(self, node, releases, prefix='', comment=False):
475 __pychecker__ = 'unusednames=comment'
476 build_list = node.GetUniqueReleases(releases)
478 out = 'typedef %s;\n' % self.GetSignature(node, build_list[-1], 'return',
480 include_version=False)
481 # Version mangle any other versions
482 for index, rel in enumerate(build_list[:-1]):
484 out += 'typedef %s;\n' % self.GetSignature(node, rel, 'return',
486 include_version=True)
487 self.Log('DefineTypedef: %s' % out)
491 def DefineEnum(self, node, releases, prefix='', comment=False):
492 __pychecker__ = 'unusednames=comment,releases'
493 self.LogEnter('DefineEnum %s' % node)
494 name = '%s%s' % (prefix, node.GetName())
495 notypedef = node.GetProperty('notypedef')
496 unnamed = node.GetProperty('unnamed')
501 out = 'enum %s {' % name
503 out = 'typedef enum {'
505 for child in node.GetListOf('EnumItem'):
506 value = child.GetProperty('VALUE')
507 comment_txt = GetNodeComments(child, tabs=1)
509 item_txt = '%s%s = %s' % (prefix, child.GetName(), value)
511 item_txt = '%s%s' % (prefix, child.GetName())
512 enumlist.append('%s %s' % (comment_txt, item_txt))
513 self.LogExit('Exit DefineEnum')
515 if unnamed or notypedef:
516 out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist))
518 out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name)
521 def DefineMember(self, node, releases, prefix='', comment=False):
522 __pychecker__ = 'unusednames=prefix,comment'
523 release = releases[0]
524 self.LogEnter('DefineMember %s' % node)
525 if node.GetProperty('ref'):
526 out = '%s;' % self.GetSignature(node, release, 'ref', '', True)
528 out = '%s;' % self.GetSignature(node, release, 'store', '', True)
529 self.LogExit('Exit DefineMember')
532 def GetStructName(self, node, release, include_version=False):
535 ver_num = node.GetVersion(release)
536 suffix = ('_%s' % ver_num).replace('.', '_')
537 return node.GetName() + suffix
539 def DefineStructInternals(self, node, release,
540 include_version=False, comment=True):
541 channel = node.GetProperty('FILE').release_map.GetChannel(release)
543 channel_comment = ' /* dev */'
547 if node.GetProperty('union'):
548 out += 'union %s {%s\n' % (
549 self.GetStructName(node, release, include_version), channel_comment)
551 out += 'struct %s {%s\n' % (
552 self.GetStructName(node, release, include_version), channel_comment)
554 channel = node.GetProperty('FILE').release_map.GetChannel(release)
555 # Generate Member Functions
557 for child in node.GetListOf('Member'):
558 if channel == 'stable' and child.NodeIsDevOnly():
560 member = self.Define(child, [release], tabs=1, comment=comment)
563 members.append(member)
564 out += '%s\n};\n' % '\n'.join(members)
568 def DefineStruct(self, node, releases, prefix='', comment=False):
569 __pychecker__ = 'unusednames=comment,prefix'
570 self.LogEnter('DefineStruct %s' % node)
572 build_list = node.GetUniqueReleases(releases)
576 for rel in build_list:
577 channel = node.GetProperty('FILE').release_map.GetChannel(rel)
578 if channel == 'stable':
582 last_rel = build_list[-1]
584 # TODO(noelallen) : Bug 157017 finish multiversion support
585 if node.IsA('Struct'):
586 if len(build_list) != 1:
587 node.Error('Can not support multiple versions of node.')
588 assert len(build_list) == 1
589 # Build the most recent one versioned, with comments
590 out = self.DefineStructInternals(node, last_rel,
591 include_version=False, comment=True)
593 if node.IsA('Interface'):
594 # Build the most recent one versioned, with comments
595 out = self.DefineStructInternals(node, last_rel,
596 include_version=True, comment=True)
597 if last_rel == newest_stable:
598 # Define an unversioned typedef for the most recent version
599 out += '\ntypedef struct %s %s;\n' % (
600 self.GetStructName(node, last_rel, include_version=True),
601 self.GetStructName(node, last_rel, include_version=False))
603 # Build the rest without comments and with the version number appended
604 for rel in build_list[0:-1]:
605 channel = node.GetProperty('FILE').release_map.GetChannel(rel)
606 # Skip dev channel interface versions that are
607 # Not the newest version, and
608 # Don't have an equivalent stable version.
609 if channel == 'dev' and rel != newest_dev:
610 if not node.DevInterfaceMatchesStable(rel):
612 out += '\n' + self.DefineStructInternals(node, rel,
613 include_version=True,
615 if rel == newest_stable:
616 # Define an unversioned typedef for the most recent version
617 out += '\ntypedef struct %s %s;\n' % (
618 self.GetStructName(node, rel, include_version=True),
619 self.GetStructName(node, rel, include_version=False))
621 self.LogExit('Exit DefineStruct')
626 # Copyright and Comment
628 # Generate a comment or copyright block
630 def Copyright(self, node, cpp_style=False):
631 lines = node.GetName().split('\n')
633 return '//' + '\n//'.join(filter(lambda f: f != '', lines)) + '\n'
634 return CommentLines(lines)
637 def Indent(self, data, tabs=0):
638 """Handles indentation and 80-column line wrapping."""
641 for line in data.split('\n'):
644 space_break = line.rfind(' ', 0, 80)
645 if len(line) <= 80 or 'http://' in line:
646 # Ignore normal line and URLs permitted by the style guide.
647 lines.append(line.rstrip())
648 elif not '(' in line and space_break >= 0:
649 # Break long typedefs on nearest space.
650 lines.append(line[0:space_break])
651 lines.append(' ' + line[space_break + 1:])
653 left = line.rfind('(') + 1
654 args = line[left:].split(',')
657 # Try to split on '(arg1)' or '(arg1, arg2)', not '()'
658 while args[0][0] == ')':
659 left = line.rfind('(', 0, left - 1) + 1
660 if left == 0: # No more parens, take the original option
664 args = line[left:].split(',')
668 if len(arg) > line_max: line_max = len(arg)
670 if left + line_max >= 80:
672 args = (',\n%s' % indent).join([arg.strip() for arg in args])
673 lines.append('%s\n%s%s' % (line[:left], indent, args))
675 indent = ' ' * (left - 1)
676 args = (',\n%s' % indent).join(args)
677 lines.append('%s%s' % (line[:left], args))
678 return '\n'.join(lines)
681 # Define a top level object.
682 def Define(self, node, releases, tabs=0, prefix='', comment=False):
683 # If this request does not match unique release, or if the release is not
684 # available (possibly deprecated) then skip.
685 unique = node.GetUniqueReleases(releases)
686 if not unique or not node.InReleases(releases):
689 self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix))
691 'Enum': CGen.DefineEnum,
692 'Function': CGen.DefineMember,
693 'Interface': CGen.DefineStruct,
694 'Member': CGen.DefineMember,
695 'Struct': CGen.DefineStruct,
696 'Typedef': CGen.DefineTypedef
700 func = declmap.get(node.cls, None)
702 ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName()))
703 define_txt = func(self, node, releases, prefix=prefix, comment=comment)
705 comment_txt = GetNodeComments(node, tabs=0)
706 if comment_txt and comment:
710 indented_out = self.Indent(out, tabs)
711 self.LogExit('Exit Define')
715 # Clean a string representing an object definition and return then string
716 # as a single space delimited set of tokens.
717 def CleanString(instr):
718 instr = instr.strip()
719 instr = instr.split()
720 return ' '.join(instr)
723 # Test a file, by comparing all it's objects, with their comments.
724 def TestFile(filenode):
728 for node in filenode.GetChildren()[2:]:
729 instr = node.GetOneOf('Comment')
730 if not instr: continue
732 instr = CleanString(instr.GetName())
734 outstr = cgen.Define(node, releases=['M14'])
735 if GetOption('verbose'):
737 outstr = CleanString(outstr)
740 ErrOut.Log('Failed match of\n>>%s<<\nto:\n>>%s<<\nFor:\n' %
742 node.Dump(1, comments=True)
747 # Build and resolve the AST and compare each file individual.
748 def TestFiles(filenames):
750 idldir = os.path.split(sys.argv[0])[0]
751 idldir = os.path.join(idldir, 'test_cgen', '*.idl')
752 filenames = glob.glob(idldir)
754 filenames = sorted(filenames)
755 ast = ParseFiles(filenames)
758 for filenode in ast.GetListOf('File'):
759 errs = TestFile(filenode)
761 ErrOut.Log('%s test failed with %d error(s).' %
762 (filenode.GetName(), errs))
766 ErrOut.Log('Failed generator test.')
768 InfoOut.Log('Passed generator test.')
772 filenames = ParseOptions(args)
773 if GetOption('test'):
774 return TestFiles(filenames)
775 ast = ParseFiles(filenames)
777 for f in ast.GetListOf('File'):
778 if f.GetProperty('ERRORS') > 0:
779 print 'Skipping %s' % f.GetName()
781 for node in f.GetChildren()[2:]:
782 print cgen.Define(node, ast.releases, comment=True, prefix='tst_')
785 if __name__ == '__main__':
786 sys.exit(main(sys.argv[1:]))