Tizen 2.0 Release
[profile/ivi/osmesa.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()