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    'mem_ptr_t': {
129      'in': 'const %s',
130      'inout': '%s',
131      'out': '%s',
132      'return': '%s',
133      'store': '%s'
134    },
135    'str_t': {
136      'in': 'const %s',
137      'inout': '%s',
138      'out': '%s',
139      'return': 'const %s',
140      'store': '%s'
141    },
142    'cstr_t': {
143      'in': '%s',
144      'inout': '%s*',
145      'out': '%s*',
146      'return': '%s',
147      'store': '%s'
148    },
149    'TypeValue': {
150      'in': '%s',
151      'constptr_in': 'const %s*',  # So we can use const* for PP_Var sometimes.
152      'inout': '%s*',
153      'out': '%s*',
154      'return': '%s',
155      'store': '%s'
156    },
157  }
158
159
160  #
161  # RemapName
162  #
163  # A diction array of PPAPI types that are converted to language specific
164  # types before being returned by by the C generator
165  #
166  RemapName = {
167  'blob_t': 'void**',
168  'float_t': 'float',
169  'double_t': 'double',
170  'handle_t': 'int',
171  'mem_t': 'void*',
172  'mem_ptr_t': 'void**',
173  'str_t': 'char*',
174  'cstr_t': 'const char*',
175  'interface_t' : 'const void*'
176  }
177
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'
184    TypeMap[ptrtype] = {
185      'in': 'const %s',
186      'inout': '%s',
187      'out': '%s',
188      'return': 'const %s',
189      'store': '%s'
190    }
191    RemapName[ptrtype] = '%s*' % gltype
192
193  def __init__(self):
194    self.dbg_depth = 0
195
196  #
197  # Debug Logging functions
198  #
199  def Log(self, txt):
200    if not GetOption('cgen_debug'): return
201    tabs = '  ' * self.dbg_depth
202    print '%s%s' % (tabs, txt)
203
204  def LogEnter(self, txt):
205    if txt: self.Log(txt)
206    self.dbg_depth += 1
207
208  def LogExit(self, txt):
209    self.dbg_depth -= 1
210    if txt: self.Log(txt)
211
212
213  def GetDefine(self, name, value):
214    out = '#define %s %s' % (name, value)
215    if len(out) > 80:
216      out = '#define %s \\\n    %s' % (name, value)
217    return '%s\n' % out
218
219  #
220  # Interface strings
221  #
222  def GetMacroHelper(self, node):
223    macro = node.GetProperty('macro')
224    if macro: return macro
225    name = node.GetName()
226    name = name.upper()
227    return "%s_INTERFACE" % name
228
229  def GetInterfaceMacro(self, node, version = None):
230    name = self.GetMacroHelper(node)
231    if version is None:
232      return name
233    return '%s_%s' % (name, str(version).replace('.', '_'))
234
235  def GetInterfaceString(self, node, version = None):
236    # If an interface name is specified, use that
237    name = node.GetProperty('iname')
238    if not name:
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]
244    if version is None:
245      return name
246    return "%s;%s" % (name, version)
247
248
249  #
250  # Return the array specification of the object.
251  #
252  def GetArraySpec(self, node):
253    assert(node.cls == 'Array')
254    fixed = node.GetProperty('FIXED')
255    if fixed:
256      return '[%s]' % fixed
257    else:
258      return '[]'
259
260  #
261  # GetTypeName
262  #
263  # For any valid 'typed' object such as Member or Typedef
264  # the typenode object contains the typename
265  #
266  # For a given node return the type name by passing mode.
267  #
268  def GetTypeName(self, node, release, prefix=''):
269    self.LogEnter('GetTypeName of %s rel=%s' % (node, release))
270
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)
275    else:
276      typeref = node
277
278    if typeref is None:
279      node.Error('No type at release %s.' % release)
280      raise CGenError('No type for %s' % node)
281
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)
288
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))
293
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())
298      else:
299        name = 'struct %s%s' % (prefix, typeref.GetName())
300
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('.','_')
306      else:
307        ver = ''
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)
311      else:
312        name = '%s%s%s' % (prefix, typeref.GetName(), ver)
313
314    else:
315      raise RuntimeError('Getting name of non-type %s.' % node)
316    self.LogExit('GetTypeName %s is %s' % (node, name))
317    return name
318
319
320  #
321  # GetRootType
322  #
323  # For a given node return basic type of that object.  This is
324  # either a 'Type', 'Callspec', or 'Array'
325  #
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'):
330      rootType = '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,
334                                            'return')
335
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),
339                                            release, mode)
340
341    # If it's an Enum, then it's normal passing rules
342    elif node.IsA('Enum'):
343      rootType = node.cls
344
345    # If it's an Interface or Struct, we may be passing by value
346    elif node.IsA('Interface', 'Struct'):
347      if mode == 'return':
348        if node.GetProperty('returnByValue'):
349          rootType = 'TypeValue'
350        else:
351          rootType = node.cls
352      else:
353        if node.GetProperty('passByValue'):
354          rootType = 'TypeValue'
355        else:
356          rootType = node.cls
357
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()
362      else:
363        rootType = 'TypeValue'
364    else:
365      raise RuntimeError('Getting root type of non-type %s.' % node)
366    self.LogExit('RootType is "%s"' % rootType)
367    return rootType, mode
368
369
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))
377    return out
378
379
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'
387    return 'return'
388
389  #
390  # GetComponents
391  #
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.
398  #
399  def GetComponents(self, node, release, mode):
400    self.LogEnter('GetComponents mode %s for %s %s' % (mode, node, release))
401
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] == '[]':
408      rtype += '*'
409      del arrayspec[0]
410
411    if node.IsA('Enum', 'Interface', 'Struct'):
412      rname = node.GetName()
413    else:
414      rname = node.GetType(release).GetName()
415
416    if rname in CGen.RemapName:
417      rname = CGen.RemapName[rname]
418    if '%' in rtype:
419      rtype = rtype % rname
420    name = node.GetName()
421    callnode = node.GetOneOf('Callspec')
422    if callnode:
423      callspec = []
424      for param in callnode.GetListOf('Param'):
425        if not param.IsRelease(release):
426          continue
427        mode = self.GetParamMode(param)
428        ptype, pname, parray, pspec = self.GetComponents(param, release, mode)
429        callspec.append((ptype, pname, parray, pspec))
430    else:
431      callspec = None
432
433    self.LogExit('GetComponents: %s, %s, %s, %s' %
434                 (rtype, name, arrayspec, callspec))
435    return (rtype, name, arrayspec, callspec)
436
437
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)
442
443    # Switch unsized array to a ptr. NOTE: Only last element can be unsized.
444    if unsized_as_ptr and arrayspec[-2:] == '[]':
445      prefix +=  '*'
446      arrayspec=arrayspec[:-2]
447
448    if not include_name:
449      name = prefix + arrayspec
450    else:
451      name = prefix + name + arrayspec
452    if callspec is None:
453      out = '%s %s' % (rtype, name)
454    else:
455      params = []
456      for ptype, pname, parray, pspec in callspec:
457        params.append(self.Compose(ptype, pname, parray, pspec, '', True,
458                                   include_name=True,
459                                   unsized_as_ptr=unsized_as_ptr))
460      if func_as_ptr:
461        name = '(*%s)' % name
462      if not params:
463        params = ['void']
464      out = '%s %s(%s)' % (rtype, name, ', '.join(params))
465    self.LogExit('Exit Compose: %s' % out)
466    return out
467
468  #
469  # GetSignature
470  #
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
476  #                 included.
477  #  include_version - if True, include version in the member name
478  #
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)
484    if include_version:
485      name = self.GetStructName(node, release, True)
486
487    # If not a callspec (such as a struct) use a ptr instead of []
488    unsized_as_ptr = not callspec
489
490    out = self.Compose(rtype, name, arrayspec, callspec, prefix,
491                       func_as_ptr, include_name, unsized_as_ptr)
492
493    self.LogExit('Exit GetSignature: %s' % out)
494    return out
495
496  # Define a Typedef.
497  def DefineTypedef(self, node, releases, prefix='', comment=False):
498    __pychecker__ = 'unusednames=comment'
499    build_list = node.GetUniqueReleases(releases)
500
501    out = 'typedef %s;\n' % self.GetSignature(node, build_list[-1], 'return',
502                                              prefix, True,
503                                              include_version=False)
504    # Version mangle any other versions
505    for index, rel in enumerate(build_list[:-1]):
506      out += '\n'
507      out += 'typedef %s;\n' % self.GetSignature(node, rel, 'return',
508                                                 prefix, True,
509                                                 include_version=True)
510    self.Log('DefineTypedef: %s' % out)
511    return out
512
513  # Define an Enum.
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')
520
521    if unnamed:
522      out = 'enum {'
523    elif notypedef:
524      out = 'enum %s {' % name
525    else:
526      out = 'typedef enum {'
527    enumlist = []
528    for child in node.GetListOf('EnumItem'):
529      value = child.GetProperty('VALUE')
530      comment_txt = GetNodeComments(child, tabs=1)
531      if value:
532        item_txt = '%s%s = %s' % (prefix, child.GetName(), value)
533      else:
534        item_txt = '%s%s' % (prefix, child.GetName())
535      enumlist.append('%s  %s' % (comment_txt, item_txt))
536    self.LogExit('Exit DefineEnum')
537
538    if unnamed or notypedef:
539      out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist))
540    else:
541      out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name)
542    return out
543
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)
550    else:
551      out = '%s;' % self.GetSignature(node, release, 'store', '', True)
552    self.LogExit('Exit DefineMember')
553    return out
554
555  def GetStructName(self, node, release, include_version=False):
556    suffix = ''
557    if include_version:
558      ver_num = node.GetVersion(release)
559      suffix = ('_%s' % ver_num).replace('.', '_')
560    return node.GetName() + suffix
561
562  def DefineStructInternals(self, node, release,
563                            include_version=False, comment=True):
564    channel = node.GetProperty('FILE').release_map.GetChannel(release)
565    if channel == 'dev':
566      channel_comment = ' /* dev */'
567    else:
568      channel_comment = ''
569    out = ''
570    if node.GetProperty('union'):
571      out += 'union %s {%s\n' % (
572          self.GetStructName(node, release, include_version), channel_comment)
573    else:
574      out += 'struct %s {%s\n' % (
575          self.GetStructName(node, release, include_version), channel_comment)
576
577    channel = node.GetProperty('FILE').release_map.GetChannel(release)
578    # Generate Member Functions
579    members = []
580    for child in node.GetListOf('Member'):
581      if channel == 'stable' and child.NodeIsDevOnly():
582        continue
583      member = self.Define(child, [release], tabs=1, comment=comment)
584      if not member:
585        continue
586      members.append(member)
587    out += '%s\n};\n' % '\n'.join(members)
588    return out
589
590
591  def DefineUnversionedInterface(self, node, rel):
592    out = '\n'
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)
599    else:
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))
604    return out
605
606
607  def DefineStruct(self, node, releases, prefix='', comment=False):
608    __pychecker__ = 'unusednames=comment,prefix'
609    self.LogEnter('DefineStruct %s' % node)
610    out = ''
611    build_list = node.GetUniqueReleases(releases)
612
613    newest_stable = None
614    newest_dev = None
615    for rel in build_list:
616      channel = node.GetProperty('FILE').release_map.GetChannel(rel)
617      if channel == 'stable':
618        newest_stable = rel
619      if channel == 'dev':
620        newest_dev = rel
621    last_rel = build_list[-1]
622
623    # TODO(noelallen) : 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)
631
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)
638
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):
647            continue
648        out += '\n' + self.DefineStructInternals(node, rel,
649                                                 include_version=True,
650                                                 comment=False)
651        if rel == newest_stable:
652          out += self.DefineUnversionedInterface(node, rel)
653
654    self.LogExit('Exit DefineStruct')
655    return out
656
657
658  #
659  # Copyright and Comment
660  #
661  # Generate a comment or copyright block
662  #
663  def Copyright(self, node, cpp_style=False):
664    lines = node.GetName().split('\n')
665    if cpp_style:
666      return '//' + '\n//'.join(filter(lambda f: f != '', lines)) + '\n'
667    return CommentLines(lines)
668
669
670  def Indent(self, data, tabs=0):
671    """Handles indentation and 80-column line wrapping."""
672    tab = '  ' * tabs
673    lines = []
674    for line in data.split('\n'):
675      # Add indentation
676      line = tab + line
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:])
685      else:
686        left = line.rfind('(') + 1
687        args = line[left:].split(',')
688        orig_args = args
689        orig_left = left
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
694            args = orig_args
695            left = orig_left
696            break
697          args = line[left:].split(',')
698
699        line_max = 0
700        for arg in args:
701          if len(arg) > line_max: line_max = len(arg)
702
703        if left + line_max >= 80:
704          indent = '%s    ' % tab
705          args =  (',\n%s' % indent).join([arg.strip() for arg in args])
706          lines.append('%s\n%s%s' % (line[:left], indent, args))
707        else:
708          indent = ' ' * (left - 1)
709          args =  (',\n%s' % indent).join(args)
710          lines.append('%s%s' % (line[:left], args))
711    return '\n'.join(lines)
712
713
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):
720      return ''
721
722    self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix))
723    declmap = dict({
724      'Enum': CGen.DefineEnum,
725      'Function': CGen.DefineMember,
726      'Interface': CGen.DefineStruct,
727      'Member': CGen.DefineMember,
728      'Struct': CGen.DefineStruct,
729      'Typedef': CGen.DefineTypedef
730    })
731
732    out = ''
733    func = declmap.get(node.cls, None)
734    if not func:
735      ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName()))
736    define_txt = func(self, node, releases, prefix=prefix, comment=comment)
737
738    comment_txt = GetNodeComments(node, tabs=0)
739    if comment_txt and comment:
740      out += comment_txt
741    out += define_txt
742
743    indented_out = self.Indent(out, tabs)
744    self.LogExit('Exit Define')
745    return indented_out
746
747
748# Clean a string representing an object definition and return then string
749# as a single space delimited set of tokens.
750def CleanString(instr):
751  instr = instr.strip()
752  instr = instr.split()
753  return ' '.join(instr)
754
755
756# Test a file, by comparing all it's objects, with their comments.
757def TestFile(filenode):
758  cgen = CGen()
759
760  errors = 0
761  for node in filenode.GetChildren()[2:]:
762    instr = node.GetOneOf('Comment')
763    if not instr: continue
764    instr.Dump()
765    instr = CleanString(instr.GetName())
766
767    outstr = cgen.Define(node, releases=['M14'])
768    if GetOption('verbose'):
769      print outstr + '\n'
770    outstr = CleanString(outstr)
771
772    if instr != outstr:
773      ErrOut.Log('Failed match of\n>>%s<<\nto:\n>>%s<<\nFor:\n' %
774                 (instr, outstr))
775      node.Dump(1, comments=True)
776      errors += 1
777  return errors
778
779
780# Build and resolve the AST and compare each file individual.
781def TestFiles(filenames):
782  if not filenames:
783    idldir = os.path.split(sys.argv[0])[0]
784    idldir = os.path.join(idldir, 'test_cgen', '*.idl')
785    filenames = glob.glob(idldir)
786
787  filenames = sorted(filenames)
788  ast = ParseFiles(filenames)
789
790  total_errs = 0
791  for filenode in ast.GetListOf('File'):
792    errs = TestFile(filenode)
793    if errs:
794      ErrOut.Log('%s test failed with %d error(s).' %
795                 (filenode.GetName(), errs))
796      total_errs += errs
797
798  if total_errs:
799    ErrOut.Log('Failed generator test.')
800  else:
801    InfoOut.Log('Passed generator test.')
802  return total_errs
803
804def main(args):
805  filenames = ParseOptions(args)
806  if GetOption('test'):
807    return TestFiles(filenames)
808  ast = ParseFiles(filenames)
809  cgen = CGen()
810  for f in ast.GetListOf('File'):
811    if f.GetProperty('ERRORS') > 0:
812      print 'Skipping %s' % f.GetName()
813      continue
814    for node in f.GetChildren()[2:]:
815      print cgen.Define(node, ast.releases, comment=True, prefix='tst_')
816
817
818if __name__ == '__main__':
819  sys.exit(main(sys.argv[1:]))
820