blob: 836de52db8797ff0fdc9ef415420abc3cc974d5d [file] [log] [blame]
Mark Lobodzinskif96a9322017-02-13 10:33:08 -07001#!/usr/bin/python3 -i
2#
3# Copyright (c) 2013-2017 The Khronos Group Inc.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import os,re,sys
18from generator import *
19
20# CGeneratorOptions - subclass of GeneratorOptions.
21#
22# Adds options used by COutputGenerator objects during C language header
23# generation.
24#
25# Additional members
26# prefixText - list of strings to prefix generated header with
27# (usually a copyright statement + calling convention macros).
28# protectFile - True if multiple inclusion protection should be
29# generated (based on the filename) around the entire header.
30# protectFeature - True if #ifndef..#endif protection should be
31# generated around a feature interface in the header file.
32# genFuncPointers - True if function pointer typedefs should be
33# generated
34# protectProto - If conditional protection should be generated
35# around prototype declarations, set to either '#ifdef'
36# to require opt-in (#ifdef protectProtoStr) or '#ifndef'
37# to require opt-out (#ifndef protectProtoStr). Otherwise
38# set to None.
39# protectProtoStr - #ifdef/#ifndef symbol to use around prototype
40# declarations, if protectProto is set
41# apicall - string to use for the function declaration prefix,
42# such as APICALL on Windows.
43# apientry - string to use for the calling convention macro,
44# in typedefs, such as APIENTRY.
45# apientryp - string to use for the calling convention macro
46# in function pointer typedefs, such as APIENTRYP.
47# indentFuncProto - True if prototype declarations should put each
48# parameter on a separate line
49# indentFuncPointer - True if typedefed function pointers should put each
50# parameter on a separate line
51# alignFuncParam - if nonzero and parameters are being put on a
52# separate line, align parameter names at the specified column
53class CGeneratorOptions(GeneratorOptions):
54 """Represents options during C interface generation for headers"""
55 def __init__(self,
56 filename = None,
57 directory = '.',
58 apiname = None,
59 profile = None,
60 versions = '.*',
61 emitversions = '.*',
62 defaultExtensions = None,
63 addExtensions = None,
64 removeExtensions = None,
65 sortProcedure = regSortFeatures,
66 prefixText = "",
67 genFuncPointers = True,
68 protectFile = True,
69 protectFeature = True,
70 protectProto = None,
71 protectProtoStr = None,
72 apicall = '',
73 apientry = '',
74 apientryp = '',
75 indentFuncProto = True,
76 indentFuncPointer = False,
77 alignFuncParam = 0):
78 GeneratorOptions.__init__(self, filename, directory, apiname, profile,
79 versions, emitversions, defaultExtensions,
80 addExtensions, removeExtensions, sortProcedure)
81 self.prefixText = prefixText
82 self.genFuncPointers = genFuncPointers
83 self.protectFile = protectFile
84 self.protectFeature = protectFeature
85 self.protectProto = protectProto
86 self.protectProtoStr = protectProtoStr
87 self.apicall = apicall
88 self.apientry = apientry
89 self.apientryp = apientryp
90 self.indentFuncProto = indentFuncProto
91 self.indentFuncPointer = indentFuncPointer
92 self.alignFuncParam = alignFuncParam
93
94# COutputGenerator - subclass of OutputGenerator.
95# Generates C-language API interfaces.
96#
97# ---- methods ----
98# COutputGenerator(errFile, warnFile, diagFile) - args as for
99# OutputGenerator. Defines additional internal state.
100# ---- methods overriding base class ----
101# beginFile(genOpts)
102# endFile()
103# beginFeature(interface, emit)
104# endFeature()
105# genType(typeinfo,name)
106# genStruct(typeinfo,name)
107# genGroup(groupinfo,name)
108# genEnum(enuminfo, name)
109# genCmd(cmdinfo)
110class COutputGenerator(OutputGenerator):
111 """Generate specified API interfaces in a specific style, such as a C header"""
112 # This is an ordered list of sections in the header file.
113 TYPE_SECTIONS = ['include', 'define', 'basetype', 'handle', 'enum',
114 'group', 'bitmask', 'funcpointer', 'struct']
115 ALL_SECTIONS = TYPE_SECTIONS + ['commandPointer', 'command']
116 def __init__(self,
117 errFile = sys.stderr,
118 warnFile = sys.stderr,
119 diagFile = sys.stdout):
120 OutputGenerator.__init__(self, errFile, warnFile, diagFile)
121 # Internal state - accumulators for different inner block text
122 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
123 #
124 def beginFile(self, genOpts):
125 OutputGenerator.beginFile(self, genOpts)
126 # C-specific
127 #
128 # Multiple inclusion protection & C++ wrappers.
129 if (genOpts.protectFile and self.genOpts.filename):
130 headerSym = re.sub('\.h', '_h_',
131 os.path.basename(self.genOpts.filename)).upper()
132 write('#ifndef', headerSym, file=self.outFile)
133 write('#define', headerSym, '1', file=self.outFile)
134 self.newline()
135 write('#ifdef __cplusplus', file=self.outFile)
136 write('extern "C" {', file=self.outFile)
137 write('#endif', file=self.outFile)
138 self.newline()
139 #
140 # User-supplied prefix text, if any (list of strings)
141 if (genOpts.prefixText):
142 for s in genOpts.prefixText:
143 write(s, file=self.outFile)
144 #
145 # Some boilerplate describing what was generated - this
146 # will probably be removed later since the extensions
147 # pattern may be very long.
148 # write('/* Generated C header for:', file=self.outFile)
149 # write(' * API:', genOpts.apiname, file=self.outFile)
150 # if (genOpts.profile):
151 # write(' * Profile:', genOpts.profile, file=self.outFile)
152 # write(' * Versions considered:', genOpts.versions, file=self.outFile)
153 # write(' * Versions emitted:', genOpts.emitversions, file=self.outFile)
154 # write(' * Default extensions included:', genOpts.defaultExtensions, file=self.outFile)
155 # write(' * Additional extensions included:', genOpts.addExtensions, file=self.outFile)
156 # write(' * Extensions removed:', genOpts.removeExtensions, file=self.outFile)
157 # write(' */', file=self.outFile)
158 def endFile(self):
159 # C-specific
160 # Finish C++ wrapper and multiple inclusion protection
161 self.newline()
162 write('#ifdef __cplusplus', file=self.outFile)
163 write('}', file=self.outFile)
164 write('#endif', file=self.outFile)
165 if (self.genOpts.protectFile and self.genOpts.filename):
166 self.newline()
167 write('#endif', file=self.outFile)
168 # Finish processing in superclass
169 OutputGenerator.endFile(self)
170 def beginFeature(self, interface, emit):
171 # Start processing in superclass
172 OutputGenerator.beginFeature(self, interface, emit)
173 # C-specific
174 # Accumulate includes, defines, types, enums, function pointer typedefs,
175 # end function prototypes separately for this feature. They're only
176 # printed in endFeature().
177 self.sections = dict([(section, []) for section in self.ALL_SECTIONS])
178 def endFeature(self):
179 # C-specific
180 # Actually write the interface to the output file.
181 if (self.emit):
182 self.newline()
183 if (self.genOpts.protectFeature):
184 write('#ifndef', self.featureName, file=self.outFile)
185 # If type declarations are needed by other features based on
186 # this one, it may be necessary to suppress the ExtraProtect,
187 # or move it below the 'for section...' loop.
188 if (self.featureExtraProtect != None):
189 write('#ifdef', self.featureExtraProtect, file=self.outFile)
190 write('#define', self.featureName, '1', file=self.outFile)
191 for section in self.TYPE_SECTIONS:
192 contents = self.sections[section]
193 if contents:
194 write('\n'.join(contents), file=self.outFile)
195 self.newline()
196 if (self.genOpts.genFuncPointers and self.sections['commandPointer']):
197 write('\n'.join(self.sections['commandPointer']), file=self.outFile)
198 self.newline()
199 if (self.sections['command']):
200 if (self.genOpts.protectProto):
201 write(self.genOpts.protectProto,
202 self.genOpts.protectProtoStr, file=self.outFile)
203 write('\n'.join(self.sections['command']), end='', file=self.outFile)
204 if (self.genOpts.protectProto):
205 write('#endif', file=self.outFile)
206 else:
207 self.newline()
208 if (self.featureExtraProtect != None):
209 write('#endif /*', self.featureExtraProtect, '*/', file=self.outFile)
210 if (self.genOpts.protectFeature):
211 write('#endif /*', self.featureName, '*/', file=self.outFile)
212 # Finish processing in superclass
213 OutputGenerator.endFeature(self)
214 #
215 # Append a definition to the specified section
216 def appendSection(self, section, text):
217 # self.sections[section].append('SECTION: ' + section + '\n')
218 self.sections[section].append(text)
219 #
220 # Type generation
221 def genType(self, typeinfo, name):
222 OutputGenerator.genType(self, typeinfo, name)
223 typeElem = typeinfo.elem
224 # If the type is a struct type, traverse the imbedded <member> tags
225 # generating a structure. Otherwise, emit the tag text.
226 category = typeElem.get('category')
227 if (category == 'struct' or category == 'union'):
228 self.genStruct(typeinfo, name)
229 else:
230 # Replace <apientry /> tags with an APIENTRY-style string
231 # (from self.genOpts). Copy other text through unchanged.
232 # If the resulting text is an empty string, don't emit it.
233 s = noneStr(typeElem.text)
234 for elem in typeElem:
235 if (elem.tag == 'apientry'):
236 s += self.genOpts.apientry + noneStr(elem.tail)
237 else:
238 s += noneStr(elem.text) + noneStr(elem.tail)
239 if s:
240 # Add extra newline after multi-line entries.
241 if '\n' in s:
242 s += '\n'
Mark Lobodzinskie273b942017-08-01 09:15:06 -0600243 # This is a temporary workaround for internal issue #877,
244 # while we consider other approaches. The problem is that
245 # function pointer types can have dependencies on structures
246 # and vice-versa, so they can't be strictly separated into
247 # sections. The workaround is to define those types in the
248 # same section, in dependency order.
249 if (category == 'funcpointer'):
250 self.appendSection('struct', s)
251 else:
252 self.appendSection(category, s)
Mark Lobodzinskif96a9322017-02-13 10:33:08 -0700253 #
254 # Struct (e.g. C "struct" type) generation.
255 # This is a special case of the <type> tag where the contents are
256 # interpreted as a set of <member> tags instead of freeform C
257 # C type declarations. The <member> tags are just like <param>
258 # tags - they are a declaration of a struct or union member.
259 # Only simple member declarations are supported (no nested
260 # structs etc.)
261 def genStruct(self, typeinfo, typeName):
262 OutputGenerator.genStruct(self, typeinfo, typeName)
263 body = 'typedef ' + typeinfo.elem.get('category') + ' ' + typeName + ' {\n'
264 # paramdecl = self.makeCParamDecl(typeinfo.elem, self.genOpts.alignFuncParam)
265 targetLen = 0;
266 for member in typeinfo.elem.findall('.//member'):
267 targetLen = max(targetLen, self.getCParamTypeLength(member))
268 for member in typeinfo.elem.findall('.//member'):
269 body += self.makeCParamDecl(member, targetLen + 4)
270 body += ';\n'
271 body += '} ' + typeName + ';\n'
272 self.appendSection('struct', body)
273 #
274 # Group (e.g. C "enum" type) generation.
275 # These are concatenated together with other types.
276 def genGroup(self, groupinfo, groupName):
277 OutputGenerator.genGroup(self, groupinfo, groupName)
278 groupElem = groupinfo.elem
279
Mark Lobodzinski1d218cc2017-03-14 10:12:43 -0600280 expandName = re.sub(r'([0-9a-z_])([A-Z0-9])',r'\1_\2',groupName).upper()
Mark Lobodzinskif96a9322017-02-13 10:33:08 -0700281
282 expandPrefix = expandName
283 expandSuffix = ''
284 expandSuffixMatch = re.search(r'[A-Z][A-Z]+$',groupName)
285 if expandSuffixMatch:
286 expandSuffix = '_' + expandSuffixMatch.group()
287 # Strip off the suffix from the prefix
288 expandPrefix = expandName.rsplit(expandSuffix, 1)[0]
289
290 # Prefix
291 body = "\ntypedef enum " + groupName + " {\n"
292
293 # @@ Should use the type="bitmask" attribute instead
294 isEnum = ('FLAG_BITS' not in expandPrefix)
295
296 # Loop over the nested 'enum' tags. Keep track of the minimum and
297 # maximum numeric values, if they can be determined; but only for
298 # core API enumerants, not extension enumerants. This is inferred
299 # by looking for 'extends' attributes.
300 minName = None
301 for elem in groupElem.findall('enum'):
302 # Convert the value to an integer and use that to track min/max.
303 # Values of form -(number) are accepted but nothing more complex.
304 # Should catch exceptions here for more complex constructs. Not yet.
305 (numVal,strVal) = self.enumToValue(elem, True)
306 name = elem.get('name')
307
308 # Extension enumerants are only included if they are required
309 if (self.isEnumRequired(elem)):
310 body += " " + name + " = " + strVal + ",\n"
311
312 if (isEnum and elem.get('extends') is None):
313 if (minName == None):
314 minName = maxName = name
315 minValue = maxValue = numVal
316 elif (numVal < minValue):
317 minName = name
318 minValue = numVal
319 elif (numVal > maxValue):
320 maxName = name
321 maxValue = numVal
322 # Generate min/max value tokens and a range-padding enum. Need some
323 # additional padding to generate correct names...
324 if isEnum:
325 body += " " + expandPrefix + "_BEGIN_RANGE" + expandSuffix + " = " + minName + ",\n"
326 body += " " + expandPrefix + "_END_RANGE" + expandSuffix + " = " + maxName + ",\n"
327 body += " " + expandPrefix + "_RANGE_SIZE" + expandSuffix + " = (" + maxName + " - " + minName + " + 1),\n"
328
329 body += " " + expandPrefix + "_MAX_ENUM" + expandSuffix + " = 0x7FFFFFFF\n"
330
331 # Postfix
332 body += "} " + groupName + ";"
333 if groupElem.get('type') == 'bitmask':
334 section = 'bitmask'
335 else:
336 section = 'group'
337 self.appendSection(section, body)
338 # Enumerant generation
339 # <enum> tags may specify their values in several ways, but are usually
340 # just integers.
341 def genEnum(self, enuminfo, name):
342 OutputGenerator.genEnum(self, enuminfo, name)
343 (numVal,strVal) = self.enumToValue(enuminfo.elem, False)
344 body = '#define ' + name.ljust(33) + ' ' + strVal
345 self.appendSection('enum', body)
346 #
347 # Command generation
348 def genCmd(self, cmdinfo, name):
349 OutputGenerator.genCmd(self, cmdinfo, name)
350 #
351 decls = self.makeCDecls(cmdinfo.elem)
352 self.appendSection('command', decls[0] + '\n')
353 if (self.genOpts.genFuncPointers):
354 self.appendSection('commandPointer', decls[1])