Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1901 | serge | 1 | #!/usr/bin/env python |
2 | |||
3 | # Mesa 3-D graphics library |
||
4 | # Version: 7.9 |
||
5 | # |
||
6 | # Copyright (C) 2010 LunarG Inc. |
||
7 | # |
||
8 | # Permission is hereby granted, free of charge, to any person obtaining a |
||
9 | # copy of this software and associated documentation files (the "Software"), |
||
10 | # to deal in the Software without restriction, including without limitation |
||
11 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||
12 | # and/or sell copies of the Software, and to permit persons to whom the |
||
13 | # Software is furnished to do so, subject to the following conditions: |
||
14 | # |
||
15 | # The above copyright notice and this permission notice shall be included |
||
16 | # in all copies or substantial portions of the Software. |
||
17 | # |
||
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||
19 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||
20 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||
21 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||
22 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||
23 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||
24 | # DEALINGS IN THE SOFTWARE. |
||
25 | # |
||
26 | # Authors: |
||
27 | # Chia-I Wu |
||
28 | |||
29 | import sys |
||
30 | import re |
||
31 | from optparse import OptionParser |
||
32 | |||
33 | # number of dynamic entries |
||
34 | ABI_NUM_DYNAMIC_ENTRIES = 256 |
||
35 | |||
36 | class ABIEntry(object): |
||
37 | """Represent an ABI entry.""" |
||
38 | |||
39 | _match_c_param = re.compile( |
||
40 | '^(?P |
||
41 | |||
42 | def __init__(self, cols, attrs): |
||
43 | self._parse(cols) |
||
44 | |||
45 | self.slot = attrs['slot'] |
||
46 | self.hidden = attrs['hidden'] |
||
47 | self.alias = attrs['alias'] |
||
48 | |||
49 | def c_prototype(self): |
||
50 | return '%s %s(%s)' % (self.c_return(), self.name, self.c_params()) |
||
51 | |||
52 | def c_return(self): |
||
53 | ret = self.ret |
||
54 | if not ret: |
||
55 | ret = 'void' |
||
56 | |||
57 | return ret |
||
58 | |||
59 | def c_params(self): |
||
60 | """Return the parameter list used in the entry prototype.""" |
||
61 | c_params = [] |
||
62 | for t, n, a in self.params: |
||
63 | sep = '' if t.endswith('*') else ' ' |
||
64 | arr = '[%d]' % a if a else '' |
||
65 | c_params.append(t + sep + n + arr) |
||
66 | if not c_params: |
||
67 | c_params.append('void') |
||
68 | |||
69 | return ", ".join(c_params) |
||
70 | |||
71 | def c_args(self): |
||
72 | """Return the argument list used in the entry invocation.""" |
||
73 | c_args = [] |
||
74 | for t, n, a in self.params: |
||
75 | c_args.append(n) |
||
76 | |||
77 | return ", ".join(c_args) |
||
78 | |||
79 | def _parse(self, cols): |
||
80 | ret = cols.pop(0) |
||
81 | if ret == 'void': |
||
82 | ret = None |
||
83 | |||
84 | name = cols.pop(0) |
||
85 | |||
86 | params = [] |
||
87 | if not cols: |
||
88 | raise Exception(cols) |
||
89 | elif len(cols) == 1 and cols[0] == 'void': |
||
90 | pass |
||
91 | else: |
||
92 | for val in cols: |
||
93 | params.append(self._parse_param(val)) |
||
94 | |||
95 | self.ret = ret |
||
96 | self.name = name |
||
97 | self.params = params |
||
98 | |||
99 | def _parse_param(self, c_param): |
||
100 | m = self._match_c_param.match(c_param) |
||
101 | if not m: |
||
102 | raise Exception('unrecognized param ' + c_param) |
||
103 | |||
104 | c_type = m.group('type').strip() |
||
105 | c_name = m.group('name') |
||
106 | c_array = m.group('array') |
||
107 | c_array = int(c_array) if c_array else 0 |
||
108 | |||
109 | return (c_type, c_name, c_array) |
||
110 | |||
111 | def __str__(self): |
||
112 | return self.c_prototype() |
||
113 | |||
114 | def __cmp__(self, other): |
||
115 | # compare slot, alias, and then name |
||
116 | res = cmp(self.slot, other.slot) |
||
117 | if not res: |
||
118 | if not self.alias: |
||
119 | res = -1 |
||
120 | elif not other.alias: |
||
121 | res = 1 |
||
122 | |||
123 | if not res: |
||
124 | res = cmp(self.name, other.name) |
||
125 | |||
126 | return res |
||
127 | |||
128 | def abi_parse_line(line): |
||
129 | cols = [col.strip() for col in line.split(',')] |
||
130 | |||
131 | attrs = { |
||
132 | 'slot': -1, |
||
133 | 'hidden': False, |
||
134 | 'alias': None, |
||
135 | } |
||
136 | |||
137 | # extract attributes from the first column |
||
138 | vals = cols[0].split(':') |
||
139 | while len(vals) > 1: |
||
140 | val = vals.pop(0) |
||
141 | if val.startswith('slot='): |
||
142 | attrs['slot'] = int(val[5:]) |
||
143 | elif val == 'hidden': |
||
144 | attrs['hidden'] = True |
||
145 | elif val.startswith('alias='): |
||
146 | attrs['alias'] = val[6:] |
||
147 | elif not val: |
||
148 | pass |
||
149 | else: |
||
150 | raise Exception('unknown attribute %s' % val) |
||
151 | cols[0] = vals[0] |
||
152 | |||
153 | return (attrs, cols) |
||
154 | |||
155 | def abi_parse(filename): |
||
156 | """Parse a CSV file for ABI entries.""" |
||
157 | fp = open(filename) if filename != '-' else sys.stdin |
||
158 | lines = [line.strip() for line in fp.readlines() |
||
159 | if not line.startswith('#') and line.strip()] |
||
160 | |||
161 | entry_dict = {} |
||
162 | next_slot = 0 |
||
163 | for line in lines: |
||
164 | attrs, cols = abi_parse_line(line) |
||
165 | |||
166 | # post-process attributes |
||
167 | if attrs['alias']: |
||
168 | try: |
||
169 | ent = entry_dict[attrs['alias']] |
||
170 | slot = ent.slot |
||
171 | except KeyError: |
||
172 | raise Exception('failed to alias %s' % attrs['alias']) |
||
173 | else: |
||
174 | slot = next_slot |
||
175 | next_slot += 1 |
||
176 | |||
177 | if attrs['slot'] < 0: |
||
178 | attrs['slot'] = slot |
||
179 | elif attrs['slot'] != slot: |
||
180 | raise Exception('invalid slot in %s' % (line)) |
||
181 | |||
182 | ent = ABIEntry(cols, attrs) |
||
183 | if entry_dict.has_key(ent.name): |
||
184 | raise Exception('%s is duplicated' % (ent.name)) |
||
185 | entry_dict[ent.name] = ent |
||
186 | |||
187 | entries = entry_dict.values() |
||
188 | entries.sort() |
||
189 | |||
190 | # sanity check |
||
191 | i = 0 |
||
192 | for slot in xrange(next_slot): |
||
193 | if entries[i].slot != slot: |
||
194 | raise Exception('entries are not ordered by slots') |
||
195 | if entries[i].alias: |
||
196 | raise Exception('first entry of slot %d aliases %s' |
||
197 | % (slot, entries[i].alias)) |
||
198 | while i < len(entries) and entries[i].slot == slot: |
||
199 | i += 1 |
||
200 | if i < len(entries): |
||
201 | raise Exception('there are %d invalid entries' % (len(entries) - 1)) |
||
202 | |||
203 | return entries |
||
204 | |||
205 | class ABIPrinter(object): |
||
206 | """MAPI Printer""" |
||
207 | |||
208 | def __init__(self, entries): |
||
209 | self.entries = entries |
||
210 | |||
211 | # sort entries by their names |
||
212 | self.entries_sorted_by_names = self.entries[:] |
||
213 | self.entries_sorted_by_names.sort(lambda x, y: cmp(x.name, y.name)) |
||
214 | |||
215 | self.indent = ' ' * 3 |
||
216 | self.noop_warn = 'noop_warn' |
||
217 | self.noop_generic = 'noop_generic' |
||
218 | |||
219 | self.api_defines = [] |
||
220 | self.api_headers = ['"KHR/khrplatform.h"'] |
||
221 | self.api_call = 'KHRONOS_APICALL' |
||
222 | self.api_entry = 'KHRONOS_APIENTRY' |
||
223 | self.api_attrs = 'KHRONOS_APIATTRIBUTES' |
||
224 | |||
225 | def c_header(self): |
||
226 | return '/* This file is automatically generated by mapi_abi.py. Do not modify. */' |
||
227 | |||
228 | def c_includes(self): |
||
229 | """Return includes of the client API headers.""" |
||
230 | defines = ['#define ' + d for d in self.api_defines] |
||
231 | includes = ['#include ' + h for h in self.api_headers] |
||
232 | return "\n".join(defines + includes) |
||
233 | |||
234 | def c_mapi_table(self): |
||
235 | """Return defines of the dispatch table size.""" |
||
236 | num_static_entries = 0 |
||
237 | for ent in self.entries: |
||
238 | if not ent.alias: |
||
239 | num_static_entries += 1 |
||
240 | |||
241 | return ('#define MAPI_TABLE_NUM_STATIC %d\n' + \ |
||
242 | '#define MAPI_TABLE_NUM_DYNAMIC %d') % ( |
||
243 | num_static_entries, ABI_NUM_DYNAMIC_ENTRIES) |
||
244 | |||
245 | def c_mapi_table_initializer(self, prefix): |
||
246 | """Return the array initializer for mapi_table_fill.""" |
||
247 | entries = [ent.name for ent in self.entries if not ent.alias] |
||
248 | pre = self.indent + '(mapi_proc) ' + prefix |
||
249 | return pre + (',\n' + pre).join(entries) |
||
250 | |||
251 | def c_mapi_table_spec(self): |
||
252 | """Return the spec for mapi_init.""" |
||
253 | specv1 = [] |
||
254 | line = '"1' |
||
255 | for ent in self.entries: |
||
256 | if not ent.alias: |
||
257 | line += '\\0"\n' |
||
258 | specv1.append(line) |
||
259 | line = '"' |
||
260 | line += '%s\\0' % ent.name |
||
261 | line += '";' |
||
262 | specv1.append(line) |
||
263 | |||
264 | return self.indent + self.indent.join(specv1) |
||
265 | |||
266 | def _c_decl(self, ent, prefix, need_attr=True): |
||
267 | """Return the C declaration for the entry.""" |
||
268 | decl = '%s %s %s%s(%s)' % (ent.c_return(), self.api_entry, |
||
269 | prefix, ent.name, ent.c_params()) |
||
270 | if need_attr and self.api_attrs: |
||
271 | decl += ' ' + self.api_attrs |
||
272 | |||
273 | return decl |
||
274 | |||
275 | def _c_cast(self, ent): |
||
276 | """Return the C cast for the entry.""" |
||
277 | cast = '%s (%s *)(%s)' % ( |
||
278 | ent.c_return(), self.api_entry, ent.c_params()) |
||
279 | |||
280 | return cast |
||
281 | |||
282 | def c_private_declarations(self, prefix): |
||
283 | """Return the declarations of private functions.""" |
||
284 | decls = [self._c_decl(ent, prefix) |
||
285 | for ent in self.entries if not ent.alias] |
||
286 | |||
287 | return ";\n".join(decls) + ";" |
||
288 | |||
289 | def c_public_dispatches(self, prefix): |
||
290 | """Return the public dispatch functions.""" |
||
291 | dispatches = [] |
||
292 | for ent in self.entries: |
||
293 | if ent.hidden: |
||
294 | continue |
||
295 | |||
296 | proto = self.api_call + ' ' + self._c_decl(ent, prefix) |
||
297 | cast = self._c_cast(ent) |
||
298 | |||
299 | ret = '' |
||
300 | if ent.ret: |
||
301 | ret = 'return ' |
||
302 | stmt1 = self.indent |
||
303 | stmt1 += 'const struct mapi_table *tbl = u_current_get();' |
||
304 | stmt2 = self.indent |
||
305 | stmt2 += 'mapi_func func = ((const mapi_func *) tbl)[%d];' % ( |
||
306 | ent.slot) |
||
307 | stmt3 = self.indent |
||
308 | stmt3 += '%s((%s) func)(%s);' % (ret, cast, ent.c_args()) |
||
309 | |||
310 | disp = '%s\n{\n%s\n%s\n%s\n}' % (proto, stmt1, stmt2, stmt3) |
||
311 | dispatches.append(disp) |
||
312 | |||
313 | return '\n\n'.join(dispatches) |
||
314 | |||
315 | def c_stub_string_pool(self): |
||
316 | """Return the string pool for use by stubs.""" |
||
317 | # sort entries by their names |
||
318 | sorted_entries = self.entries[:] |
||
319 | sorted_entries.sort(lambda x, y: cmp(x.name, y.name)) |
||
320 | |||
321 | pool = [] |
||
322 | offsets = {} |
||
323 | count = 0 |
||
324 | for ent in sorted_entries: |
||
325 | offsets[ent] = count |
||
326 | pool.append('%s' % (ent.name)) |
||
327 | count += len(ent.name) + 1 |
||
328 | |||
329 | pool_str = self.indent + '"' + \ |
||
330 | ('\\0"\n' + self.indent + '"').join(pool) + '";' |
||
331 | return (pool_str, offsets) |
||
332 | |||
333 | def c_stub_initializer(self, prefix, pool_offsets): |
||
334 | """Return the initializer for struct mapi_stub array.""" |
||
335 | stubs = [] |
||
336 | for ent in self.entries_sorted_by_names: |
||
337 | stubs.append('%s{ (mapi_func) %s%s, %d, (void *) %d }' % ( |
||
338 | self.indent, prefix, ent.name, ent.slot, pool_offsets[ent])) |
||
339 | |||
340 | return ',\n'.join(stubs) |
||
341 | |||
342 | def c_noop_functions(self, prefix, warn_prefix): |
||
343 | """Return the noop functions.""" |
||
344 | noops = [] |
||
345 | for ent in self.entries: |
||
346 | if ent.alias: |
||
347 | continue |
||
348 | |||
349 | proto = 'static ' + self._c_decl(ent, prefix) |
||
350 | |||
351 | stmt1 = self.indent + '%s("%s%s");' % ( |
||
352 | self.noop_warn, warn_prefix, ent.name) |
||
353 | |||
354 | if ent.ret: |
||
355 | stmt2 = self.indent + 'return (%s) 0;' % (ent.ret) |
||
356 | noop = '%s\n{\n%s\n%s\n}' % (proto, stmt1, stmt2) |
||
357 | else: |
||
358 | noop = '%s\n{\n%s\n}' % (proto, stmt1) |
||
359 | |||
360 | noops.append(noop) |
||
361 | |||
362 | return '\n\n'.join(noops) |
||
363 | |||
364 | def c_noop_initializer(self, prefix, use_generic): |
||
365 | """Return an initializer for the noop dispatch table.""" |
||
366 | entries = [prefix + ent.name for ent in self.entries if not ent.alias] |
||
367 | if use_generic: |
||
368 | entries = [self.noop_generic] * len(entries) |
||
369 | |||
370 | entries.extend([self.noop_generic] * ABI_NUM_DYNAMIC_ENTRIES) |
||
371 | |||
372 | pre = self.indent + '(mapi_func) ' |
||
373 | return pre + (',\n' + pre).join(entries) |
||
374 | |||
375 | def c_asm_gcc(self, prefix): |
||
376 | asm = [] |
||
377 | to_name = None |
||
378 | |||
379 | asm.append('__asm__(') |
||
380 | for ent in self.entries: |
||
381 | name = prefix + ent.name |
||
382 | |||
383 | if ent.hidden: |
||
384 | asm.append('".hidden %s\\n"' % (name)) |
||
385 | |||
386 | if ent.alias: |
||
387 | asm.append('".globl %s\\n"' % (name)) |
||
388 | asm.append('".set %s, %s\\n"' % (name, to_name)) |
||
389 | else: |
||
390 | asm.append('STUB_ASM_ENTRY("%s")"\\n"' % (name)) |
||
391 | asm.append('"\\t"STUB_ASM_CODE("%d")"\\n"' % (ent.slot)) |
||
392 | to_name = name |
||
393 | asm.append(');') |
||
394 | |||
395 | return "\n".join(asm) |
||
396 | |||
397 | def output_for_lib(self): |
||
398 | print self.c_header() |
||
399 | |||
400 | print '#ifdef MAPI_TMP_DEFINES' |
||
401 | print self.c_includes() |
||
402 | print '#undef MAPI_TMP_DEFINES' |
||
403 | print '#endif /* MAPI_TMP_DEFINES */' |
||
404 | |||
405 | print '#ifdef MAPI_TMP_TABLE' |
||
406 | print self.c_mapi_table() |
||
407 | print '#undef MAPI_TMP_TABLE' |
||
408 | print '#endif /* MAPI_TMP_TABLE */' |
||
409 | |||
410 | |||
411 | pool, pool_offsets = self.c_stub_string_pool() |
||
412 | print '#ifdef MAPI_TMP_PUBLIC_STUBS' |
||
413 | print 'static const char public_string_pool[] =' |
||
414 | print pool |
||
415 | |||
416 | print 'static const struct mapi_stub public_stubs[] = {' |
||
417 | print self.c_stub_initializer(self.prefix_lib, pool_offsets) |
||
418 | print '};' |
||
419 | print '#undef MAPI_TMP_PUBLIC_STUBS' |
||
420 | print '#endif /* MAPI_TMP_PUBLIC_STUBS */' |
||
421 | |||
422 | |||
423 | print '#ifdef MAPI_TMP_PUBLIC_ENTRIES' |
||
424 | print self.c_public_dispatches(self.prefix_lib) |
||
425 | print '#undef MAPI_TMP_PUBLIC_ENTRIES' |
||
426 | print '#endif /* MAPI_TMP_PUBLIC_ENTRIES */' |
||
427 | |||
428 | |||
429 | print '#ifdef MAPI_TMP_NOOP_ARRAY' |
||
430 | print '#ifdef DEBUG' |
||
431 | |||
432 | print self.c_noop_functions(self.prefix_noop, self.prefix_lib) |
||
433 | |||
434 | print 'const mapi_func table_%s_array[] = {' % (self.prefix_noop) |
||
435 | print self.c_noop_initializer(self.prefix_noop, False) |
||
436 | print '};' |
||
437 | |||
438 | print '#else /* DEBUG */' |
||
439 | |||
440 | print 'const mapi_func table_%s_array[] = {' % (self.prefix_noop) |
||
441 | print self.c_noop_initializer(self.prefix_noop, True) |
||
442 | print '};' |
||
443 | print '#endif /* DEBUG */' |
||
444 | print '#undef MAPI_TMP_NOOP_ARRAY' |
||
445 | print '#endif /* MAPI_TMP_NOOP_ARRAY */' |
||
446 | |||
447 | |||
448 | print '#ifdef MAPI_TMP_STUB_ASM_GCC' |
||
449 | print self.c_asm_gcc(self.prefix_lib) |
||
450 | print '#undef MAPI_TMP_STUB_ASM_GCC' |
||
451 | print '#endif /* MAPI_TMP_STUB_ASM_GCC */' |
||
452 | |||
453 | def output_for_app(self): |
||
454 | print self.c_header() |
||
455 | |||
456 | print self.c_private_declarations(self.prefix_app) |
||
457 | |||
458 | print '#ifdef API_TMP_DEFINE_SPEC' |
||
459 | |||
460 | print 'static const char %s_spec[] =' % (self.prefix_app) |
||
461 | print self.c_mapi_table_spec() |
||
462 | |||
463 | print 'static const mapi_proc %s_procs[] = {' % (self.prefix_app) |
||
464 | print self.c_mapi_table_initializer(self.prefix_app) |
||
465 | print '};' |
||
466 | |||
467 | print '#endif /* API_TMP_DEFINE_SPEC */' |
||
468 | |||
469 | class GLAPIPrinter(ABIPrinter): |
||
470 | """OpenGL API Printer""" |
||
471 | |||
472 | def __init__(self, entries): |
||
473 | super(GLAPIPrinter, self).__init__(entries) |
||
474 | |||
475 | self.api_defines = ['GL_GLEXT_PROTOTYPES'] |
||
476 | self.api_headers = ['"GL/gl.h"', '"GL/glext.h"'] |
||
477 | self.api_call = 'GLAPI' |
||
478 | self.api_entry = 'APIENTRY' |
||
479 | self.api_attrs = '' |
||
480 | |||
481 | self.prefix_lib = 'gl' |
||
482 | self.prefix_app = '_mesa_' |
||
483 | self.prefix_noop = 'noop' |
||
484 | |||
485 | def output_for_app(self): |
||
486 | # not used |
||
487 | pass |
||
488 | |||
489 | class ES1APIPrinter(GLAPIPrinter): |
||
490 | """OpenGL ES 1.x API Printer""" |
||
491 | |||
492 | def __init__(self, entries): |
||
493 | super(ES1APIPrinter, self).__init__(entries) |
||
494 | |||
495 | self.api_headers = ['"GLES/gl.h"', '"GLES/glext.h"'] |
||
496 | self.api_call = 'GL_API' |
||
497 | self.api_entry = 'GL_APIENTRY' |
||
498 | |||
499 | class ES2APIPrinter(GLAPIPrinter): |
||
500 | """OpenGL ES 2.x API Printer""" |
||
501 | |||
502 | def __init__(self, entries): |
||
503 | super(ES2APIPrinter, self).__init__(entries) |
||
504 | |||
505 | self.api_headers = ['"GLES2/gl2.h"', '"GLES2/gl2ext.h"'] |
||
506 | self.api_call = 'GL_APICALL' |
||
507 | self.api_entry = 'GL_APIENTRY' |
||
508 | |||
509 | class VGAPIPrinter(ABIPrinter): |
||
510 | """OpenVG API Printer""" |
||
511 | |||
512 | def __init__(self, entries): |
||
513 | super(VGAPIPrinter, self).__init__(entries) |
||
514 | |||
515 | self.api_defines = ['VG_VGEXT_PROTOTYPES'] |
||
516 | self.api_headers = ['"VG/openvg.h"', '"VG/vgext.h"'] |
||
517 | self.api_call = 'VG_API_CALL' |
||
518 | self.api_entry = 'VG_API_ENTRY' |
||
519 | self.api_attrs = 'VG_API_EXIT' |
||
520 | |||
521 | self.prefix_lib = 'vg' |
||
522 | self.prefix_app = 'vega' |
||
523 | self.prefix_noop = 'noop' |
||
524 | |||
525 | def parse_args(): |
||
526 | printers = ['glapi', 'es1api', 'es2api', 'vgapi'] |
||
527 | modes = ['lib', 'app'] |
||
528 | |||
529 | parser = OptionParser(usage='usage: %prog [options] |
||
530 | parser.add_option('-p', '--printer', dest='printer', |
||
531 | help='printer to use: %s' % (", ".join(printers))) |
||
532 | parser.add_option('-m', '--mode', dest='mode', |
||
533 | help='target user: %s' % (", ".join(modes))) |
||
534 | |||
535 | options, args = parser.parse_args() |
||
536 | if not args or options.printer not in printers or \ |
||
537 | options.mode not in modes: |
||
538 | parser.print_help() |
||
539 | sys.exit(1) |
||
540 | |||
541 | return (args[0], options) |
||
542 | |||
543 | def main(): |
||
544 | printers = { |
||
545 | 'vgapi': VGAPIPrinter, |
||
546 | 'glapi': GLAPIPrinter, |
||
547 | 'es1api': ES1APIPrinter, |
||
548 | 'es2api': ES2APIPrinter |
||
549 | } |
||
550 | |||
551 | filename, options = parse_args() |
||
552 | |||
553 | entries = abi_parse(filename) |
||
554 | printer = printers[options.printer](entries) |
||
555 | if options.mode == 'lib': |
||
556 | printer.output_for_lib() |
||
557 | else: |
||
558 | printer.output_for_app() |
||
559 | |||
560 | if __name__ == '__main__': |
||
561 | main()>>> |