293 lines
12 KiB
Python
293 lines
12 KiB
Python
#!/usr/bin/python3 -i
|
|
#
|
|
# Copyright (c) 2013-2019 The Khronos Group Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import sys
|
|
|
|
from generator import GeneratorOptions, OutputGenerator, regSortFeatures, noneStr, write
|
|
|
|
class DocGeneratorOptions(GeneratorOptions):
|
|
"""DocGeneratorOptions - subclass of GeneratorOptions.
|
|
|
|
Shares many members with CGeneratorOptions, since
|
|
both are writing C-style declarations:
|
|
|
|
prefixText - list of strings to prefix generated header with
|
|
(usually a copyright statement + calling convention macros).
|
|
apicall - string to use for the function declaration prefix,
|
|
such as APICALL on Windows.
|
|
apientry - string to use for the calling convention macro,
|
|
in typedefs, such as APIENTRY.
|
|
apientryp - string to use for the calling convention macro
|
|
in function pointer typedefs, such as APIENTRYP.
|
|
directory - directory into which to generate include files
|
|
indentFuncProto - True if prototype declarations should put each
|
|
parameter on a separate line
|
|
indentFuncPointer - True if typedefed function pointers should put each
|
|
parameter on a separate line
|
|
alignFuncParam - if nonzero and parameters are being put on a
|
|
separate line, align parameter names at the specified column
|
|
|
|
Additional members:
|
|
|
|
expandEnumerants - if True, add BEGIN/END_RANGE macros in enumerated
|
|
type declarations
|
|
secondaryInclude - if True, add secondary (no xref anchor) versions
|
|
of generated files
|
|
"""
|
|
|
|
def __init__(self,
|
|
conventions = None,
|
|
filename = None,
|
|
directory = '.',
|
|
apiname = None,
|
|
profile = None,
|
|
versions = '.*',
|
|
emitversions = '.*',
|
|
defaultExtensions = None,
|
|
addExtensions = None,
|
|
removeExtensions = None,
|
|
emitExtensions = None,
|
|
sortProcedure = regSortFeatures,
|
|
prefixText = "",
|
|
apicall = '',
|
|
apientry = '',
|
|
apientryp = '',
|
|
indentFuncProto = True,
|
|
indentFuncPointer = False,
|
|
alignFuncParam = 0,
|
|
secondaryInclude = False,
|
|
expandEnumerants = True):
|
|
GeneratorOptions.__init__(self, conventions, filename, directory, apiname, profile,
|
|
versions, emitversions, defaultExtensions,
|
|
addExtensions, removeExtensions,
|
|
emitExtensions, sortProcedure)
|
|
self.prefixText = prefixText
|
|
self.apicall = apicall
|
|
self.apientry = apientry
|
|
self.apientryp = apientryp
|
|
self.indentFuncProto = indentFuncProto
|
|
self.indentFuncPointer = indentFuncPointer
|
|
self.alignFuncParam = alignFuncParam
|
|
self.secondaryInclude = secondaryInclude
|
|
self.expandEnumerants = expandEnumerants
|
|
|
|
# DocOutputGenerator - subclass of OutputGenerator.
|
|
# Generates AsciiDoc includes with C-language API interfaces, for reference
|
|
# pages and the corresponding specification. Similar to COutputGenerator,
|
|
# but each interface is written into a different file as determined by the
|
|
# options, only actual C types are emitted, and none of the boilerplate
|
|
# preprocessor code is emitted.
|
|
#
|
|
# ---- methods ----
|
|
# DocOutputGenerator(errFile, warnFile, diagFile) - args as for
|
|
# OutputGenerator. Defines additional internal state.
|
|
# ---- methods overriding base class ----
|
|
# beginFile(genOpts)
|
|
# endFile()
|
|
# beginFeature(interface, emit)
|
|
# endFeature()
|
|
# genType(typeinfo,name)
|
|
# genStruct(typeinfo,name)
|
|
# genGroup(groupinfo,name)
|
|
# genEnum(enuminfo, name)
|
|
# genCmd(cmdinfo)
|
|
class DocOutputGenerator(OutputGenerator):
|
|
"""Generate specified API interfaces in a specific style, such as a C header"""
|
|
|
|
def __init__(self,
|
|
errFile = sys.stderr,
|
|
warnFile = sys.stderr,
|
|
diagFile = sys.stdout):
|
|
OutputGenerator.__init__(self, errFile, warnFile, diagFile)
|
|
# Keep track of all extension numbers
|
|
self.extension_numbers = set()
|
|
|
|
def beginFile(self, genOpts):
|
|
OutputGenerator.beginFile(self, genOpts)
|
|
|
|
def endFile(self):
|
|
OutputGenerator.endFile(self)
|
|
|
|
def beginFeature(self, interface, emit):
|
|
# Start processing in superclass
|
|
OutputGenerator.beginFeature(self, interface, emit)
|
|
# Verify that each extension has a unique number during doc generation
|
|
extension_number = interface.get('number')
|
|
if extension_number is not None and extension_number != "0":
|
|
if extension_number in self.extension_numbers:
|
|
self.logMsg('error', 'Duplicate extension number ', extension_number, ' detected in feature ', interface.get('name'), '\n')
|
|
exit(1)
|
|
else:
|
|
self.extension_numbers.add(extension_number)
|
|
|
|
def endFeature(self):
|
|
# Finish processing in superclass
|
|
OutputGenerator.endFeature(self)
|
|
|
|
# Generate an include file
|
|
#
|
|
# directory - subdirectory to put file in
|
|
# basename - base name of the file
|
|
# contents - contents of the file (Asciidoc boilerplate aside)
|
|
def writeInclude(self, directory, basename, contents):
|
|
# Create subdirectory, if needed
|
|
directory = self.genOpts.directory + '/' + directory
|
|
self.makeDir(directory)
|
|
|
|
# Create file
|
|
filename = directory + '/' + basename + '.txt'
|
|
self.logMsg('diag', '# Generating include file:', filename)
|
|
fp = open(filename, 'w', encoding='utf-8')
|
|
|
|
# Asciidoc anchor
|
|
write(self.genOpts.conventions.warning_comment, file=fp)
|
|
write('[[{0},{0}]]'.format(basename), file=fp)
|
|
write('[source,c++]', file=fp)
|
|
write('----', file=fp)
|
|
write(contents, file=fp)
|
|
write('----', file=fp)
|
|
fp.close()
|
|
|
|
if self.genOpts.secondaryInclude:
|
|
# Create secondary no cross-reference include file
|
|
filename = directory + '/' + basename + '.no-xref.txt'
|
|
self.logMsg('diag', '# Generating include file:', filename)
|
|
fp = open(filename, 'w', encoding='utf-8')
|
|
|
|
# Asciidoc anchor
|
|
write(self.genOpts.conventions.warning_comment, file=fp)
|
|
write('// Include this no-xref version without cross reference id for multiple includes of same file', file=fp)
|
|
write('[source,c++]', file=fp)
|
|
write('----', file=fp)
|
|
write(contents, file=fp)
|
|
write('----', file=fp)
|
|
fp.close()
|
|
|
|
#
|
|
# Type generation
|
|
def genType(self, typeinfo, name, alias):
|
|
OutputGenerator.genType(self, typeinfo, name, alias)
|
|
typeElem = typeinfo.elem
|
|
# If the type is a struct type, traverse the embedded <member> tags
|
|
# generating a structure. Otherwise, emit the tag text.
|
|
category = typeElem.get('category')
|
|
|
|
body = ''
|
|
if category in ('struct', 'union'):
|
|
# If the type is a struct type, generate it using the
|
|
# special-purpose generator.
|
|
self.genStruct(typeinfo, name, alias)
|
|
else:
|
|
if alias:
|
|
# If the type is an alias, just emit a typedef declaration
|
|
body = 'typedef ' + alias + ' ' + name + ';\n'
|
|
self.writeInclude(OutputGenerator.categoryToPath[category],
|
|
name, body)
|
|
else:
|
|
# Replace <apientry /> tags with an APIENTRY-style string
|
|
# (from self.genOpts). Copy other text through unchanged.
|
|
# If the resulting text is an empty string, don't emit it.
|
|
body = noneStr(typeElem.text)
|
|
for elem in typeElem:
|
|
if elem.tag == 'apientry':
|
|
body += self.genOpts.apientry + noneStr(elem.tail)
|
|
else:
|
|
body += noneStr(elem.text) + noneStr(elem.tail)
|
|
|
|
if body:
|
|
if category in OutputGenerator.categoryToPath:
|
|
self.writeInclude(OutputGenerator.categoryToPath[category],
|
|
name, body + '\n')
|
|
else:
|
|
self.logMsg('diag', '# NOT writing include file for type:',
|
|
name, '- bad category: ', category)
|
|
else:
|
|
self.logMsg('diag', '# NOT writing empty include file for type', name)
|
|
|
|
# Struct (e.g. C "struct" type) generation.
|
|
# This is a special case of the <type> tag where the contents are
|
|
# interpreted as a set of <member> tags instead of freeform C
|
|
# C type declarations. The <member> tags are just like <param>
|
|
# tags - they are a declaration of a struct or union member.
|
|
# Only simple member declarations are supported (no nested
|
|
# structs etc.)
|
|
# If alias is not None, then this struct aliases another; just
|
|
# generate a typedef of that alias.
|
|
def genStruct(self, typeinfo, typeName, alias):
|
|
OutputGenerator.genStruct(self, typeinfo, typeName, alias)
|
|
|
|
typeElem = typeinfo.elem
|
|
|
|
if alias:
|
|
body = 'typedef ' + alias + ' ' + typeName + ';\n'
|
|
else:
|
|
body = 'typedef ' + typeElem.get('category') + ' ' + typeName + ' {\n'
|
|
|
|
targetLen = 0
|
|
for member in typeElem.findall('.//member'):
|
|
targetLen = max(targetLen, self.getCParamTypeLength(member))
|
|
for member in typeElem.findall('.//member'):
|
|
body += self.makeCParamDecl(member, targetLen + 4)
|
|
body += ';\n'
|
|
body += '} ' + typeName + ';'
|
|
|
|
self.writeInclude('structs', typeName, body)
|
|
|
|
# Group (e.g. C "enum" type) generation.
|
|
# These are concatenated together with other types.
|
|
# If alias is not None, it is the name of another group type
|
|
# which aliases this type; just generate that alias.
|
|
def genGroup(self, groupinfo, groupName, alias):
|
|
OutputGenerator.genGroup(self, groupinfo, groupName, alias)
|
|
|
|
if alias:
|
|
# If the group name is aliased, just emit a typedef declaration
|
|
# for the alias.
|
|
body = 'typedef ' + alias + ' ' + groupName + ';\n'
|
|
else:
|
|
expand = self.genOpts.expandEnumerants
|
|
(_, body) = self.buildEnumCDecl(expand, groupinfo, groupName)
|
|
|
|
self.writeInclude('enums', groupName, body)
|
|
|
|
# Enumerant generation
|
|
# <enum> tags may specify their values in several ways, but are usually
|
|
# just integers.
|
|
def genEnum(self, enuminfo, name, alias):
|
|
OutputGenerator.genEnum(self, enuminfo, name, alias)
|
|
self.logMsg('diag', '# NOT writing compile-time constant', name)
|
|
|
|
# (_, strVal) = self.enumToValue(enuminfo.elem, False)
|
|
# body = '#define ' + name.ljust(33) + ' ' + strVal
|
|
# self.writeInclude('consts', name, body)
|
|
|
|
# Command generation
|
|
def genCmd(self, cmdinfo, name, alias):
|
|
OutputGenerator.genCmd(self, cmdinfo, name, alias)
|
|
|
|
return_type = cmdinfo.elem.find('proto/type')
|
|
if self.genOpts.conventions.requires_error_validation(return_type):
|
|
# This command returns an API result code, so check that it
|
|
# returns at least the required errors.
|
|
required_errors = self.genOpts.conventions.required_errors
|
|
errorcodes = cmdinfo.elem.get('errorcodes').split(',')
|
|
if not required_errors.issubset(set(errorcodes)):
|
|
self.logMsg('error', 'Missing required error code for command: ', name, '\n')
|
|
exit(1)
|
|
|
|
decls = self.makeCDecls(cmdinfo.elem)
|
|
self.writeInclude('protos', name, decls[0])
|