OILS / asdl / gen_cpp.py View on Github | oils.pub

1114 lines, 687 significant
1"""
2gen_cpp.py - Generate C++ classes from an ASDL schema.
3
4TODO:
5
6- Integrate some of the lessons here:
7 - https://github.com/oilshell/blog-code/tree/master/asdl
8 - And maybe mycpp/target_lang.cc
9
10- pretty printing methods
11 - so asdl/format.py get translated?
12
13- NoOp needs to be instantiated without args?
14- dict becomes Dict[str, str] ?
15
16- How do optional ASDL values like int? work? Use C++ default values?
17 - This means that all the optionals have to be on the end. That seems OK.
18 - I guess that's how Python does it.
19"""
20from __future__ import print_function
21
22import sys
23
24from collections import defaultdict
25
26from asdl import ast
27from asdl import visitor
28from asdl.util import log
29
30from typing import List, Dict, Tuple, IO, Union, Optional, Any, cast, TYPE_CHECKING
31
32if TYPE_CHECKING:
33 from asdl.asdl_main import Abbrev
34
35_ = log
36
37
38# Used by core/asdl_gen.py to generate _devbuild/gen/osh-types.h, with
39# lex_mode__*
40class CEnumVisitor(visitor.AsdlVisitor):
41
42 def VisitSimpleSum(self, sum, name, depth):
43 # type: (ast.SimpleSum, str, int) -> None
44
45 # Just use #define, since enums aren't namespaced.
46 for i, variant in enumerate(sum.types):
47 self.Emit('#define %s__%s %d' % (name, variant.name, i + 1), depth)
48 self.Emit("", depth)
49
50
51def WriteDebugInfo(debug_info, ns, debug_info_path):
52 # type: (Dict[str, Any], str, str) -> None
53
54 with open(debug_info_path, 'w') as f:
55 from pprint import pformat
56 f.write('''\
57cpp_namespace = %r
58tags_to_types = \\
59%s
60''' % (ns, pformat(debug_info)))
61
62
63def _WriteUses(f, schema_ast):
64 # type: (IO[bytes], ast.Module) -> None
65 for use in schema_ast.uses:
66 # HACK to work around limitation: we can't "use" simple enums
67 if use.module_parts == ['frontend', 'id_kind']:
68 f.write('namespace %s_asdl { typedef uint16_t Id_t; }\n' %
69 use.module_parts[-1])
70 continue
71
72 # Forward declarations in the header, like
73 # namespace syntax_asdl { class command_t; }
74 # must come BEFORE namespace, so it can't be in the visitor.
75
76 # assume sum type for now!
77 cpp_names = []
78 for n in use.type_names:
79 py_name, _ = ast.TypeNameHeuristic(n)
80 cpp_names.append('class %s;' % py_name)
81
82 f.write('namespace %s_asdl { %s }\n' %
83 (use.module_parts[-1], ' '.join(cpp_names)))
84 f.write('\n')
85
86 for extern in schema_ast.externs:
87 names = extern.names
88 type_name = names[-1]
89 cpp_namespace = names[-2]
90
91 # TODO: This 'extern' feature isn't enough for Oils
92 # I think we would have to export header to
93 # _gen/bin/oils_for_unix.mycpp.cc or something
94 # Does that create circular dependencies?
95 #
96 # Or maybe of 'extern' we can have 'include' or something?
97 # Maybe we need `.pyi` files in MyPy?
98
99 f.write("""\
100namespace %s {
101class %s {
102public:
103hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen);
104};
105}
106""" % (cpp_namespace, type_name))
107
108
109def WriteHeaderFile(schema_ast, ARG_0, ns, pretty_print_methods, out_prefix):
110 # type: (ast.Module, str, str, bool, str) -> Dict[str, int]
111 guard = ns.upper()
112 with open(out_prefix + '.h', 'w') as f:
113 f.write("""\
114// %s.h is generated by %s
115
116#ifndef %s
117#define %s
118
119#include <cstdint>
120#include "mycpp/runtime.h"
121
122""" % (out_prefix, ARG_0, guard, guard))
123
124 if pretty_print_methods:
125 f.write('#include "asdl/cpp_runtime.h"\n')
126
127 # uses, externs
128 _WriteUses(f, schema_ast)
129
130 f.write("""\
131namespace %s {
132
133// use struct instead of namespace so 'using' works consistently
134#define ASDL_NAMES struct
135
136""" % ns)
137
138 # Must be in the namespace
139 v1 = ForwardDeclareVisitor(f)
140 v1.VisitModule(schema_ast)
141
142 # Fill in debug_info
143 debug_info = {} # type: Dict[str, int]
144 v2 = ClassDefVisitor(f,
145 pretty_print_methods=pretty_print_methods,
146 debug_info=debug_info)
147 v2.VisitModule(schema_ast)
148
149 f.write("""
150} // namespace %s
151
152#endif // %s
153""" % (ns, guard))
154
155 return debug_info
156
157
158def WriteCppFile(schema_ast, ARG_0, ns, abbrev, out_prefix):
159 # type: (ast.Module, str, str, Abbrev, str) -> None
160
161 with open(out_prefix + '.cc', 'w') as f:
162 f.write("""\
163// %s.cc is generated by %s
164
165#include "%s.h"
166#include <assert.h>
167""" % (out_prefix, ARG_0, out_prefix))
168
169 if abbrev.mod_entries:
170 # This is somewhat hacky, works for frontend/syntax_abbrev.py and
171 # prebuilt/frontend/syntax_abbrev.mycpp.h
172 part0, part1 = abbrev.module.split('.')
173 f.write('#include "prebuilt/%s/%s.mycpp.h"\n' % (part0, part1))
174
175 # e.g. runtime::NewRecord() and TraversalState are from asdl/runtime.py
176 # TODO: it would be nice to get rid of this
177 f.write('#include "prebuilt/asdl/runtime.mycpp.h" // from mycpp\n')
178
179 # To call pretty-printing methods
180 for use in schema_ast.uses:
181 f.write('#include "_gen/%s.asdl.h" // "use" in ASDL\n' %
182 '/'.join(use.module_parts))
183 # HACK
184 if use.module_parts == ['frontend', 'id_kind']:
185 f.write('using id_kind_asdl::Id_str;\n')
186
187 f.write("""\
188
189// Generated code uses these types
190using hnode_asdl::hnode;
191using hnode_asdl::Field;
192using hnode_asdl::color_e;
193
194namespace %s {
195
196""" % ns)
197
198 v3 = MethodDefVisitor(f,
199 abbrev_ns=abbrev.ns,
200 abbrev_mod_entries=abbrev.mod_entries)
201 v3.VisitModule(schema_ast)
202
203 f.write("""
204} // namespace %s
205""" % ns)
206
207
208class ForwardDeclareVisitor(visitor.AsdlVisitor):
209 """Print forward declarations.
210
211 ASDL allows forward references of types, but C++ doesn't.
212 """
213
214 def VisitCompoundSum(self, sum, name, depth):
215 # type: (ast.Sum, str, int) -> None
216 self.Emit("class %(name)s_t;" % locals(), depth)
217
218 def VisitProduct(self, product, name, depth):
219 # type: (ast.Product, str, int) -> None
220 self.Emit("class %(name)s;" % locals(), depth)
221
222 def EmitFooter(self):
223 # type: () -> None
224 self.Emit("", 0) # blank line
225
226
227_PRIMITIVES = {
228 'string': 'BigStr*', # declared in containers.h
229 'int': 'int',
230 'uint16': 'uint16_t',
231 'BigInt': 'mops::BigInt',
232 'float': 'double',
233 'bool': 'bool',
234 'any': 'void*',
235}
236
237
238def _GetCppType(typ):
239 # type: (ast.type_expr_t) -> str
240 if isinstance(typ, ast.ParameterizedType):
241 type_name = typ.type_name
242
243 if type_name == 'Dict':
244 k_type = _GetCppType(typ.children[0])
245 v_type = _GetCppType(typ.children[1])
246 return 'Dict<%s, %s>*' % (k_type, v_type)
247
248 if type_name == 'List':
249 c_type = _GetCppType(typ.children[0])
250 return 'List<%s>*' % (c_type)
251
252 if type_name == 'Optional':
253 c_type = _GetCppType(typ.children[0])
254 return c_type
255
256 raise AssertionError()
257
258 elif isinstance(typ, ast.NamedType):
259
260 if typ.resolved:
261 if isinstance(typ.resolved, ast.SimpleSum):
262 return '%s_t' % typ.name
263 if isinstance(typ.resolved, ast.Sum):
264 return '%s_t*' % typ.name
265 if isinstance(typ.resolved, ast.Product):
266 return '%s*' % typ.name
267 if isinstance(typ.resolved, ast.Use):
268 py_name, is_pointer = ast.TypeNameHeuristic(typ.name)
269 star = '*' if is_pointer else ''
270 return '%s_asdl::%s%s' % (typ.resolved.module_parts[-1],
271 py_name, star)
272 if isinstance(typ.resolved, ast.Extern):
273 r = typ.resolved
274 type_name = r.names[-1]
275 cpp_namespace = r.names[-2]
276 return '%s::%s*' % (cpp_namespace, type_name)
277 raise AssertionError()
278
279 return _PRIMITIVES[typ.name]
280
281 else:
282 raise AssertionError()
283
284
285def _IsManagedType(typ):
286 # type: (ast.type_expr_t) -> bool
287 # This is a little cheesy, but works
288 return _GetCppType(typ).endswith('*')
289
290
291def _DefaultValue(typ, conditional=True):
292 # type: (ast.type_expr_t, bool) -> str
293 """Values that the ::CreateNull() constructor passes."""
294
295 if isinstance(typ, ast.ParameterizedType):
296 type_name = typ.type_name
297
298 if type_name == 'Dict': # TODO: can respect alloc_dicts=True
299 return 'nullptr'
300
301 elif type_name == 'List':
302 c_type = _GetCppType(typ.children[0])
303
304 d = 'Alloc<List<%s>>()' % (c_type)
305 if conditional:
306 return 'alloc_lists ? %s : nullptr' % d
307 else:
308 return d
309
310 elif type_name == 'Optional':
311 return 'nullptr'
312
313 else:
314 raise AssertionError(type_name)
315
316 elif isinstance(typ, ast.NamedType):
317 type_name = typ.name
318
319 if type_name in ('int', 'uint16', 'BigInt'):
320 default = '-1'
321 elif type_name == 'id': # hard-coded HACK
322 default = '-1'
323 elif type_name == 'bool':
324 default = 'false'
325 elif type_name == 'float':
326 default = '0.0' # or should it be NaN?
327 elif type_name == 'string':
328 default = 'kEmptyString'
329
330 elif typ.resolved and isinstance(typ.resolved, ast.SimpleSum):
331 sum_type = typ.resolved
332 # Just make it the first variant. We could define "Undef" for
333 # each enum, but it doesn't seem worth it.
334 default = '%s_e::%s' % (type_name, sum_type.types[0].name)
335
336 else:
337 default = 'nullptr' # Sum or Product
338 return default
339
340 else:
341 raise AssertionError()
342
343
344def _HNodeExpr(typ, var_name):
345 # type: (ast.type_expr_t, str) -> Tuple[str, bool]
346 none_guard = False
347
348 if ast.IsOptional(typ):
349 narrow = cast(ast.ParameterizedType, typ)
350 typ = narrow.children[0] # descend one level
351
352 if isinstance(typ, ast.ParameterizedType):
353 code_str = '%s->PrettyTree()' % var_name
354 none_guard = True
355
356 elif isinstance(typ, ast.NamedType):
357
358 type_name = typ.name
359
360 if type_name in ('bool', 'int', 'uint16', 'BigInt', 'float', 'string'):
361 code_str = "ToPretty(%s)" % var_name
362
363 elif type_name == 'any':
364 # This is used for _BuiltinFunc, _BuiltinProc. There is not that much to customize here.
365 code_str = 'Alloc<hnode::Leaf>(StrFromC("<extern>"), color_e::External)' # % var_name
366
367 elif type_name == 'id': # was meta.UserType
368 code_str = 'Alloc<hnode::Leaf>(Id_str(%s, false), color_e::UserType)' % var_name
369
370 elif typ.resolved and isinstance(typ.resolved, ast.SimpleSum):
371 # ASDL could generate ToPretty<T> ?
372 code_str = 'Alloc<hnode::Leaf>(%s_str(%s), color_e::TypeName)' % (
373 type_name, var_name)
374
375 else:
376 code_str = '%s->PrettyTree(do_abbrev, seen)' % var_name
377 none_guard = True
378
379 else:
380 raise AssertionError()
381
382 return code_str, none_guard
383
384
385# Variant tags are NOT unique:
386# for each sum type, they go from 0, 1, 2 ... N
387# There can be up to 64
388# Product types tags ARE unique, because any of them can be SHARED between sum types
389# They start at 64
390MAX_VARIANTS_PER_SUM = 64
391
392
393class ClassDefVisitor(visitor.AsdlVisitor):
394 """Generate C++ declarations and type-safe enums."""
395
396 def __init__(
397 self,
398 f, # type: IO[bytes]
399 pretty_print_methods=True, # type: bool
400 debug_info=None, # type: Optional[Dict[str, Any]]
401 ):
402 # type: (...) -> None
403 """
404 Args:
405 f: file to write to
406 debug_info: dictionary fill in with info for GDB
407 """
408 visitor.AsdlVisitor.__init__(self, f)
409 self.pretty_print_methods = pretty_print_methods
410 self.debug_info = debug_info if debug_info is not None else {}
411
412 self._shared_type_tags = {} # type: Dict[str, int]
413 self._product_counter = MAX_VARIANTS_PER_SUM
414
415 self._products = [] # type: List[Any]
416 self._base_classes = defaultdict(list) # type: Dict[str, List[str]]
417
418 self._subtypes = [] # type: List[Any]
419
420 def _EmitEnum(self, sum, sum_name, depth, strong=False, is_simple=False):
421 # type: (ast.Sum, str, int, bool, bool) -> Dict[int, str]
422 enum = []
423 int_to_type = {}
424 add_suffix = not ('no_namespace_suffix' in sum.generate)
425 for i, variant in enumerate(sum.types):
426 if variant.shared_type: # Copied from gen_python.py
427 tag_num = self._shared_type_tags[variant.shared_type]
428 # e.g. DoubleQuoted may have base types expr_t, word_part_t
429 base_class = sum_name + '_t'
430 bases = self._base_classes[variant.shared_type]
431 if base_class in bases:
432 raise RuntimeError(
433 "Two tags in sum %r refer to product type %r" %
434 (sum_name, variant.shared_type))
435 else:
436 bases.append(base_class)
437 type_str = variant.shared_type
438 else:
439 tag_num = i + 1
440 type_str = '%s__%s' % (sum_name, variant.name)
441 int_to_type[tag_num] = type_str
442 enum.append((variant.name, tag_num)) # zero is reserved
443
444 if strong:
445 enum_name = '%s_e' % sum_name if add_suffix else sum_name
446
447 # Simple sum types can be STRONG since there's no possibility of multiple
448 # inheritance!
449
450 self.Emit('enum class %s {' % enum_name, depth)
451 for name, tag_num in enum:
452 self.Emit('%s = %d,' % (name, tag_num), depth + 1)
453 self.Emit('};', depth)
454
455 # type alias to match Python code
456 self.Emit('typedef %s %s_t;' % (enum_name, sum_name), depth)
457 self.Emit('', depth)
458
459 if self.pretty_print_methods:
460 sum_name = ast.NameHack(sum_name)
461 self.Emit(
462 'BigStr* %s_str(%s tag, bool dot = true);' %
463 (sum_name, enum_name), depth)
464 self.Emit('', depth)
465
466 else:
467 if is_simple:
468 enum_name = '%s_i' % sum_name if add_suffix else sum_name
469 else:
470 enum_name = '%s_e' % sum_name if add_suffix else sum_name
471
472 # Awkward struct/enum C++ idiom because:
473
474 # 1. namespace can't be "imported" with 'using'
475 # 2. plain enum pollutes outer namespace
476 # 3. C++ 11 'enum class' does not allow conversion to int
477 # 4. namespace and 'static const int' or 'static constexpr int' gives
478 # weird link errors
479 # https://quuxplusone.github.io/blog/2020/09/19/value-or-pitfall/
480
481 self.Emit('ASDL_NAMES %s {' % enum_name, depth)
482 self.Emit(' enum no_name {', depth)
483 for name, tag_num in enum:
484 self.Emit('%s = %d,' % (name, tag_num), depth + 1)
485
486 if is_simple:
487 # Help in sizing array. Note that we're 1-based.
488 self.Emit('ARRAY_SIZE = %d,' % (len(enum) + 1), depth + 1)
489
490 self.Emit(' };', depth)
491 self.Emit('};', depth)
492
493 self.Emit('', depth)
494
495 if self.pretty_print_methods:
496 sum_name = ast.NameHack(sum_name)
497 self.Emit(
498 'BigStr* %s_str(int tag, bool dot = true);' % sum_name,
499 depth)
500 self.Emit('', depth)
501
502 return int_to_type
503
504 def VisitSimpleSum(self, sum, name, depth):
505 # type: (ast.SimpleSum, str, int) -> None
506 # Note: there can be more than 128 variants in a simple sum, because it's an
507 # integer and doesn't have an object header.
508
509 if 'integers' in sum.generate:
510 self._EmitEnum(sum, name, depth, strong=False, is_simple=True)
511 self.Emit('typedef int %s_t;' % name)
512 self.Emit('')
513 elif 'uint16' in sum.generate:
514 self._EmitEnum(sum, name, depth, strong=False, is_simple=True)
515 self.Emit('typedef uint16_t %s_t;' % name)
516 self.Emit('')
517 else:
518 self._EmitEnum(sum, name, depth, strong=True)
519
520 def VisitCompoundSum(self, sum, sum_name, depth):
521 # type: (ast.Sum, str, int) -> None
522 #log('%d variants in %s', len(sum.types), sum_name)
523
524 # Must fit in 7 bit Obj::type_tag
525 assert len(sum.types) < MAX_VARIANTS_PER_SUM, \
526 'sum type %r has too many variants' % sum_name
527
528 # This is a sign that Python needs string interpolation!!!
529 def Emit(s, depth=depth):
530 # type: (str, int) -> None
531 self.Emit(s % sys._getframe(1).f_locals, depth)
532
533 int_to_type = self._EmitEnum(sum, sum_name, depth)
534
535 # Only add debug info for compound sums.
536 self.debug_info['%s_t' % sum_name] = int_to_type
537
538 # This is the base class.
539 Emit('class %(sum_name)s_t {')
540
541 # to implement Walkable, we'd need all types to fit in in the GC header
542 #Emit('class %(sum_name)s_t : public Walkable {')
543
544 # Can't be constructed directly. Note: this shows up in uftrace in debug
545 # mode, e.g. when we instantiate Token. Do we need it?
546 Emit(' protected:')
547 Emit(' %s_t() {' % sum_name)
548 Emit(' }')
549 Emit(' public:')
550 Emit(' int tag() const {')
551 # There's no inheritance relationship, so we have to reinterpret_cast.
552 Emit(' return ObjHeader::FromObject(this)->type_tag;')
553 Emit(' }')
554
555 if self.pretty_print_methods:
556 self.Emit(
557 ' hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen = nullptr);'
558 )
559
560 Emit('')
561 Emit(' DISALLOW_COPY_AND_ASSIGN(%(sum_name)s_t)')
562 Emit('};')
563 Emit('')
564
565 for variant in sum.types:
566 if variant.shared_type:
567 continue # Don't generate a class.
568 super_name = '%s_t' % sum_name
569 tag = 'static_cast<uint16_t>(%s_e::%s)' % (sum_name, variant.name)
570 class_name = '%s__%s' % (sum_name, variant.name)
571 self._GenClass(variant.fields, class_name, [super_name], depth,
572 tag)
573
574 # Generate 'extern' declarations for zero arg singleton globals
575 for variant in sum.types:
576 if not variant.shared_type and len(variant.fields) == 0:
577 variant_name = variant.name
578 Emit(
579 'extern GcGlobal<%(sum_name)s__%(variant_name)s> g%(sum_name)s__%(variant_name)s;'
580 )
581
582 # Allow expr::Const in addition to expr.Const.
583 Emit('ASDL_NAMES %(sum_name)s {')
584 for variant in sum.types:
585 if variant.shared_type:
586 continue
587
588 # TODO: This produces a lint error, but IS USED via % reflection
589 variant_name = variant.name
590
591 if len(variant.fields) == 0:
592 Emit(
593 ' static %(sum_name)s__%(variant_name)s* %(variant_name)s;'
594 )
595 else:
596 Emit(
597 ' typedef %(sum_name)s__%(variant_name)s %(variant_name)s;'
598 )
599 Emit('};')
600 Emit('')
601
602 def _GenClassBegin(self, class_name, base_classes, depth):
603 # type: (str, List[str], int) -> None
604 if base_classes:
605 bases = ', '.join('public %s' % b for b in base_classes)
606 self.Emit("class %s : %s {" % (class_name, bases), depth)
607 else:
608 self.Emit("class %s {" % class_name, depth)
609 #self.Emit("class %s : public Walkable {" % class_name, depth)
610
611 self.Emit(" public:", depth)
612
613 def _GenClassEnd(self, class_name, depth):
614 # type: (str, int) -> None
615 self.Emit(' DISALLOW_COPY_AND_ASSIGN(%s)' % class_name)
616 self.Emit('};', depth)
617 self.Emit('', depth)
618
619 def _EmitMethodsInHeader(self, obj_header_str):
620 # type: (str) -> None
621 """Generate PrettyTree(), type_id(), obj_header()"""
622 if self.pretty_print_methods:
623 self.Emit(
624 'hnode_t* PrettyTree(bool do_abbrev, Dict<int, bool>* seen = nullptr);'
625 )
626 self.Emit('')
627
628 self.Emit('static constexpr ObjHeader obj_header() {')
629 self.Emit(' return %s;' % obj_header_str)
630 self.Emit('}')
631
632 def _GenClassForList(self, class_name, base_classes, tag_num):
633 # type: (str, List[str], int) -> None
634 depth = 0
635 self._GenClassBegin(class_name, base_classes, depth)
636
637 base_type_str = [b for b in base_classes if b.startswith('List<')][0]
638
639 # Zero arg constructor
640 self.Emit(' %s() : %s() {' % (class_name, base_type_str), depth)
641 self.Emit(' }', depth)
642
643 # One arg constructor used by Take.
644 # This should be is PROTECTED, like the superclass, but Alloc<T> calls
645 # it. Hm.
646 #self.Emit(' protected:')
647 self.Emit(
648 ' %s(%s* plain_list) : %s(plain_list) {' %
649 (class_name, base_type_str, base_type_str), depth)
650 self.Emit(' }', depth)
651
652 self.Emit(' static %s* New() {' % class_name, depth)
653 self.Emit(' return Alloc<%s>();' % class_name, depth)
654 self.Emit(' }', depth)
655
656 # Take() constructor
657 self.Emit(
658 ' static %s* Take(%s* plain_list) {' %
659 (class_name, base_type_str), depth)
660 self.Emit(' auto* result = Alloc<%s>(plain_list);' % class_name,
661 depth)
662 self.Emit(' plain_list->SetTaken();', depth)
663 self.Emit(' return result;', depth)
664 self.Emit(' }', depth)
665
666 # field_mask() should call List superclass, since say word_t won't have it
667 obj_header_str = 'ObjHeader::TaggedSubtype(%d, field_mask())' % tag_num
668 self.Indent()
669 self._EmitMethodsInHeader(obj_header_str)
670 self.Dedent()
671
672 self._GenClassEnd(class_name, depth)
673
674 def _GenClass(
675 self,
676 fields, # type: List[ast.Field]
677 class_name, # type: str
678 base_classes, # type: List[str]
679 depth, # type: int
680 tag_num, # type: Union[int, str]
681 obj_header_str='', # type: str
682 ):
683 # type: (...) -> None
684 """For Product and Constructor."""
685 self._GenClassBegin(class_name, base_classes, depth)
686
687 # Ensure that the member variables are ordered such that GC managed objects
688 # come before any unmanaged ones because we use `HeapTag::Scanned`.
689 managed_fields = []
690 unmanaged_fields = []
691 for f in fields:
692 if _IsManagedType(f.typ):
693 managed_fields.append(f)
694 else:
695 unmanaged_fields.append(f)
696 all_fields = managed_fields + unmanaged_fields
697
698 def FieldInitJoin(strs):
699 # type: (List[str]) -> str
700 # reflow doesn't work well here, so do it manually
701 return ',\n '.join(strs)
702
703 # Ensure that the constructor params are listed in the same order as the
704 # equivalent python constructors for compatibility in translated code.
705 params = []
706 for f in fields:
707 params.append('%s %s' % (_GetCppType(f.typ), f.name))
708
709 # Member initializers are in the same order as the member variables to
710 # avoid compiler warnings (the order doesn't affect the semantics).
711 inits = []
712 for f in all_fields:
713 inits.append('%s(%s)' % (f.name, f.name))
714
715 # Define constructor with N args
716 if len(inits):
717 self.Emit(' %s(%s)' % (class_name, ', '.join(params)), depth)
718 self.Emit(' : %s {' % FieldInitJoin(inits),
719 depth,
720 reflow=False)
721 self.Emit(' }')
722 else:
723 self.Emit(' %s(%s) {}' % (class_name, ', '.join(params)), depth)
724 self.Emit('')
725
726 # Define static constructor with ZERO args. Don't emit for types with no
727 # fields.
728 if fields:
729 init_args = []
730 for field in fields:
731 init_args.append(_DefaultValue(field.typ))
732
733 self.Emit(
734 ' static %s* CreateNull(bool alloc_lists = false) { ' %
735 class_name, depth)
736 self.Emit(
737 ' return Alloc<%s>(%s);' %
738 (class_name, ', '.join(init_args)), depth)
739 self.Emit(' }')
740 self.Emit('')
741
742 obj_header_str = 'ObjHeader::AsdlClass(%s, %d)' % (tag_num,
743 len(managed_fields))
744 self.Indent()
745 self._EmitMethodsInHeader(obj_header_str)
746 self.Dedent()
747
748 #
749 # Members
750 #
751 for field in all_fields:
752 self.Emit(" %s %s;" % (_GetCppType(field.typ), field.name))
753 self.Emit('')
754
755 self._GenClassEnd(class_name, depth)
756
757 def VisitSubType(self, subtype):
758 # type: (ast.SubTypeDecl) -> None
759 self._shared_type_tags[subtype.name] = self._product_counter
760
761 # Also create these last. They may inherit from sum types that have yet
762 # to be defined.
763 self._subtypes.append((subtype, self._product_counter))
764 self._product_counter += 1
765
766 def VisitProduct(self, product, name, depth):
767 # type: (ast.Product, str, int) -> None
768 self._shared_type_tags[name] = self._product_counter
769 # Create a tuple of _GenClass args to create LAST. They may inherit from
770 # sum types that have yet to be defined.
771 self._products.append((product, name, depth, self._product_counter))
772 self._product_counter += 1
773
774 def EmitFooter(self):
775 # type: () -> None
776 # Now generate all the product types we deferred.
777 for args in self._products:
778 ast_node, name, depth, tag_num = args
779 # Figure out base classes AFTERWARD.
780 bases = self._base_classes[name]
781 self._GenClass(ast_node.fields, name, bases, depth, tag_num)
782
783 for args in self._subtypes:
784 subtype, tag_num = args
785 # Figure out base classes AFTERWARD.
786 bases = self._base_classes[subtype.name]
787
788 cpp_type = _GetCppType(subtype.base_class)
789 assert cpp_type.endswith('*') # hack
790 cpp_type = cpp_type[:-1]
791 bases.append(cpp_type)
792
793 t = subtype.base_class.type_name
794 if t == 'List':
795 self._GenClassForList(subtype.name, bases, tag_num)
796
797 elif t == 'Dict':
798 raise AssertionError()
799 else:
800 #obj_header_str = ''
801 raise AssertionError()
802
803
804class MethodDefVisitor(visitor.AsdlVisitor):
805 """Generate the body of pretty printing methods.
806
807 We have to do this in another pass because types and schemas have
808 circular dependencies.
809 """
810
811 def __init__(self, f, abbrev_ns=None, abbrev_mod_entries=None):
812 # type: (IO[bytes], Optional[Any], List[Any]) -> None
813 visitor.AsdlVisitor.__init__(self, f)
814 self.abbrev_ns = abbrev_ns
815 self.abbrev_mod_entries = abbrev_mod_entries or []
816
817 def _EmitList(self, list_str, item_type, out_val_name):
818 # type: (str, ast.type_expr_t, str) -> None
819 # used in format strings
820 c_item_type = _GetCppType(item_type)
821
822 def _Emit(s):
823 # type: (str) -> None
824 self.Emit(s % sys._getframe(1).f_locals, self.current_depth)
825
826 _Emit(
827 'hnode::Array* %(out_val_name)s = Alloc<hnode::Array>(Alloc<List<hnode_t*>>());'
828 )
829 _Emit(
830 'for (ListIter<%(c_item_type)s> it(%(list_str)s); !it.Done(); it.Next()) {'
831 )
832 _Emit(' %(c_item_type)s v_ = it.Value();')
833
834 child_code_str, none_guard = _HNodeExpr(item_type, 'v_')
835 if none_guard: # e.g. for List[Optional[value_t]]
836 # TODO: could consolidate this logic with asdl/runtime.py NewLeaf(), which is prebuilt/
837 child_code_str = (
838 '(v_ == nullptr) ? Alloc<hnode::Leaf>(StrFromC("_"), color_e::OtherConst) : %s'
839 % child_code_str)
840 _Emit(' hnode_t* h = %(child_code_str)s;')
841 _Emit(' %(out_val_name)s->children->append(h);')
842 _Emit('}')
843
844 def _EmitListPrettyPrint(self, field, out_val_name):
845 # type: (ast.Field, str) -> None
846 typ = field.typ
847 if ast.IsOptional(typ): # descend one level
848 typ = cast(ast.ParameterizedType, typ).children[0]
849 item_type = cast(ast.ParameterizedType, typ).children[0]
850
851 self._EmitList('this->%s' % field.name, item_type, out_val_name)
852
853 def _EmitDictPrettyPrint(self, field):
854 # type: (ast.Field) -> None
855 typ = field.typ
856 if ast.IsOptional(typ): # descend one level
857 typ = cast(ast.ParameterizedType, typ).children[0]
858
859 k_typ = cast(ast.ParameterizedType, typ).children[0]
860 v_typ = cast(ast.ParameterizedType, typ).children[1]
861
862 k_c_type = _GetCppType(k_typ)
863 v_c_type = _GetCppType(v_typ)
864
865 k_code_str, _ = _HNodeExpr(k_typ, 'k')
866 v_code_str, _ = _HNodeExpr(v_typ, 'v')
867
868 self.Emit('auto* unnamed = NewList<hnode_t*>();')
869 self.Emit(
870 'auto* hdict = Alloc<hnode::Record>(kEmptyString, StrFromC("{"), StrFromC("}"), NewList<Field*>(), unnamed);'
871 )
872 self.Emit(
873 'for (DictIter<%s, %s> it(this->%s); !it.Done(); it.Next()) {' %
874 (k_c_type, v_c_type, field.name))
875 self.Emit(' auto k = it.Key();')
876 self.Emit(' auto v = it.Value();')
877 self.Emit(' unnamed->append(%s);' % k_code_str)
878 self.Emit(' unnamed->append(%s);' % v_code_str)
879 self.Emit('}')
880 self.Emit('L->append(Alloc<Field>(StrFromC("%s"), hdict));' %
881 field.name)
882
883 def _EmitCodeForField(self, field, counter):
884 # type: (ast.Field, int) -> None
885 """Generate code that returns an hnode for a field."""
886 out_val_name = 'x%d' % counter
887
888 if ast.IsList(field.typ):
889 self.Emit('if (this->%s != nullptr) { // List' % field.name)
890 self.Indent()
891 self._EmitListPrettyPrint(field, out_val_name)
892 self.Emit('L->append(Alloc<Field>(StrFromC("%s"), %s));' %
893 (field.name, out_val_name))
894 self.Dedent()
895 self.Emit('}')
896
897 elif ast.IsDict(field.typ):
898 self.Emit('if (this->%s != nullptr) { // Dict' % field.name)
899 self.Indent()
900 self._EmitDictPrettyPrint(field)
901 self.Dedent()
902 self.Emit('}')
903
904 elif ast.IsOptional(field.typ):
905 typ = cast(ast.ParameterizedType, field.typ).children[0]
906
907 self.Emit('if (this->%s) { // Optional' % field.name)
908 child_code_str, _ = _HNodeExpr(typ, 'this->%s' % field.name)
909 self.Emit(' hnode_t* %s = %s;' % (out_val_name, child_code_str))
910 self.Emit(' L->append(Alloc<Field>(StrFromC("%s"), %s));' %
911 (field.name, out_val_name))
912 self.Emit('}')
913
914 else:
915 var_name = 'this->%s' % field.name
916 code_str, obj_none_guard = _HNodeExpr(field.typ, var_name)
917
918 depth = self.current_depth
919 if obj_none_guard: # to satisfy MyPy type system
920 pass
921 self.Emit('hnode_t* %s = %s;' % (out_val_name, code_str), depth)
922
923 self.Emit(
924 'L->append(Alloc<Field>(StrFromC("%s"), %s));' %
925 (field.name, out_val_name), depth)
926
927 def _EmitPrettyPrintMethods(self,
928 class_name,
929 all_fields,
930 sum_name=None,
931 list_item_type=None):
932 # type: (str, List[ast.Field], Optional[str], Optional[Any]) -> None
933 """
934 """
935 self.Emit('')
936 self.Emit(
937 'hnode_t* %s::PrettyTree(bool do_abbrev, Dict<int, bool>* seen) {'
938 % class_name)
939
940 # Similar to j8::HeapValueId()
941 self.Emit(' seen = seen ? seen : Alloc<Dict<int, bool>>();')
942 self.Emit(' int heap_id = ObjectId(this);')
943 self.Emit(' if (dict_contains(seen, heap_id)) {')
944 self.Emit(' return Alloc<hnode::AlreadySeen>(heap_id);')
945 self.Emit(' }')
946 self.Emit(' seen->set(heap_id, true);')
947 self.Emit('')
948
949 if list_item_type:
950 self.Indent()
951 self._EmitList('this', list_item_type, 'out_node')
952 self.Dedent()
953 else:
954 if sum_name is not None:
955 n = '%s_str(this->tag())' % sum_name
956 else:
957 n = 'StrFromC("%s")' % class_name
958
959 abbrev_name = '_%s' % class_name
960
961 if abbrev_name in self.abbrev_mod_entries:
962 self.Emit(' if (do_abbrev) {')
963 self.Emit(' auto* p = %s::%s(this);' %
964 (self.abbrev_ns, abbrev_name))
965 self.Emit(' if (p) {')
966 self.Emit(' return p;')
967 self.Emit(' }')
968 self.Emit(' }')
969 else:
970 #self.Emit(' // no abbrev %s' % abbrev_name)
971 pass
972
973 self.Emit(' hnode::Record* out_node = runtime::NewRecord(%s);' %
974 n)
975 if all_fields:
976 self.Emit(' List<Field*>* L = out_node->fields;')
977 self.Emit('')
978
979 # Use the runtime type to be more like asdl/format.py
980 for local_id, field in enumerate(all_fields):
981 #log('%s :: %s', field_name, field_desc)
982 self.Indent()
983 self._EmitCodeForField(field, local_id)
984 self.Dedent()
985 self.Emit('')
986 self.Emit(' return out_node;')
987 self.Emit('}')
988 self.Emit('')
989
990 def _EmitStrFunction(self,
991 sum,
992 sum_name,
993 depth,
994 strong=False,
995 simple=False):
996 # type: (ast.Sum, str, int, bool, bool) -> None
997 add_suffix = not ('no_namespace_suffix' in sum.generate)
998 if add_suffix:
999 if simple:
1000 enum_name = '%s_i' % sum_name
1001 else:
1002 enum_name = '%s_e' % sum_name
1003 else:
1004 enum_name = sum_name
1005
1006 if strong:
1007 self.Emit(
1008 'BigStr* %s_str(%s tag, bool dot) {' % (sum_name, enum_name),
1009 depth)
1010 else:
1011 self.Emit('BigStr* %s_str(int tag, bool dot) {' % sum_name, depth)
1012
1013 buf_size = 32
1014 v_max = max(len(variant.name) for variant in sum.types)
1015 s_max = v_max + 1 + len(sum_name) + 1 # for . and NUL
1016 if s_max > buf_size:
1017 raise RuntimeError('Sum name %r + variant name is too long' %
1018 sum_name)
1019
1020 self.Emit(' char buf[%d];' % buf_size, depth)
1021 self.Emit(' const char* v = nullptr;', depth)
1022 self.Emit(' switch (tag) {', depth)
1023 for variant in sum.types:
1024 self.Emit('case %s::%s:' % (enum_name, variant.name), depth + 1)
1025 self.Emit(' v = "%s"; break;' % variant.name, depth + 1)
1026
1027 self.Emit('default:', depth + 1)
1028 self.Emit(' assert(0);', depth + 1)
1029
1030 self.Emit(' }', depth)
1031 self.Emit(' if (dot) {', depth)
1032 self.Emit(' snprintf(buf, %d, "%s.%%s", v);' % (buf_size, sum_name),
1033 depth)
1034 self.Emit(' return StrFromC(buf);', depth)
1035 self.Emit(' } else {', depth)
1036 self.Emit(' return StrFromC(v);', depth)
1037 self.Emit(' }', depth)
1038 self.Emit('}', depth)
1039
1040 def VisitSimpleSum(self, sum, name, depth):
1041 # type: (ast.SimpleSum, str, int) -> None
1042 if 'integers' in sum.generate or 'uint16' in sum.generate:
1043 self._EmitStrFunction(sum, name, depth, strong=False, simple=True)
1044 else:
1045 self._EmitStrFunction(sum, name, depth, strong=True)
1046
1047 def VisitCompoundSum(self, sum, sum_name, depth):
1048 # type: (ast.Sum, str, int) -> None
1049 self._EmitStrFunction(sum, sum_name, depth)
1050
1051 # Generate definitions for the for zero arg singleton globals
1052 for variant in sum.types:
1053 if variant.shared_type:
1054 continue
1055 if len(variant.fields) == 0:
1056 variant_name = variant.name
1057 self.Emit('')
1058 self.Emit('%s__%s* %s::%s = &g%s__%s.obj;' %
1059 (sum_name, variant_name, sum_name, variant_name,
1060 sum_name, variant_name))
1061 self.Emit('')
1062 self.Emit('GcGlobal<%s__%s> g%s__%s = ' %
1063 (sum_name, variant_name, sum_name, variant_name))
1064 self.Emit(' { ObjHeader::Global(%s_e::%s) };' %
1065 (sum_name, variant_name))
1066
1067 for variant in sum.types:
1068 if variant.shared_type:
1069 continue
1070 all_fields = variant.fields
1071 class_name = '%s__%s' % (sum_name, variant.name)
1072 self._EmitPrettyPrintMethods(class_name,
1073 all_fields,
1074 sum_name=sum_name)
1075
1076 # Emit dispatch WITHOUT using 'virtual'
1077 self.Emit('')
1078 self.Emit(
1079 'hnode_t* %s_t::PrettyTree(bool do_abbrev, Dict<int, bool>* seen) {'
1080 % sum_name)
1081 self.Emit(' switch (this->tag()) {', depth)
1082
1083 for variant in sum.types:
1084 if variant.shared_type:
1085 subtype_name = variant.shared_type
1086 else:
1087 subtype_name = '%s__%s' % (sum_name, variant.name)
1088
1089 self.Emit(' case %s_e::%s: {' % (sum_name, variant.name), depth)
1090 self.Emit(
1091 ' %s* obj = static_cast<%s*>(this);' %
1092 (subtype_name, subtype_name), depth)
1093 self.Emit(' return obj->PrettyTree(do_abbrev, seen);', depth)
1094 self.Emit(' }', depth)
1095
1096 self.Emit(' default:', depth)
1097 self.Emit(' assert(0);', depth)
1098
1099 self.Emit(' }')
1100 self.Emit('}')
1101
1102 def VisitProduct(self, product, name, depth):
1103 # type: (ast.Product, str, int) -> None
1104 self._EmitPrettyPrintMethods(name, product.fields)
1105
1106 def VisitSubType(self, subtype):
1107 # type: (ast.SubTypeDecl) -> None
1108 list_item_type = None
1109 b = subtype.base_class
1110 if isinstance(b, ast.ParameterizedType):
1111 if b.type_name == 'List':
1112 list_item_type = b.children[0]
1113 self._EmitPrettyPrintMethods(subtype.name, [],
1114 list_item_type=list_item_type)