OILS / mycpp / mycpp_main.py View on Github | oils.pub

261 lines, 162 significant
1#!/usr/bin/env python3
2from __future__ import print_function
3"""
4mycpp_main.py - Translate a subset of Python to C++, using MyPy's typed AST.
5"""
6
7import optparse
8import os
9import sys
10import time
11
12START_TIME = time.time() # measure before imports
13
14# MyPy deps
15from mypy.build import build as mypy_build
16from mypy.main import process_options
17
18from mycpp.util import log
19from mycpp import translate
20
21from typing import (List, Optional, Tuple, Any, Iterator, TYPE_CHECKING)
22
23if TYPE_CHECKING:
24 from mypy.nodes import MypyFile
25 from mypy.modulefinder import BuildSource
26 from mypy.build import BuildResult
27
28
29def 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 p.add_option('--cc-out',
41 dest='cc_out',
42 default=None,
43 help='.cc file to write to')
44
45 p.add_option('--to-header',
46 dest='to_header',
47 action='append',
48 default=[],
49 help='Export this module to a header, e.g. frontend.args')
50
51 p.add_option('--header-out',
52 dest='header_out',
53 default=None,
54 help='Write this header')
55
56 p.add_option(
57 '--stack-roots-warn',
58 dest='stack_roots_warn',
59 default=None,
60 type='int',
61 help='Emit warnings about functions with too many stack roots')
62
63 p.add_option('--minimize-stack-roots',
64 dest='minimize_stack_roots',
65 action='store_true',
66 default=False,
67 help='Try to minimize the number of GC stack roots.')
68
69 return p
70
71
72# Copied from mypyc/build.py
73def get_mypy_config(
74 paths: List[str],
75 mypy_options: Optional[List[str]]) -> Tuple[List['BuildSource'], Any]:
76 """Construct mypy BuildSources and Options from file and options lists"""
77 # It is kind of silly to do this but oh well
78 mypy_options = mypy_options or []
79 mypy_options.append('--')
80 mypy_options.extend(paths)
81
82 sources, options = process_options(mypy_options)
83
84 options.show_traceback = True
85 # Needed to get types for all AST nodes
86 options.export_types = True
87 # TODO: Support incremental checking
88 options.incremental = False
89 # 10/2019: FIX for MyPy 0.730. Not sure why I need this but I do.
90 options.preserve_asts = True
91
92 # 1/2023: Workaround for conditional import in osh/builtin_comp.py
93 # Same as devtools/types.sh
94 options.warn_unused_ignores = False
95
96 for source in sources:
97 options.per_module_options.setdefault(source.module,
98 {})['mypyc'] = True
99
100 return sources, options
101
102
103_FIRST = ('asdl.runtime', 'core.vm')
104
105# should be LAST because they use base classes
106_LAST = ('builtin.bracket_osh', 'builtin.completion_osh', 'core.shell')
107
108
109def ModulesToCompile(result: 'BuildResult',
110 mod_names: List[str]) -> Iterator[Tuple[str, 'MypyFile']]:
111 # HACK TO PUT asdl/runtime FIRST.
112 #
113 # Another fix is to hoist those to the declaration phase? Not sure if that
114 # makes sense.
115
116 # FIRST files. Somehow the MyPy builder reorders the modules.
117 for name, module in result.files.items():
118 if name in _FIRST:
119 yield name, module
120
121 for name, module in result.files.items():
122 # Only translate files that were mentioned on the command line
123 suffix = name.split('.')[-1]
124 if suffix not in mod_names:
125 continue
126
127 if name in _FIRST: # We already did these
128 continue
129
130 if name in _LAST: # We'll do these later
131 continue
132
133 yield name, module
134
135 # LAST files
136 for name, module in result.files.items():
137 if name in _LAST:
138 yield name, module
139
140
141def _DedupeHack(
142 to_compile: List[Tuple[str,
143 'MypyFile']]) -> List[Tuple[str, 'MypyFile']]:
144 # Filtering step
145 filtered = []
146 seen = set()
147 for name, module in to_compile:
148 # HACK: Why do I get oil.asdl.tdop in addition to asdl.tdop?
149 if name.startswith('oil.'):
150 name = name[4:]
151
152 # ditto with testpkg.module1
153 if name.startswith('mycpp.'):
154 name = name[6:]
155
156 if name not in seen: # remove dupe
157 filtered.append((name, module))
158 seen.add(name)
159 return filtered
160
161
162def main(argv: List[str]) -> int:
163 timer = translate.Timer(START_TIME)
164
165 # Hack:
166 mypy_options = [
167 '--py2',
168 '--strict',
169 '--no-implicit-optional',
170 '--no-strict-optional',
171 # for consistency?
172 '--follow-imports=silent',
173 #'--verbose',
174 ]
175
176 o = Options()
177 opts, argv = o.parse_args(argv)
178 paths = argv[1:] # e.g. asdl/typed_arith_parse.py
179
180 timer.Section('mycpp: LOADING %s', ' '.join(paths))
181
182 #log('\tmycpp: MYPYPATH = %r', os.getenv('MYPYPATH'))
183
184 if 0:
185 print(opts)
186 print(paths)
187 return
188
189 # e.g. asdl/typed_arith_parse.py -> 'typed_arith_parse'
190 mod_names = [os.path.basename(p) for p in paths]
191 mod_names = [os.path.splitext(name)[0] for name in mod_names]
192
193 # Ditto
194 to_header = opts.to_header
195
196 #log('to_header %s', to_header)
197
198 sources, options = get_mypy_config(paths, mypy_options)
199 if 0:
200 for source in sources:
201 log('source %s', source)
202 log('')
203 #log('options %s', options)
204
205 #
206 # Type checking, which builds a Dict[Expression, Type] (12+ seconds)
207 #
208 result = mypy_build(sources=sources, options=options)
209
210 if result.errors:
211 log('')
212 log('-' * 80)
213 for e in result.errors:
214 log(e)
215 log('-' * 80)
216 log('')
217 return 1
218
219 # no-op
220 if 0:
221 for name in result.graph:
222 log('result %s %s', name, result.graph[name])
223 log('')
224
225 to_compile = list(ModulesToCompile(result, mod_names))
226 to_compile = _DedupeHack(to_compile)
227
228 if 0:
229 for name, module in to_compile:
230 log('to_compile %s', name)
231 log('')
232 #import pickle
233 # can't pickle but now I see deserialize() nodes and stuff
234 #s = pickle.dumps(module)
235 #log('%d pickle', len(s))
236
237 if opts.cc_out:
238 f = open(opts.cc_out, 'w')
239 else:
240 f = sys.stdout
241
242 header_f = None
243 if opts.header_out:
244 header_f = open(opts.header_out, 'w') # Not closed
245
246 return translate.Run(timer,
247 f,
248 header_f,
249 result.types,
250 to_header,
251 to_compile,
252 stack_roots_warn=opts.stack_roots_warn,
253 minimize_stack_roots=opts.minimize_stack_roots)
254
255
256if __name__ == '__main__':
257 try:
258 sys.exit(main(sys.argv))
259 except RuntimeError as e:
260 print('FATAL: %s' % e, file=sys.stderr)
261 sys.exit(1)