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',
151 'constptr_in': 'const %s*', # So we can use const* for PP_Var sometimes.
163 # A diction array of PPAPI types that are converted to language specific
164 # types before being returned by by the C generator
169 'double_t': 'double',
172 'mem_ptr_t': 'void**',
174 'cstr_t': 'const char*',
175 'interface_t' : 'const void*'
178 # Tell how to handle pointers to GL types.
179 for gltype
in ['GLbitfield', 'GLboolean', 'GLbyte', 'GLclampf',
180 'GLclampx', 'GLenum', 'GLfixed', 'GLfloat', 'GLint',
181 'GLintptr', 'GLshort', 'GLsizei', 'GLsizeiptr',
182 'GLubyte', 'GLuint', 'GLushort']:
183 ptrtype
= gltype
+ '_ptr_t'
188 'return': 'const %s',
191 RemapName
[ptrtype
] = '%s*' % gltype
197 # Debug Logging functions
200 if not GetOption('cgen_debug'): return
201 tabs
= ' ' * self
.dbg_depth
202 print '%s%s' % (tabs
, txt
)
204 def LogEnter(self
, txt
):
205 if txt
: self
.Log(txt
)
208 def LogExit(self
, txt
):
210 if txt
: self
.Log(txt
)
213 def GetDefine(self
, name
, value
):
214 out
= '#define %s %s' % (name
, value
)
216 out
= '#define %s \\\n %s' % (name
, value
)
222 def GetMacroHelper(self
, node
):
223 macro
= node
.GetProperty('macro')
224 if macro
: return macro
225 name
= node
.GetName()
227 return "%s_INTERFACE" % name
229 def GetInterfaceMacro(self
, node
, version
= None):
230 name
= self
.GetMacroHelper(node
)
233 return '%s_%s' % (name
, str(version
).replace('.', '_'))
235 def GetInterfaceString(self
, node
, version
= None):
236 # If an interface name is specified, use that
237 name
= node
.GetProperty('iname')
239 # Otherwise, the interface name is the object's name
240 # With '_Dev' replaced by '(Dev)' if it's a Dev interface.
241 name
= node
.GetName()
242 if name
.endswith('_Dev'):
243 name
= '%s(Dev)' % name
[:-4]
246 return "%s;%s" % (name
, version
)
250 # Return the array specification of the object.
252 def GetArraySpec(self
, node
):
253 assert(node
.cls
== 'Array')
254 fixed
= node
.GetProperty('FIXED')
256 return '[%s]' % fixed
263 # For any valid 'typed' object such as Member or Typedef
264 # the typenode object contains the typename
266 # For a given node return the type name by passing mode.
268 def GetTypeName(self
, node
, release
, prefix
=''):
269 self
.LogEnter('GetTypeName of %s rel=%s' % (node
, release
))
271 # For Members, Params, and Typedefs get the type it refers to otherwise
272 # the node in question is it's own type (struct, union etc...)
273 if node
.IsA('Member', 'Param', 'Typedef'):
274 typeref
= node
.GetType(release
)
279 node
.Error('No type at release %s.' % release
)
280 raise CGenError('No type for %s' % node
)
282 # If the type is a (BuiltIn) Type then return it's name
283 # remapping as needed
284 if typeref
.IsA('Type'):
285 name
= CGen
.RemapName
.get(typeref
.GetName(), None)
286 if name
is None: name
= typeref
.GetName()
287 name
= '%s%s' % (prefix
, name
)
289 # For Interfaces, use the name + version
290 elif typeref
.IsA('Interface'):
291 rel
= typeref
.first_release
[release
]
292 name
= 'struct %s%s' % (prefix
, self
.GetStructName(typeref
, rel
, True))
294 # For structures, preceed with 'struct' or 'union' as appropriate
295 elif typeref
.IsA('Struct'):
296 if typeref
.GetProperty('union'):
297 name
= 'union %s%s' % (prefix
, typeref
.GetName())
299 name
= 'struct %s%s' % (prefix
, typeref
.GetName())
301 # If it's an enum, or typedef then return the Enum's name
302 elif typeref
.IsA('Enum', 'Typedef'):
303 if not typeref
.LastRelease(release
):
304 first
= node
.first_release
[release
]
305 ver
= '_' + node
.GetVersion(first
).replace('.','_')
308 # The enum may have skipped having a typedef, we need prefix with 'enum'.
309 if typeref
.GetProperty('notypedef'):
310 name
= 'enum %s%s%s' % (prefix
, typeref
.GetName(), ver
)
312 name
= '%s%s%s' % (prefix
, typeref
.GetName(), ver
)
315 raise RuntimeError('Getting name of non-type %s.' % node
)
316 self
.LogExit('GetTypeName %s is %s' % (node
, name
))
323 # For a given node return basic type of that object. This is
324 # either a 'Type', 'Callspec', or 'Array'
326 def GetRootTypeMode(self
, node
, release
, mode
):
327 self
.LogEnter('GetRootType of %s' % node
)
328 # If it has an array spec, then treat it as an array regardless of type
329 if node
.GetOneOf('Array'):
331 # Or if it has a callspec, treat it as a function
332 elif node
.GetOneOf('Callspec'):
333 rootType
, mode
= self
.GetRootTypeMode(node
.GetType(release
), release
,
336 # If it's a plain typedef, try that object's root type
337 elif node
.IsA('Member', 'Param', 'Typedef'):
338 rootType
, mode
= self
.GetRootTypeMode(node
.GetType(release
),
341 # If it's an Enum, then it's normal passing rules
342 elif node
.IsA('Enum'):
345 # If it's an Interface or Struct, we may be passing by value
346 elif node
.IsA('Interface', 'Struct'):
348 if node
.GetProperty('returnByValue'):
349 rootType
= 'TypeValue'
353 if node
.GetProperty('passByValue'):
354 rootType
= 'TypeValue'
358 # If it's an Basic Type, check if it's a special type
359 elif node
.IsA('Type'):
360 if node
.GetName() in CGen
.TypeMap
:
361 rootType
= node
.GetName()
363 rootType
= 'TypeValue'
365 raise RuntimeError('Getting root type of non-type %s.' % node
)
366 self
.LogExit('RootType is "%s"' % rootType
)
367 return rootType
, mode
370 def GetTypeByMode(self
, node
, release
, mode
):
371 self
.LogEnter('GetTypeByMode of %s mode=%s release=%s' %
372 (node
, mode
, release
))
373 name
= self
.GetTypeName(node
, release
)
374 ntype
, mode
= self
.GetRootTypeMode(node
, release
, mode
)
375 out
= CGen
.TypeMap
[ntype
][mode
] % name
376 self
.LogExit('GetTypeByMode %s = %s' % (node
, out
))
380 # Get the passing mode of the object (in, out, inout).
381 def GetParamMode(self
, node
):
382 self
.Log('GetParamMode for %s' % node
)
383 if node
.GetProperty('in'): return 'in'
384 if node
.GetProperty('out'): return 'out'
385 if node
.GetProperty('inout'): return 'inout'
386 if node
.GetProperty('constptr_in'): return 'constptr_in'
392 # Returns the signature components of an object as a tuple of
393 # (rtype, name, arrays, callspec) where:
394 # rtype - The store or return type of the object.
395 # name - The name of the object.
396 # arrays - A list of array dimensions as [] or [<fixed_num>].
397 # args - None if not a function, otherwise a list of parameters.
399 def GetComponents(self
, node
, release
, mode
):
400 self
.LogEnter('GetComponents mode %s for %s %s' % (mode
, node
, release
))
402 # Generate passing type by modifying root type
403 rtype
= self
.GetTypeByMode(node
, release
, mode
)
404 # If this is an array output, change it from type* foo[] to type** foo.
405 # type* foo[] means an array of pointers to type, which is confusing.
406 arrayspec
= [self
.GetArraySpec(array
) for array
in node
.GetListOf('Array')]
407 if mode
== 'out' and len(arrayspec
) == 1 and arrayspec
[0] == '[]':
411 if node
.IsA('Enum', 'Interface', 'Struct'):
412 rname
= node
.GetName()
414 rname
= node
.GetType(release
).GetName()
416 if rname
in CGen
.RemapName
:
417 rname
= CGen
.RemapName
[rname
]
419 rtype
= rtype
% rname
420 name
= node
.GetName()
421 callnode
= node
.GetOneOf('Callspec')
424 for param
in callnode
.GetListOf('Param'):
425 if not param
.IsRelease(release
):
427 mode
= self
.GetParamMode(param
)
428 ptype
, pname
, parray
, pspec
= self
.GetComponents(param
, release
, mode
)
429 callspec
.append((ptype
, pname
, parray
, pspec
))
433 self
.LogExit('GetComponents: %s, %s, %s, %s' %
434 (rtype
, name
, arrayspec
, callspec
))
435 return (rtype
, name
, arrayspec
, callspec
)
438 def Compose(self
, rtype
, name
, arrayspec
, callspec
, prefix
, func_as_ptr
,
439 include_name
, unsized_as_ptr
):
440 self
.LogEnter('Compose: %s %s' % (rtype
, name
))
441 arrayspec
= ''.join(arrayspec
)
443 # Switch unsized array to a ptr. NOTE: Only last element can be unsized.
444 if unsized_as_ptr
and arrayspec
[-2:] == '[]':
446 arrayspec
=arrayspec
[:-2]
449 name
= prefix
+ arrayspec
451 name
= prefix
+ name
+ arrayspec
453 out
= '%s %s' % (rtype
, name
)
456 for ptype
, pname
, parray
, pspec
in callspec
:
457 params
.append(self
.Compose(ptype
, pname
, parray
, pspec
, '', True,
459 unsized_as_ptr
=unsized_as_ptr
))
461 name
= '(*%s)' % name
464 out
= '%s %s(%s)' % (rtype
, name
, ', '.join(params
))
465 self
.LogExit('Exit Compose: %s' % out
)
471 # Returns the 'C' style signature of the object
472 # prefix - A prefix for the object's name
473 # func_as_ptr - Formats a function as a function pointer
474 # include_name - If true, include member name in the signature.
475 # If false, leave it out. In any case, prefix is always
477 # include_version - if True, include version in the member name
479 def GetSignature(self
, node
, release
, mode
, prefix
='', func_as_ptr
=True,
480 include_name
=True, include_version
=False):
481 self
.LogEnter('GetSignature %s %s as func=%s' %
482 (node
, mode
, func_as_ptr
))
483 rtype
, name
, arrayspec
, callspec
= self
.GetComponents(node
, release
, mode
)
485 name
= self
.GetStructName(node
, release
, True)
487 # If not a callspec (such as a struct) use a ptr instead of []
488 unsized_as_ptr
= not callspec
490 out
= self
.Compose(rtype
, name
, arrayspec
, callspec
, prefix
,
491 func_as_ptr
, include_name
, unsized_as_ptr
)
493 self
.LogExit('Exit GetSignature: %s' % out
)
497 def DefineTypedef(self
, node
, releases
, prefix
='', comment
=False):
498 __pychecker__
= 'unusednames=comment'
499 build_list
= node
.GetUniqueReleases(releases
)
501 out
= 'typedef %s;\n' % self
.GetSignature(node
, build_list
[-1], 'return',
503 include_version
=False)
504 # Version mangle any other versions
505 for index
, rel
in enumerate(build_list
[:-1]):
507 out
+= 'typedef %s;\n' % self
.GetSignature(node
, rel
, 'return',
509 include_version
=True)
510 self
.Log('DefineTypedef: %s' % out
)
514 def DefineEnum(self
, node
, releases
, prefix
='', comment
=False):
515 __pychecker__
= 'unusednames=comment,releases'
516 self
.LogEnter('DefineEnum %s' % node
)
517 name
= '%s%s' % (prefix
, node
.GetName())
518 notypedef
= node
.GetProperty('notypedef')
519 unnamed
= node
.GetProperty('unnamed')
524 out
= 'enum %s {' % name
526 out
= 'typedef enum {'
528 for child
in node
.GetListOf('EnumItem'):
529 value
= child
.GetProperty('VALUE')
530 comment_txt
= GetNodeComments(child
, tabs
=1)
532 item_txt
= '%s%s = %s' % (prefix
, child
.GetName(), value
)
534 item_txt
= '%s%s' % (prefix
, child
.GetName())
535 enumlist
.append('%s %s' % (comment_txt
, item_txt
))
536 self
.LogExit('Exit DefineEnum')
538 if unnamed
or notypedef
:
539 out
= '%s\n%s\n};\n' % (out
, ',\n'.join(enumlist
))
541 out
= '%s\n%s\n} %s;\n' % (out
, ',\n'.join(enumlist
), name
)
544 def DefineMember(self
, node
, releases
, prefix
='', comment
=False):
545 __pychecker__
= 'unusednames=prefix,comment'
546 release
= releases
[0]
547 self
.LogEnter('DefineMember %s' % node
)
548 if node
.GetProperty('ref'):
549 out
= '%s;' % self
.GetSignature(node
, release
, 'ref', '', True)
551 out
= '%s;' % self
.GetSignature(node
, release
, 'store', '', True)
552 self
.LogExit('Exit DefineMember')
555 def GetStructName(self
, node
, release
, include_version
=False):
558 ver_num
= node
.GetVersion(release
)
559 suffix
= ('_%s' % ver_num
).replace('.', '_')
560 return node
.GetName() + suffix
562 def DefineStructInternals(self
, node
, release
,
563 include_version
=False, comment
=True):
564 channel
= node
.GetProperty('FILE').release_map
.GetChannel(release
)
566 channel_comment
= ' /* dev */'
570 if node
.GetProperty('union'):
571 out
+= 'union %s {%s\n' % (
572 self
.GetStructName(node
, release
, include_version
), channel_comment
)
574 out
+= 'struct %s {%s\n' % (
575 self
.GetStructName(node
, release
, include_version
), channel_comment
)
577 channel
= node
.GetProperty('FILE').release_map
.GetChannel(release
)
578 # Generate Member Functions
580 for child
in node
.GetListOf('Member'):
581 if channel
== 'stable' and child
.NodeIsDevOnly():
583 member
= self
.Define(child
, [release
], tabs
=1, comment
=comment
)
586 members
.append(member
)
587 out
+= '%s\n};\n' % '\n'.join(members
)
591 def DefineUnversionedInterface(self
, node
, rel
):
593 if node
.GetProperty('force_struct_namespace'):
594 # Duplicate the definition to put it in struct namespace. This
595 # attribute is only for legacy APIs like OpenGLES2 and new APIs
596 # must not use this. See http://crbug.com/411799
597 out
+= self
.DefineStructInternals(node
, rel
,
598 include_version
=False, comment
=True)
600 # Define an unversioned typedef for the most recent version
601 out
+= 'typedef struct %s %s;\n' % (
602 self
.GetStructName(node
, rel
, include_version
=True),
603 self
.GetStructName(node
, rel
, include_version
=False))
607 def DefineStruct(self
, node
, releases
, prefix
='', comment
=False):
608 __pychecker__
= 'unusednames=comment,prefix'
609 self
.LogEnter('DefineStruct %s' % node
)
611 build_list
= node
.GetUniqueReleases(releases
)
615 for rel
in build_list
:
616 channel
= node
.GetProperty('FILE').release_map
.GetChannel(rel
)
617 if channel
== 'stable':
621 last_rel
= build_list
[-1]
623 # TODO(bradnelson) : Bug 157017 finish multiversion support
624 if node
.IsA('Struct'):
625 if len(build_list
) != 1:
626 node
.Error('Can not support multiple versions of node.')
627 assert len(build_list
) == 1
628 # Build the most recent one versioned, with comments
629 out
= self
.DefineStructInternals(node
, last_rel
,
630 include_version
=False, comment
=True)
632 if node
.IsA('Interface'):
633 # Build the most recent one versioned, with comments
634 out
= self
.DefineStructInternals(node
, last_rel
,
635 include_version
=True, comment
=True)
636 if last_rel
== newest_stable
:
637 out
+= self
.DefineUnversionedInterface(node
, last_rel
)
639 # Build the rest without comments and with the version number appended
640 for rel
in build_list
[0:-1]:
641 channel
= node
.GetProperty('FILE').release_map
.GetChannel(rel
)
642 # Skip dev channel interface versions that are
643 # Not the newest version, and
644 # Don't have an equivalent stable version.
645 if channel
== 'dev' and rel
!= newest_dev
:
646 if not node
.DevInterfaceMatchesStable(rel
):
648 out
+= '\n' + self
.DefineStructInternals(node
, rel
,
649 include_version
=True,
651 if rel
== newest_stable
:
652 out
+= self
.DefineUnversionedInterface(node
, rel
)
654 self
.LogExit('Exit DefineStruct')
659 # Copyright and Comment
661 # Generate a comment or copyright block
663 def Copyright(self
, node
, cpp_style
=False):
664 lines
= node
.GetName().split('\n')
666 return '//' + '\n//'.join(filter(lambda f
: f
!= '', lines
)) + '\n'
667 return CommentLines(lines
)
670 def Indent(self
, data
, tabs
=0):
671 """Handles indentation and 80-column line wrapping."""
674 for line
in data
.split('\n'):
677 space_break
= line
.rfind(' ', 0, 80)
678 if len(line
) <= 80 or 'http://' in line
:
679 # Ignore normal line and URLs permitted by the style guide.
680 lines
.append(line
.rstrip())
681 elif not '(' in line
and space_break
>= 0:
682 # Break long typedefs on nearest space.
683 lines
.append(line
[0:space_break
])
684 lines
.append(' ' + line
[space_break
+ 1:])
686 left
= line
.rfind('(') + 1
687 args
= line
[left
:].split(',')
690 # Try to split on '(arg1)' or '(arg1, arg2)', not '()'
691 while args
[0][0] == ')':
692 left
= line
.rfind('(', 0, left
- 1) + 1
693 if left
== 0: # No more parens, take the original option
697 args
= line
[left
:].split(',')
701 if len(arg
) > line_max
: line_max
= len(arg
)
703 if left
+ line_max
>= 80:
705 args
= (',\n%s' % indent
).join([arg
.strip() for arg
in args
])
706 lines
.append('%s\n%s%s' % (line
[:left
], indent
, args
))
708 indent
= ' ' * (left
- 1)
709 args
= (',\n%s' % indent
).join(args
)
710 lines
.append('%s%s' % (line
[:left
], args
))
711 return '\n'.join(lines
)
714 # Define a top level object.
715 def Define(self
, node
, releases
, tabs
=0, prefix
='', comment
=False):
716 # If this request does not match unique release, or if the release is not
717 # available (possibly deprecated) then skip.
718 unique
= node
.GetUniqueReleases(releases
)
719 if not unique
or not node
.InReleases(releases
):
722 self
.LogEnter('Define %s tab=%d prefix="%s"' % (node
,tabs
,prefix
))
724 'Enum': CGen
.DefineEnum
,
725 'Function': CGen
.DefineMember
,
726 'Interface': CGen
.DefineStruct
,
727 'Member': CGen
.DefineMember
,
728 'Struct': CGen
.DefineStruct
,
729 'Typedef': CGen
.DefineTypedef
733 func
= declmap
.get(node
.cls
, None)
735 ErrOut
.Log('Failed to define %s named %s' % (node
.cls
, node
.GetName()))
736 define_txt
= func(self
, node
, releases
, prefix
=prefix
, comment
=comment
)
738 comment_txt
= GetNodeComments(node
, tabs
=0)
739 if comment_txt
and comment
:
743 indented_out
= self
.Indent(out
, tabs
)
744 self
.LogExit('Exit Define')
748 # Clean a string representing an object definition and return then string
749 # as a single space delimited set of tokens.
750 def CleanString(instr
):
751 instr
= instr
.strip()
752 instr
= instr
.split()
753 return ' '.join(instr
)
756 # Test a file, by comparing all it's objects, with their comments.
757 def TestFile(filenode
):
761 for node
in filenode
.GetChildren()[2:]:
762 instr
= node
.GetOneOf('Comment')
763 if not instr
: continue
765 instr
= CleanString(instr
.GetName())
767 outstr
= cgen
.Define(node
, releases
=['M14'])
768 if GetOption('verbose'):
770 outstr
= CleanString(outstr
)
773 ErrOut
.Log('Failed match of\n>>%s<<\nto:\n>>%s<<\nFor:\n' %
775 node
.Dump(1, comments
=True)
780 # Build and resolve the AST and compare each file individual.
781 def TestFiles(filenames
):
783 idldir
= os
.path
.split(sys
.argv
[0])[0]
784 idldir
= os
.path
.join(idldir
, 'test_cgen', '*.idl')
785 filenames
= glob
.glob(idldir
)
787 filenames
= sorted(filenames
)
788 ast
= ParseFiles(filenames
)
791 for filenode
in ast
.GetListOf('File'):
792 errs
= TestFile(filenode
)
794 ErrOut
.Log('%s test failed with %d error(s).' %
795 (filenode
.GetName(), errs
))
799 ErrOut
.Log('Failed generator test.')
801 InfoOut
.Log('Passed generator test.')
805 filenames
= ParseOptions(args
)
806 if GetOption('test'):
807 return TestFiles(filenames
)
808 ast
= ParseFiles(filenames
)
810 for f
in ast
.GetListOf('File'):
811 if f
.GetProperty('ERRORS') > 0:
812 print 'Skipping %s' % f
.GetName()
814 for node
in f
.GetChildren()[2:]:
815 print cgen
.Define(node
, ast
.releases
, comment
=True, prefix
='tst_')
818 if __name__
== '__main__':
819 sys
.exit(main(sys
.argv
[1:]))