Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1901 | serge | 1 | #!/usr/bin/python |
2 | # |
||
3 | # Copyright (C) 2009 Chia-I Wu |
||
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 |
||
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 |
||
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 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 |
||
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 |
||
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 |
||
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()> |