mesa: Move api_exec_es*.c into mesa/main
[mesa.git] / src / mesa / main / APIspec.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a
6 # copy of this software and associated documentation files (the "Software"),
7 # to deal in the Software without restriction, including without limitation
8 # on the rights to use, copy, modify, merge, publish, distribute, sub
9 # license, and/or sell copies of the Software, and to permit persons to whom
10 # the Software is furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice (including the next
13 # paragraph) shall be included in all copies or substantial portions of the
14 # Software.
15 #
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
19 # IBM AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22 # IN THE SOFTWARE.
23 """
24 A parser for APIspec.
25 """
26
27 class SpecError(Exception):
28 """Error in the spec file."""
29
30
31 class Spec(object):
32 """A Spec is an abstraction of the API spec."""
33
34 def __init__(self, doc):
35 self.doc = doc
36
37 self.spec_node = doc.getRootElement()
38 self.tmpl_nodes = {}
39 self.api_nodes = {}
40 self.impl_node = None
41
42 # parse <apispec>
43 node = self.spec_node.children
44 while node:
45 if node.type == "element":
46 if node.name == "template":
47 self.tmpl_nodes[node.prop("name")] = node
48 elif node.name == "api":
49 self.api_nodes[node.prop("name")] = node
50 else:
51 raise SpecError("unexpected node %s in apispec" %
52 node.name)
53 node = node.next
54
55 # find an implementation
56 for name, node in self.api_nodes.iteritems():
57 if node.prop("implementation") == "true":
58 self.impl_node = node
59 break
60 if not self.impl_node:
61 raise SpecError("unable to find an implementation")
62
63 def get_impl(self):
64 """Return the implementation."""
65 return API(self, self.impl_node)
66
67 def get_api(self, name):
68 """Return an API."""
69 return API(self, self.api_nodes[name])
70
71
72 class API(object):
73 """An API consists of categories and functions."""
74
75 def __init__(self, spec, api_node):
76 self.name = api_node.prop("name")
77 self.is_impl = (api_node.prop("implementation") == "true")
78
79 self.categories = []
80 self.functions = []
81
82 # parse <api>
83 func_nodes = []
84 node = api_node.children
85 while node:
86 if node.type == "element":
87 if node.name == "category":
88 cat = node.prop("name")
89 self.categories.append(cat)
90 elif node.name == "function":
91 func_nodes.append(node)
92 else:
93 raise SpecError("unexpected node %s in api" % node.name)
94 node = node.next
95
96 # realize functions
97 for func_node in func_nodes:
98 tmpl_node = spec.tmpl_nodes[func_node.prop("template")]
99 try:
100 func = Function(tmpl_node, func_node, self.is_impl,
101 self.categories)
102 except SpecError, e:
103 func_name = func_node.prop("name")
104 raise SpecError("failed to parse %s: %s" % (func_name, e))
105 self.functions.append(func)
106
107 def match(self, func, conversions={}):
108 """Find a matching function in the API."""
109 match = None
110 need_conv = False
111 for f in self.functions:
112 matched, conv = f.match(func, conversions)
113 if matched:
114 match = f
115 need_conv = conv
116 # exact match
117 if not need_conv:
118 break
119 return (match, need_conv)
120
121
122 class Function(object):
123 """Parse and realize a <template> node."""
124
125 def __init__(self, tmpl_node, func_node, force_skip_desc=False, categories=[]):
126 self.tmpl_name = tmpl_node.prop("name")
127 self.direction = tmpl_node.prop("direction")
128
129 self.name = func_node.prop("name")
130 self.prefix = func_node.prop("default_prefix")
131 self.is_external = (func_node.prop("external") == "true")
132
133 if force_skip_desc:
134 self._skip_desc = True
135 else:
136 self._skip_desc = (func_node.prop("skip_desc") == "true")
137
138 self._categories = categories
139
140 # these attributes decide how the template is realized
141 self._gltype = func_node.prop("gltype")
142 if func_node.hasProp("vector_size"):
143 self._vector_size = int(func_node.prop("vector_size"))
144 else:
145 self._vector_size = 0
146 self._expand_vector = (func_node.prop("expand_vector") == "true")
147
148 self.return_type = "void"
149 param_nodes = []
150
151 # find <proto>
152 proto_node = tmpl_node.children
153 while proto_node:
154 if proto_node.type == "element" and proto_node.name == "proto":
155 break
156 proto_node = proto_node.next
157 if not proto_node:
158 raise SpecError("no proto")
159 # and parse it
160 node = proto_node.children
161 while node:
162 if node.type == "element":
163 if node.name == "return":
164 self.return_type = node.prop("type")
165 elif node.name == "param" or node.name == "vector":
166 if self.support_node(node):
167 # make sure the node is not hidden
168 if not (self._expand_vector and
169 (node.prop("hide_if_expanded") == "true")):
170 param_nodes.append(node)
171 else:
172 raise SpecError("unexpected node %s in proto" % node.name)
173 node = node.next
174
175 self._init_params(param_nodes)
176 self._init_descs(tmpl_node, param_nodes)
177
178 def __str__(self):
179 return "%s %s%s(%s)" % (self.return_type, self.prefix, self.name,
180 self.param_string(True))
181
182 def _init_params(self, param_nodes):
183 """Parse and initialize parameters."""
184 self.params = []
185
186 for param_node in param_nodes:
187 size = self.param_node_size(param_node)
188 # when no expansion, vector is just like param
189 if param_node.name == "param" or not self._expand_vector:
190 param = Parameter(param_node, self._gltype, size)
191 self.params.append(param)
192 continue
193
194 if not size or size > param_node.lsCountNode():
195 raise SpecError("could not expand %s with unknown or "
196 "mismatch sizes" % param.name)
197
198 # expand the vector
199 expanded_params = []
200 child = param_node.children
201 while child:
202 if (child.type == "element" and child.name == "param" and
203 self.support_node(child)):
204 expanded_params.append(Parameter(child, self._gltype))
205 if len(expanded_params) == size:
206 break
207 child = child.next
208 # just in case that lsCountNode counts unknown nodes
209 if len(expanded_params) < size:
210 raise SpecError("not enough named parameters")
211
212 self.params.extend(expanded_params)
213
214 def _init_descs(self, tmpl_node, param_nodes):
215 """Parse and initialize parameter descriptions."""
216 self.checker = Checker()
217 if self._skip_desc:
218 return
219
220 node = tmpl_node.children
221 while node:
222 if node.type == "element" and node.name == "desc":
223 if self.support_node(node):
224 # parse <desc>
225 desc = Description(node, self._categories)
226 self.checker.add_desc(desc)
227 node = node.next
228
229 self.checker.validate(self, param_nodes)
230
231 def support_node(self, node):
232 """Return true if a node is in the supported category."""
233 return (not node.hasProp("category") or
234 node.prop("category") in self._categories)
235
236 def get_param(self, name):
237 """Return the named parameter."""
238 for param in self.params:
239 if param.name == name:
240 return param
241 return None
242
243 def param_node_size(self, param):
244 """Return the size of a vector."""
245 if param.name != "vector":
246 return 0
247
248 size = param.prop("size")
249 if size.isdigit():
250 size = int(size)
251 else:
252 size = 0
253 if not size:
254 size = self._vector_size
255 if not size and self._expand_vector:
256 # return the number of named parameters
257 size = param.lsCountNode()
258 return size
259
260 def param_string(self, declaration):
261 """Return the C code of the parameters."""
262 args = []
263 if declaration:
264 for param in self.params:
265 sep = "" if param.type.endswith("*") else " "
266 args.append("%s%s%s" % (param.type, sep, param.name))
267 if not args:
268 args.append("void")
269 else:
270 for param in self.params:
271 args.append(param.name)
272 return ", ".join(args)
273
274 def match(self, other, conversions={}):
275 """Return true if the functions match, probably with a conversion."""
276 if (self.tmpl_name != other.tmpl_name or
277 self.return_type != other.return_type or
278 len(self.params) != len(other.params)):
279 return (False, False)
280
281 need_conv = False
282 for i in xrange(len(self.params)):
283 src = other.params[i]
284 dst = self.params[i]
285 if (src.is_vector != dst.is_vector or src.size != dst.size):
286 return (False, False)
287 if src.type != dst.type:
288 if dst.base_type() in conversions.get(src.base_type(), []):
289 need_conv = True
290 else:
291 # unable to convert
292 return (False, False)
293
294 return (True, need_conv)
295
296
297 class Parameter(object):
298 """A parameter of a function."""
299
300 def __init__(self, param_node, gltype=None, size=0):
301 self.is_vector = (param_node.name == "vector")
302
303 self.name = param_node.prop("name")
304 self.size = size
305
306 type = param_node.prop("type")
307 if gltype:
308 type = type.replace("GLtype", gltype)
309 elif type.find("GLtype") != -1:
310 raise SpecError("parameter %s has unresolved type" % self.name)
311
312 self.type = type
313
314 def base_type(self):
315 """Return the base GL type by stripping qualifiers."""
316 return [t for t in self.type.split(" ") if t.startswith("GL")][0]
317
318
319 class Checker(object):
320 """A checker is the collection of all descriptions on the same level.
321 Descriptions of the same parameter are concatenated.
322 """
323
324 def __init__(self):
325 self.switches = {}
326 self.switch_constants = {}
327
328 def add_desc(self, desc):
329 """Add a description."""
330 # TODO allow index to vary
331 const_attrs = ["index", "error", "convert", "size_str"]
332 if desc.name not in self.switches:
333 self.switches[desc.name] = []
334 self.switch_constants[desc.name] = {}
335 for attr in const_attrs:
336 self.switch_constants[desc.name][attr] = None
337
338 # some attributes, like error code, should be the same for all descs
339 consts = self.switch_constants[desc.name]
340 for attr in const_attrs:
341 if getattr(desc, attr) is not None:
342 if (consts[attr] is not None and
343 consts[attr] != getattr(desc, attr)):
344 raise SpecError("mismatch %s for %s" % (attr, desc.name))
345 consts[attr] = getattr(desc, attr)
346
347 self.switches[desc.name].append(desc)
348
349 def validate(self, func, param_nodes):
350 """Validate the checker against a function."""
351 tmp = Checker()
352
353 for switch in self.switches.itervalues():
354 valid_descs = []
355 for desc in switch:
356 if desc.validate(func, param_nodes):
357 valid_descs.append(desc)
358 # no possible values
359 if not valid_descs:
360 return False
361 for desc in valid_descs:
362 if not desc._is_noop:
363 tmp.add_desc(desc)
364
365 self.switches = tmp.switches
366 self.switch_constants = tmp.switch_constants
367 return True
368
369 def flatten(self, name=None):
370 """Return a flat list of all descriptions of the named parameter."""
371 flat_list = []
372 for switch in self.switches.itervalues():
373 for desc in switch:
374 if not name or desc.name == name:
375 flat_list.append(desc)
376 flat_list.extend(desc.checker.flatten(name))
377 return flat_list
378
379 def always_check(self, name):
380 """Return true if the parameter is checked in all possible pathes."""
381 if name in self.switches:
382 return True
383
384 # a param is always checked if any of the switch always checks it
385 for switch in self.switches.itervalues():
386 # a switch always checks it if all of the descs always check it
387 always = True
388 for desc in switch:
389 if not desc.checker.always_check(name):
390 always = False
391 break
392 if always:
393 return True
394 return False
395
396 def _c_switch(self, name, indent="\t"):
397 """Output C switch-statement for the named parameter, for debug."""
398 switch = self.switches.get(name, [])
399 # make sure there are valid values
400 need_switch = False
401 for desc in switch:
402 if desc.values:
403 need_switch = True
404 if not need_switch:
405 return []
406
407 stmts = []
408 var = switch[0].name
409 if switch[0].index >= 0:
410 var += "[%d]" % switch[0].index
411 stmts.append("switch (%s) { /* assume GLenum */" % var)
412
413 for desc in switch:
414 if desc.values:
415 for val in desc.values:
416 stmts.append("case %s:" % val)
417 for dep_name in desc.checker.switches.iterkeys():
418 dep_stmts = [indent + s for s in desc.checker._c_switch(dep_name, indent)]
419 stmts.extend(dep_stmts)
420 stmts.append(indent + "break;")
421
422 stmts.append("default:")
423 stmts.append(indent + "ON_ERROR(%s);" % switch[0].error);
424 stmts.append(indent + "break;")
425 stmts.append("}")
426
427 return stmts
428
429 def dump(self, indent="\t"):
430 """Dump the descriptions in C code."""
431 stmts = []
432 for name in self.switches.iterkeys():
433 c_switch = self._c_switch(name)
434 print "\n".join(c_switch)
435
436
437 class Description(object):
438 """A description desribes a parameter and its relationship with other
439 parameters.
440 """
441
442 def __init__(self, desc_node, categories=[]):
443 self._categories = categories
444 self._is_noop = False
445
446 self.name = desc_node.prop("name")
447 self.index = -1
448
449 self.error = desc_node.prop("error") or "GL_INVALID_ENUM"
450 # vector_size may be C code
451 self.size_str = desc_node.prop("vector_size")
452
453 self._has_enum = False
454 self.values = []
455 dep_nodes = []
456
457 # parse <desc>
458 valid_names = ["value", "range", "desc"]
459 node = desc_node.children
460 while node:
461 if node.type == "element":
462 if node.name in valid_names:
463 # ignore nodes that require unsupported categories
464 if (node.prop("category") and
465 node.prop("category") not in self._categories):
466 node = node.next
467 continue
468 else:
469 raise SpecError("unexpected node %s in desc" % node.name)
470
471 if node.name == "value":
472 val = node.prop("name")
473 if not self._has_enum and val.startswith("GL_"):
474 self._has_enum = True
475 self.values.append(val)
476 elif node.name == "range":
477 first = int(node.prop("from"))
478 last = int(node.prop("to"))
479 base = node.prop("base") or ""
480 if not self._has_enum and base.startswith("GL_"):
481 self._has_enum = True
482 # expand range
483 for i in xrange(first, last + 1):
484 self.values.append("%s%d" % (base, i))
485 else: # dependent desc
486 dep_nodes.append(node)
487 node = node.next
488
489 # default to convert if there is no enum
490 self.convert = not self._has_enum
491 if desc_node.hasProp("convert"):
492 self.convert = (desc_node.prop("convert") == "true")
493
494 self._init_deps(dep_nodes)
495
496 def _init_deps(self, dep_nodes):
497 """Parse and initialize dependents."""
498 self.checker = Checker()
499
500 for dep_node in dep_nodes:
501 # recursion!
502 dep = Description(dep_node, self._categories)
503 self.checker.add_desc(dep)
504
505 def _search_param_node(self, param_nodes, name=None):
506 """Search the template parameters for the named node."""
507 param_node = None
508 param_index = -1
509
510 if not name:
511 name = self.name
512 for node in param_nodes:
513 if name == node.prop("name"):
514 param_node = node
515 elif node.name == "vector":
516 child = node.children
517 idx = 0
518 while child:
519 if child.type == "element" and child.name == "param":
520 if name == child.prop("name"):
521 param_node = node
522 param_index = idx
523 break
524 idx += 1
525 child = child.next
526 if param_node:
527 break
528 return (param_node, param_index)
529
530 def _find_final(self, func, param_nodes):
531 """Find the final parameter."""
532 param = func.get_param(self.name)
533 param_index = -1
534
535 # the described param is not in the final function
536 if not param:
537 # search the template parameters
538 node, index = self._search_param_node(param_nodes)
539 if not node:
540 raise SpecError("invalid desc %s in %s" %
541 (self.name, func.name))
542
543 # a named parameter of a vector
544 if index >= 0:
545 param = func.get_param(node.prop("name"))
546 param_index = index
547 elif node.name == "vector":
548 # must be an expanded vector, check its size
549 if self.size_str and self.size_str.isdigit():
550 size = int(self.size_str)
551 expanded_size = func.param_node_size(node)
552 if size != expanded_size:
553 return (False, None, -1)
554 # otherwise, it is a valid, but no-op, description
555
556 return (True, param, param_index)
557
558 def validate(self, func, param_nodes):
559 """Validate a description against certain function."""
560 if self.checker.switches and not self.values:
561 raise SpecError("no valid values for %s" % self.name)
562
563 valid, param, param_index = self._find_final(func, param_nodes)
564 if not valid:
565 return False
566
567 # the description is valid, but the param is gone
568 # mark it no-op so that it will be skipped
569 if not param:
570 self._is_noop = True
571 return True
572
573 if param.is_vector:
574 # if param was known, this should have been done in __init__
575 if self._has_enum:
576 self.size_str = "1"
577 # size mismatch
578 if (param.size and self.size_str and self.size_str.isdigit() and
579 param.size != int(self.size_str)):
580 return False
581 elif self.size_str:
582 # only vector accepts vector_size
583 raise SpecError("vector_size is invalid for %s" % param.name)
584
585 if not self.checker.validate(func, param_nodes):
586 return False
587
588 # update the description
589 self.name = param.name
590 self.index = param_index
591
592 return True
593
594
595 def main():
596 import libxml2
597
598 filename = "APIspec.xml"
599 apinames = ["GLES1.1", "GLES2.0"]
600
601 doc = libxml2.readFile(filename, None,
602 libxml2.XML_PARSE_DTDLOAD +
603 libxml2.XML_PARSE_DTDVALID +
604 libxml2.XML_PARSE_NOBLANKS)
605
606 spec = Spec(doc)
607 impl = spec.get_impl()
608 for apiname in apinames:
609 spec.get_api(apiname)
610
611 doc.freeDoc()
612
613 print "%s is successfully parsed" % filename
614
615
616 if __name__ == "__main__":
617 main()