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)
|