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