3 # Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
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:
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
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
27 class SpecError(Exception):
28 """Error in the spec file."""
32 """A Spec is an abstraction of the API spec."""
34 def __init__(self
, doc
):
37 self
.spec_node
= doc
.getRootElement()
43 node
= self
.spec_node
.children
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
51 raise SpecError("unexpected node %s in apispec" %
55 # find an implementation
56 for name
, node
in self
.api_nodes
.iteritems():
57 if node
.prop("implementation") == "true":
60 if not self
.impl_node
:
61 raise SpecError("unable to find an implementation")
64 """Return the implementation."""
65 return API(self
, self
.impl_node
)
67 def get_api(self
, name
):
69 return API(self
, self
.api_nodes
[name
])
73 """An API consists of categories and functions."""
75 def __init__(self
, spec
, api_node
):
76 self
.name
= api_node
.prop("name")
77 self
.is_impl
= (api_node
.prop("implementation") == "true")
84 node
= api_node
.children
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
)
93 raise SpecError("unexpected node %s in api" % node
.name
)
97 for func_node
in func_nodes
:
98 tmpl_node
= spec
.tmpl_nodes
[func_node
.prop("template")]
100 func
= Function(tmpl_node
, func_node
, self
.is_impl
,
103 func_name
= func_node
.prop("name")
104 raise SpecError("failed to parse %s: %s" % (func_name
, e
))
105 self
.functions
.append(func
)
107 def match(self
, func
, conversions
={}):
108 """Find a matching function in the API."""
111 for f
in self
.functions
:
112 matched
, conv
= f
.match(func
, conversions
)
119 return (match
, need_conv
)
122 class Function(object):
123 """Parse and realize a <template> node."""
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")
129 self
.name
= func_node
.prop("name")
130 self
.prefix
= func_node
.prop("default_prefix")
131 self
.is_external
= (func_node
.prop("external") == "true")
134 self
._skip
_desc
= True
136 self
._skip
_desc
= (func_node
.prop("skip_desc") == "true")
138 self
._categories
= categories
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"))
145 self
._vector
_size
= 0
146 self
._expand
_vector
= (func_node
.prop("expand_vector") == "true")
148 self
.return_type
= "void"
152 proto_node
= tmpl_node
.children
154 if proto_node
.type == "element" and proto_node
.name
== "proto":
156 proto_node
= proto_node
.next
158 raise SpecError("no proto")
160 node
= proto_node
.children
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
)
172 raise SpecError("unexpected node %s in proto" % node
.name
)
175 self
._init
_params
(param_nodes
)
176 self
._init
_descs
(tmpl_node
, param_nodes
)
179 return "%s %s%s(%s)" % (self
.return_type
, self
.prefix
, self
.name
,
180 self
.param_string(True))
182 def _init_params(self
, param_nodes
):
183 """Parse and initialize parameters."""
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
)
194 if not size
or size
> param_node
.lsCountNode():
195 raise SpecError("could not expand %s with unknown or "
196 "mismatch sizes" % param
.name
)
200 child
= param_node
.children
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
:
208 # just in case that lsCountNode counts unknown nodes
209 if len(expanded_params
) < size
:
210 raise SpecError("not enough named parameters")
212 self
.params
.extend(expanded_params
)
214 def _init_descs(self
, tmpl_node
, param_nodes
):
215 """Parse and initialize parameter descriptions."""
216 self
.checker
= Checker()
220 node
= tmpl_node
.children
222 if node
.type == "element" and node
.name
== "desc":
223 if self
.support_node(node
):
225 desc
= Description(node
, self
._categories
)
226 self
.checker
.add_desc(desc
)
229 self
.checker
.validate(self
, param_nodes
)
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
)
236 def get_param(self
, name
):
237 """Return the named parameter."""
238 for param
in self
.params
:
239 if param
.name
== name
:
243 def param_node_size(self
, param
):
244 """Return the size of a vector."""
245 if param
.name
!= "vector":
248 size
= param
.prop("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()
260 def param_string(self
, declaration
):
261 """Return the C code of the parameters."""
264 for param
in self
.params
:
265 sep
= "" if param
.type.endswith("*") else " "
266 args
.append("%s%s%s" % (param
.type, sep
, param
.name
))
270 for param
in self
.params
:
271 args
.append(param
.name
)
272 return ", ".join(args
)
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)
282 for i
in xrange(len(self
.params
)):
283 src
= other
.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(), []):
292 return (False, False)
294 return (True, need_conv
)
297 class Parameter(object):
298 """A parameter of a function."""
300 def __init__(self
, param_node
, gltype
=None, size
=0):
301 self
.is_vector
= (param_node
.name
== "vector")
303 self
.name
= param_node
.prop("name")
306 type = param_node
.prop("type")
308 type = type.replace("GLtype", gltype
)
309 elif type.find("GLtype") != -1:
310 raise SpecError("parameter %s has unresolved type" % self
.name
)
315 """Return the base GL type by stripping qualifiers."""
316 return [t
for t
in self
.type.split(" ") if t
.startswith("GL")][0]
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.
326 self
.switch_constants
= {}
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
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
)
347 self
.switches
[desc
.name
].append(desc
)
349 def validate(self
, func
, param_nodes
):
350 """Validate the checker against a function."""
353 for switch
in self
.switches
.itervalues():
356 if desc
.validate(func
, param_nodes
):
357 valid_descs
.append(desc
)
361 for desc
in valid_descs
:
362 if not desc
._is
_noop
:
365 self
.switches
= tmp
.switches
366 self
.switch_constants
= tmp
.switch_constants
369 def flatten(self
, name
=None):
370 """Return a flat list of all descriptions of the named parameter."""
372 for switch
in self
.switches
.itervalues():
374 if not name
or desc
.name
== name
:
375 flat_list
.append(desc
)
376 flat_list
.extend(desc
.checker
.flatten(name
))
379 def always_check(self
, name
):
380 """Return true if the parameter is checked in all possible pathes."""
381 if name
in self
.switches
:
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
389 if not desc
.checker
.always_check(name
):
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
409 if switch
[0].index
>= 0:
410 var
+= "[%d]" % switch
[0].index
411 stmts
.append("switch (%s) { /* assume GLenum */" % var
)
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;")
422 stmts
.append("default:")
423 stmts
.append(indent
+ "ON_ERROR(%s);" % switch
[0].error
);
424 stmts
.append(indent
+ "break;")
429 def dump(self
, indent
="\t"):
430 """Dump the descriptions in C code."""
432 for name
in self
.switches
.iterkeys():
433 c_switch
= self
._c
_switch
(name
)
434 print "\n".join(c_switch
)
437 class Description(object):
438 """A description desribes a parameter and its relationship with other
442 def __init__(self
, desc_node
, categories
=[]):
443 self
._categories
= categories
444 self
._is
_noop
= False
446 self
.name
= desc_node
.prop("name")
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")
453 self
._has
_enum
= False
458 valid_names
= ["value", "range", "desc"]
459 node
= desc_node
.children
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
):
469 raise SpecError("unexpected node %s in desc" % node
.name
)
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
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
)
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")
494 self
._init
_deps
(dep_nodes
)
496 def _init_deps(self
, dep_nodes
):
497 """Parse and initialize dependents."""
498 self
.checker
= Checker()
500 for dep_node
in dep_nodes
:
502 dep
= Description(dep_node
, self
._categories
)
503 self
.checker
.add_desc(dep
)
505 def _search_param_node(self
, param_nodes
, name
=None):
506 """Search the template parameters for the named node."""
512 for node
in param_nodes
:
513 if name
== node
.prop("name"):
515 elif node
.name
== "vector":
516 child
= node
.children
519 if child
.type == "element" and child
.name
== "param":
520 if name
== child
.prop("name"):
528 return (param_node
, param_index
)
530 def _find_final(self
, func
, param_nodes
):
531 """Find the final parameter."""
532 param
= func
.get_param(self
.name
)
535 # the described param is not in the final function
537 # search the template parameters
538 node
, index
= self
._search
_param
_node
(param_nodes
)
540 raise SpecError("invalid desc %s in %s" %
541 (self
.name
, func
.name
))
543 # a named parameter of a vector
545 param
= func
.get_param(node
.prop("name"))
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
556 return (True, param
, param_index
)
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
)
563 valid
, param
, param_index
= self
._find
_final
(func
, param_nodes
)
567 # the description is valid, but the param is gone
568 # mark it no-op so that it will be skipped
574 # if param was known, this should have been done in __init__
578 if (param
.size
and self
.size_str
and self
.size_str
.isdigit() and
579 param
.size
!= int(self
.size_str
)):
582 # only vector accepts vector_size
583 raise SpecError("vector_size is invalid for %s" % param
.name
)
585 if not self
.checker
.validate(func
, param_nodes
):
588 # update the description
589 self
.name
= param
.name
590 self
.index
= param_index
598 filename
= "APIspec.xml"
599 apinames
= ["GLES1.1", "GLES2.0"]
601 doc
= libxml2
.readFile(filename
, None,
602 libxml2
.XML_PARSE_DTDLOAD
+
603 libxml2
.XML_PARSE_DTDVALID
+
604 libxml2
.XML_PARSE_NOBLANKS
)
607 impl
= spec
.get_impl()
608 for apiname
in apinames
:
609 spec
.get_api(apiname
)
613 print "%s is successfully parsed" % filename
616 if __name__
== "__main__":