1 # -*- coding: utf-8 -*-
3 import sys, logging, re
5 from collections import OrderedDict
6 from functools import wraps, partial
8 log = logging.getLogger(__name__)
14 def warnElem(elem, fmt, *args):
15 warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args)
18 def __init__(self, **kwargs):
19 self.__dict__.update(kwargs)
21 class Located(Object):
24 class Group(Located): pass
25 class Enum(Located): pass
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)
47 value=eEnum.get('value'),
48 type=eEnum.get('type'),
49 alias=eEnum.get('alias'))
51 class Param(Located): pass
53 class Command(Located):
62 class Interface(Object): pass
65 def __init__(self, items=[], **kwargs):
68 self.__dict__.update(kwargs)
71 def append(self, item):
72 keys = self.getkeys(item)
75 self.items.append(item)
77 def update(self, items):
82 return iter(self.items)
84 def nextkey(self, key):
87 def getkeys(self, item):
90 def __contains__(self, key):
91 return key in self.index
93 def __setitem__(self, key, item):
95 self.duplicateKey(key, item)
97 self.index[key] = item
99 def duplicateKey(self, key, item):
100 warning("Duplicate %s: %r", type(item).__name__.lower(), key)
102 def __getitem__(self, key):
106 return self.index[key]
109 key = self.nextkey(key)
111 item = self.missingKey(key)
115 def missingKey(self, key):
119 return len(self.items)
121 class ElemNameIndex(Index):
122 def getkeys(self, item):
123 return [item.get('name')]
125 def duplicateKey(self, key, item):
126 warnElem(item, "Duplicate key: %s", key)
128 class CommandIndex(Index):
129 def getkeys(self, item):
130 return [item.findtext('proto/name'), item.findtext('alias')]
132 class NameApiIndex(Index):
133 def getkeys(self, item):
134 return [(item.get('name'), item.get('api'))]
136 def nextkey(self, key):
137 if len(key) == 2 and key[1] is not None:
141 def duplicateKey(self, key, item):
142 warnElem(item, "Duplicate key: %s", key)
144 class TypeIndex(NameApiIndex):
145 def getkeys(self, item):
146 return [(item.get('name') or item.findtext('name'), item.get('api'))]
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 [])
153 def duplicateKey(self, (name, api), item):
154 if name == item.get('alias'):
155 warnElem(item, "Alias already present: %s", name)
157 warnElem(item, "Already present")
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'))
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
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'))]
182 class NameIndex(Index):
186 def getkeys(self, item):
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)
196 def matchApi(api1, api2):
197 return api1 is None or api2 is None or api1 == api2
199 class Interface(Object):
202 def extractAlias(eCommand):
203 aliases = eCommand.xpath('alias/@name')
204 return aliases[0] if aliases else None
206 def getExtensionName(eExtension):
207 return eExtension.get('name')
209 def extensionSupports(eExtension, api, profile=None):
210 if api == 'gl' and profile == 'core':
211 needSupport = 'glcore'
214 supporteds = eExtension.get('supported').split('|')
215 return needSupport in supporteds
217 class InterfaceSpec(Object):
221 self.commands = set()
223 def addComponent(self, eComponent):
224 if eComponent.tag == 'require':
225 def modify(items, item): items.add(item)
227 assert eComponent.tag == 'remove'
228 def modify(items, item):
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)
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)
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')):
252 warnElem(eFeature, 'API %s is not supported', api)
253 self.addComponents(eFeature, api, profile)
255 def addExtension(self, eExtension, api=None, profile=None, force=False):
256 if not extensionSupports(eExtension, api, profile):
258 warnElem(eExtension, '%s is not supported in API %s' % (getExtensionName(eExtension), api))
259 self.addComponents(eExtension, api, profile)
261 def createInterface(registry, spec, api=None):
262 def parseType(eType):
264 #requires = eType.get('requires')
265 #if requires is not None:
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'))
274 def createType(name):
275 info('Add type %s', name)
277 return parseType(registry.types[name, api])
279 return Type(name=name)
281 def createEnum(enumName):
282 info('Add enum %s', enumName)
283 return parseEnum(registry.enums[enumName, api])
285 def extractPtype(elem):
286 ePtype = elem.find('ptype')
289 return types[ePtype.text]
291 def extractGroup(elem):
292 groupName = elem.get('group')
293 if groupName is None:
295 return groups[groupName]
297 def parseParam(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))
306 def createCommand(commandName):
307 info('Add command %s', commandName)
308 eCmd = registry.commands[commandName]
309 eProto = eCmd.find('proto')
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'))))
320 def createGroup(name):
321 info('Add group %s', name)
323 eGroup = registry.groups[name]
325 return Group(name=name)
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')
333 def sortedIndex(items):
334 return NameIndex(sorted(items, key=lambda item: item.location))
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")
344 # This is a mess because the registry contains alias chains whose
345 # midpoints might not be included in the interface even though
347 for command in commands:
348 alias = command.alias
350 while alias is not None:
351 aliasCommand = registry.commands[alias]
352 alias = extractAlias(aliasCommand)
354 if aliasCommand is not None:
355 name = aliasCommand.findtext('proto/name')
357 command.alias = commands[name]
360 types=sortedIndex(types),
361 enums=sortedIndex(enums),
362 groups=sortedIndex(groups),
363 commands=sortedIndex(commands))
366 def spec(registry, api, version=None, profile=None, extensionNames=[], protects=[], force=False):
367 available = set(protects)
368 spec = InterfaceSpec()
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
375 def check(v): return v <= version
377 for eFeature in registry.getFeatures(api, check):
378 spec.addFeature(eFeature, api, profile, force)
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)
387 spec.addExtension(eExtension, api, profile, force)
388 available.add(extName)
392 def interface(registry, api, **kwargs):
393 s = spec(registry, api, **kwargs)
394 return createInterface(registry, s, api)
397 return Registry(etree.parse(path))