1 | #!/usr/bin/env python2
|
2 | """
|
3 | asdl_main.py - Generate Python and C from ASDL schemas.
|
4 | """
|
5 | from __future__ import print_function
|
6 |
|
7 | import optparse
|
8 | import os
|
9 | import sys
|
10 |
|
11 | from asdl import ast
|
12 | from asdl import front_end
|
13 | from asdl import gen_cpp
|
14 | from asdl import gen_python
|
15 | #from asdl.util import log
|
16 |
|
17 | ARG_0 = os.path.basename(sys.argv[0])
|
18 |
|
19 |
|
20 | def Options():
|
21 | """Returns an option parser instance."""
|
22 |
|
23 | p = optparse.OptionParser()
|
24 | p.add_option('--no-pretty-print-methods',
|
25 | dest='pretty_print_methods',
|
26 | action='store_false',
|
27 | default=True,
|
28 | help='Whether to generate pretty printing methods')
|
29 |
|
30 | # Control Python constructors
|
31 |
|
32 | # for hnode.asdl
|
33 | p.add_option('--py-init-N',
|
34 | dest='py_init_n',
|
35 | action='store_true',
|
36 | default=False,
|
37 | help='Generate Python __init__ that requires every field')
|
38 |
|
39 | # The default, which matches C++
|
40 | p.add_option(
|
41 | '--init-zero-N',
|
42 | dest='init_zero_n',
|
43 | action='store_true',
|
44 | default=True,
|
45 | help='Generate 0 arg and N arg constructors, in Python and C++')
|
46 |
|
47 | p.add_option('--abbrev-module',
|
48 | dest='abbrev_module',
|
49 | default=None,
|
50 | help='Import this module to find abbreviations')
|
51 |
|
52 | return p
|
53 |
|
54 |
|
55 | class UserType(object):
|
56 | """TODO: Delete this class after we have modules with 'use'?"""
|
57 |
|
58 | def __init__(self, mod_name, type_name):
|
59 | self.mod_name = mod_name
|
60 | self.type_name = type_name
|
61 |
|
62 | def __repr__(self):
|
63 | return '<UserType %s %s>' % (self.mod_name, self.type_name)
|
64 |
|
65 |
|
66 | def main(argv):
|
67 | o = Options()
|
68 | opts, argv = o.parse_args(argv)
|
69 |
|
70 | try:
|
71 | action = argv[1]
|
72 | except IndexError:
|
73 | raise RuntimeError('Action required')
|
74 |
|
75 | try:
|
76 | schema_path = argv[2]
|
77 | except IndexError:
|
78 | raise RuntimeError('Schema path required')
|
79 |
|
80 | schema_filename = os.path.basename(schema_path)
|
81 | if schema_filename in ('syntax.asdl', 'runtime.asdl'):
|
82 | app_types = {'id': UserType('id_kind_asdl', 'Id_t')}
|
83 | else:
|
84 | app_types = {}
|
85 |
|
86 | if opts.abbrev_module:
|
87 | # Weird Python rule for importing: fromlist needs to be non-empty.
|
88 | abbrev_mod = __import__(opts.abbrev_module, fromlist=['.'])
|
89 | else:
|
90 | abbrev_mod = None
|
91 |
|
92 | abbrev_mod_entries = dir(abbrev_mod) if abbrev_mod else []
|
93 | # e.g. syntax_abbrev
|
94 | abbrev_ns = opts.abbrev_module.split('.')[-1] if abbrev_mod else None
|
95 |
|
96 | if action == 'c': # Generate C code for the lexer
|
97 | with open(schema_path) as f:
|
98 | schema_ast = front_end.LoadSchema(f, app_types)
|
99 |
|
100 | v = gen_cpp.CEnumVisitor(sys.stdout)
|
101 | v.VisitModule(schema_ast)
|
102 |
|
103 | elif action == 'cpp': # Generate C++ code for ASDL schemas
|
104 | out_prefix = argv[3]
|
105 |
|
106 | with open(schema_path) as f:
|
107 | schema_ast = front_end.LoadSchema(f, app_types)
|
108 |
|
109 | # asdl/typed_arith.asdl -> typed_arith_asdl
|
110 | ns = os.path.basename(schema_path).replace('.', '_')
|
111 |
|
112 | with open(out_prefix + '.h', 'w') as f:
|
113 | guard = ns.upper()
|
114 | f.write("""\
|
115 | // %s.h is generated by %s
|
116 |
|
117 | #ifndef %s
|
118 | #define %s
|
119 |
|
120 | """ % (out_prefix, ARG_0, guard, guard))
|
121 |
|
122 | f.write("""\
|
123 | #include <cstdint>
|
124 | """)
|
125 | f.write("""
|
126 | #include "mycpp/runtime.h"
|
127 | """)
|
128 | if opts.pretty_print_methods:
|
129 | if 1:
|
130 | # TODO: gradually migrate to this templated code, reducing code gen
|
131 | f.write('#include "asdl/cpp_runtime.h"\n')
|
132 | else:
|
133 | f.write("""\
|
134 | #include "_gen/asdl/hnode.asdl.h"
|
135 | using hnode_asdl::hnode_t;
|
136 |
|
137 | """)
|
138 |
|
139 | if app_types:
|
140 | f.write("""\
|
141 | #include "_gen/frontend/id_kind.asdl.h"
|
142 | using id_kind_asdl::Id_t;
|
143 |
|
144 | """)
|
145 | # Only works with gross hacks
|
146 | if 0:
|
147 | #if schema_path.endswith('/syntax.asdl'):
|
148 | f.write(
|
149 | '#include "prebuilt/frontend/syntax_abbrev.mycpp.h"\n')
|
150 |
|
151 | for use in schema_ast.uses:
|
152 | # Forward declarations in the header, like
|
153 | # namespace syntax_asdl { class command_t; }
|
154 | # must come BEFORE namespace, so it can't be in the visitor.
|
155 |
|
156 | # assume sum type for now!
|
157 | cpp_names = [
|
158 | 'class %s;' % ast.TypeNameHeuristic(n)
|
159 | for n in use.type_names
|
160 | ]
|
161 | f.write('namespace %s_asdl { %s }\n' %
|
162 | (use.module_parts[-1], ' '.join(cpp_names)))
|
163 | f.write('\n')
|
164 |
|
165 | for extern in schema_ast.externs:
|
166 | names = extern.names
|
167 | type_name = names[-1]
|
168 | cpp_namespace = names[-2]
|
169 |
|
170 | # TODO: This isn't enough for Oils
|
171 | # I think we would have to export header to
|
172 | # _gen/bin/oils_for_unix.mycpp.cc or something
|
173 | # Does that create circular dependencies?
|
174 | #
|
175 | # Or maybe of 'extern' we can have 'include' or something?
|
176 | # Maybe we need `.pyi` files in MyPy?
|
177 |
|
178 | f.write("""\
|
179 | namespace %s {
|
180 | class %s {
|
181 | public:
|
182 | hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
|
183 | };
|
184 | }
|
185 | """ % (cpp_namespace, type_name))
|
186 |
|
187 | f.write("""\
|
188 | namespace %s {
|
189 |
|
190 | // use struct instead of namespace so 'using' works consistently
|
191 | #define ASDL_NAMES struct
|
192 |
|
193 | """ % ns)
|
194 |
|
195 | v = gen_cpp.ForwardDeclareVisitor(f)
|
196 | v.VisitModule(schema_ast)
|
197 |
|
198 | debug_info = {}
|
199 | v2 = gen_cpp.ClassDefVisitor(
|
200 | f,
|
201 | pretty_print_methods=opts.pretty_print_methods,
|
202 | debug_info=debug_info)
|
203 | v2.VisitModule(schema_ast)
|
204 |
|
205 | f.write("""
|
206 | } // namespace %s
|
207 |
|
208 | #endif // %s
|
209 | """ % (ns, guard))
|
210 |
|
211 | try:
|
212 | debug_info_path = argv[4]
|
213 | except IndexError:
|
214 | pass
|
215 | else:
|
216 | with open(debug_info_path, 'w') as f:
|
217 | from pprint import pformat
|
218 | f.write('''\
|
219 | cpp_namespace = %r
|
220 | tags_to_types = \\
|
221 | %s
|
222 | ''' % (ns, pformat(debug_info)))
|
223 |
|
224 | if not opts.pretty_print_methods:
|
225 | # No .cc file at all
|
226 | return
|
227 |
|
228 | with open(out_prefix + '.cc', 'w') as f:
|
229 | f.write("""\
|
230 | // %s.cc is generated by %s
|
231 |
|
232 | #include "%s.h"
|
233 | #include <assert.h>
|
234 | """ % (out_prefix, ARG_0, out_prefix))
|
235 |
|
236 | if abbrev_mod_entries:
|
237 | # This is somewhat hacky, works for frontend/syntax_abbrev.py and
|
238 | # prebuilt/frontend/syntax_abbrev.mycpp.h
|
239 | part0, part1 = opts.abbrev_module.split('.')
|
240 | f.write("""\
|
241 | #include "prebuilt/%s/%s.mycpp.h"
|
242 | """ % (part0, part1))
|
243 |
|
244 | f.write("""\
|
245 | #include "prebuilt/asdl/runtime.mycpp.h" // generated code uses wrappers here
|
246 | """)
|
247 |
|
248 | # To call pretty-printing methods
|
249 | for use in schema_ast.uses:
|
250 | f.write('#include "_gen/%s.asdl.h" // "use" in ASDL \n' %
|
251 | '/'.join(use.module_parts))
|
252 |
|
253 | f.write("""\
|
254 |
|
255 | // Generated code uses these types
|
256 | using hnode_asdl::hnode;
|
257 | using hnode_asdl::Field;
|
258 | using hnode_asdl::color_e;
|
259 |
|
260 | """)
|
261 |
|
262 | if app_types:
|
263 | f.write('using id_kind_asdl::Id_str;\n')
|
264 |
|
265 | f.write("""
|
266 | namespace %s {
|
267 |
|
268 | """ % ns)
|
269 |
|
270 | v3 = gen_cpp.MethodDefVisitor(
|
271 | f,
|
272 | abbrev_ns=abbrev_ns,
|
273 | abbrev_mod_entries=abbrev_mod_entries)
|
274 | v3.VisitModule(schema_ast)
|
275 |
|
276 | f.write("""
|
277 | } // namespace %s
|
278 | """ % ns)
|
279 |
|
280 | elif action == 'mypy': # Generated typed MyPy code
|
281 | with open(schema_path) as f:
|
282 | schema_ast = front_end.LoadSchema(f, app_types)
|
283 |
|
284 | f = sys.stdout
|
285 |
|
286 | # TODO: Remove Any once we stop using it
|
287 | f.write("""\
|
288 | from asdl import pybase
|
289 | from mycpp import mops
|
290 | from typing import Optional, List, Tuple, Dict, Any, cast, TYPE_CHECKING
|
291 | """)
|
292 |
|
293 | if schema_ast.uses:
|
294 | f.write('\n')
|
295 | f.write('if TYPE_CHECKING:\n')
|
296 | for use in schema_ast.uses:
|
297 | py_names = [ast.TypeNameHeuristic(n) for n in use.type_names]
|
298 | # indented
|
299 | f.write(' from _devbuild.gen.%s_asdl import %s\n' %
|
300 | (use.module_parts[-1], ', '.join(py_names)))
|
301 |
|
302 | if schema_ast.externs:
|
303 | f.write('\n')
|
304 | f.write('if TYPE_CHECKING:\n')
|
305 | for extern in schema_ast.externs:
|
306 | n = extern.names
|
307 | mod_parts = n[:-2]
|
308 | f.write(' from %s import %s\n' % ('.'.join(mod_parts), n[-2]))
|
309 |
|
310 | for typ in app_types.itervalues():
|
311 | if isinstance(typ, UserType):
|
312 | f.write('from _devbuild.gen.%s import %s\n' %
|
313 | (typ.mod_name, typ.type_name))
|
314 | # HACK
|
315 | f.write('from _devbuild.gen.%s import Id_str\n' % typ.mod_name)
|
316 |
|
317 | if opts.pretty_print_methods:
|
318 | f.write("""
|
319 | from asdl import runtime # For runtime.NO_SPID
|
320 | from asdl.runtime import NewRecord, NewLeaf, TraversalState
|
321 | from _devbuild.gen.hnode_asdl import color_e, hnode, hnode_e, hnode_t, Field
|
322 |
|
323 | """)
|
324 | if opts.abbrev_module:
|
325 | f.write('from %s import *\n' % opts.abbrev_module)
|
326 | f.write('\n')
|
327 |
|
328 | v = gen_python.GenMyPyVisitor(
|
329 | f,
|
330 | abbrev_mod_entries,
|
331 | pretty_print_methods=opts.pretty_print_methods,
|
332 | py_init_n=opts.py_init_n)
|
333 | v.VisitModule(schema_ast)
|
334 |
|
335 | else:
|
336 | raise RuntimeError('Invalid action %r' % action)
|
337 |
|
338 |
|
339 | if __name__ == '__main__':
|
340 | try:
|
341 | main(sys.argv)
|
342 | except RuntimeError as e:
|
343 | print('%s: FATAL: %s' % (ARG_0, e), file=sys.stderr)
|
344 | sys.exit(1)
|