// Copyright(c) 2023, NVIDIA CORPORATION. All rights reserved. // // 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. #pragma once #include #include #include #include #include #include #include #include #include struct TypeInfo; void checkAttributes( int line, std::map const & attributes, std::map> const & required, std::map> const & optional ); void checkElements( int line, std::vector const & elements, std::map const & required, std::set const & optional = {} ); void checkForError( bool condition, int line, std::string const & message ); void checkForWarning( bool condition, int line, std::string const & message ); std::string generateCopyrightMessage( std::string const & comment ); std::string generateStandardArrayWrapper( std::string const & type, std::vector const & sizes ); std::map getAttributes( tinyxml2::XMLElement const * element ); template std::vector getChildElements( ElementContainer const * element ); std::string readComment( tinyxml2::XMLElement const * element ); std::pair, std::string> readModifiers( tinyxml2::XMLNode const * node ); TypeInfo readTypeInfo( tinyxml2::XMLElement const * element ); std::string replaceWithMap( std::string const & input, std::map replacements ); std::string stripPostfix( std::string const & value, std::string const & postfix ); std::string stripPrefix( std::string const & value, std::string const & prefix ); std::string toCamelCase( std::string const & value, bool keepSeparatedNumbersSeparated = false ); std::vector tokenize( std::string const & tokenString, std::string const & separator ); std::string toString( tinyxml2::XMLError error ); std::string toUpperCase( std::string const & name ); std::string trim( std::string const & input ); std::string trimEnd( std::string const & input ); std::string trimStars( std::string const & input ); void writeToFile( std::string const & str, std::string const & fileName ); struct TypeInfo { std::string compose( std::string const & nameSpace, std::string const & prefix_ = "Vk" ) const { return prefix + ( prefix.empty() ? "" : " " ) + ( nameSpace.empty() ? type : ( ( ( type.starts_with( prefix_ ) ) ? ( nameSpace + "::" ) : "" ) + stripPrefix( type, prefix_ ) ) ) + ( postfix.empty() ? "" : " " ) + postfix; } bool operator==( TypeInfo const & rhs ) const noexcept { return ( prefix == rhs.prefix ) && ( type == rhs.type ) && ( postfix == rhs.postfix ); } bool operator!=( TypeInfo const & rhs ) const noexcept { return !operator==( rhs ); } bool operator<( TypeInfo const & rhs ) const noexcept { return ( prefix < rhs.prefix ) || ( ( prefix == rhs.prefix ) && ( ( type < rhs.type ) || ( ( type == rhs.type ) && ( postfix < rhs.postfix ) ) ) ); } bool isConstPointer() const noexcept { return isPointer() && ( prefix.find( "const" ) != std::string::npos ); } bool isNonConstPointer() const noexcept { return isPointer() && ( prefix.find( "const" ) == std::string::npos ); } bool isPointer() const noexcept { return postfix.find( '*' ) != std::string::npos; } bool isValue() const noexcept { return ( ( prefix.find( '*' ) == std::string::npos ) && ( postfix.find( '*' ) == std::string::npos ) ); } std::string prefix = {}; std::string type = {}; std::string postfix = {}; }; struct ExternalTypeData { std::string require = {}; int xmlLine = 0; }; struct IncludeData { int xmlLine = {}; }; enum class TypeCategory { Bitmask, BaseType, Constant, Define, Enum, ExternalType, FuncPointer, Handle, Include, Struct, Union, Unknown }; struct TypeData { TypeCategory category = TypeCategory::Unknown; std::set requiredBy = {}; int xmlLine = {}; }; // check the validity of an attributes map // line : the line in the xml file where the attributes are listed // attributes : the map of name/value pairs of the encountered attributes // required : the required attributes, with a set of allowed values per attribute // optional : the optional attributes, with a set of allowed values per attribute inline void checkAttributes( int line, std::map const & attributes, std::map> const & required, std::map> const & optional ) { // check if all required attributes are included and if there is a set of allowed values, check if the actual // value is part of that set for ( auto const & r : required ) { auto attributesIt = attributes.find( r.first ); checkForError( attributesIt != attributes.end(), line, "missing attribute <" + r.first + ">" ); if ( !r.second.empty() ) { std::vector values = tokenize( attributesIt->second, "," ); for ( auto const & v : values ) { checkForError( r.second.find( v ) != r.second.end(), line, "unexpected attribute value <" + v + "> in attribute <" + attributesIt->first + ">" ); } } } // check if all not required attributes or optional, and if there is a set of allowed values, check if the // actual value is part of that set for ( auto const & a : attributes ) { if ( required.find( a.first ) == required.end() ) { auto optionalIt = optional.find( a.first ); if ( optionalIt == optional.end() ) { checkForWarning( false, line, "unknown attribute <" + a.first + ">" ); continue; } else if ( !optionalIt->second.empty() ) { std::vector values = tokenize( a.second, "," ); for ( auto const & v : values ) { checkForWarning( optionalIt->second.find( v ) != optionalIt->second.end(), line, "unexpected attribute value <" + v + "> in attribute <" + a.first + ">" ); } } } } } inline void checkElements( int line, std::vector const & elements, std::map const & required, std::set const & optional ) { std::map encountered; for ( auto const & e : elements ) { std::string value = e->Value(); encountered[value]++; checkForWarning( ( required.find( value ) != required.end() ) || ( optional.find( value ) != optional.end() ), e->GetLineNum(), "unknown element <" + value + ">" ); } for ( auto const & r : required ) { auto encounteredIt = encountered.find( r.first ); checkForError( encounteredIt != encountered.end(), line, "missing required element <" + r.first + ">" ); // check: r.second (means: required excactly once) => (encouteredIt->second == 1) checkForError( !r.second || ( encounteredIt->second == 1 ), line, "required element <" + r.first + "> is supposed to be listed exactly once, but is listed " + std::to_string( encounteredIt->second ) + " times" ); } } inline void checkForError( bool condition, int line, std::string const & message ) { if ( !condition ) { throw std::runtime_error( "VulkanHppGenerator: Spec error on line " + std::to_string( line ) + ": " + message ); } } inline void checkForWarning( bool condition, int line, std::string const & message ) { if ( !condition ) { std::cerr << "VulkanHppGenerator: Spec warning on line " << std::to_string( line ) << ": " << message << "!" << std::endl; } } inline std::string generateCopyrightMessage( std::string const & comment ) { std::string copyrightMessage = comment; // replace any '\n' with "\n// " for ( size_t pos = copyrightMessage.find( '\n' ); pos != std::string::npos; pos = copyrightMessage.find( '\n', pos + 1 ) ) { copyrightMessage.replace( pos, 1, "\n// " ); } // remove any trailing spaces copyrightMessage = trimEnd( copyrightMessage ); // and add a little message on our own copyrightMessage += "\n\n// This header is generated from the Khronos Vulkan XML API Registry."; return trim( copyrightMessage ) + "\n"; } inline std::string generateStandardArrayWrapper( std::string const & type, std::vector const & sizes ) { std::string arrayString = "VULKAN_HPP_NAMESPACE::ArrayWrapper" + std::to_string( sizes.size() ) + "D<" + type; for ( auto const & size : sizes ) { arrayString += ", " + size; } arrayString += ">"; return arrayString; } inline std::map getAttributes( tinyxml2::XMLElement const * element ) { assert( element ); std::map attributes; for ( auto attribute = element->FirstAttribute(); attribute; attribute = attribute->Next() ) { assert( attributes.find( attribute->Name() ) == attributes.end() ); attributes[attribute->Name()] = attribute->Value(); } return attributes; } template inline std::vector getChildElements( ElementContainer const * element ) { std::vector childElements; for ( tinyxml2::XMLElement const * childElement = element->FirstChildElement(); childElement; childElement = childElement->NextSiblingElement() ) { childElements.push_back( childElement ); } return childElements; } inline bool isHexNumber( std::string const & name ) noexcept { return name.starts_with( "0x" ) && ( name.find_first_not_of( "0123456789ABCDEF", 2 ) == std::string::npos ); } inline bool isNumber( std::string const & name ) noexcept { return name.find_first_not_of( "0123456789" ) == std::string::npos; } inline std::string readComment( tinyxml2::XMLElement const * element ) { const int line = element->GetLineNum(); checkAttributes( line, getAttributes( element ), {}, {} ); checkElements( line, getChildElements( element ), {} ); return element->GetText(); } inline std::pair, std::string> readModifiers( tinyxml2::XMLNode const * node ) { std::vector arraySizes; std::string bitCount; if ( node && node->ToText() ) { // following the name there might be some array size std::string value = trim( node->Value() ); assert( !value.empty() ); if ( value[0] == '[' ) { std::string::size_type endPos = 0; while ( endPos + 1 != value.length() ) { const std::string::size_type startPos = value.find( '[', endPos ); checkForError( startPos != std::string::npos, node->GetLineNum(), "could not find '[' in <" + value + ">" ); endPos = value.find( ']', startPos ); checkForError( endPos != std::string::npos, node->GetLineNum(), "could not find ']' in <" + value + ">" ); checkForError( startPos + 2 <= endPos, node->GetLineNum(), "missing content between '[' and ']' in <" + value + ">" ); arraySizes.push_back( value.substr( startPos + 1, endPos - startPos - 1 ) ); } } else if ( value[0] == ':' ) { bitCount = trim( value.substr( 1 ) ); } else { checkForError( ( value[0] == ';' ) || ( value[0] == ')' ), node->GetLineNum(), "unknown modifier <" + value + ">" ); } } return std::make_pair( arraySizes, bitCount ); } inline TypeInfo readTypeInfo( tinyxml2::XMLElement const * element ) { TypeInfo typeInfo; tinyxml2::XMLNode const * previousSibling = element->PreviousSibling(); if ( previousSibling && previousSibling->ToText() ) { typeInfo.prefix = trim( previousSibling->Value() ); } typeInfo.type = element->GetText(); tinyxml2::XMLNode const * nextSibling = element->NextSibling(); if ( nextSibling && nextSibling->ToText() ) { typeInfo.postfix = trimStars( trimEnd( nextSibling->Value() ) ); } return typeInfo; } inline std::string replaceWithMap( std::string const & input, std::map replacements ) { // This will match ${someVariable} and contain someVariable in match group 1 std::regex re( R"(\$\{([^\}]+)\})" ); auto it = std::sregex_iterator( input.begin(), input.end(), re ); auto end = std::sregex_iterator(); // No match, just return the original string if ( it == end ) { assert( replacements.empty() ); return input; } #if !defined( NDEBUG ) std::set matchedReplacements; #endif std::string result = ""; while ( it != end ) { std::smatch match = *it; auto itReplacement = replacements.find( match[1].str() ); assert( itReplacement != replacements.end() ); #if !defined( NDEBUG ) matchedReplacements.insert( match[1].str() ); #endif result += match.prefix().str() + ( ( itReplacement != replacements.end() ) ? itReplacement->second : match[0].str() ); ++it; // we've passed the last match. Append the rest of the orignal string if ( it == end ) { result += match.suffix().str(); } } #if !defined( NDEBUG ) std::set missedReplacements; for ( auto r : replacements ) { if ( matchedReplacements.find( r.first ) == matchedReplacements.end() ) { missedReplacements.insert( r.first ); } } assert( missedReplacements.empty() ); #endif return result; } inline std::string stripPostfix( std::string const & value, std::string const & postfix ) { std::string strippedValue = value; if ( strippedValue.ends_with( postfix ) ) { strippedValue.erase( strippedValue.length() - postfix.length() ); } return strippedValue; } inline std::string stripPrefix( std::string const & value, std::string const & prefix ) { std::string strippedValue = value; if ( strippedValue.starts_with( prefix ) ) { strippedValue.erase( 0, prefix.length() ); } return strippedValue; } inline std::string toCamelCase( std::string const & value, bool keepSeparatedNumbersSeparated ) { assert( value.empty() || isupper( value[0] ) || isdigit( value[0] ) ); std::string result; result.reserve( value.size() ); for ( size_t i = 0; i < value.size(); ++i ) { if ( value[i] == '_' ) { if ( keepSeparatedNumbersSeparated && ( 0 < i ) && isdigit( value[i - 1] ) && ( i < value.size() - 1 ) && isdigit( value[i + 1] ) ) { result.push_back( '_' ); } } else { result.push_back( ( ( 0 == i ) || ( value[i - 1] == '_' ) || isdigit( value[i - 1] ) ) ? value[i] : static_cast( tolower( value[i] ) ) ); } } return result; } inline std::vector tokenize( std::string const & tokenString, std::string const & separator ) { std::vector tokens; if ( !tokenString.empty() ) { size_t start = 0, end; do { end = tokenString.find( separator, start ); if ( start != end ) { tokens.push_back( trim( tokenString.substr( start, end - start ) ) ); } start = end + separator.length(); } while ( end != std::string::npos ); } return tokens; } inline std::string toString( tinyxml2::XMLError error ) { switch ( error ) { case tinyxml2::XML_SUCCESS: return "XML_SUCCESS"; case tinyxml2::XML_NO_ATTRIBUTE: return "XML_NO_ATTRIBUTE"; case tinyxml2::XML_WRONG_ATTRIBUTE_TYPE: return "XML_WRONG_ATTRIBUTE_TYPE"; case tinyxml2::XML_ERROR_FILE_NOT_FOUND: return "XML_ERROR_FILE_NOT_FOUND"; case tinyxml2::XML_ERROR_FILE_COULD_NOT_BE_OPENED: return "XML_ERROR_FILE_COULD_NOT_BE_OPENED"; case tinyxml2::XML_ERROR_FILE_READ_ERROR: return "XML_ERROR_FILE_READ_ERROR"; case tinyxml2::XML_ERROR_PARSING_ELEMENT: return "XML_ERROR_PARSING_ELEMENT"; case tinyxml2::XML_ERROR_PARSING_ATTRIBUTE: return "XML_ERROR_PARSING_ATTRIBUTE"; case tinyxml2::XML_ERROR_PARSING_TEXT: return "XML_ERROR_PARSING_TEXT"; case tinyxml2::XML_ERROR_PARSING_CDATA: return "XML_ERROR_PARSING_CDATA"; case tinyxml2::XML_ERROR_PARSING_COMMENT: return "XML_ERROR_PARSING_COMMENT"; case tinyxml2::XML_ERROR_PARSING_DECLARATION: return "XML_ERROR_PARSING_DECLARATION"; case tinyxml2::XML_ERROR_PARSING_UNKNOWN: return "XML_ERROR_PARSING_UNKNOWN"; case tinyxml2::XML_ERROR_EMPTY_DOCUMENT: return "XML_ERROR_EMPTY_DOCUMENT"; case tinyxml2::XML_ERROR_MISMATCHED_ELEMENT: return "XML_ERROR_MISMATCHED_ELEMENT"; case tinyxml2::XML_ERROR_PARSING: return "XML_ERROR_PARSING"; case tinyxml2::XML_CAN_NOT_CONVERT_TEXT: return "XML_CAN_NOT_CONVERT_TEXT"; case tinyxml2::XML_NO_TEXT_NODE: return "XML_NO_TEXT_NODE"; default: return "unknown error code <" + std::to_string( error ) + ">"; } } std::string toUpperCase( std::string const & name ) { std::string convertedName; bool previousIsLowerCase = false; bool previousIsDigit = false; for ( auto c : name ) { if ( ( isupper( c ) && ( previousIsLowerCase || previousIsDigit ) ) || ( isdigit( c ) && previousIsLowerCase ) ) { convertedName.push_back( '_' ); } convertedName.push_back( static_cast( toupper( c ) ) ); previousIsLowerCase = !!islower( c ); previousIsDigit = !!isdigit( c ); } return convertedName; } inline std::string trim( std::string const & input ) { std::string result = input; result.erase( result.begin(), std::find_if( result.begin(), result.end(), []( char c ) noexcept { return !std::isspace( c ); } ) ); result.erase( std::find_if( result.rbegin(), result.rend(), []( char c ) noexcept { return !std::isspace( c ); } ).base(), result.end() ); return result; } inline std::string trimEnd( std::string const & input ) { std::string result = input; result.erase( std::find_if( result.rbegin(), result.rend(), []( char c ) noexcept { return !std::isspace( c ); } ).base(), result.end() ); return result; } inline std::string trimStars( std::string const & input ) { std::string result = input; size_t pos = result.find( '*' ); while ( pos != std::string::npos ) { if ( ( 0 < pos ) && ( result[pos - 1] != ' ' ) && ( result[pos - 1] != '*' ) ) { result.insert( pos, 1, ' ' ); ++pos; } else if ( ( pos < result.length() - 1 ) && ( result[pos + 1] != ' ' ) && ( result[pos + 1] != '*' ) ) { result.insert( pos + 1, 1, ' ' ); } pos = result.find( '*', pos + 1 ); } return result; } void writeToFile( std::string const & str, std::string const & fileName ) { std::ofstream ofs( fileName ); assert( !ofs.fail() ); ofs << str; ofs.close(); #if defined( CLANG_FORMAT_EXECUTABLE ) std::cout << "VulkanHppGenerator: Formatting " << fileName << " ..." << std::endl; const std::string commandString = "\"" CLANG_FORMAT_EXECUTABLE "\" -i --style=file " + fileName; const int ret = std::system( commandString.c_str() ); if ( ret != 0 ) { std::cout << "VulkanHppGenerator: failed to format file " << fileName << " with error <" << ret << ">\n"; } #endif }