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
23import sys, logging, re
24from lxml import etree
25from collections import OrderedDict
26from functools import wraps, partial
27
28log = logging.getLogger(__name__)
29
30debug = log.debug
31info = log.info
32warning = log.warning
33
34def warnElem(elem, fmt, *args):
35	warning('%s:%d, %s %s: ' + fmt, elem.base, elem.sourceline, elem.tag, elem.get('name') or '', *args)
36
37class Object(object):
38	def __init__(self, **kwargs):
39		self.__dict__.update(kwargs)
40
41class Located(Object):
42	location = None
43
44class Group(Located): pass
45class Enum(Located): pass
46class Enums(Located):
47	name = None
48	comment = None
49	enums = None
50
51class Type(Located):
52	location = None
53	name=None
54	definition=None
55	api=None
56	requires=None
57
58def 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
64def parseEnum(eEnum):
65	return makeObject(
66		Enum, eEnum,
67		value=eEnum.get('value'),
68		type=eEnum.get('type'),
69		alias=eEnum.get('alias'))
70
71class Param(Located): pass
72
73class Command(Located):
74	name=None
75	declaration=None
76	type=None
77	ptype=None
78	group=None
79	params=None
80	alias=None
81
82class Interface(Object): pass
83
84class 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
141class 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
148class CommandIndex(Index):
149	def getkeys(self, item):
150		return [item.findtext('proto/name'), item.findtext('alias')]
151
152class 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
164class TypeIndex(NameApiIndex):
165	def getkeys(self, item):
166		return [(item.get('name') or item.findtext('name'), item.get('api'))]
167
168class 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
179class 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
202class 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
216def matchApi(api1, api2):
217	return api1 is None or api2 is None or api1 == api2
218
219class Interface(Object):
220	pass
221
222def extractAlias(eCommand):
223	aliases = eCommand.xpath('alias/@name')
224	return aliases[0] if aliases else None
225
226def getExtensionName(eExtension):
227	return eExtension.get('name')
228
229def 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
237class 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
281def 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
386def 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
412def interface(registry, api, **kwargs):
413	s = spec(registry, api, **kwargs)
414	return createInterface(registry, s, api)
415
416def parse(path):
417	return Registry(etree.parse(path))
418