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

653 lines, 423 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, cast
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}
24
25
26def _MyPyType(typ):
27 """ASDL type to MyPy Type."""
28 if isinstance(typ, ast.ParameterizedType):
29
30 if typ.type_name == 'Dict':
31 k_type = _MyPyType(typ.children[0])
32 v_type = _MyPyType(typ.children[1])
33 return 'Dict[%s, %s]' % (k_type, v_type)
34
35 if typ.type_name == 'List':
36 return 'List[%s]' % _MyPyType(typ.children[0])
37
38 if typ.type_name == 'Optional':
39 return 'Optional[%s]' % _MyPyType(typ.children[0])
40
41 elif isinstance(typ, ast.NamedType):
42 if typ.resolved:
43 if isinstance(typ.resolved, ast.Sum): # includes SimpleSum
44 return '%s_t' % typ.name
45 if isinstance(typ.resolved, ast.Product):
46 return typ.name
47 if isinstance(typ.resolved, ast.Use):
48 return ast.TypeNameHeuristic(typ.name)
49 if isinstance(typ.resolved, ast.Extern):
50 r = typ.resolved
51 type_name = r.names[-1]
52 py_module = r.names[-2]
53 return '%s.%s' % (py_module, type_name)
54 raise AssertionError(typ)
55
56 return _PRIMITIVES[typ.name]
57
58 else:
59 raise AssertionError()
60
61
62def _CastedNull(mypy_type):
63 # type: (str) -> str
64 return "cast('%s', None)" % mypy_type
65
66
67def _DefaultValue(typ, mypy_type):
68 """Values that the static CreateNull() constructor passes.
69
70 mypy_type is used to cast None, to maintain mypy --strict for ASDL.
71
72 We circumvent the type system on CreateNull(). Then the user is
73 responsible for filling in all the fields. If they do so, we can
74 rely on it when reading fields at runtime.
75 """
76 if isinstance(typ, ast.ParameterizedType):
77 type_name = typ.type_name
78
79 if type_name == 'Optional':
80 return _CastedNull(mypy_type)
81
82 if type_name == 'List':
83 return "[] if alloc_lists else cast('%s', None)" % mypy_type
84
85 if type_name == 'Dict': # TODO: can respect alloc_dicts=True
86 return _CastedNull(mypy_type)
87
88 raise AssertionError(type_name)
89
90 if isinstance(typ, ast.NamedType):
91 type_name = typ.name
92
93 if type_name == 'id': # hard-coded HACK
94 return '-1'
95
96 if type_name == 'int':
97 return '-1'
98
99 if type_name == 'BigInt':
100 return 'mops.BigInt(-1)'
101
102 if type_name == 'bool':
103 return 'False'
104
105 if type_name == 'float':
106 return '0.0' # or should it be NaN?
107
108 if type_name == 'string':
109 return "''"
110
111 if isinstance(typ.resolved, ast.SimpleSum):
112 sum_type = typ.resolved
113 # Just make it the first variant. We could define "Undef" for
114 # each enum, but it doesn't seem worth it.
115 return '%s_e.%s' % (type_name, sum_type.types[0].name)
116
117 # CompoundSum or Product type
118 return _CastedNull(mypy_type)
119
120 else:
121 raise AssertionError()
122
123
124def _HNodeExpr(typ, var_name):
125 # type: (ast.type_expr_t, str) -> Tuple[str, bool]
126 none_guard = False
127
128 if ast.IsOptional(typ):
129 narrow = cast(ast.ParameterizedType, typ)
130 typ = narrow.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 sum_name = ast.NameHack(sum_name)
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 ast.IsList(field.typ):
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 ast.IsDict(field.typ):
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 ast.IsOptional(field.typ):
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 sum_name = ast.NameHack(sum_name)
553
554 self.Emit('def %s_str(tag, dot=True):' % sum_name, depth)
555 self.Emit(' # type: (int, bool) -> str', depth)
556 self.Emit(' v = _%s_str[tag]' % sum_name, depth)
557 self.Emit(' if dot:', depth)
558 self.Emit(' return "%s.%%s" %% v' % sum_name, depth)
559 self.Emit(' else:', depth)
560 self.Emit(' return v', depth)
561 self.Emit('', depth)
562
563 # the base class, e.g. 'command_t'
564 self.Emit('class %s_t(pybase.CompoundObj):' % sum_name, depth)
565 self.Indent()
566 depth = self.current_depth
567
568 # To imitate C++ API
569 self.Emit('def tag(self):')
570 self.Emit(' # type: () -> int')
571 self.Emit(' return self._type_tag')
572
573 self.Dedent()
574 depth = self.current_depth
575
576 self.Emit('')
577
578 # Declare any zero argument singleton classes outside of the main
579 # "namespace" class.
580 for i, variant in enumerate(sum.types):
581 if variant.shared_type:
582 continue # Don't generate a class for shared types.
583 if len(variant.fields) == 0:
584 # We must use the old-style naming here, ie. command__NoOp, in order
585 # to support zero field variants as constants.
586 class_name = '%s__%s' % (sum_name, variant.name)
587 self._GenClass(variant.fields, class_name, (sum_name + '_t', ),
588 i + 1)
589
590 # Class that's just a NAMESPACE, e.g. for value.Str
591 self.Emit('class %s(object):' % sum_name, depth)
592
593 self.Indent()
594
595 for i, variant in enumerate(sum.types):
596 if variant.shared_type:
597 continue
598
599 if len(variant.fields) == 0:
600 self.Emit('%s = %s__%s()' %
601 (variant.name, sum_name, variant.name))
602 self.Emit('')
603 else:
604 # Use fully-qualified name, so we can have osh_cmd.Simple and
605 # oil_cmd.Simple.
606 fq_name = variant.name
607 self._GenClass(variant.fields,
608 fq_name, (sum_name + '_t', ),
609 i + 1,
610 class_ns=sum_name + '.')
611 self.Emit(' pass', depth) # in case every variant is first class
612
613 self.Dedent()
614 self.Emit('')
615
616 def VisitSubType(self, subtype):
617 self._shared_type_tags[subtype.name] = self._product_counter
618
619 # Also create these last. They may inherit from sum types that have yet
620 # to be defined.
621 self._subtypes.append((subtype, self._product_counter))
622 self._product_counter += 1
623
624 def VisitProduct(self, product, name, depth):
625 self._shared_type_tags[name] = self._product_counter
626 # Create a tuple of _GenClass args to create LAST. They may inherit from
627 # sum types that have yet to be defined.
628 self._products.append((product, name, depth, self._product_counter))
629 self._product_counter += 1
630
631 def EmitFooter(self):
632 # Now generate all the product types we deferred.
633 for args in self._products:
634 ast_node, name, depth, tag_num = args
635 # Figure out base classes AFTERWARD.
636 bases = self._base_classes[name]
637 if not bases:
638 bases = ['pybase.CompoundObj']
639 self._GenClass(ast_node.fields, name, bases, tag_num)
640
641 for args in self._subtypes:
642 subtype, tag_num = args
643 # Figure out base classes AFTERWARD.
644 bases = self._base_classes[subtype.name]
645 if not bases:
646 bases = ['pybase.CompoundObj']
647
648 bases.append(_MyPyType(subtype.base_class))
649
650 if ast.IsList(subtype.base_class):
651 self._GenListSubclass(subtype.name, bases, tag_num)
652 else:
653 self._GenClass([], subtype.name, bases, tag_num)