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

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