am 08b5461b: am 6105d36c: Require renderability of SRGB,UNSIGNED_BYTE from NV_sRGB_fo...
[platform/upstream/VK-GL-CTS.git] / scripts / khr_util / registry.py
1 # -*- coding: utf-8 -*-
2
3 import sys, logging, re
4 from lxml import etree
5 from collections import OrderedDict
6 from functools import wraps, partial
7
8 log = logging.getLogger(__name__)
9
10 debug = log.debug
11 info = log.info
12 warning = log.warning
13
14 def warnElem(elem, fmt, *args):
15         warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args)
16
17 class Object(object):
18         def __init__(self, **kwargs):
19                 self.__dict__.update(kwargs)
20
21 class Located(Object):
22         location = None
23
24 class Group(Located): pass
25 class Enum(Located): pass
26 class Enums(Located):
27         name = None
28         comment = None
29         enums = None
30
31 class Type(Located):
32         location = None
33         name=None
34         definition=None
35         api=None
36         requires=None
37
38 def makeObject(cls, elem, **kwargs):
39         kwargs.setdefault('name', elem.get('name'))
40         kwargs.setdefault('comment', elem.get('comment'))
41         kwargs['location'] = (elem.base, elem.sourceline)
42         return cls(**kwargs)
43
44 def parseEnum(eEnum):
45         return makeObject(
46                 Enum, eEnum,
47                 value=eEnum.get('value'),
48                 type=eEnum.get('type'),
49                 alias=eEnum.get('alias'))
50
51 class Param(Located): pass
52
53 class Command(Located):
54         name=None
55         declaration=None
56         type=None
57         ptype=None
58         group=None
59         params=None
60         alias=None
61
62 class Interface(Object): pass
63
64 class Index:
65         def __init__(self, items=[], **kwargs):
66                 self.index = {}
67                 self.items = []
68                 self.__dict__.update(kwargs)
69                 self.update(items)
70
71         def append(self, item):
72                 keys = self.getkeys(item)
73                 for key in keys:
74                         self[key] = item
75                 self.items.append(item)
76
77         def update(self, items):
78                 for item in items:
79                         self.append(item)
80
81         def __iter__(self):
82                 return iter(self.items)
83
84         def nextkey(self, key):
85                 raise KeyError
86
87         def getkeys(self, item):
88                 return []
89
90         def __contains__(self, key):
91                 return key in self.index
92
93         def __setitem__(self, key, item):
94                 if key in self.index:
95                         self.duplicateKey(key, item)
96                 else:
97                         self.index[key] = item
98
99         def duplicateKey(self, key, item):
100                 warning("Duplicate %s: %r", type(item).__name__.lower(), key)
101
102         def __getitem__(self, key):
103                 try:
104                         while True:
105                                 try:
106                                         return self.index[key]
107                                 except KeyError:
108                                         pass
109                                 key = self.nextkey(key)
110                 except KeyError:
111                         item = self.missingKey(key)
112                         self.append(item)
113                         return item
114
115         def missingKey(self, key):
116                 raise KeyError(key)
117
118         def __len__(self):
119                 return len(self.items)
120
121 class ElemNameIndex(Index):
122         def getkeys(self, item):
123                 return [item.get('name')]
124
125         def duplicateKey(self, key, item):
126                 warnElem(item, "Duplicate key: %s", key)
127
128 class CommandIndex(Index):
129         def getkeys(self, item):
130                 return [item.findtext('proto/name'), item.findtext('alias')]
131
132 class NameApiIndex(Index):
133         def getkeys(self, item):
134                 return [(item.get('name'), item.get('api'))]
135
136         def nextkey(self, key):
137                 if len(key) == 2 and key[1] is not None:
138                         return key[0], None
139                 raise KeyError
140
141         def duplicateKey(self, key, item):
142                 warnElem(item, "Duplicate key: %s", key)
143
144 class TypeIndex(NameApiIndex):
145         def getkeys(self, item):
146                 return [(item.get('name') or item.findtext('name'), item.get('api'))]
147
148 class EnumIndex(NameApiIndex):
149         def getkeys(self, item):
150                 name, api, alias = (item.get(attrib) for attrib in ['name', 'api', 'alias'])
151                 return [(name, api)] + ([(alias, api)] if alias is not None else [])
152
153         def duplicateKey(self, (name, api), item):
154                 if name == item.get('alias'):
155                         warnElem(item, "Alias already present: %s", name)
156                 else:
157                         warnElem(item, "Already present")
158
159 class Registry:
160         def __init__(self, eRegistry):
161                 self.types = TypeIndex(eRegistry.findall('types/type'))
162                 self.groups = ElemNameIndex(eRegistry.findall('groups/group'))
163                 self.enums = EnumIndex(eRegistry.findall('enums/enum'))
164                 for eEnum in self.enums:
165                         groupName = eEnum.get('group')
166                         if groupName is not None:
167                                 self.groups[groupName] = eEnum
168                 self.commands = CommandIndex(eRegistry.findall('commands/command'))
169                 self.features = ElemNameIndex(eRegistry.findall('feature'))
170                 self.apis = {}
171                 for eFeature in self.features:
172                         self.apis.setdefault(eFeature.get('api'), []).append(eFeature)
173                 for apiFeatures in self.apis.itervalues():
174                         apiFeatures.sort(key=lambda eFeature: eFeature.get('number'))
175                 self.extensions = ElemNameIndex(eRegistry.findall('extensions/extension'))
176                 self.element = eRegistry
177
178         def getFeatures(self, api, checkVersion=None):
179                 return [eFeature for eFeature in self.apis[api]
180                                 if checkVersion is None or checkVersion(eFeature.get('number'))]
181
182 class NameIndex(Index):
183         createMissing = None
184         kind = "item"
185
186         def getkeys(self, item):
187                 return [item.name]
188
189         def missingKey(self, key):
190                 if self.createMissing:
191                         warning("Reference to implicit %s: %r", self.kind, key)
192                         return self.createMissing(name=key)
193                 else:
194                         raise KeyError
195
196 def matchApi(api1, api2):
197         return api1 is None or api2 is None or api1 == api2
198
199 class Interface(Object):
200         pass
201
202 def extractAlias(eCommand):
203         aliases = eCommand.xpath('alias/@name')
204         return aliases[0] if aliases else None
205
206 def getExtensionName(eExtension):
207         return eExtension.get('name')
208
209 def extensionSupports(eExtension, api, profile=None):
210         if api == 'gl' and profile == 'core':
211                 needSupport = 'glcore'
212         else:
213                 needSupport = api
214         supporteds = eExtension.get('supported').split('|')
215         return needSupport in supporteds
216
217 class InterfaceSpec(Object):
218         def __init__(self):
219                 self.enums = set()
220                 self.types = set()
221                 self.commands = set()
222
223         def addComponent(self, eComponent):
224                 if eComponent.tag == 'require':
225                         def modify(items, item): items.add(item)
226                 else:
227                         assert eComponent.tag == 'remove'
228                         def modify(items, item):
229                                 try:
230                                         items.remove(item)
231                                 except KeyError:
232                                         warning("Tried to remove absent item: %s", item)
233                 for typeName in eComponent.xpath('type/@name'):
234                         modify(self.types, typeName)
235                 for enumName in eComponent.xpath('enum/@name'):
236                         modify(self.enums, enumName)
237                 for commandName in eComponent.xpath('command/@name'):
238                         modify(self.commands, commandName)
239
240         def addComponents(self, elem, api, profile=None):
241                 for eComponent in elem.xpath('require|remove'):
242                         cApi = eComponent.get('api')
243                         cProfile = eComponent.get('profile')
244                         if (matchApi(api, eComponent.get('api')) and
245                                 matchApi(profile, eComponent.get('profile'))):
246                                 self.addComponent(eComponent)
247
248         def addFeature(self, eFeature, api=None, profile=None, force=False):
249                 info('Feature %s', eFeature.get('name'))
250                 if not matchApi(api, eFeature.get('api')):
251                         if not force: return
252                         warnElem(eFeature, 'API %s is not supported', api)
253                 self.addComponents(eFeature, api, profile)
254
255         def addExtension(self, eExtension, api=None, profile=None, force=False):
256                 if not extensionSupports(eExtension, api, profile):
257                         if not force: return
258                         warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api))
259                 self.addComponents(eExtension, api, profile)
260
261 def createInterface(registry, spec, api=None):
262         def parseType(eType):
263                 # todo: apientry
264                 #requires = eType.get('requires')
265                 #if requires is not None:
266                 #    types[requires]
267                 return makeObject(
268                         Type, eType,
269                         name=eType.get('name') or eType.findtext('name'),
270                         definition=''.join(eType.xpath('.//text()')),
271                         api=eType.get('api'),
272                         requires=eType.get('requires'))
273
274         def createType(name):
275                 info('Add type %s', name)
276                 try:
277                         return parseType(registry.types[name, api])
278                 except KeyError:
279                         return Type(name=name)
280
281         def createEnum(enumName):
282                 info('Add enum %s', enumName)
283                 return parseEnum(registry.enums[enumName, api])
284
285         def extractPtype(elem):
286                 ePtype = elem.find('ptype')
287                 if ePtype is None:
288                         return None
289                 return types[ePtype.text]
290
291         def extractGroup(elem):
292                 groupName = elem.get('group')
293                 if groupName is None:
294                         return None
295                 return groups[groupName]
296
297         def parseParam(eParam):
298                 return makeObject(
299                         Param, eParam,
300                         name=eParam.get('name') or eParam.findtext('name'),
301                         declaration=''.join(eParam.xpath('.//text()')).strip(),
302                         type=''.join(eParam.xpath('(.|ptype)/text()')).strip(),
303                         ptype=extractPtype(eParam),
304                         group=extractGroup(eParam))
305
306         def createCommand(commandName):
307                 info('Add command %s', commandName)
308                 eCmd = registry.commands[commandName]
309                 eProto = eCmd.find('proto')
310                 return makeObject(
311                         Command, eCmd,
312                         name=eCmd.findtext('proto/name'),
313                         declaration=''.join(eProto.xpath('.//text()')).strip(),
314                         type=''.join(eProto.xpath('(.|ptype)/text()')).strip(),
315                         ptype=extractPtype(eProto),
316                         group=extractGroup(eProto),
317                         alias=extractAlias(eCmd),
318                         params=NameIndex(map(parseParam, eCmd.findall('param'))))
319
320         def createGroup(name):
321                 info('Add group %s', name)
322                 try:
323                         eGroup = registry.groups[name]
324                 except KeyError:
325                         return Group(name=name)
326                 return makeObject(
327                         Group, eGroup,
328                         # Missing enums are often from exotic extensions. Don't create dummy entries,
329                         # just filter them out.
330                         enums=NameIndex(enums[name] for name in eGroup.xpath('enum/@name')
331                                                         if name in enums))
332
333         def sortedIndex(items):
334                 return NameIndex(sorted(items, key=lambda item: item.location))
335
336         groups = NameIndex(createMissing=createGroup, kind="group")
337         types = NameIndex(map(createType, spec.types),
338                                           createMissing=createType, kind="type")
339         enums = NameIndex(map(createEnum, spec.enums),
340                                           createMissing=Enum, kind="enum")
341         commands = NameIndex(map(createCommand, spec.commands),
342                                                  createMissing=Command, kind="command")
343
344         # This is a mess because the registry contains alias chains whose
345         # midpoints might not be included in the interface even though
346         # endpoints are.
347         for command in commands:
348                 alias = command.alias
349                 aliasCommand = None
350                 while alias is not None:
351                         aliasCommand = registry.commands[alias]
352                         alias = extractAlias(aliasCommand)
353                 command.alias = None
354                 if aliasCommand is not None:
355                         name = aliasCommand.findtext('proto/name')
356                         if name in commands:
357                                 command.alias = commands[name]
358
359         return Interface(
360                 types=sortedIndex(types),
361                 enums=sortedIndex(enums),
362                 groups=sortedIndex(groups),
363                 commands=sortedIndex(commands))
364
365
366 def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False):
367         available = set(protects)
368         spec = InterfaceSpec()
369
370         if version is None or version is False:
371                 def check(v): return False
372         elif version is True:
373                 def check(v): return True
374         else:
375                 def check(v): return v <= version
376
377         for eFeature in registry.getFeatures(api, check):
378                 spec.addFeature(eFeature, api, profile, force)
379
380         for extName in extensionNames:
381                 eExtension = registry.extensions[extName]
382                 protect = eExtension.get('protect')
383                 if protect is not None and protect not in available:
384                         warnElem(eExtension, "Unavailable dependency %s", protect)
385                         if not force:
386                                 continue
387                 spec.addExtension(eExtension, api, profile, force)
388                 available.add(extName)
389
390         return spec
391
392 def interface(registry, api, **kwargs):
393         s = spec(registry, api, **kwargs)
394         return createInterface(registry, s, api)
395
396 def parse(path):
397         return Registry(etree.parse(path))