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