OILS / frontend / flag_gen.py View on Github | oils.pub

546 lines, 341 significant
1#!/usr/bin/env python2
2""" flag_gen.py - generate Python and C++ from flag specs """
3from __future__ import print_function
4
5import itertools
6import sys
7
8from _devbuild.gen.runtime_asdl import flag_type_e
9from _devbuild.gen.value_asdl import value_e
10from frontend import args
11from frontend import flag_def # side effect: flags are defined!
12from frontend import flag_spec
13from mycpp import mops
14from mycpp.mylib import log, switch
15# This causes a circular build dependency! That is annoying.
16# builtin_comp -> core/completion -> pylib/{os_path,path_stat,...} -> posix_
17#from osh import builtin_comp
18
19_ = flag_def
20
21
22def CString(s):
23 # HACKS for now
24
25 assert '"' not in s, s
26 assert '\\' not in s, s
27
28 # For the default of write --end
29 if s == '\n':
30 return '"\\n"'
31
32 return '"%s"' % s
33
34
35def _CleanFieldName(name):
36 # Avoid C++ keyword for invoke --extern
37 if name == 'extern':
38 return 'extern_'
39 if name == 'private':
40 return 'private_'
41
42 return name.replace('-', '_')
43
44
45def _WriteStrArray(f, var_name, a):
46 c_strs = ', '.join(CString(s) for s in sorted(a))
47 f.write('const char* %s[] = {%s, nullptr};\n' % (var_name, c_strs))
48 f.write('\n')
49
50
51def _WriteActionParams(f, actions, counter):
52 param_names = []
53 for key in sorted(actions):
54 action = actions[key]
55 to_write = None
56
57 if isinstance(action, args.SetToString):
58 if action.valid:
59 to_write = action.valid
60
61 elif isinstance(action, args.SetNamedOption):
62 if action.names:
63 to_write = action.names
64
65 elif isinstance(action, args.SetNamedAction):
66 if action.names:
67 to_write = action.names
68
69 if to_write:
70 uniq = counter.next()
71 var_name = 'params_%d' % uniq
72
73 _WriteStrArray(f, var_name, to_write)
74 else:
75 var_name = None
76
77 param_names.append(var_name)
78
79 return param_names
80
81
82def _WriteActions(f, var_name, actions, counter):
83 # TODO: 'osh' and 'set' duplicate shopt params!!! Maybe we want the entire
84 # action not to be duplicated?
85 param_names = _WriteActionParams(f, actions, counter)
86
87 f.write('Action_c %s[] = {\n' % var_name)
88 for i, key in enumerate(sorted(actions)):
89 action = actions[key]
90 #log('%s %s', key, action)
91
92 name = None
93 if isinstance(action, args.SetToString):
94 if action.quit_parsing_flags:
95 action_type = 'SetToString_q'
96 else:
97 action_type = 'SetToString'
98 name = action.name
99
100 elif isinstance(action, args.SetToInt):
101 action_type = 'SetToInt'
102 name = action.name
103
104 elif isinstance(action, args.SetToFloat):
105 action_type = 'SetToFloat'
106 name = action.name
107
108 elif isinstance(action, args.SetToTrue):
109 action_type = 'SetToTrue'
110 name = action.name
111
112 elif isinstance(action, args.SetAttachedBool):
113 action_type = 'SetAttachedBool'
114 name = action.name
115
116 elif isinstance(action, args.SetOption):
117 action_type = 'SetOption'
118 name = action.name
119
120 elif isinstance(action, args.SetNamedOption):
121 if action.shopt:
122 action_type = 'SetNamedOption_shopt'
123 else:
124 action_type = 'SetNamedOption'
125
126 elif isinstance(action, args.SetAction):
127 action_type = 'SetAction'
128 name = action.name
129
130 elif isinstance(action, args.SetNamedAction):
131 action_type = 'SetNamedAction'
132
133 elif isinstance(action, args.AppendEvalFlag):
134 action_type = 'AppendEvalFlag'
135 name = action.name
136
137 else:
138 raise AssertionError(action)
139
140 name_str = ('"%s"' % name) if name else 'nullptr'
141 params_str = param_names[i] or 'nullptr'
142 f.write(' {"%s", ActionType_c::%s, %s, %s},\n' %
143 (key, action_type, name_str, params_str))
144 #cc_f.write('SetToArg_c %s[] = {\n' % arity1_name)
145 f.write('''\
146 {},
147};
148
149''')
150
151
152def _WriteDefaults(cc_f, defaults_name, defaults):
153 cc_f.write('DefaultPair_c %s[] = {\n' % defaults_name)
154
155 for name in sorted(defaults):
156 val = defaults[name]
157 if val.tag() == value_e.Bool:
158 typ = 'Bool'
159 v = '{.b = %s}' % ('true' if val.b else 'false')
160 elif val.tag() == value_e.Int:
161 typ = 'Int'
162 v = '{.i = %s}' % mops.BigTruncate(val.i)
163 elif val.tag() == value_e.Float:
164 typ = 'Float'
165 # printing this to C++ is problematic
166 if val.f != -1.0:
167 raise AssertionError('Float default not supported %r' % val.f)
168 v = '{.f = -1.0}'
169 elif val.tag() == value_e.Undef:
170 typ = 'Str' # default for string
171 v = '{}'
172 elif val.tag() == value_e.Str:
173 # NOTE: 'osh' FlagSpecAndMore_ has default='nice' and default='abbrev-text'
174 typ = 'Str'
175 v = '{.s = %s}' % CString(val.s)
176
177 else:
178 raise AssertionError(val)
179
180 cc_f.write(' {%s, flag_type_e::%s, %s},\n' %
181 (CString(name), typ, v))
182
183 cc_f.write('''\
184 {},
185};
186
187''')
188
189
190def Cpp(specs, header_f, cc_f):
191 counter = itertools.count()
192
193 header_f.write("""\
194// arg_types.h is generated by frontend/flag_gen.py
195
196#ifndef ARG_TYPES_H
197#define ARG_TYPES_H
198
199#include "cpp/frontend_flag_spec.h" // for FlagSpec_c
200#include "mycpp/gc_mylib.h"
201
202using value_asdl::value;
203using value_asdl::value_e;
204
205namespace arg_types {
206""")
207 for spec_name in sorted(specs):
208 spec = specs[spec_name]
209
210 if not spec.fields:
211 continue # skip empty 'eval' spec
212
213 #
214 # Figure out how to initialize the class
215 #
216
217 init_vals = []
218 field_names = []
219 field_decls = []
220 bits = []
221 for field_name in sorted(spec.fields):
222 typ = spec.fields[field_name]
223 field_name = _CleanFieldName(field_name)
224 field_names.append(field_name)
225
226 with switch(typ) as case:
227 if case(flag_type_e.Bool):
228 init_vals.append(
229 'static_cast<value::Bool*>(attrs->at(StrFromC("%s")))->b'
230 % field_name)
231 field_decls.append('bool %s;' % field_name)
232
233 # Bug that test should find
234 #bits.append('maskbit(offsetof(%s, %s))' % (spec_name, field_name))
235
236 elif case(flag_type_e.Str):
237 # TODO: This code is ugly and inefficient! Generate something
238 # better. At least get rid of 'new' everywhere?
239 init_vals.append('''\
240attrs->at(StrFromC("%s"))->tag() == value_e::Undef
241 ? nullptr
242 : static_cast<value::Str*>(attrs->at(StrFromC("%s")))->s''' %
243 (field_name, field_name))
244
245 field_decls.append('BigStr* %s;' % field_name)
246
247 # BigStr* is a pointer type, so add a field here
248 bits.append('maskbit(offsetof(%s, %s))' %
249 (spec_name, field_name))
250
251 elif case(flag_type_e.Int):
252 init_vals.append('''\
253attrs->at(StrFromC("%s"))->tag() == value_e::Undef
254 ? -1
255 : static_cast<value::Int*>(attrs->at(StrFromC("%s")))->i''' %
256 (field_name, field_name))
257 field_decls.append('int %s;' % field_name)
258
259 elif case(flag_type_e.Float):
260 init_vals.append('''\
261attrs->at(StrFromC("%s"))->tag() == value_e::Undef
262 ? -1
263 : static_cast<value::Float*>(attrs->at(StrFromC("%s")))->f''' %
264 (field_name, field_name))
265 field_decls.append('float %s;' % field_name)
266
267 else:
268 raise AssertionError(typ)
269
270 #
271 # Now emit the class
272 #
273
274 if bits:
275 obj_tag = 'HeapTag::FixedSize'
276 mask_str = 'field_mask()'
277 else:
278 obj_tag = 'HeapTag::Opaque'
279 mask_str = 'kZeroMask'
280
281 header_f.write("""
282class %s {
283 public:
284 %s(Dict<BigStr*, value_asdl::value_t*>* attrs)""" % (spec_name, spec_name))
285
286 if field_names:
287 header_f.write('\n : ')
288 for i, field_name in enumerate(field_names):
289 if i != 0:
290 header_f.write(',\n ')
291 header_f.write('%s(%s)' % (field_name, init_vals[i]))
292 header_f.write(' {\n')
293 header_f.write(' }\n')
294 header_f.write('\n')
295
296 for decl in field_decls:
297 header_f.write(' %s\n' % decl)
298
299 header_f.write('\n')
300 header_f.write(' static constexpr ObjHeader obj_header() {\n')
301 header_f.write(' return ObjHeader::Class(%s, %s, sizeof(%s));\n' %
302 (obj_tag, mask_str, spec_name))
303 header_f.write(' }\n')
304
305 if bits:
306 header_f.write('\n')
307 header_f.write(' static constexpr uint32_t field_mask() {\n')
308 header_f.write(' return\n')
309 header_f.write(' ')
310 header_f.write('\n | '.join(bits))
311 header_f.write(';\n')
312 header_f.write(' }\n')
313 header_f.write('\n')
314
315 header_f.write("""\
316};
317""")
318
319 header_f.write("""
320extern FlagSpec_c kFlagSpecs[];
321extern FlagSpecAndMore_c kFlagSpecsAndMore[];
322
323} // namespace arg_types
324
325#endif // ARG_TYPES_H
326
327""")
328
329 cc_f.write("""\
330// arg_types.cc is generated by frontend/flag_gen.py
331
332#include "arg_types.h"
333using runtime_asdl::flag_type_e;
334
335namespace arg_types {
336
337""")
338
339 var_names = []
340 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC)):
341 spec = specs[spec_name]
342 arity0_name = None
343 arity1_name = None
344 actions_long_name = None
345 plus_name = None
346 defaults_name = None
347
348 if spec.arity0:
349 arity0_name = 'arity0_%d' % i
350 _WriteStrArray(cc_f, arity0_name, spec.arity0)
351
352 if spec.arity1:
353 arity1_name = 'arity1_%d' % i
354 _WriteActions(cc_f, arity1_name, spec.arity1, counter)
355
356 if spec.actions_long:
357 actions_long_name = 'actions_long_%d' % i
358 _WriteActions(cc_f, actions_long_name, spec.actions_long, counter)
359
360 if spec.plus_flags:
361 plus_name = 'plus_%d' % i
362 _WriteStrArray(cc_f, plus_name, spec.plus_flags)
363
364 if spec.defaults:
365 defaults_name = 'defaults_%d' % i
366 _WriteDefaults(cc_f, defaults_name, spec.defaults)
367
368 var_names.append((arity0_name, arity1_name, actions_long_name,
369 plus_name, defaults_name))
370
371 cc_f.write('FlagSpec_c kFlagSpecs[] = {\n')
372
373 # Now print a table
374 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC)):
375 spec = specs[spec_name]
376 names = var_names[i]
377 cc_f.write(' { "%s", %s, %s, %s, %s, %s },\n' % (
378 spec_name,
379 names[0] or 'nullptr',
380 names[1] or 'nullptr',
381 names[2] or 'nullptr',
382 names[3] or 'nullptr',
383 names[4] or 'nullptr',
384 ))
385
386 cc_f.write("""\
387 {},
388};
389
390""")
391
392 n = len(var_names)
393 var_names = []
394 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC_AND_MORE)):
395 spec = specs[spec_name]
396 actions_short_name = None
397 actions_long_name = None
398 plus_name = None
399 defaults_name = None
400
401 if spec.actions_short:
402 actions_short_name = 'short_%d' % (n + i)
403 _WriteActions(cc_f, actions_short_name, spec.actions_short,
404 counter)
405
406 #if spec.actions_long:
407 if spec.actions_long:
408 actions_long_name = 'long_%d' % (n + i)
409 _WriteActions(cc_f, actions_long_name, spec.actions_long, counter)
410
411 if spec.plus_flags:
412 plus_name = 'plus_%d' % i
413 _WriteStrArray(cc_f, plus_name, spec.plus_flags)
414
415 if spec.defaults:
416 defaults_name = 'defaults_%d' % (n + i)
417 _WriteDefaults(cc_f, defaults_name, spec.defaults)
418
419 var_names.append(
420 (actions_short_name, actions_long_name, plus_name, defaults_name))
421
422 cc_f.write('FlagSpecAndMore_c kFlagSpecsAndMore[] = {\n')
423 for i, spec_name in enumerate(sorted(flag_spec.FLAG_SPEC_AND_MORE)):
424 names = var_names[i]
425 cc_f.write(' { "%s", %s, %s, %s, %s },\n' % (
426 spec_name,
427 names[0] or 'nullptr',
428 names[1] or 'nullptr',
429 names[2] or 'nullptr',
430 names[3] or 'nullptr',
431 ))
432
433 cc_f.write("""\
434 {},
435};
436""")
437
438 cc_f.write("""\
439} // namespace arg_types
440""")
441
442
443def main(argv):
444 try:
445 action = argv[1]
446 except IndexError:
447 raise RuntimeError('Action required')
448
449 if 0:
450 for spec_name in sorted(flag_spec.FLAG_SPEC_AND_MORE):
451 log('%s', spec_name)
452
453 # Both kinds of specs have 'fields' attributes
454 specs = {}
455 specs.update(flag_spec.FLAG_SPEC)
456 specs.update(flag_spec.FLAG_SPEC_AND_MORE)
457 #log('SPECS %s', specs)
458
459 for spec_name in sorted(specs):
460 spec = specs[spec_name]
461 #spec.spec.PrettyPrint(f=sys.stderr)
462 #log('spec.arity1 %s', spec.spec.arity1)
463 #log('%s', spec_name)
464
465 #print(dir(spec))
466 #print(spec.arity0)
467 #print(spec.arity1)
468 #print(spec.options)
469 # Every flag has a default
470 #log('%s', spec.fields)
471
472 if action == 'cpp':
473 prefix = argv[2]
474
475 with open(prefix + '.h', 'w') as header_f:
476 with open(prefix + '.cc', 'w') as cc_f:
477 Cpp(specs, header_f, cc_f)
478
479 elif action == 'mypy':
480 print("""
481from _devbuild.gen.value_asdl import value, value_e, value_t
482from frontend.args import _Attributes
483from mycpp import mops
484from typing import cast, Dict, Optional
485""")
486 for spec_name in sorted(specs):
487 spec = specs[spec_name]
488
489 #log('%s spec.fields %s', spec_name, spec.fields)
490 if not spec.fields:
491 continue # skip empty specs, e.g. eval
492
493 print("""
494class %s(object):
495 def __init__(self, attrs):
496 # type: (Dict[str, value_t]) -> None
497""" % spec_name)
498
499 i = 0
500 for field_name in sorted(spec.fields):
501 typ = spec.fields[field_name]
502 field_name = _CleanFieldName(field_name)
503
504 with switch(typ) as case:
505 if case(flag_type_e.Bool):
506 print(
507 ' self.%s = cast(value.Bool, attrs[%r]).b # type: bool'
508 % (field_name, field_name))
509
510 elif case(flag_type_e.Str):
511 tmp = 'val%d' % i
512 print(' %s = attrs[%r]' % (tmp, field_name))
513 print(
514 ' self.%s = None if %s.tag() == value_e.Undef else cast(value.Str, %s).s # type: Optional[str]'
515 % (field_name, tmp, tmp))
516
517 elif case(flag_type_e.Int):
518 tmp = 'val%d' % i
519 print(' %s = attrs[%r]' % (tmp, field_name))
520 print(
521 ' self.%s = mops.BigInt(-1) if %s.tag() == value_e.Undef else cast(value.Int, %s).i # type: mops.BigInt'
522 % (field_name, tmp, tmp))
523
524 elif case(flag_type_e.Float):
525 tmp = 'val%d' % i
526 print(' %s = attrs[%r]' % (tmp, field_name))
527 print(
528 ' self.%s = -1.0 if %s.tag() == value_e.Undef else cast(value.Float, %s).f # type: float'
529 % (field_name, tmp, tmp))
530 else:
531 raise AssertionError(typ)
532
533 i += 1
534
535 print()
536
537 else:
538 raise RuntimeError('Invalid action %r' % action)
539
540
541if __name__ == '__main__':
542 try:
543 main(sys.argv)
544 except RuntimeError as e:
545 print('FATAL: %s' % e, file=sys.stderr)
546 sys.exit(1)