1#!/usr/bin/python3 -i 2# 3# Copyright 2013-2023 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7# Base class for working-group-specific style conventions, 8# used in generation. 9 10from enum import Enum 11import abc 12import re 13 14# Type categories that respond "False" to isStructAlwaysValid 15# basetype is home to typedefs like ..Bool32 16CATEGORIES_REQUIRING_VALIDATION = set(('handle', 17 'enum', 18 'bitmask', 19 'basetype', 20 None)) 21 22# These are basic C types pulled in via openxr_platform_defines.h 23TYPES_KNOWN_ALWAYS_VALID = set(('char', 24 'float', 25 'int8_t', 'uint8_t', 26 'int16_t', 'uint16_t', 27 'int32_t', 'uint32_t', 28 'int64_t', 'uint64_t', 29 'size_t', 30 'intptr_t', 'uintptr_t', 31 'int', 32 )) 33 34# Split an extension name into vendor ID and name portions 35EXT_NAME_DECOMPOSE_RE = re.compile(r'[A-Z]+_(?P<vendor>[A-Z]+)_(?P<name>[\w_]+)') 36 37# Match an API version name. 38# This could be refined further for specific APIs. 39API_VERSION_NAME_RE = re.compile(r'[A-Z]+_VERSION_[0-9]') 40 41 42class ProseListFormats(Enum): 43 """A connective, possibly with a quantifier.""" 44 AND = 0 45 EACH_AND = 1 46 OR = 2 47 ANY_OR = 3 48 49 @classmethod 50 def from_string(cls, s): 51 if s == 'or': 52 return cls.OR 53 if s == 'and': 54 return cls.AND 55 raise RuntimeError("Unrecognized string connective: " + s) 56 57 @property 58 def connective(self): 59 if self in (ProseListFormats.OR, ProseListFormats.ANY_OR): 60 return 'or' 61 return 'and' 62 63 def quantifier(self, n): 64 """Return the desired quantifier for a list of a given length.""" 65 if self == ProseListFormats.ANY_OR: 66 if n > 1: 67 return 'any of ' 68 elif self == ProseListFormats.EACH_AND: 69 if n > 2: 70 return 'each of ' 71 if n == 2: 72 return 'both of ' 73 return '' 74 75 76class ConventionsBase(abc.ABC): 77 """WG-specific conventions.""" 78 79 def __init__(self): 80 self._command_prefix = None 81 self._type_prefix = None 82 83 def formatExtension(self, name): 84 """Mark up an extension name as a link the spec.""" 85 return '`<<{}>>`'.format(name) 86 87 @property 88 @abc.abstractmethod 89 def null(self): 90 """Preferred spelling of NULL.""" 91 raise NotImplementedError 92 93 def makeProseList(self, elements, fmt=ProseListFormats.AND, with_verb=False, *args, **kwargs): 94 """Make a (comma-separated) list for use in prose. 95 96 Adds a connective (by default, 'and') 97 before the last element if there are more than 1. 98 99 Adds the right one of "is" or "are" to the end if with_verb is true. 100 101 Optionally adds a quantifier (like 'any') before a list of 2 or more, 102 if specified by fmt. 103 104 Override with a different method or different call to 105 _implMakeProseList if you want to add a comma for two elements, 106 or not use a serial comma. 107 """ 108 return self._implMakeProseList(elements, fmt, with_verb, *args, **kwargs) 109 110 @property 111 def struct_macro(self): 112 """Get the appropriate format macro for a structure. 113 114 May override. 115 """ 116 return 'slink:' 117 118 @property 119 def external_macro(self): 120 """Get the appropriate format macro for an external type like uint32_t. 121 122 May override. 123 """ 124 return 'code:' 125 126 @property 127 @abc.abstractmethod 128 def structtype_member_name(self): 129 """Return name of the structure type member. 130 131 Must implement. 132 """ 133 raise NotImplementedError() 134 135 @property 136 @abc.abstractmethod 137 def nextpointer_member_name(self): 138 """Return name of the structure pointer chain member. 139 140 Must implement. 141 """ 142 raise NotImplementedError() 143 144 @property 145 @abc.abstractmethod 146 def xml_api_name(self): 147 """Return the name used in the default API XML registry for the default API""" 148 raise NotImplementedError() 149 150 @abc.abstractmethod 151 def generate_structure_type_from_name(self, structname): 152 """Generate a structure type name, like XR_TYPE_CREATE_INSTANCE_INFO. 153 154 Must implement. 155 """ 156 raise NotImplementedError() 157 158 def makeStructName(self, name): 159 """Prepend the appropriate format macro for a structure to a structure type name. 160 161 Uses struct_macro, so just override that if you want to change behavior. 162 """ 163 return self.struct_macro + name 164 165 def makeExternalTypeName(self, name): 166 """Prepend the appropriate format macro for an external type like uint32_t to a type name. 167 168 Uses external_macro, so just override that if you want to change behavior. 169 """ 170 return self.external_macro + name 171 172 def _implMakeProseList(self, elements, fmt, with_verb, comma_for_two_elts=False, serial_comma=True): 173 """Internal-use implementation to make a (comma-separated) list for use in prose. 174 175 Adds a connective (by default, 'and') 176 before the last element if there are more than 1, 177 and only includes commas if there are more than 2 178 (if comma_for_two_elts is False). 179 180 Adds the right one of "is" or "are" to the end if with_verb is true. 181 182 Optionally adds a quantifier (like 'any') before a list of 2 or more, 183 if specified by fmt. 184 185 Do not edit these defaults, override self.makeProseList(). 186 """ 187 assert(serial_comma) # did not implement what we did not need 188 if isinstance(fmt, str): 189 fmt = ProseListFormats.from_string(fmt) 190 191 my_elts = list(elements) 192 if len(my_elts) > 1: 193 my_elts[-1] = '{} {}'.format(fmt.connective, my_elts[-1]) 194 195 if not comma_for_two_elts and len(my_elts) <= 2: 196 prose = ' '.join(my_elts) 197 else: 198 prose = ', '.join(my_elts) 199 200 quantifier = fmt.quantifier(len(my_elts)) 201 202 parts = [quantifier, prose] 203 204 if with_verb: 205 if len(my_elts) > 1: 206 parts.append(' are') 207 else: 208 parts.append(' is') 209 return ''.join(parts) 210 211 @property 212 @abc.abstractmethod 213 def file_suffix(self): 214 """Return suffix of generated Asciidoctor files""" 215 raise NotImplementedError 216 217 @abc.abstractmethod 218 def api_name(self, spectype=None): 219 """Return API or specification name for citations in ref pages. 220 221 spectype is the spec this refpage is for. 222 'api' (the default value) is the main API Specification. 223 If an unrecognized spectype is given, returns None. 224 225 Must implement.""" 226 raise NotImplementedError 227 228 def should_insert_may_alias_macro(self, genOpts): 229 """Return true if we should insert a "may alias" macro in this file. 230 231 Only used by OpenXR right now.""" 232 return False 233 234 @property 235 def command_prefix(self): 236 """Return the expected prefix of commands/functions. 237 238 Implemented in terms of api_prefix.""" 239 if not self._command_prefix: 240 self._command_prefix = self.api_prefix[:].replace('_', '').lower() 241 return self._command_prefix 242 243 @property 244 def type_prefix(self): 245 """Return the expected prefix of type names. 246 247 Implemented in terms of command_prefix (and in turn, api_prefix).""" 248 if not self._type_prefix: 249 self._type_prefix = ''.join( 250 (self.command_prefix[0:1].upper(), self.command_prefix[1:])) 251 return self._type_prefix 252 253 @property 254 @abc.abstractmethod 255 def api_prefix(self): 256 """Return API token prefix. 257 258 Typically two uppercase letters followed by an underscore. 259 260 Must implement.""" 261 raise NotImplementedError 262 263 @property 264 def api_version_prefix(self): 265 """Return API core version token prefix. 266 267 Implemented in terms of api_prefix. 268 269 May override.""" 270 return self.api_prefix + 'VERSION_' 271 272 @property 273 def KHR_prefix(self): 274 """Return extension name prefix for KHR extensions. 275 276 Implemented in terms of api_prefix. 277 278 May override.""" 279 return self.api_prefix + 'KHR_' 280 281 @property 282 def EXT_prefix(self): 283 """Return extension name prefix for EXT extensions. 284 285 Implemented in terms of api_prefix. 286 287 May override.""" 288 return self.api_prefix + 'EXT_' 289 290 def writeFeature(self, featureExtraProtect, filename): 291 """Return True if OutputGenerator.endFeature should write this feature. 292 293 Defaults to always True. 294 Used in COutputGenerator. 295 296 May override.""" 297 return True 298 299 def requires_error_validation(self, return_type): 300 """Return True if the return_type element is an API result code 301 requiring error validation. 302 303 Defaults to always False. 304 305 May override.""" 306 return False 307 308 @property 309 def required_errors(self): 310 """Return a list of required error codes for validation. 311 312 Defaults to an empty list. 313 314 May override.""" 315 return [] 316 317 def is_voidpointer_alias(self, tag, text, tail): 318 """Return True if the declaration components (tag,text,tail) of an 319 element represents a void * type. 320 321 Defaults to a reasonable implementation. 322 323 May override.""" 324 return tag == 'type' and text == 'void' and tail.startswith('*') 325 326 def make_voidpointer_alias(self, tail): 327 """Reformat a void * declaration to include the API alias macro. 328 329 Defaults to a no-op. 330 331 Must override if you actually want to use this feature in your project.""" 332 return tail 333 334 def category_requires_validation(self, category): 335 """Return True if the given type 'category' always requires validation. 336 337 Defaults to a reasonable implementation. 338 339 May override.""" 340 return category in CATEGORIES_REQUIRING_VALIDATION 341 342 def type_always_valid(self, typename): 343 """Return True if the given type name is always valid (never requires validation). 344 345 This is for things like integers. 346 347 Defaults to a reasonable implementation. 348 349 May override.""" 350 return typename in TYPES_KNOWN_ALWAYS_VALID 351 352 @property 353 def should_skip_checking_codes(self): 354 """Return True if more than the basic validation of return codes should 355 be skipped for a command.""" 356 357 return False 358 359 @property 360 def generate_index_terms(self): 361 """Return True if asiidoctor index terms should be generated as part 362 of an API interface from the docgenerator.""" 363 364 return False 365 366 @property 367 def generate_enum_table(self): 368 """Return True if asciidoctor tables describing enumerants in a 369 group should be generated as part of group generation.""" 370 return False 371 372 @property 373 def generate_max_enum_in_docs(self): 374 """Return True if MAX_ENUM tokens should be generated in 375 documentation includes.""" 376 return False 377 378 @abc.abstractmethod 379 def extension_file_path(self, name): 380 """Return file path to an extension appendix relative to a directory 381 containing all such appendices. 382 - name - extension name 383 384 Must implement.""" 385 raise NotImplementedError 386 387 def extension_include_string(self, name): 388 """Return format string for include:: line for an extension appendix 389 file. 390 - name - extension name""" 391 392 return 'include::{{appendices}}/{}[]'.format( 393 self.extension_file_path(name)) 394 395 @property 396 def provisional_extension_warning(self): 397 """Return True if a warning should be included in extension 398 appendices for provisional extensions.""" 399 return True 400 401 @property 402 def generated_include_path(self): 403 """Return path relative to the generated reference pages, to the 404 generated API include files.""" 405 406 return '{generated}' 407 408 @property 409 def include_extension_appendix_in_refpage(self): 410 """Return True if generating extension refpages by embedding 411 extension appendix content (default), False otherwise 412 (OpenXR).""" 413 414 return True 415 416 def valid_flag_bit(self, bitpos): 417 """Return True if bitpos is an allowed numeric bit position for 418 an API flag. 419 420 Behavior depends on the data type used for flags (which may be 32 421 or 64 bits), and may depend on assumptions about compiler 422 handling of sign bits in enumerated types, as well.""" 423 return True 424 425 @property 426 def duplicate_aliased_structs(self): 427 """ 428 Should aliased structs have the original struct definition listed in the 429 generated docs snippet? 430 """ 431 return False 432 433 @property 434 def protectProtoComment(self): 435 """Return True if generated #endif should have a comment matching 436 the protection symbol used in the opening #ifdef/#ifndef.""" 437 return False 438 439 @property 440 def extra_refpage_headers(self): 441 """Return any extra headers (preceding the title) for generated 442 reference pages.""" 443 return '' 444 445 @property 446 def extra_refpage_body(self): 447 """Return any extra text (following the title) for generated 448 reference pages.""" 449 return '' 450 451 def is_api_version_name(self, name): 452 """Return True if name is an API version name.""" 453 454 return API_VERSION_NAME_RE.match(name) is not None 455