blob: f92bf149d6460323517eb20919ba2e96b8cbfb04 [file] [log] [blame]
#!/usr/bin/python
#
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Generator for C style prototypes and definitions """
import glob
import os
import sys
from idl_log import ErrOut, InfoOut, WarnOut
from idl_node import IDLNode
from idl_ast import IDLAst
from idl_option import GetOption, Option, ParseOptions
from idl_parser import ParseFiles
Option('cgen_debug', 'Debug generate.')
class CGenError(Exception):
def __init__(self, msg):
self.value = value
def __str__(self):
return repr(self.value)
class CGen(object):
# TypeMap
#
# TypeMap modifies how an object is stored or passed, for example pointers
# are passed as 'const' if they are 'in' parameters, and structures are
# preceeded by the keyword 'struct' as well as using a pointer.
#
TypeMap = {
'Array': {
'in': 'const %s',
'inout': '%s*',
'out': '%s*',
'store': '%s',
'return': '%s'
},
'Callspec': {
'in': '%s',
'inout': '%s',
'out': '%s',
'store': '%s',
'return': '%s'
},
'Enum': {
'in': '%s',
'inout': '%s*',
'out': '%s*',
'store': '%s',
'return': '%s'
},
'Struct': {
'in': 'const %s*',
'inout': '%s*',
'out': '%s*',
'return': ' %s*',
'store': '%s'
},
'mem_t': {
'in': 'const %s',
'inout': '%s',
'out': '%s',
'return': '%s',
'store': '%s'
},
'str_t': {
'in': 'const %s',
'inout': '%s',
'out': '%s',
'return': 'const %s',
'store': '%s'
},
'TypeValue': {
'in': '%s',
'inout': '%s*',
'out': '%s*',
'return': '%s',
'store': '%s'
},
}
#
# RemapName
#
# A diction array of PPAPI types that are converted to language specific
# types before being returned by by the C generator
#
RemapName = {
'float_t': 'float',
'double_t': 'double',
'handle_t': 'int',
'mem_t': 'void*',
'str_t': 'char*',
'interface_t' : 'const void*'
}
def __init__(self):
self.dbg_depth = 0
self.vmin = 0.0
self.vmax = 1e100
self.release = 'M14'
def SetVersionMap(self, node):
self.vmin = 0.0
self.vmax = 1e100
for version in node.GetListOf('LabelItem'):
if version.GetName() == GetOption('version'):
self.vmin = float(version.GetProperty('VALUE'))
self.vmax = float(version.GetProperty('VALUE'))
#
# Debug Logging functions
#
def Log(self, txt):
if not GetOption('cgen_debug'): return
tabs = ''
for tab in range(self.dbg_depth): tabs += ' '
print '%s%s' % (tabs, txt)
def LogEnter(self, txt):
if txt: self.Log(txt)
self.dbg_depth += 1
def LogExit(self, txt):
self.dbg_depth -= 1
if txt: self.Log(txt)
#
# Return the array specification of the object.
#
def GetArraySpec(self, node):
assert(node.cls == 'Array')
out = ''
fixed = node.GetProperty('FIXED')
if fixed:
return '[%s]' % fixed
else:
return '[]'
#
# GetTypeName
#
# For any valid 'typed' object such as Member or Typedef
# the typenode object contains the typename
#
# For a given node return the type name by passing mode.
#
def GetTypeName(self, node, prefix=''):
self.LogEnter('GetTypeName of %s' % node)
# For Members, Params, and Typedef's your want type it refers to
if node.IsA('Member', 'Param', 'Typedef'):
typeref = node.GetType(self.release)
else:
typeref = node
if typeref is None:
raise CGenError('No type for %s' % node)
# If the type is a (BuiltIn) Type then return it's name
# remapping as needed
if typeref.IsA('Type'):
name = CGen.RemapName.get(typeref.GetName(), None)
if name is None: name = typeref.GetName()
name = '%s%s' % (prefix, name)
# For structures, preceed with 'struct' or 'union' as appropriate
elif typeref.IsA('Interface', 'Struct'):
if typeref.GetProperty('union'):
name = 'union %s%s' % (prefix, typeref.GetName())
else:
name = 'struct %s%s' % (prefix, typeref.GetName())
# If it's an enum, or typedef then return the Enum's name
elif typeref.IsA('Enum', 'Typedef'):
name = '%s%s' % (prefix, typeref.GetName())
else:
raise RuntimeError('Getting name of non-type %s.' % node)
self.LogExit('GetTypeName %s is %s' % (node, name))
return name
#
# GetRootType
#
# For a given node return basic type of that object. This is
# either a 'Type', 'Callspec', or 'Array'
#
def GetRootTypeMode(self, node, mode):
self.LogEnter('GetRootType of %s' % node)
# If it has an array spec, then treat it as an array regardless of type
if node.GetOneOf('Array'):
rootType = 'Array'
# Or if it has a callspec, treat it as a function
elif node.GetOneOf('Callspec'):
rootType, mode = self.GetRootTypeMode(node.GetType(self.release),
'return')
# If it's a plain typedef, try that object's root type
elif node.IsA('Member', 'Param', 'Typedef'):
rootType, mode = self.GetRootTypeMode(node.GetType(self.release), mode)
# If it's an Enum, then it's normal passing rules
elif node.IsA('Enum'):
rootType = node.cls
# If it's an Interface or Struct, we may be passing by value
elif node.IsA('Interface', 'Struct'):
if mode == 'return':
if node.GetProperty('returnByValue'):
rootType = 'TypeValue'
else:
rootType = node.cls
else:
if node.GetProperty('passByValue'):
rootType = 'TypeValue'
else:
rootType = node.cls
# If it's an Basic Type, check if it's a special type
elif node.IsA('Type'):
if node.GetName() in CGen.TypeMap:
rootType = node.GetName()
else:
rootType = 'TypeValue'
else:
raise RuntimeError('Getting root type of non-type %s.' % node)
self.LogExit('RootType is "%s"' % rootType)
return rootType, mode
def GetTypeByMode(self, node, mode):
self.LogEnter('GetTypeByMode of %s mode=%s' % (node, mode))
name = self.GetTypeName(node)
ntype, mode = self.GetRootTypeMode(node, mode)
out = CGen.TypeMap[ntype][mode] % name
self.LogExit('GetTypeByMode %s = %s' % (node, out))
return out
# Get the passing mode of the object (in, out, inout).
def GetParamMode(self, node):
self.Log('GetParamMode for %s' % node)
if node.GetProperty('in'): return 'in'
if node.GetProperty('out'): return 'out'
if node.GetProperty('inout'): return 'inout'
return 'return'
#
# GetComponents
#
# Returns the signature components of an object as a tuple of
# (rtype, name, arrays, callspec) where:
# rtype - The store or return type of the object.
# name - The name of the object.
# arrays - A list of array dimensions as [] or [<fixed_num>].
# args - None of not a function, otherwise a list of parameters.
#
def GetComponents(self, node, mode):
self.LogEnter('GetComponents mode %s for %s' % (mode, node))
# Generate passing type by modifying root type
rtype = self.GetTypeByMode(node, mode)
if node.IsA('Enum', 'Interface', 'Struct'):
rname = node.GetName()
else:
rname = node.GetType(self.release).GetName()
if rname in CGen.RemapName:
rname = CGen.RemapName[rname]
if '%' in rtype:
rtype = rtype % rname
name = node.GetName()
arrayspec = [self.GetArraySpec(array) for array in node.GetListOf('Array')]
callnode = node.GetOneOf('Callspec')
if callnode:
callspec = []
for param in callnode.GetListOf('Param'):
mode = self.GetParamMode(param)
ptype, pname, parray, pspec = self.GetComponents(param, mode)
callspec.append((ptype, pname, parray, pspec))
else:
callspec = None
self.LogExit('GetComponents: %s, %s, %s, %s' %
(rtype, name, arrayspec, callspec))
return (rtype, name, arrayspec, callspec)
def Compose(self, rtype, name, arrayspec, callspec, prefix, func_as_ptr):
self.LogEnter('Compose: %s %s' % (rtype, name))
arrayspec = ''.join(arrayspec)
name = '%s%s%s' % (prefix, name, arrayspec)
if callspec is None:
out = '%s %s' % (rtype, name)
else:
params = []
for ptype, pname, parray, pspec in callspec:
params.append(self.Compose(ptype, pname, parray, pspec, '', True))
if func_as_ptr: name = '(*%s)' % name
out = '%s %s(%s)' % (rtype, name, ', '.join(params))
self.LogExit('Exit Compose: %s' % out)
return out
#
# GetSignature
#
# Returns the 'C' style signature of the object
# prefix - A prefix for the object's name
# func_as_ptr - Formats a function as a function pointer
#
def GetSignature(self, node, mode, prefix='', func_as_ptr=True):
self.LogEnter('GetSignature %s %s as func=%s' % (node, mode, func_as_ptr))
rtype, name, arrayspec, callspec = self.GetComponents(node, mode)
out = self.Compose(rtype, name, arrayspec, callspec, prefix, func_as_ptr)
self.LogExit('Exit GetSignature: %s' % out)
return out
def GetMacro(self, node):
name = node.GetName()
name = name.upper()
return "%s_INTERFACE" % name
def GetDefine(self, name, value):
out = '#define %s %s' % (name, value)
if len(out) > 80:
out = '#define %s \\\n %s' % (name, value)
return '%s\n' % out
# Define an Typedef.
def DefineTypedef(self, node, prefix='', comment=False):
out = 'typedef %s;\n' % self.GetSignature(node, 'return', prefix, True)
self.Log('DefineTypedef: %s' % out)
return out
# Define an Enum.
def DefineEnum(self, node, prefix='', comment=False):
self.LogEnter('DefineEnum %s' % node)
unnamed = node.GetProperty('unnamed')
if unnamed:
out = 'enum {'
else:
out = 'typedef enum {'
name = '%s%s' % (prefix, node.GetName())
enumlist = []
for child in node.GetListOf('EnumItem'):
value = child.GetProperty('VALUE')
comment_txt = ''
if comment:
for comment_node in child.GetListOf('Comment'):
comment_txt += self.Comment(comment_node, tabs=1)
if comment_txt:
comment_txt = '%s' % comment_txt
if value:
item_txt = '%s%s = %s' % (prefix, child.GetName(), value)
else:
item_txt = '%s%s' % (prefix, child.GetName())
enumlist.append('%s %s' % (comment_txt, item_txt))
self.LogExit('Exit DefineEnum')
if unnamed:
out = '%s\n%s\n};\n' % (out, ',\n'.join(enumlist))
else:
out = '%s\n%s\n} %s;\n' % (out, ',\n'.join(enumlist), name)
return out
def DefineMember(self, node, prefix='', comment=False):
self.LogEnter('DefineMember %s' % node)
# out = ''
# if comment:
# for doc in node.GetListOf('Comment'):
# out += self.Comment(doc)
out = '%s;' % self.GetSignature(node, 'store', '', True)
self.LogExit('Exit DefineMember')
return out
# Define a Struct.
def DefineStruct(self, node, prefix='', comment=False):
out = ''
if node.IsA('Interface'):
release = 'M14'
name = node.GetName()
macro = node.GetProperty('macro')
if not macro:
macro = self.GetMacro(node)
label = node.GetLabel()
if label:
for vers in label.versions:
strver = str(vers).replace('.', '_')
out += self.GetDefine('%s_%s' % (macro, strver),
'"%s;%s"' % (name, vers))
if label.GetRelease(vers) == release:
out += self.GetDefine(macro, '%s_%s' % (macro, strver))
out += '\n'
self.LogEnter('DefineStruct %s' % node)
if node.GetProperty('union'):
out += 'union %s%s {\n' % (prefix, node.GetName())
else:
out += 'struct %s%s {\n' % (prefix, node.GetName())
# Generate Member Functions
members = []
for child in node.GetListOf('Member'):
member = self.Define(child, tabs=1, comment=comment)
if not member:
continue
members.append(member)
out += '%s\n};\n' % '\n'.join(members)
self.LogExit('Exit DefineStruct')
return out
def DefineType(self, node, prefix='', comment=False):
return ''
#
# Copyright and Comment
#
# Generate a comment or copyright block
#
def Copyright(self, node, tabs=0):
lines = node.GetName().split('\n')
return self.CommentLines(lines, tabs)
def Comment(self, node, prefix=None, tabs=0):
comment = node.GetName()
# Ignore comments that do not have a '*' marker
# if comment[0] != '*' and not prefix: return ''
lines = comment.split('\n')
if prefix:
prefix = prefix.split('\n')
if prefix[0] == '*' and lines[0] == '*':
lines = prefix + lines[1:]
else:
lines = prefix + lines;
return self.CommentLines(lines, tabs)
def CommentLines(self, lines, tabs=0):
tab = ''.join([' ' for i in range(tabs)])
if lines[-1] == '':
return '%s/*' % tab + ('\n%s *' % tab).join(lines) + '/\n'
else:
return '%s/*' % tab + ('\n%s *' % tab).join(lines) + ' */\n'
# Define a top level object.
def Define(self, node, tabs=0, prefix='', comment=False):
if True:
# try:
self.LogEnter('Define %s tab=%d prefix="%s"' % (node,tabs,prefix))
node_nim = node.GetProperty('version')
node_max = node.GetProperty('deprecate')
if node_nim is not None:
node_nim = float(node_nim)
else:
node_nim = 0.0
if node_max is not None:
node_max = float(node_max)
else:
node_max = 1.0e100
label = node.GetLabel()
if label:
lver = label.GetVersion('M14')
# Verify that we are in a valid version.
if node_max <= lver: return ''
if node_nim > lver: return ''
declmap = {
'Describe' : CGen.DefineType,
'Enum' : CGen.DefineEnum,
'Function' : CGen.DefineMember,
'Interface' : CGen.DefineStruct,
'Member' : CGen.DefineMember,
'Struct' : CGen.DefineStruct,
'Type' : CGen.DefineType,
'Typedef' : CGen.DefineTypedef,
}
if node.cls == 'Inline':
return node.GetProperty('VALUE')
if node.cls == 'Label':
return ''
out = ''
comment_txt = ''
if comment:
for doc in node.GetListOf('Comment'):
comment_txt += self.Comment(doc)
func = declmap.get(node.cls)
if not func:
ErrOut.Log('Failed to define %s named %s' % (node.cls, node.GetName()))
define_txt = func(self, node, prefix=prefix, comment=comment)
if comment_txt:
out += '%s%s' % (comment_txt, define_txt)
else:
out += define_txt
tab = ''
for i in range(tabs):
tab += ' '
lines = []
for line in out.split('\n'):
# Add indentation
line = '%s%s' % (tab, line)
if len(line) > 80:
left = line.rfind('(') + 1
args = line[left:].split(',')
line_max = 0
for arg in args:
if len(arg) > line_max: line_max = len(arg)
if left + line_max >= 80:
space = '%s ' % tab
args = (',\n%s' % space).join([arg.strip() for arg in args])
lines.append('%s\n%s%s' % (line[:left], space, args))
else:
space = ' '.join(['' for i in range(left)])
args = (',\n%s' % space).join(args)
lines.append('%s%s' % (line[:left], args))
else:
lines.append(line.rstrip())
# out = tab + ('\n%s' % tab).join(out.split('\n')) + '\n'
self.LogExit('Exit Define')
return '\n'.join(lines)
# except:
if False:
node.Error('Failed to resolve.')
return ''
# Clean a string representing an object definition and return then string
# as a single space delimited set of tokens.
def CleanString(instr):
instr = instr.strip()
instr = instr.split()
return ' '.join(instr)
# Test a file, by comparing all it's objects, with their comments.
def TestFile(filenode):
cgen = CGen()
errors = 0
for node in filenode.GetChildren()[2:]:
instr = node.GetOneOf('Comment')
if not instr: continue
instr.Dump()
instr = CleanString(instr.GetName())
outstr = cgen.Define(node)
if GetOption('verbose'):
print outstr + '\n'
outstr = CleanString(outstr)
if instr != outstr:
ErrOut.Log('Failed match of\n>>%s<<\n>>%s<<\nto:' % (instr, outstr))
node.Dump(1, comments=True)
errors += 1
return errors
# Build and resolve the AST and compare each file individual.
def TestFiles(filenames):
if not filenames:
idldir = os.path.split(sys.argv[0])[0]
idldir = os.path.join(idldir, 'test_cgen', '*.idl')
filenames = glob.glob(idldir)
filenames = sorted(filenames)
ast = ParseFiles(filenames)
total_errs = 0
for filenode in ast.GetListOf('File'):
errs = TestFile(filenode)
if errs:
ErrOut.Log('%s test failed with %d error(s).' %
(filenode.GetName(), errs))
total_errs += errs
if total_errs:
ErrOut.Log('Failed generator test.')
else:
InfoOut.Log('Passed generator test.')
return total_errs
def Main(args):
filenames = ParseOptions(args)
if GetOption('test'):
return TestFiles(filenames)
ast = ParseFiles(filenames)
for f in ast.GetListOf('File'):
if f.GetProperty('ERRORS') > 0:
print 'Skipping %s' % f.GetName()
continue
print DefineDepends(node)
for node in f.GetChildren()[2:]:
print Define(node, comment=True, prefix='tst_')
if __name__ == '__main__':
sys.exit(Main(sys.argv[1:]))