1#!/usr/bin/env python 2 3# Copyright 2015 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17# Utility functions used to parse a list of DLL entry points. 18# Expected format: 19# 20# <empty-line> -> ignored 21# #<comment> -> ignored 22# %<verbatim> -> verbatim output for header files. 23# !<prefix> -> prefix name for header files. 24# <return-type> <function-name> <signature> ; -> entry point declaration. 25# 26# Anything else is an error. 27 28import re 29import sys 30import argparse 31 32re_func = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_]*)\((.*)\);$""") 33re_param = re.compile(r"""^(.*[\* ])([A-Za-z_][A-Za-z0-9_\[\]]*)$""") 34 35class Entry: 36 """Small class used to model a single DLL entry point.""" 37 def __init__(self, func_name, return_type, parameters): 38 """Initialize Entry instance. |func_name| is the function name, 39 |return_type| its return type, and |parameters| is a list of 40 (type,name) tuples from the entry's signature. 41 """ 42 self.__name__ = func_name 43 self.return_type = return_type 44 self.parameters = "" 45 self.vartypes = [] 46 self.varnames = [] 47 self.call = "" 48 comma = "" 49 for param in parameters: 50 self.vartypes.append(param[0]) 51 self.varnames.append(param[1]) 52 self.parameters += "%s%s %s" % (comma, param[0], param[1]) 53 self.call += "%s%s" % (comma, param[1]) 54 comma = ", " 55 56def banner_command(argv): 57 """Return sanitized command-line description. 58 |argv| must be a list of command-line parameters, e.g. sys.argv. 59 Return a string corresponding to the command, with platform-specific 60 paths removed.""" 61 62 # Remove path from first parameter 63 argv = argv[:] 64 argv[0] = "android/scripts/gen-entries.py" 65 return ' '.join(argv) 66 67def parse_entries_file(lines): 68 """Parse an .entries file and return a tuple of: 69 entries: list of Entry instances from the file. 70 prefix_name: prefix name from the file, or None. 71 verbatim: list of verbatim lines from the file. 72 errors: list of errors in the file, prefixed by line number. 73 """ 74 entries = [] 75 verbatim = [] 76 errors = [] 77 lineno = 0 78 prefix_name = None 79 namespaces = [] 80 for line in lines: 81 lineno += 1 82 line = line.strip() 83 if len(line) == 0: # Ignore empty lines 84 continue 85 if line[0] == '#': # Ignore comments 86 continue 87 if line[0] == '!': # Prefix name 88 prefix_name = line[1:] 89 continue 90 if line[0] == '%': # Verbatim line copy 91 verbatim.append(line[1:]) 92 continue 93 if line.startswith("namespaces"): # Namespaces 94 namespaces = list(map(lambda t: t.strip(), line.split("namespaces")[1].strip().split(","))) 95 continue 96 # Must be a function signature. 97 m = re_func.match(line) 98 if not m: 99 errors.append("%d: '%s'" % (lineno, line)) 100 continue 101 102 return_type, func_name, parameters = m.groups() 103 return_type = return_type.strip() 104 parameters = parameters.strip() 105 params = [] 106 failure = False 107 if parameters != "void": 108 for parameter in parameters.split(','): 109 parameter = parameter.strip() 110 m = re_param.match(parameter) 111 if not m: 112 errors.append("%d: parameter '%s'" % (lineno, parameter)) 113 failure = True 114 break 115 else: 116 param_type, param_name = m.groups() 117 params.append((param_type.strip(), param_name.strip())) 118 119 if not failure: 120 entries.append(Entry(func_name, return_type, params)) 121 122 return (entries, prefix_name, verbatim, namespaces, errors) 123 124 125def gen_functions_header(entries, prefix_name, verbatim, filename, with_args): 126 """Generate a C header containing a macro listing all entry points. 127 |entries| is a list of Entry instances. 128 |prefix_name| is a prefix-name, it will be converted to upper-case. 129 |verbatim| is a list of verbatim lines that must appear before the 130 macro declaration. Useful to insert #include <> statements. 131 |filename| is the name of the original file. 132 """ 133 prefix_name = prefix_name.upper() 134 135 print("// Auto-generated with: %s" % banner_command(sys.argv)) 136 print("// DO NOT EDIT THIS FILE") 137 print("") 138 print("#ifndef %s_FUNCTIONS_H" % prefix_name) 139 print("#define %s_FUNCTIONS_H" % prefix_name) 140 print("") 141 for line in verbatim: 142 print(line) 143 144 print("#define LIST_%s_FUNCTIONS(X) \\" % prefix_name) 145 146 new_header_apis = [ 147 # AEMU private exts 148 "eglGetMaxGLESVersion", 149 "eglBlitFromCurrentReadBufferANDROID", 150 "eglSetImageFenceANDROID", 151 "eglWaitImageFenceANDROID", 152 "eglAddLibrarySearchPathANDROID", 153 "eglQueryVulkanInteropSupportANDROID", 154 "eglQueryVulkanInteropSupportANDROID", 155 # For snapshotting 156 "eglLoadConfig", 157 "eglLoadContext", 158 "eglLoadAllImages", 159 "eglSaveConfig", 160 "eglSaveContext", 161 "eglSaveAllImages", 162 "eglPreSaveContext", 163 "eglPostLoadAllImages", 164 "eglPostSaveContext", 165 "eglUseOsEglApi", 166 "eglFillUsages", 167 "eglSetMaxGLESVersion", 168 ] 169 170 need_decls = [] 171 172 for entry in entries: 173 if entry.__name__ in new_header_apis: 174 need_decls.append(entry) 175 176 if with_args: 177 print(" X(%s, %s, (%s), (%s)) \\" % \ 178 (entry.return_type, entry.__name__, entry.parameters, 179 entry.call)) 180 else: 181 print(" X(%s, %s, (%s)) \\" % \ 182 (entry.return_type, entry.__name__, entry.parameters)) 183 184 185 print("") 186 187 for entry in need_decls: 188 print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) 189 190 print("") 191 print("#endif // %s_FUNCTIONS_H" % prefix_name) 192 193def gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, with_args): 194 """Generate a C++ header containing a header for |entries| 195 with nesting inside |namespaces|. 196 |prefix_name| is a prefix-name, it will be converted to upper-case. 197 |verbatim| is a list of verbatim lines that must appear before the 198 macro declaration. Useful to insert #include <> statements. 199 |filename| is the name of the original file. 200 """ 201 prefix_name = prefix_name.upper() 202 203 print("// Auto-generated with: %s" % banner_command(sys.argv)) 204 print("// DO NOT EDIT THIS FILE") 205 print("") 206 print("#pragma once") 207 print("") 208 for line in verbatim: 209 print(line) 210 211 for ns in namespaces: 212 print("namespace %s {" % ns) 213 214 for entry in entries: 215 if "gles" in filename: 216 print("GL_APICALL %s GL_APIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) 217 else: 218 print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) 219 220 for ns in namespaces: 221 print("} // namespace %s" % ns) 222 223def gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, with_args): 224 """Generate a C++ header containing a header for |entries| 225 with nesting inside |namespaces|. 226 |prefix_name| is a prefix-name, it will be converted to upper-case. 227 |verbatim| is a list of verbatim lines that must appear before the 228 macro declaration. Useful to insert #include <> statements. 229 |filename| is the name of the original file. 230 """ 231 prefix_name = prefix_name.upper() 232 233 print("// Auto-generated with: %s" % banner_command(sys.argv)) 234 print("// DO NOT EDIT THIS FILE") 235 print("") 236 for line in verbatim: 237 print(line) 238 239 for ns in namespaces: 240 print("namespace %s {" % ns) 241 242 for entry in entries: 243 if "gles" in filename: 244 if "void" == entry.return_type: 245 return_part = "return" 246 else: 247 return_part = "return (%s)0" % entry.return_type 248 249 print("GL_APICALL %s GL_APIENTRY %s(%s) { %s; }" % (entry.return_type, entry.__name__, ", ".join(entry.vartypes), return_part)) 250 else: 251 print("EGLAPI %s EGLAPIENTRY %s(%s);" % (entry.return_type, entry.__name__, entry.parameters)) 252 253 namespaces.reverse() 254 255 for ns in namespaces: 256 print("} // namespace %s" % ns) 257 258# The purpose of gen_translator() 259# is to quickly generate implementations on the host Translator, 260# which processes commands that just got onto the renderthread off goldfish pipe 261# and are fed to system OpenGL. 262 263def gen_translator(entries): 264 # Definitions for custom implementation bodies go in 265 # android/scripts/gles3translatorgen/gles30_custom.py 266 # android/scripts/gles3translatorgen/gles31_custom.py 267 from gles3translatorgen import gles30_custom 268 from gles3translatorgen import gles31_custom 269 270 translator_custom_share_processing = { } 271 for (k, v) in list(gles30_custom.custom_share_processing.items()): 272 translator_custom_share_processing[k] = v 273 for (k, v) in list(gles31_custom.custom_share_processing.items()): 274 translator_custom_share_processing[k] = v 275 276 translator_custom_pre = { } 277 for (k, v) in list(gles30_custom.custom_preprocesses.items()): 278 translator_custom_pre[k] = v 279 for (k, v) in list(gles31_custom.custom_preprocesses.items()): 280 translator_custom_pre[k] = v 281 282 translator_custom_post = { } 283 for (k, v) in list(gles30_custom.custom_postprocesses.items()): 284 translator_custom_post[k] = v 285 for (k, v) in list(gles31_custom.custom_postprocesses.items()): 286 translator_custom_post[k] = v 287 288 translator_no_passthrough = {} 289 for (k, v) in list(gles30_custom.no_passthrough.items()): 290 translator_no_passthrough[k] = v 291 for (k, v) in list(gles31_custom.no_passthrough.items()): 292 translator_no_passthrough[k] = v 293 294 translator_needexternc = { 295 "glGetStringi": 1, 296 "glUniform4ui": 1, 297 "glGetUniformIndices": 1, 298 "glTransformFeedbackVaryings": 1, 299 "glCreateShaderProgramv": 1, 300 "glProgramUniform2ui": 1, 301 "glProgramUniform3ui": 1, 302 "glProgramUniform4ui": 1, 303 "glBindVertexBuffer": 1, 304 }; 305 translator_nocontext_fail_codes = { 306 "glClientWaitSync" : "GL_WAIT_FAILED", 307 }; 308 def needExternC(entry): 309 if entry.__name__ in translator_needexternc: 310 return "extern \"C\" " 311 else: 312 return "" 313 def get_fail_code(entry): 314 if entry.__name__ in translator_nocontext_fail_codes: 315 return translator_nocontext_fail_codes[entry.__name__]; 316 else: 317 return "0" 318 def gen_cxt_getter(entry): 319 if (entry.return_type == "void"): 320 print(" GET_CTX_V2();") 321 else: 322 print(" GET_CTX_V2_RET(%s);" % get_fail_code(entry)) 323 324 def gen_validations_custom_impl(entry): 325 isGen = entry.__name__.startswith("glGen") 326 isDelete = entry.__name__.startswith("glDelete") 327 isBufferOp = "Buffer" in entry.__name__ 328 329 hasTargetArg = "target" in entry.varnames 330 hasProgramArg = "program" in entry.varnames 331 332 def mySetError(condition, glerr): 333 if entry.return_type == "void": 334 return "SET_ERROR_IF(%s,%s)" % (condition, glerr); 335 else: 336 return "RET_AND_SET_ERROR_IF(%s,%s,%s)" % (condition, glerr, get_fail_code(entry)); 337 338 if (isGen or isDelete) and ("n" in entry.varnames): 339 print(" %s;" % mySetError("n < 0", "GL_INVALID_VALUE")); 340 if (isBufferOp and hasTargetArg): 341 print(" %s;" % mySetError("!GLESv2Validate::bufferTarget(ctx, target)", "GL_INVALID_ENUM")); 342 if entry.__name__ in translator_custom_pre: 343 print(translator_custom_pre[entry.__name__]) 344 345 def gen_call_ret(entry): 346 globalNameTypes = { 347 ("GLuint", "program") : "NamedObjectType::SHADER_OR_PROGRAM", 348 ("GLuint", "texture") : "NamedObjectType::TEXTURE", 349 ("GLuint", "buffer") : "NamedObjectType::VERTEXBUFFER", 350 ("GLuint", "sampler") : "NamedObjectType::SAMPLER", 351 ("GLuint", "query") : "NamedObjectType::QUERY", 352 } 353 globalNames = { 354 ("GLuint", "program") : "globalProgramName", 355 ("GLuint", "texture") : "globalTextureName", 356 ("GLuint", "buffer") : "globalBufferName", 357 ("GLuint", "sampler") : "globalSampler", 358 ("GLuint", "query") : "globalQuery", 359 } 360 361 needsShareGroup = False 362 for v in zip(entry.vartypes, entry.varnames): 363 if v in list(globalNameTypes.keys()): 364 needsShareGroup = True 365 366 if needsShareGroup: 367 print(" if (ctx->shareGroup().get()) {") 368 for key in zip(entry.vartypes, entry.varnames): 369 vartype, varname = key 370 if key in globalNames: 371 print(" const GLuint %s = ctx->shareGroup()->getGlobalName(%s, %s);" % (globalNames[key], globalNameTypes[key], varname)) 372 373 globalCall = ", ".join([globalNames.get(k, k[1]) for k in zip(entry.vartypes, entry.varnames)]) 374 375 if needsShareGroup and entry.__name__ in translator_custom_share_processing: 376 print(translator_custom_share_processing[entry.__name__]) 377 378 if (entry.return_type == "void"): 379 if (needsShareGroup): 380 print(" ") 381 382 if entry.__name__ not in translator_no_passthrough: 383 print(" ctx->dispatcher().%s(%s);" % (entry.__name__, globalCall)) 384 385 if needsShareGroup: 386 print(" }") 387 if entry.__name__ in translator_custom_post: 388 print(translator_custom_post[entry.__name__]); 389 else: 390 if (needsShareGroup): 391 print(" ") 392 if entry.__name__ not in translator_no_passthrough: 393 print(" %s %s = ctx->dispatcher().%s(%s);" % (entry.return_type, entry.__name__ + "RET", entry.__name__, globalCall)) 394 else: 395 print(" %s %s = %s" % (entry.return_type, entry_func_name + "RET", get_fail_code(entry))) 396 397 if entry.__name__ in translator_custom_post: 398 print(translator_custom_post[entry.__name__]); 399 400 print(" return %s;" % (entry.__name__ + "RET")); 401 if needsShareGroup: 402 print(" } else return %s;" % (get_fail_code(entry))) 403 404 print("// Auto-generated with: %s" % banner_command(sys.argv)) 405 print("// This file is best left unedited.") 406 print("// Try to make changes through gen_translator in gen-entries.py,") 407 print("// and/or parcel out custom functionality in separate code.") 408 for entry in entries: 409 print("%sGL_APICALL %s GL_APIENTRY %s(%s) {" % (needExternC(entry), entry.return_type, entry.__name__, entry.parameters)) 410 gen_cxt_getter(entry); 411 gen_validations_custom_impl(entry); 412 gen_call_ret(entry); 413 print("}\n") 414 415def gen_dll_wrapper(entries, prefix_name, verbatim, filename): 416 """Generate a C source file that contains functions that act as wrappers 417 for entry points located in another shared library. This allows the 418 code that calls these functions to perform lazy-linking to system 419 libraries. 420 |entries|, |prefix_name|, |verbatim| and |filename| are the same as 421 for gen_functions_header() above. 422 """ 423 upper_name = prefix_name.upper() 424 425 ENTRY_PREFIX = "__dll_" 426 427 print("// Auto-generated with: %s" % banner_command(sys.argv)) 428 print("// DO NOT EDIT THIS FILE") 429 print("") 430 print("#include <dlfcn.h>") 431 for line in verbatim: 432 print(line) 433 434 print("") 435 print("///") 436 print("/// W R A P P E R P O I N T E R S") 437 print("///") 438 print("") 439 for entry in entries: 440 ptr_name = ENTRY_PREFIX + entry.__name__ 441 print("static %s (*%s)(%s) = 0;" % \ 442 (entry.return_type, ptr_name, entry.parameters)) 443 444 print("") 445 print("///") 446 print("/// W R A P P E R F U N C T I O N S") 447 print("///") 448 print("") 449 450 for entry in entries: 451 print("%s %s(%s) {" % \ 452 (entry.return_type, entry.__name__, entry.parameters)) 453 ptr_name = ENTRY_PREFIX + entry.__name__ 454 if entry.return_type != "void": 455 print(" return %s(%s);" % (ptr_name, entry.call)) 456 else: 457 print(" %s(%s);" % (ptr_name, entry.call)) 458 print("}\n") 459 460 print("") 461 print("///") 462 print("/// I N I T I A L I Z A T I O N F U N C T I O N") 463 print("///") 464 print("") 465 466 print("int %s_dynlink_init(void* lib) {" % prefix_name) 467 for entry in entries: 468 ptr_name = ENTRY_PREFIX + entry.__name__ 469 print(" %s = (%s(*)(%s))dlsym(lib, \"%s\");" % \ 470 (ptr_name, 471 entry.return_type, 472 entry.parameters, 473 entry.__name__)) 474 print(" if (!%s) return -1;" % ptr_name) 475 print(" return 0;") 476 print("}") 477 478 479def gen_windows_def_file(entries): 480 """Generate a windows DLL .def file. |entries| is a list of Entry instances. 481 """ 482 print("EXPORTS") 483 for entry in entries: 484 print(" %s" % entry.__name__) 485 486 487def gen_unix_sym_file(entries): 488 """Generate an ELF linker version file. |entries| is a list of Entry 489 instances. 490 """ 491 print("VERSION {") 492 print("\tglobal:") 493 for entry in entries: 494 print("\t\t%s;" % entry.__name__) 495 print("\tlocal:") 496 print("\t\t*;") 497 print("};") 498 499def gen_symbols(entries, underscore): 500 """Generate a list of symbols from |entries|, a list of Entry instances. 501 |underscore| is a boolean. If True, then prepend an underscore to each 502 symbol name. 503 """ 504 prefix = "" 505 if underscore: 506 prefix = "_" 507 for entry in entries: 508 print("%s%s" % (prefix, entry.__name__)) 509 510 511VARTYPE_TO_PRINT_FORMAT = { 512 'GLenum': '0x%X', 513 'GLboolean': '%d', 514 'GLbitfield': '%d', 515 'GLvoid': '%d', 516 'GLubyte': '%u', 517 'GLbyte': '%c', 518 'GLshort': '%d', 519 'GLushort': '%d', 520 'GLint': '%d', 521 'GLuint': '%d', 522 'GLclampx': '%d', 523 'GLsizei': '%d', 524 'GLfloat': '%f', 525 'GLclampf': '%f', 526 'GLdouble': '%f', 527 'GLclampd': '%f', 528 'GLchar': '%c', 529 'GLcharARB': '%c', 530 'GLfixed': '%d', 531 'GLintptr': '%ld', 532 'GLsizeiptr': '%ld', 533 'GLsync': '%p', 534 'GLuint64': "%lu", 535 'GLeglImageOES': '%p', 536} 537 538def get_printf_format(var_type, var_name): 539 if '*' in var_type: 540 return "%p" 541 542 if '[' in var_name: 543 return "%p" 544 545 # Function pointer 546 if 'PROC' in var_type: 547 return "%p" 548 549 return VARTYPE_TO_PRINT_FORMAT[var_type] 550 551def get_printf_name(var_name): 552 if '[' in var_name: 553 return var_name[:var_name.index('[')] 554 555 return var_name 556 557def gen_dispatch_logging_wrappers(entries): 558 print("// Auto-generated with: %s" % banner_command(sys.argv)) 559 print("// DO NOT EDIT THIS FILE") 560 print("") 561 562 for entry in entries: 563 print("%s %s_dispatchLoggingWrapper(%s) {" % \ 564 (entry.return_type, entry.__name__, entry.parameters)) 565 566 print_var_formats = [] 567 print_var_names = [] 568 for (param_type, param_name) in zip(entry.vartypes, entry.varnames): 569 print_var_formats.append("%s:%s" % (param_name, get_printf_format(param_type, param_name))) 570 print_var_names.append(get_printf_name(param_name)) 571 572 optional_comma = ", " if print_var_formats else "" 573 print("\tDISPATCH_DEBUG_LOG(\"%s(%s)\"%s%s);" 574 % (entry.__name__, ", ".join(print_var_formats), optional_comma, ", ".join(print_var_names))) 575 576 if entry.return_type == "void": 577 optional_return = "" 578 else: 579 optional_return = "return " 580 581 print("\t%sGLDispatch::%s_underlying(%s);" % (optional_return, entry.__name__, ", ".join(print_var_names))) 582 print("}") 583 print("") 584 585 586def parse_file(filename, lines, mode): 587 """Generate one of possible outputs from |filename|. |lines| must be a list 588 of text lines from the file, and |mode| is one of the --mode option 589 values. 590 """ 591 entries, prefix_name, verbatim, namespaces, errors = parse_entries_file(lines) 592 if errors: 593 for error in errors: 594 sys.stderr.write("ERROR: %s:%s" % (filename, error)) 595 sys.exit(1) 596 597 if not prefix_name: 598 prefix_name = "unknown" 599 600 if mode == 'def': 601 gen_windows_def_file(entries) 602 elif mode == 'sym': 603 gen_unix_sym_file(entries) 604 elif mode == 'translator_passthrough': 605 gen_translator(entries) 606 elif mode == 'wrapper': 607 gen_dll_wrapper(entries, prefix_name, verbatim, filename) 608 elif mode == 'symbols': 609 gen_symbols(entries, False) 610 elif mode == '_symbols': 611 gen_symbols(entries, True) 612 elif mode == 'functions': 613 gen_functions_header(entries, prefix_name, verbatim, filename, False) 614 elif mode == 'funcargs': 615 gen_functions_header(entries, prefix_name, verbatim, filename, True) 616 elif mode == 'static_translator_namespaced_header': 617 gen_static_translator_namespaced_header(entries, namespaces, prefix_name, verbatim, filename, True) 618 elif mode == 'static_translator_namespaced_stubs': 619 gen_static_translator_namespaced_stubs(entries, namespaces, prefix_name, verbatim, filename, True) 620 elif mode == 'dispatch_logging_wrappers': 621 gen_dispatch_logging_wrappers(entries) 622 623# List of valid --mode option values. 624mode_list = [ 625 'def', 'sym', 'translator_passthrough', 'wrapper', 'symbols', '_symbols', 'functions', 'funcargs', 'static_translator_namespaced_header', 'static_translator_namespaced_stubs', 'dispatch_logging_wrappers', 626] 627 628# Argument parsing. 629parser = argparse.ArgumentParser( 630 formatter_class=argparse.RawDescriptionHelpFormatter, 631 description="""\ 632A script used to parse an .entries input file containing a list of function 633declarations, and generate various output files depending on the value of 634the --mode option, which can be: 635 636 def Generate a windows DLL .def file. 637 sym Generate a Unix .so linker script. 638 wrapper Generate a C source file containing wrapper functions. 639 symbols Generate a simple list of symbols, one per line. 640 _symbols Generate a simple list of symbols, prefixed with _. 641 functions Generate a C header containing a macro listing all functions. 642 funcargs Like 'functions', but adds function call arguments to listing. 643 static_translator_namespaced_header Generate C++ header with namespaced versions of the api declarations. 644 dispatch_logging_wrappers Generate C++ functions which debug log the function with arguments and then call the underlying function. 645 646""") 647parser.add_argument("--mode", help="Output mode", choices=mode_list) 648parser.add_argument("--output", help="output file") 649parser.add_argument("file", help=".entries file path") 650 651args = parser.parse_args() 652 653if not args.mode: 654 sys,stderr.write("ERROR: Please use --mode=<name>, see --help.") 655 sys.exit(1) 656 657if args.output: 658 sys.stdout = open(args.output, "w+") 659 660if args.file == '--': 661 parse_file("<stdin>", sys.stdin, args.mode) 662else: 663 parse_file(args.file, open(args.file), args.mode) 664