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

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