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

651 lines, 420 significant
1#!/usr/bin/env python2
2"""gen_python.py: Generate Python code from an ASDL schema."""
3from __future__ import print_function
4
5from collections import defaultdict
6
7from asdl import ast
8from asdl import visitor
9from asdl.util import log
10
11from typing import Tuple
12
13_ = log # shut up lint
14
15_PRIMITIVES = {
16 'string': 'str',
17 'int': 'int',
18 'uint16': 'int',
19 'BigInt': 'mops.BigInt',
20 'float': 'float',
21 'bool': 'bool',
22 'any': 'Any',
23 # TODO: frontend/syntax.asdl should properly import id enum instead of
24 # hard-coding it here.
25 'id': 'Id_t',
26}
27
28
29def _MyPyType(typ):
30 """ASDL type to MyPy Type."""
31 if isinstance(typ, ast.ParameterizedType):
32
33 if typ.type_name == 'Dict':
34 k_type = _MyPyType(typ.children[0])
35 v_type = _MyPyType(typ.children[1])
36 return 'Dict[%s, %s]' % (k_type, v_type)
37
38 if typ.type_name == 'List':
39 return 'List[%s]' % _MyPyType(typ.children[0])
40
41 if typ.type_name == 'Optional':
42 return 'Optional[%s]' % _MyPyType(typ.children[0])
43
44 elif isinstance(typ, ast.NamedType):
45 if typ.resolved:
46 if isinstance(typ.resolved, ast.Sum): # includes SimpleSum
47 return '%s_t' % typ.name
48 if isinstance(typ.resolved, ast.Product):
49 return typ.name
50 if isinstance(typ.resolved, ast.Use):
51 return ast.TypeNameHeuristic(typ.name)
52 if isinstance(typ.resolved, ast.Extern):
53 r = typ.resolved
54 type_name = r.names[-1]
55 py_module = r.names[-2]
56 return '%s.%s' % (py_module, type_name)
57
58 # 'id' falls through here
59 return _PRIMITIVES[typ.name]
60
61 else:
62 raise AssertionError()
63
64
65def _CastedNull(mypy_type):
66 # type: (str) -> str
67 return "cast('%s', None)" % mypy_type
68
69
70def _DefaultValue(typ, mypy_type):
71 """Values that the static CreateNull() constructor passes.
72
73 mypy_type is used to cast None, to maintain mypy --strict for ASDL.
74
75 We circumvent the type system on CreateNull(). Then the user is
76 responsible for filling in all the fields. If they do so, we can
77 rely on it when reading fields at runtime.
78 """
79 if isinstance(typ, ast.ParameterizedType):
80 type_name = typ.type_name
81
82 if type_name == 'Optional':
83 return _CastedNull(mypy_type)
84
85 if type_name == 'List':
86 return "[] if alloc_lists else cast('%s', None)" % mypy_type
87
88 if type_name == 'Dict': # TODO: can respect alloc_dicts=True
89 return _CastedNull(mypy_type)
90
91 raise AssertionError(type_name)
92
93 if isinstance(typ, ast.NamedType):
94 type_name = typ.name
95
96 if type_name == 'id': # hard-coded HACK
97 return '-1'
98
99 if type_name == 'int':
100 return '-1'
101
102 if type_name == 'BigInt':
103 return 'mops.BigInt(-1)'
104
105 if type_name == 'bool':
106 return 'False'
107
108 if type_name == 'float':
109 return '0.0' # or should it be NaN?
110
111 if type_name == 'string':
112 return "''"
113
114 if isinstance(typ.resolved, ast.SimpleSum):
115 sum_type = typ.resolved
116 # Just make it the first variant. We could define "Undef" for
117 # each enum, but it doesn't seem worth it.
118 return '%s_e.%s' % (type_name, sum_type.types[0].name)
119
120 # CompoundSum or Product type
121 return _CastedNull(mypy_type)
122
123 else:
124 raise AssertionError()
125
126
127def _HNodeExpr(typ, var_name):
128 # type: (ast.AST, str) -> Tuple[str, bool]
129 none_guard = False
130
131 if typ.IsOptional():
132 typ = typ.children[0] # descend one level
133
134 if isinstance(typ, ast.ParameterizedType):
135 code_str = '%s.PrettyTree()' % var_name
136 none_guard = True
137
138 elif isinstance(typ, ast.NamedType):
139 type_name = typ.name
140
141 if type_name == 'bool':
142 code_str = "hnode.Leaf('T' if %s else 'F', color_e.OtherConst)" % var_name
143
144 elif type_name in ('int', 'uint16'):
145 code_str = 'hnode.Leaf(str(%s), color_e.OtherConst)' % var_name
146
147 elif type_name == 'BigInt':
148 code_str = 'hnode.Leaf(mops.ToStr(%s), color_e.OtherConst)' % var_name
149
150 elif type_name == 'float':
151 code_str = 'hnode.Leaf(str(%s), color_e.OtherConst)' % var_name
152
153 elif type_name == 'string':
154 code_str = 'NewLeaf(%s, color_e.StringConst)' % var_name
155
156 elif type_name == 'any':
157 code_str = 'NewLeaf(str(%s), color_e.External)' % var_name
158
159 elif type_name == 'id': # was meta.UserType
160 # This assumes it's Id, which is a simple SumType. TODO: Remove this.
161 code_str = 'hnode.Leaf(Id_str(%s, dot=False), color_e.UserType)' % var_name
162
163 elif typ.resolved and isinstance(typ.resolved, ast.SimpleSum):
164 code_str = 'hnode.Leaf(%s_str(%s), color_e.TypeName)' % (type_name,
165 var_name)
166
167 else:
168 code_str = '%s.PrettyTree(do_abbrev, trav=trav)' % var_name
169 none_guard = True
170
171 else:
172 raise AssertionError()
173
174 return code_str, none_guard
175
176
177class GenMyPyVisitor(visitor.AsdlVisitor):
178 """Generate Python code with MyPy type annotations."""
179
180 def __init__(self,
181 f,
182 abbrev_mod_entries=None,
183 pretty_print_methods=True,
184 py_init_n=False,
185 simple_int_sums=None):
186
187 visitor.AsdlVisitor.__init__(self, f)
188 self.abbrev_mod_entries = abbrev_mod_entries or []
189 self.pretty_print_methods = pretty_print_methods
190 self.py_init_n = py_init_n
191
192 # For Id to use different code gen. It's used like an integer, not just
193 # like an enum.
194 self.simple_int_sums = simple_int_sums or []
195
196 self._shared_type_tags = {}
197 self._product_counter = 64 # matches asdl/gen_cpp.py
198
199 self._products = []
200 self._base_classes = defaultdict(list)
201
202 self._subtypes = []
203
204 def _EmitDict(self, name, d, depth):
205 self.Emit('_%s_str = {' % name, depth)
206 for k in sorted(d):
207 self.Emit('%d: %r,' % (k, d[k]), depth + 1)
208 self.Emit('}', depth)
209 self.Emit('', depth)
210
211 def VisitSimpleSum(self, sum, sum_name, depth):
212 int_to_str = {}
213 variants = []
214 for i, variant in enumerate(sum.types):
215 tag_num = i + 1
216 int_to_str[tag_num] = variant.name
217 variants.append((variant, tag_num))
218
219 add_suffix = not ('no_namespace_suffix' in sum.generate)
220 gen_integers = 'integers' in sum.generate or 'uint16' in sum.generate
221
222 if gen_integers:
223 self.Emit('%s_t = int # type alias for integer' % sum_name)
224 self.Emit('')
225
226 i_name = ('%s_i' % sum_name) if add_suffix else sum_name
227
228 self.Emit('class %s(object):' % i_name, depth)
229
230 for variant, tag_num in variants:
231 line = ' %s = %d' % (variant.name, tag_num)
232 self.Emit(line, depth)
233
234 # Help in sizing array. Note that we're 1-based.
235 line = ' %s = %d' % ('ARRAY_SIZE', len(variants) + 1)
236 self.Emit(line, depth)
237
238 else:
239 # First emit a type
240 self.Emit('class %s_t(pybase.SimpleObj):' % sum_name, depth)
241 self.Emit(' pass', depth)
242 self.Emit('', depth)
243
244 # Now emit a namespace
245 e_name = ('%s_e' % sum_name) if add_suffix else sum_name
246 self.Emit('class %s(object):' % e_name, depth)
247
248 for variant, tag_num in variants:
249 line = ' %s = %s_t(%d)' % (variant.name, sum_name, tag_num)
250 self.Emit(line, depth)
251
252 self.Emit('', depth)
253
254 self._EmitDict(sum_name, int_to_str, depth)
255
256 self.Emit('def %s_str(val, dot=True):' % sum_name, depth)
257 self.Emit(' # type: (%s_t, bool) -> str' % sum_name, depth)
258 self.Emit(' v = _%s_str[val]' % sum_name, depth)
259 self.Emit(' if dot:', depth)
260 self.Emit(' return "%s.%%s" %% v' % sum_name, depth)
261 self.Emit(' else:', depth)
262 self.Emit(' return v', depth)
263 self.Emit('', depth)
264
265 def _EmitCodeForField(self, field, counter):
266 """Generate code that returns an hnode for a field."""
267 out_val_name = 'x%d' % counter
268
269 if field.typ.IsList():
270 iter_name = 'i%d' % counter
271
272 typ = field.typ
273 if typ.type_name == 'Optional': # descend one level
274 typ = typ.children[0]
275 item_type = typ.children[0]
276
277 self.Emit(' if self.%s is not None: # List' % field.name)
278 self.Emit(' %s = hnode.Array([])' % out_val_name)
279 self.Emit(' for %s in self.%s:' % (iter_name, field.name))
280 child_code_str, none_guard = _HNodeExpr(item_type, iter_name)
281
282 if none_guard: # e.g. for List[Optional[value_t]]
283 # TODO: could consolidate with asdl/runtime.py NewLeaf(), which
284 # also uses _ to mean None/nullptr
285 self.Emit(
286 ' h = (hnode.Leaf("_", color_e.OtherConst) if %s is None else %s)'
287 % (iter_name, child_code_str))
288 self.Emit(' %s.children.append(h)' % out_val_name)
289 else:
290 self.Emit(' %s.children.append(%s)' %
291 (out_val_name, child_code_str))
292
293 self.Emit(' L.append(Field(%r, %s))' %
294 (field.name, out_val_name))
295
296 elif field.typ.IsDict():
297 k = 'k%d' % counter
298 v = 'v%d' % counter
299
300 typ = field.typ
301 if typ.type_name == 'Optional': # descend one level
302 typ = typ.children[0]
303
304 k_typ = typ.children[0]
305 v_typ = typ.children[1]
306
307 k_code_str, _ = _HNodeExpr(k_typ, k)
308 v_code_str, _ = _HNodeExpr(v_typ, v)
309
310 unnamed = 'unnamed%d' % counter
311 self.Emit(' if self.%s is not None: # Dict' % field.name)
312 self.Emit(' %s = [] # type: List[hnode_t]' % unnamed)
313 self.Emit(' %s = hnode.Record("", "{", "}", [], %s)' %
314 (out_val_name, unnamed))
315 self.Emit(' for %s, %s in self.%s.iteritems():' %
316 (k, v, field.name))
317 self.Emit(' %s.append(%s)' % (unnamed, k_code_str))
318 self.Emit(' %s.append(%s)' % (unnamed, v_code_str))
319 self.Emit(' L.append(Field(%r, %s))' %
320 (field.name, out_val_name))
321
322 elif field.typ.IsOptional():
323 typ = field.typ.children[0]
324
325 self.Emit(' if self.%s is not None: # Optional' % field.name)
326 child_code_str, _ = _HNodeExpr(typ, 'self.%s' % field.name)
327 self.Emit(' %s = %s' % (out_val_name, child_code_str))
328 self.Emit(' L.append(Field(%r, %s))' %
329 (field.name, out_val_name))
330
331 else:
332 var_name = 'self.%s' % field.name
333 code_str, obj_none_guard = _HNodeExpr(field.typ, var_name)
334 depth = self.current_depth
335 if obj_none_guard: # to satisfy MyPy type system
336 self.Emit(' assert self.%s is not None' % field.name)
337 self.Emit(' %s = %s' % (out_val_name, code_str), depth)
338
339 self.Emit(' L.append(Field(%r, %s))' % (field.name, out_val_name),
340 depth)
341
342 def _GenClassBegin(self, class_name, base_classes, tag_num):
343 self.Emit('class %s(%s):' % (class_name, ', '.join(base_classes)))
344 self.Emit(' _type_tag = %d' % tag_num)
345
346 def _GenListSubclass(self, class_name, base_classes, tag_num, class_ns=''):
347 self._GenClassBegin(class_name, base_classes, tag_num)
348
349 # TODO: Do something nicer
350 base_class_str = [b for b in base_classes if b.startswith('List[')][0]
351
352 # Needed for c = CompoundWord() to work
353 # TODO: make it
354 # c = CompoundWord.New()
355 if 0:
356 self.Emit(' def __init__(self, other=None):')
357 self.Emit(' # type: (Optional[%s]) -> None' % base_class_str,
358 reflow=False)
359 self.Emit(' if other is not None:')
360 self.Emit(' self.extend(other)')
361 self.Emit('')
362
363 # Use our own constructor
364 self.Emit(' @staticmethod')
365 self.Emit(' def New():')
366 self.Emit(' # type: () -> %s' % class_name)
367 self.Emit(' return %s()' % class_name)
368 self.Emit('')
369
370 self.Emit(' @staticmethod')
371 self.Emit(' def Take(plain_list):')
372 self.Emit(' # type: (%s) -> %s' % (base_class_str, class_name))
373 self.Emit(' result = %s(plain_list)' % class_name)
374 self.Emit(' del plain_list[:]')
375 self.Emit(' return result')
376 self.Emit('')
377
378 if self.pretty_print_methods:
379 self._EmitPrettyPrintMethodsForList(class_name)
380
381 def _GenClass(self,
382 fields,
383 class_name,
384 base_classes,
385 tag_num,
386 class_ns=''):
387 """Generate a typed Python class.
388
389 Used for both Sum variants ("constructors") and Product types.
390
391 Args:
392 class_ns: for variants like value.Str
393 """
394 self._GenClassBegin(class_name, base_classes, tag_num)
395
396 field_names = [f.name for f in fields]
397
398 quoted_fields = repr(tuple(field_names))
399 self.Emit(' __slots__ = %s' % quoted_fields)
400 self.Emit('')
401
402 #
403 # __init__
404 #
405
406 args = [f.name for f in fields]
407 arg_types = []
408 default_vals = []
409 for f in fields:
410 mypy_type = _MyPyType(f.typ)
411 arg_types.append(mypy_type)
412
413 d_str = _DefaultValue(f.typ, mypy_type)
414 default_vals.append(d_str)
415
416 self.Emit(' def __init__(self, %s):' % ', '.join(args))
417 self.Emit(' # type: (%s) -> None' % ', '.join(arg_types),
418 reflow=False)
419
420 if not fields:
421 self.Emit(' pass') # for types like NoOp
422
423 for f in fields:
424 # don't wrap the type comment
425 self.Emit(' self.%s = %s' % (f.name, f.name), reflow=False)
426
427 self.Emit('')
428
429 # CreateNull() - another way of initializing
430 if len(fields) and not self.py_init_n:
431 self.Emit(' @staticmethod')
432 self.Emit(' def CreateNull(alloc_lists=False):')
433 self.Emit(' # type: () -> %s%s' % (class_ns, class_name))
434 self.Emit(' return %s%s(%s)' %
435 (class_ns, class_name, ', '.join(default_vals)),
436 reflow=False)
437 self.Emit('')
438
439 # PrettyTree()
440 if self.pretty_print_methods:
441 self._EmitPrettyPrintMethods(class_name, class_ns, fields)
442
443 def _EmitPrettyBegin(self):
444 self.Emit(' def PrettyTree(self, do_abbrev, trav=None):')
445 self.Emit(' # type: (bool, Optional[TraversalState]) -> hnode_t')
446 self.Emit(' trav = trav or TraversalState()')
447 self.Emit(' heap_id = id(self)')
448 self.Emit(' if heap_id in trav.seen:')
449 # cut off recursion
450 self.Emit(' return hnode.AlreadySeen(heap_id)')
451 self.Emit(' trav.seen[heap_id] = True')
452
453 def _EmitPrettyPrintMethodsForList(self, class_name):
454 self._EmitPrettyBegin()
455 self.Emit(' h = runtime.NewRecord(%r)' % class_name)
456 self.Emit(
457 ' h.unnamed_fields = [c.PrettyTree(do_abbrev) for c in self]')
458 self.Emit(' return h')
459 self.Emit('')
460
461 def _EmitPrettyPrintMethods(self, class_name, class_ns, fields):
462 if len(fields) == 0:
463 # value__Stdin -> value.Stdin (defined at top level)
464 pretty_cls_name = class_name.replace('__', '.')
465 else:
466 # value.Str (defined inside the 'class value') namespace
467 pretty_cls_name = '%s%s' % (class_ns, class_name)
468
469 # def PrettyTree(...):
470
471 self._EmitPrettyBegin()
472 self.Emit('')
473
474 if class_ns:
475 # e.g. _command__Simple
476 assert class_ns.endswith('.')
477 abbrev_name = '_%s__%s' % (class_ns[:-1], class_name)
478 else:
479 # e.g. _Token
480 abbrev_name = '_%s' % class_name
481
482 if abbrev_name in self.abbrev_mod_entries:
483 self.Emit(' if do_abbrev:')
484 self.Emit(' p = %s(self)' % abbrev_name)
485 self.Emit(' if p:')
486 self.Emit(' return p')
487 self.Emit('')
488
489 self.Emit(' out_node = NewRecord(%r)' % pretty_cls_name)
490 self.Emit(' L = out_node.fields')
491 self.Emit('')
492
493 # Use the runtime type to be more like asdl/format.py
494 for local_id, field in enumerate(fields):
495 #log('%s :: %s', field_name, field_desc)
496 self.Indent()
497 self._EmitCodeForField(field, local_id)
498 self.Dedent()
499 self.Emit('')
500 self.Emit(' return out_node')
501 self.Emit('')
502
503 def VisitCompoundSum(self, sum, sum_name, depth):
504 """Note that the following is_simple:
505
506 cflow = Break | Continue
507
508 But this is compound:
509
510 cflow = Break | Continue | Return(int val)
511
512 The generated code changes depending on which one it is.
513 """
514 #log('%d variants in %s', len(sum.types), sum_name)
515
516 # We emit THREE Python types for each meta.CompoundType:
517 #
518 # 1. enum for tag (cflow_e)
519 # 2. base class for inheritance (cflow_t)
520 # 3. namespace for classes (cflow) -- TODO: Get rid of this one.
521 #
522 # Should code use cflow_e.tag or isinstance()?
523 # isinstance() is better for MyPy I think. But tag is better for C++.
524 # int tag = static_cast<cflow>(node).tag;
525
526 int_to_str = {}
527
528 # enum for the tag
529 self.Emit('class %s_e(object):' % sum_name, depth)
530
531 for i, variant in enumerate(sum.types):
532 if variant.shared_type:
533 tag_num = self._shared_type_tags[variant.shared_type]
534 # e.g. DoubleQuoted may have base types expr_t, word_part_t
535 base_class = sum_name + '_t'
536 bases = self._base_classes[variant.shared_type]
537 if base_class in bases:
538 raise RuntimeError(
539 "Two tags in sum %r refer to product type %r" %
540 (sum_name, variant.shared_type))
541
542 else:
543 bases.append(base_class)
544 else:
545 tag_num = i + 1
546 self.Emit(' %s = %d' % (variant.name, tag_num), depth)
547 int_to_str[tag_num] = variant.name
548 self.Emit('', depth)
549
550 self._EmitDict(sum_name, int_to_str, depth)
551
552 self.Emit('def %s_str(tag, dot=True):' % sum_name, depth)
553 self.Emit(' # type: (int, bool) -> str', depth)
554 self.Emit(' v = _%s_str[tag]' % sum_name, depth)
555 self.Emit(' if dot:', depth)
556 self.Emit(' return "%s.%%s" %% v' % sum_name, depth)
557 self.Emit(' else:', depth)
558 self.Emit(' return v', depth)
559 self.Emit('', depth)
560
561 # the base class, e.g. 'command_t'
562 self.Emit('class %s_t(pybase.CompoundObj):' % sum_name, depth)
563 self.Indent()
564 depth = self.current_depth
565
566 # To imitate C++ API
567 self.Emit('def tag(self):')
568 self.Emit(' # type: () -> int')
569 self.Emit(' return self._type_tag')
570
571 self.Dedent()
572 depth = self.current_depth
573
574 self.Emit('')
575
576 # Declare any zero argument singleton classes outside of the main
577 # "namespace" class.
578 for i, variant in enumerate(sum.types):
579 if variant.shared_type:
580 continue # Don't generate a class for shared types.
581 if len(variant.fields) == 0:
582 # We must use the old-style naming here, ie. command__NoOp, in order
583 # to support zero field variants as constants.
584 class_name = '%s__%s' % (sum_name, variant.name)
585 self._GenClass(variant.fields, class_name, (sum_name + '_t', ),
586 i + 1)
587
588 # Class that's just a NAMESPACE, e.g. for value.Str
589 self.Emit('class %s(object):' % sum_name, depth)
590
591 self.Indent()
592
593 for i, variant in enumerate(sum.types):
594 if variant.shared_type:
595 continue
596
597 if len(variant.fields) == 0:
598 self.Emit('%s = %s__%s()' %
599 (variant.name, sum_name, variant.name))
600 self.Emit('')
601 else:
602 # Use fully-qualified name, so we can have osh_cmd.Simple and
603 # oil_cmd.Simple.
604 fq_name = variant.name
605 self._GenClass(variant.fields,
606 fq_name, (sum_name + '_t', ),
607 i + 1,
608 class_ns=sum_name + '.')
609 self.Emit(' pass', depth) # in case every variant is first class
610
611 self.Dedent()
612 self.Emit('')
613
614 def VisitSubType(self, subtype):
615 self._shared_type_tags[subtype.name] = self._product_counter
616
617 # Also create these last. They may inherit from sum types that have yet
618 # to be defined.
619 self._subtypes.append((subtype, self._product_counter))
620 self._product_counter += 1
621
622 def VisitProduct(self, product, name, depth):
623 self._shared_type_tags[name] = self._product_counter
624 # Create a tuple of _GenClass args to create LAST. They may inherit from
625 # sum types that have yet to be defined.
626 self._products.append((product, name, depth, self._product_counter))
627 self._product_counter += 1
628
629 def EmitFooter(self):
630 # Now generate all the product types we deferred.
631 for args in self._products:
632 ast_node, name, depth, tag_num = args
633 # Figure out base classes AFTERWARD.
634 bases = self._base_classes[name]
635 if not bases:
636 bases = ['pybase.CompoundObj']
637 self._GenClass(ast_node.fields, name, bases, tag_num)
638
639 for args in self._subtypes:
640 subtype, tag_num = args
641 # Figure out base classes AFTERWARD.
642 bases = self._base_classes[subtype.name]
643 if not bases:
644 bases = ['pybase.CompoundObj']
645
646 bases.append(_MyPyType(subtype.base_class))
647
648 if subtype.base_class.IsList():
649 self._GenListSubclass(subtype.name, bases, tag_num)
650 else:
651 self._GenClass([], subtype.name, bases, tag_num)