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

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