Mercurial > games > semicongine
comparison fuhtark_test/Vulkan-Headers-1.4.334/registry/parse_dependency.py @ 1501:f40d9d814c08 default tip
did: correct vulkan-api generator
| author | sam <sam@basx.dev> |
|---|---|
| date | Wed, 26 Nov 2025 23:34:29 +0700 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 1500:91c8c3b7cbf0 | 1501:f40d9d814c08 |
|---|---|
| 1 #!/usr/bin/env python3 | |
| 2 | |
| 3 # Copyright 2022-2025 The Khronos Group Inc. | |
| 4 # Copyright 2003-2019 Paul McGuire | |
| 5 # SPDX-License-Identifier: MIT | |
| 6 | |
| 7 # apirequirements.py - parse 'depends' expressions in API XML | |
| 8 # Supported methods: | |
| 9 # dependency - the expression string | |
| 10 # | |
| 11 # evaluateDependency(dependency, isSupported) evaluates the expression, | |
| 12 # returning a boolean result. isSupported takes an extension or version name | |
| 13 # string and returns a boolean. | |
| 14 # | |
| 15 # dependencyLanguage(dependency) returns an English string equivalent | |
| 16 # to the expression, suitable for header file comments. | |
| 17 # | |
| 18 # dependencyNames(dependency) returns a set of the extension and | |
| 19 # version names in the expression. | |
| 20 # | |
| 21 # dependencyMarkup(dependency) returns a string containing asciidoctor | |
| 22 # markup for English equivalent to the expression, suitable for extension | |
| 23 # appendices. | |
| 24 # | |
| 25 # All may throw a ParseException if the expression cannot be parsed or is | |
| 26 # not completely consumed by parsing. | |
| 27 | |
| 28 # Supported expressions at present: | |
| 29 # - extension names | |
| 30 # - '+' as AND connector | |
| 31 # - ',' as OR connector | |
| 32 # - parenthesization for grouping | |
| 33 | |
| 34 # Based on `examples/fourFn.py` from the | |
| 35 # https://github.com/pyparsing/pyparsing/ repository. | |
| 36 | |
| 37 from pyparsing import ( | |
| 38 Literal, | |
| 39 Word, | |
| 40 Group, | |
| 41 Forward, | |
| 42 alphas, | |
| 43 alphanums, | |
| 44 Regex, | |
| 45 ParseException, | |
| 46 CaselessKeyword, | |
| 47 Suppress, | |
| 48 delimitedList, | |
| 49 infixNotation, | |
| 50 ) | |
| 51 import math | |
| 52 import operator | |
| 53 import pyparsing as pp | |
| 54 import re | |
| 55 | |
| 56 from apiconventions import APIConventions as APIConventions | |
| 57 conventions = APIConventions() | |
| 58 | |
| 59 def markupPassthrough(name): | |
| 60 """Pass a name (leaf or operator) through without applying markup""" | |
| 61 return name | |
| 62 | |
| 63 def leafMarkupAsciidoc(name): | |
| 64 """Markup a leaf name as an asciidoc link to an API version or extension | |
| 65 anchor. | |
| 66 | |
| 67 - name - version or extension name""" | |
| 68 | |
| 69 return conventions.formatVersionOrExtension(name) | |
| 70 | |
| 71 def leafMarkupC(name): | |
| 72 """Markup a leaf name as a C expression, using conventions of the | |
| 73 Vulkan Validation Layers | |
| 74 | |
| 75 - name - version or extension name""" | |
| 76 | |
| 77 (apivariant, major, minor) = apiVersionNameMatch(name) | |
| 78 | |
| 79 if apivariant is not None: | |
| 80 return name | |
| 81 else: | |
| 82 return f'ext.{name}' | |
| 83 | |
| 84 opMarkupAsciidocMap = { '+' : 'and', ',' : 'or' } | |
| 85 | |
| 86 def opMarkupAsciidoc(op): | |
| 87 """Markup an operator as an asciidoc spec markup equivalent | |
| 88 | |
| 89 - op - operator ('+' or ',')""" | |
| 90 | |
| 91 return opMarkupAsciidocMap[op] | |
| 92 | |
| 93 opMarkupCMap = { '+' : '&&', ',' : '||' } | |
| 94 | |
| 95 def opMarkupC(op): | |
| 96 """Markup an operator as a C language equivalent | |
| 97 | |
| 98 - op - operator ('+' or ',')""" | |
| 99 | |
| 100 return opMarkupCMap[op] | |
| 101 | |
| 102 | |
| 103 # Unfortunately global to be used in pyparsing | |
| 104 exprStack = [] | |
| 105 | |
| 106 def push_first(toks): | |
| 107 """Push a token on the global stack | |
| 108 | |
| 109 - toks - first element is the token to push""" | |
| 110 | |
| 111 exprStack.append(toks[0]) | |
| 112 | |
| 113 # An identifier (version, feature boolean, or extension name) | |
| 114 dependencyIdent = Word(f"{alphanums}_:") | |
| 115 | |
| 116 # Infix expression for depends expressions | |
| 117 dependencyExpr = pp.infixNotation(dependencyIdent, | |
| 118 [ (pp.oneOf(', +'), 2, pp.opAssoc.LEFT), ]) | |
| 119 | |
| 120 # BNF grammar for depends expressions | |
| 121 _bnf = None | |
| 122 def dependencyBNF(): | |
| 123 """ | |
| 124 boolop :: '+' | ',' | |
| 125 extname :: Char(alphas) | |
| 126 atom :: extname | '(' expr ')' | |
| 127 expr :: atom [ boolop atom ]* | |
| 128 """ | |
| 129 global _bnf | |
| 130 if _bnf is None: | |
| 131 and_, or_ = map(Literal, '+,') | |
| 132 lpar, rpar = map(Suppress, '()') | |
| 133 boolop = and_ | or_ | |
| 134 | |
| 135 expr = Forward() | |
| 136 expr_list = delimitedList(Group(expr)) | |
| 137 atom = ( | |
| 138 boolop[...] | |
| 139 + ( | |
| 140 (dependencyIdent).setParseAction(push_first) | |
| 141 | Group(lpar + expr + rpar) | |
| 142 ) | |
| 143 ) | |
| 144 | |
| 145 expr <<= atom + (boolop + atom).setParseAction(push_first)[...] | |
| 146 _bnf = expr | |
| 147 return _bnf | |
| 148 | |
| 149 | |
| 150 # map operator symbols to corresponding arithmetic operations | |
| 151 _opn = { | |
| 152 '+': operator.and_, | |
| 153 ',': operator.or_, | |
| 154 } | |
| 155 | |
| 156 def evaluateStack(stack, isSupported): | |
| 157 """Evaluate an expression stack, returning a boolean result. | |
| 158 | |
| 159 - stack - the stack | |
| 160 - isSupported - function taking a version or extension name string and | |
| 161 returning True or False if that name is supported or not.""" | |
| 162 | |
| 163 op, num_args = stack.pop(), 0 | |
| 164 if isinstance(op, tuple): | |
| 165 op, num_args = op | |
| 166 | |
| 167 if op in '+,': | |
| 168 # Note: operands are pushed onto the stack in reverse order | |
| 169 op2 = evaluateStack(stack, isSupported) | |
| 170 op1 = evaluateStack(stack, isSupported) | |
| 171 return _opn[op](op1, op2) | |
| 172 elif op[0].isalpha(): | |
| 173 return isSupported(op) | |
| 174 else: | |
| 175 raise Exception(f'invalid op: {op}') | |
| 176 | |
| 177 def evaluateDependency(dependency, isSupported): | |
| 178 """Evaluate a dependency expression, returning a boolean result. | |
| 179 | |
| 180 - dependency - the expression | |
| 181 - isSupported - function taking a version or extension name string and | |
| 182 returning True or False if that name is supported or not.""" | |
| 183 | |
| 184 global exprStack | |
| 185 exprStack = [] | |
| 186 results = dependencyBNF().parseString(dependency, parseAll=True) | |
| 187 val = evaluateStack(exprStack[:], isSupported) | |
| 188 return val | |
| 189 | |
| 190 def evalDependencyLanguage(stack, leafMarkup, opMarkup, parenthesize, root): | |
| 191 """Evaluate an expression stack, returning an English equivalent | |
| 192 | |
| 193 - stack - the stack | |
| 194 - leafMarkup, opMarkup, parenthesize - same as dependencyLanguage | |
| 195 - root - True only if this is the outer (root) expression level""" | |
| 196 | |
| 197 op, num_args = stack.pop(), 0 | |
| 198 if isinstance(op, tuple): | |
| 199 op, num_args = op | |
| 200 if op in '+,': | |
| 201 # Could parenthesize, not needed yet | |
| 202 rhs = evalDependencyLanguage(stack, leafMarkup, opMarkup, parenthesize, root = False) | |
| 203 opname = opMarkup(op) | |
| 204 lhs = evalDependencyLanguage(stack, leafMarkup, opMarkup, parenthesize, root = False) | |
| 205 if parenthesize and not root: | |
| 206 return f'({lhs} {opname} {rhs})' | |
| 207 else: | |
| 208 return f'{lhs} {opname} {rhs}' | |
| 209 elif op[0].isalpha(): | |
| 210 # This is an extension or feature name | |
| 211 return leafMarkup(op) | |
| 212 else: | |
| 213 raise Exception(f'invalid op: {op}') | |
| 214 | |
| 215 def dependencyLanguage(dependency, leafMarkup, opMarkup, parenthesize): | |
| 216 """Return an API dependency expression translated to a form suitable for | |
| 217 asciidoctor conditionals or header file comments. | |
| 218 | |
| 219 - dependency - the expression | |
| 220 - leafMarkup - function taking an extension / version name and | |
| 221 returning an equivalent marked up version | |
| 222 - opMarkup - function taking an operator ('+' / ',') name name and | |
| 223 returning an equivalent marked up version | |
| 224 - parenthesize - True if parentheses should be used in the resulting | |
| 225 expression, False otherwise""" | |
| 226 | |
| 227 global exprStack | |
| 228 exprStack = [] | |
| 229 results = dependencyBNF().parseString(dependency, parseAll=True) | |
| 230 return evalDependencyLanguage(exprStack, leafMarkup, opMarkup, parenthesize, root = True) | |
| 231 | |
| 232 # aka specmacros = False | |
| 233 def dependencyLanguageComment(dependency): | |
| 234 """Return dependency expression translated to a form suitable for | |
| 235 comments in headers of emitted C code, as used by the | |
| 236 docgenerator.""" | |
| 237 return dependencyLanguage(dependency, leafMarkup = markupPassthrough, opMarkup = opMarkupAsciidoc, parenthesize = True) | |
| 238 | |
| 239 # aka specmacros = True | |
| 240 def dependencyLanguageSpecMacros(dependency): | |
| 241 """Return dependency expression translated to a form suitable for | |
| 242 comments in headers of emitted C code, as used by the | |
| 243 interfacegenerator.""" | |
| 244 return dependencyLanguage(dependency, leafMarkup = leafMarkupAsciidoc, opMarkup = opMarkupAsciidoc, parenthesize = False) | |
| 245 | |
| 246 def dependencyLanguageC(dependency): | |
| 247 """Return dependency expression translated to a form suitable for | |
| 248 use in C expressions""" | |
| 249 return dependencyLanguage(dependency, leafMarkup = leafMarkupC, opMarkup = opMarkupC, parenthesize = True) | |
| 250 | |
| 251 def evalDependencyNames(stack): | |
| 252 """Evaluate an expression stack, returning the set of extension and | |
| 253 feature names used in the expression. | |
| 254 | |
| 255 - stack - the stack""" | |
| 256 | |
| 257 op, num_args = stack.pop(), 0 | |
| 258 if isinstance(op, tuple): | |
| 259 op, num_args = op | |
| 260 if op in '+,': | |
| 261 # Do not evaluate the operation. We only care about the names. | |
| 262 return evalDependencyNames(stack) | evalDependencyNames(stack) | |
| 263 elif op[0].isalpha(): | |
| 264 return { op } | |
| 265 else: | |
| 266 raise Exception(f'invalid op: {op}') | |
| 267 | |
| 268 def dependencyNames(dependency): | |
| 269 """Return a set of the extension and version names in an API dependency | |
| 270 expression. Used when determining transitive dependencies for spec | |
| 271 generation with specific extensions included. | |
| 272 | |
| 273 - dependency - the expression""" | |
| 274 | |
| 275 global exprStack | |
| 276 exprStack = [] | |
| 277 results = dependencyBNF().parseString(dependency, parseAll=True) | |
| 278 # print(f'names(): stack = {exprStack}') | |
| 279 return evalDependencyNames(exprStack) | |
| 280 | |
| 281 def markupTraverse(expr, level = 0, root = True): | |
| 282 """Recursively process a dependency in infix form, transforming it into | |
| 283 asciidoctor markup with expression nesting indicated by indentation | |
| 284 level. | |
| 285 | |
| 286 - expr - expression to process | |
| 287 - level - indentation level to render expression at | |
| 288 - root - True only on initial call""" | |
| 289 | |
| 290 if level > 0: | |
| 291 prefix = f"{'{nbsp}{nbsp}' * level * 2} " | |
| 292 else: | |
| 293 prefix = '' | |
| 294 str = '' | |
| 295 | |
| 296 for elem in expr: | |
| 297 if isinstance(elem, pp.ParseResults): | |
| 298 if not root: | |
| 299 nextlevel = level + 1 | |
| 300 else: | |
| 301 # Do not indent the outer expression | |
| 302 nextlevel = level | |
| 303 | |
| 304 str = str + markupTraverse(elem, level = nextlevel, root = False) | |
| 305 elif elem in ('+', ','): | |
| 306 str = f"{str}{prefix}{opMarkupAsciidoc(elem)} +\n" | |
| 307 else: | |
| 308 str = f"{str}{prefix}{leafMarkupAsciidoc(elem)} +\n" | |
| 309 | |
| 310 return str | |
| 311 | |
| 312 def dependencyMarkup(dependency): | |
| 313 """Return asciidoctor markup for a human-readable equivalent of an API | |
| 314 dependency expression, suitable for use in extension appendix | |
| 315 metadata. | |
| 316 | |
| 317 - dependency - the expression""" | |
| 318 | |
| 319 parsed = dependencyExpr.parseString(dependency) | |
| 320 return markupTraverse(parsed) | |
| 321 | |
| 322 if __name__ == "__main__": | |
| 323 for str in [ 'VK_VERSION_1_0', 'cl_khr_extension_name', 'XR_VERSION_3_2', 'CL_VERSION_1_0' ]: | |
| 324 print(f'{str} -> {conventions.formatVersionOrExtension(str)}') | |
| 325 import sys | |
| 326 sys.exit(0) | |
| 327 | |
| 328 termdict = { | |
| 329 'VK_VERSION_1_1' : True, | |
| 330 'false' : False, | |
| 331 'true' : True, | |
| 332 } | |
| 333 termSupported = lambda name: name in termdict and termdict[name] | |
| 334 | |
| 335 def test(dependency, expected): | |
| 336 val = False | |
| 337 try: | |
| 338 val = evaluateDependency(dependency, termSupported) | |
| 339 except ParseException as pe: | |
| 340 print(dependency, f'failed parse: {dependency}') | |
| 341 except Exception as e: | |
| 342 print(dependency, f'failed eval: {dependency}') | |
| 343 | |
| 344 if val == expected: | |
| 345 True | |
| 346 # print(f'{dependency} = {val} (as expected)') | |
| 347 else: | |
| 348 print(f'{dependency} ERROR: {val} != {expected}') | |
| 349 | |
| 350 # Verify expressions are evaluated left-to-right | |
| 351 | |
| 352 test('false,false+false', False) | |
| 353 test('false,false+true', False) | |
| 354 test('false,true+false', False) | |
| 355 test('false,true+true', True) | |
| 356 test('true,false+false', False) | |
| 357 test('true,false+true', True) | |
| 358 test('true,true+false', False) | |
| 359 test('true,true+true', True) | |
| 360 | |
| 361 test('false,(false+false)', False) | |
| 362 test('false,(false+true)', False) | |
| 363 test('false,(true+false)', False) | |
| 364 test('false,(true+true)', True) | |
| 365 test('true,(false+false)', True) | |
| 366 test('true,(false+true)', True) | |
| 367 test('true,(true+false)', True) | |
| 368 test('true,(true+true)', True) | |
| 369 | |
| 370 | |
| 371 test('false+false,false', False) | |
| 372 test('false+false,true', True) | |
| 373 test('false+true,false', False) | |
| 374 test('false+true,true', True) | |
| 375 test('true+false,false', False) | |
| 376 test('true+false,true', True) | |
| 377 test('true+true,false', True) | |
| 378 test('true+true,true', True) | |
| 379 | |
| 380 test('false+(false,false)', False) | |
| 381 test('false+(false,true)', False) | |
| 382 test('false+(true,false)', False) | |
| 383 test('false+(true,true)', False) | |
| 384 test('true+(false,false)', False) | |
| 385 test('true+(false,true)', True) | |
| 386 test('true+(true,false)', True) | |
| 387 test('true+(true,true)', True) | |
| 388 | |
| 389 # Check formatting | |
| 390 for dependency in [ | |
| 391 #'true', | |
| 392 #'true+true+false', | |
| 393 'true+false', | |
| 394 'true+(true+false),(false,true)', | |
| 395 #'true+((true+false),(false,true))', | |
| 396 'VK_VERSION_1_0+VK_KHR_display', | |
| 397 #'VK_VERSION_1_1+(true,false)', | |
| 398 ]: | |
| 399 print(f'expr = {dependency}\n{dependencyMarkup(dependency)}') | |
| 400 print(f' spec language = {dependencyLanguageSpecMacros(dependency)}') | |
| 401 print(f' comment language = {dependencyLanguageComment(dependency)}') | |
| 402 print(f' C language = {dependencyLanguageC(dependency)}') | |
| 403 print(f' names = {dependencyNames(dependency)}') | |
| 404 print(f' value = {evaluateDependency(dependency, termSupported)}') |
