| 1 | #!/usr/bin/env python3
|
| 2 | """
|
| 3 | pea_main.py
|
| 4 |
|
| 5 | A potential rewrite of mycpp.
|
| 6 | """
|
| 7 | import io
|
| 8 | import optparse
|
| 9 | import pickle
|
| 10 | import sys
|
| 11 | import time
|
| 12 |
|
| 13 | START_TIME = time.time()
|
| 14 |
|
| 15 | if 0:
|
| 16 | for p in sys.path:
|
| 17 | print('*** syspath: %s' % p)
|
| 18 |
|
| 19 | from typing import Any, Dict, List, Tuple
|
| 20 |
|
| 21 | from mycpp import translate
|
| 22 |
|
| 23 | from pea import gen_cpp
|
| 24 | from pea import mypy_shim
|
| 25 | from pea import parse
|
| 26 | from pea.header import (TypeSyntaxError, Program, log)
|
| 27 |
|
| 28 |
|
| 29 | def Options() -> optparse.OptionParser:
|
| 30 | """Returns an option parser instance."""
|
| 31 |
|
| 32 | p = optparse.OptionParser()
|
| 33 | p.add_option('-v',
|
| 34 | '--verbose',
|
| 35 | dest='verbose',
|
| 36 | action='store_true',
|
| 37 | default=False,
|
| 38 | help='Show details about translation')
|
| 39 |
|
| 40 | # Control which modules are exported to the header. Used by
|
| 41 | # build/translate.sh.
|
| 42 | p.add_option('--to-header',
|
| 43 | dest='to_header',
|
| 44 | action='append',
|
| 45 | default=[],
|
| 46 | help='Export this module to a header, e.g. frontend.args')
|
| 47 |
|
| 48 | p.add_option('--header-out',
|
| 49 | dest='header_out',
|
| 50 | default=None,
|
| 51 | help='Write this header')
|
| 52 |
|
| 53 | return p
|
| 54 |
|
| 55 |
|
| 56 | def main(argv: list[str]) -> int:
|
| 57 |
|
| 58 | o = Options()
|
| 59 | opts, argv = o.parse_args(argv)
|
| 60 |
|
| 61 | action = argv[1]
|
| 62 |
|
| 63 | # TODO: get rid of 'parse'
|
| 64 | if action in ('parse', 'cpp'):
|
| 65 | files = argv[2:]
|
| 66 |
|
| 67 | # TODO:
|
| 68 | # pass_state.Virtual
|
| 69 | # this loops over functions and methods. But it has to be done BEFORE
|
| 70 | # the PrototypesPass, or we need two passes. Gah!
|
| 71 | # Could it be done in ConstVisitor? ConstVirtualVisitor?
|
| 72 |
|
| 73 | # local_vars
|
| 74 |
|
| 75 | prog = Program()
|
| 76 | log('Pea begin')
|
| 77 |
|
| 78 | if not parse.ParseFiles(files, prog):
|
| 79 | return 1
|
| 80 | log('Parsed %d files and their type comments', len(files))
|
| 81 | prog.PrintStats()
|
| 82 |
|
| 83 | # This is the first pass
|
| 84 |
|
| 85 | const_lookup: dict[str, int] = {}
|
| 86 |
|
| 87 | v = gen_cpp.ConstVisitor(const_lookup)
|
| 88 | for py_file in prog.py_files:
|
| 89 | v.visit(py_file.module)
|
| 90 |
|
| 91 | log('Collected %d constants', len(const_lookup))
|
| 92 |
|
| 93 | # TODO: respect header_out for these two passes
|
| 94 | #out_f = sys.stdout
|
| 95 | out_f = io.StringIO()
|
| 96 |
|
| 97 | # ForwardDeclPass: module -> class
|
| 98 | # TODO: Move trivial ForwardDeclPass into ParsePass, BEFORE constants,
|
| 99 | # after comparing output with mycpp.
|
| 100 | pass2 = gen_cpp.ForwardDeclPass(out_f)
|
| 101 | for py_file in prog.py_files:
|
| 102 | namespace = py_file.namespace
|
| 103 | pass2.DoPyFile(py_file)
|
| 104 |
|
| 105 | log('Wrote forward declarations')
|
| 106 | prog.PrintStats()
|
| 107 |
|
| 108 | try:
|
| 109 | # PrototypesPass: module -> class/method, func
|
| 110 |
|
| 111 | pass3 = gen_cpp.PrototypesPass(opts, prog, out_f)
|
| 112 | for py_file in prog.py_files:
|
| 113 | pass3.DoPyFile(py_file) # parses type comments in signatures
|
| 114 |
|
| 115 | log('Wrote prototypes')
|
| 116 | prog.PrintStats()
|
| 117 |
|
| 118 | # ImplPass: module -> class/method, func; then probably a fully recursive thing
|
| 119 |
|
| 120 | pass4 = gen_cpp.ImplPass(prog, out_f)
|
| 121 | for py_file in prog.py_files:
|
| 122 | pass4.DoPyFile(py_file) # parses type comments in assignments
|
| 123 |
|
| 124 | log('Wrote implementation')
|
| 125 | prog.PrintStats()
|
| 126 |
|
| 127 | except TypeSyntaxError as e:
|
| 128 | log('Type comment syntax error on line %d of %s: %r', e.lineno,
|
| 129 | py_file.filename, e.code_str)
|
| 130 | return 1
|
| 131 |
|
| 132 | log('Done')
|
| 133 |
|
| 134 | elif action == 'mycpp':
|
| 135 | paths = argv[2:]
|
| 136 | _ = paths
|
| 137 |
|
| 138 | #log('pea mycpp %s', sys.argv)
|
| 139 |
|
| 140 | timer = translate.Timer(START_TIME)
|
| 141 | timer.Section('PEA loading %s', ' '.join(paths))
|
| 142 |
|
| 143 | f = sys.stdout
|
| 144 | header_f = sys.stdout
|
| 145 |
|
| 146 | # TODO: Dict[Expression, Type]
|
| 147 | types: Dict[Any, Any] = {}
|
| 148 |
|
| 149 | to_header: List[str] = []
|
| 150 | to_compile: List[Tuple[str, Any]] = []
|
| 151 |
|
| 152 | for path in paths:
|
| 153 | # defs, imports
|
| 154 | # Ah this is an empty file!
|
| 155 | m = mypy_shim.CreateMyPyFile(path)
|
| 156 |
|
| 157 | to_compile.append((path, m))
|
| 158 |
|
| 159 | return translate.Run(timer, f, header_f, types, to_header, to_compile)
|
| 160 |
|
| 161 | elif action == 'dump-pickles':
|
| 162 | files = argv[2:]
|
| 163 |
|
| 164 | prog = Program()
|
| 165 | log('Pea begin')
|
| 166 |
|
| 167 | if not parse.ParseFiles(files, prog):
|
| 168 | return 1
|
| 169 | log('Parsed %d files and their type comments', len(files))
|
| 170 | prog.PrintStats()
|
| 171 |
|
| 172 | # Note: can't use marshal here, because it only accepts simple types
|
| 173 | pickle.dump(prog.py_files, sys.stdout.buffer)
|
| 174 | log('Dumped pickle')
|
| 175 |
|
| 176 | elif action == 'load-pickles':
|
| 177 | while True:
|
| 178 | try:
|
| 179 | py_files = pickle.load(sys.stdin.buffer)
|
| 180 | except EOFError:
|
| 181 | break
|
| 182 | log('Loaded pickle with %d files', len(py_files))
|
| 183 |
|
| 184 | else:
|
| 185 | raise RuntimeError('Invalid action %r' % action)
|
| 186 |
|
| 187 | return 0
|
| 188 |
|
| 189 |
|
| 190 | if __name__ == '__main__':
|
| 191 | try:
|
| 192 | sys.exit(main(sys.argv))
|
| 193 | except RuntimeError as e:
|
| 194 | print('FATAL: %s' % e, file=sys.stderr)
|
| 195 | sys.exit(1)
|