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