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

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