1 | """
|
2 | cppgen_pass.py - AST pass that prints C++ code
|
3 | """
|
4 | import itertools
|
5 | import json # for "C escaping"
|
6 |
|
7 | from typing import Union, Optional, Dict
|
8 |
|
9 | import mypy
|
10 | from mycpp import visitor
|
11 | from mypy.types import (Type, AnyType, NoneTyp, TupleType, Instance,
|
12 | Overloaded, CallableType, UnionType, UninhabitedType,
|
13 | PartialType, TypeAliasType)
|
14 | from mypy.nodes import (Expression, Statement, NameExpr, IndexExpr, MemberExpr,
|
15 | TupleExpr, ExpressionStmt, IfStmt, StrExpr, SliceExpr,
|
16 | FuncDef, UnaryExpr, OpExpr, CallExpr, ListExpr,
|
17 | DictExpr, ClassDef, ForStmt, AssignmentStmt)
|
18 |
|
19 | from mycpp import format_strings
|
20 | from mycpp import pass_state
|
21 | from mycpp import util
|
22 | from mycpp.util import log, SymbolToString, SplitPyName
|
23 |
|
24 | from typing import Tuple, List, Any, TYPE_CHECKING
|
25 | if TYPE_CHECKING:
|
26 | from mycpp import const_pass
|
27 | from mycpp import conversion_pass
|
28 |
|
29 |
|
30 | def _IsContextManager(class_name: util.SymbolPath) -> bool:
|
31 | return class_name[-1].startswith('ctx_')
|
32 |
|
33 |
|
34 | def _GetCTypeForCast(type_expr: Expression) -> str:
|
35 | """ MyPy cast() """
|
36 |
|
37 | if isinstance(type_expr, MemberExpr):
|
38 | left = type_expr.expr
|
39 | assert isinstance(left, NameExpr), left # assume it's module.Type
|
40 | subtype_name = '%s::%s' % (left.name, type_expr.name)
|
41 | elif isinstance(type_expr, IndexExpr):
|
42 | # List[word_t] would be a problem.
|
43 | # But worked around it in osh/word_parse.py
|
44 | #subtype_name = 'List<word_t>'
|
45 | raise AssertionError()
|
46 | elif isinstance(type_expr, StrExpr):
|
47 | parts = type_expr.value.split('.')
|
48 | subtype_name = '::'.join(parts)
|
49 | elif isinstance(type_expr, NameExpr):
|
50 | subtype_name = type_expr.name
|
51 | else:
|
52 | raise AssertionError()
|
53 |
|
54 | # Hack for now
|
55 | if subtype_name != 'int' and subtype_name != 'mops::BigInt':
|
56 | subtype_name += '*'
|
57 | return subtype_name
|
58 |
|
59 |
|
60 | def _GetCastKind(module_path: str, cast_to_type: str) -> str:
|
61 | """Translate MyPy cast to C++ cast.
|
62 |
|
63 | Prefer static_cast, but sometimes we need reinterpret_cast.
|
64 | """
|
65 | cast_kind = 'static_cast'
|
66 |
|
67 | # Hack for Id.Expr_CastedDummy in expr_to_ast.py
|
68 | if 'expr_to_ast.py' in module_path:
|
69 | for name in (
|
70 | 'YshArrayLiteral',
|
71 | 'CommandSub',
|
72 | 'BracedVarSub',
|
73 | 'DoubleQuoted',
|
74 | 'SingleQuoted',
|
75 | # Another kind of hack, not because of CastDummy
|
76 | 'y_lhs_t',
|
77 | ):
|
78 | if name in cast_to_type:
|
79 | cast_kind = 'reinterpret_cast'
|
80 | break
|
81 |
|
82 | # The other side of Id.Expr_CastedDummy
|
83 | if 'expr_parse.py' in module_path:
|
84 | for name in ('Token', ):
|
85 | if name in cast_to_type:
|
86 | cast_kind = 'reinterpret_cast'
|
87 | break
|
88 |
|
89 | if 'process.py' in module_path and 'mylib::Writer' in cast_to_type:
|
90 | cast_kind = 'reinterpret_cast'
|
91 |
|
92 | return cast_kind
|
93 |
|
94 |
|
95 | def _ContainsFunc(t: Type) -> Optional[str]:
|
96 | """ x in y """
|
97 | contains_func = None
|
98 |
|
99 | if isinstance(t, Instance):
|
100 | type_name = t.type.fullname
|
101 |
|
102 | if type_name == 'builtins.list':
|
103 | contains_func = 'list_contains'
|
104 |
|
105 | elif type_name == 'builtins.str':
|
106 | contains_func = 'str_contains'
|
107 |
|
108 | elif type_name == 'builtins.dict':
|
109 | contains_func = 'dict_contains'
|
110 |
|
111 | elif isinstance(t, UnionType):
|
112 | # Special case for Optional[T] == Union[T, None]
|
113 | if len(t.items) != 2:
|
114 | raise NotImplementedError('Expected Optional, got %s' % t)
|
115 |
|
116 | if not isinstance(t.items[1], NoneTyp):
|
117 | raise NotImplementedError('Expected Optional, got %s' % t)
|
118 |
|
119 | contains_func = _ContainsFunc(t.items[0])
|
120 |
|
121 | return contains_func # None checked later
|
122 |
|
123 |
|
124 | def _EqualsFunc(left_type: Type) -> Optional[str]:
|
125 | if util.IsStr(left_type):
|
126 | return 'str_equals'
|
127 |
|
128 | if (isinstance(left_type, UnionType) and len(left_type.items) == 2 and
|
129 | util.IsStr(left_type.items[0]) and
|
130 | isinstance(left_type.items[1], NoneTyp)):
|
131 | return 'maybe_str_equals'
|
132 |
|
133 | return None
|
134 |
|
135 |
|
136 | _EXPLICIT = ('builtins.str', 'builtins.list', 'builtins.dict')
|
137 |
|
138 |
|
139 | def _CheckCondition(node: Expression, types: Dict[Expression, Type]) -> bool:
|
140 | """
|
141 | Ban
|
142 | if (mystr)
|
143 | if (mylist)
|
144 | if (mydict)
|
145 |
|
146 | They mean non-empty in Python.
|
147 | """
|
148 | #log('NODE %s', node)
|
149 |
|
150 | if isinstance(node, UnaryExpr) and node.op == 'not':
|
151 | return _CheckCondition(node.expr, types)
|
152 |
|
153 | if isinstance(node, OpExpr):
|
154 | #log('OpExpr node %s %s', node, dir(node))
|
155 |
|
156 | # if x > 0 and not mylist, etc.
|
157 | return (_CheckCondition(node.left, types) and
|
158 | _CheckCondition(node.right, types))
|
159 |
|
160 | t = types[node]
|
161 |
|
162 | if isinstance(t, Instance):
|
163 | type_name = t.type.fullname
|
164 | if type_name in _EXPLICIT:
|
165 | return False
|
166 |
|
167 | elif isinstance(t, UnionType):
|
168 | if len(t.items) == 2 and isinstance(t.items[1], NoneTyp):
|
169 | t2 = t.items[0]
|
170 | assert isinstance(t2, Instance), t2
|
171 | if t2.type.fullname in _EXPLICIT:
|
172 | return False
|
173 |
|
174 | return True
|
175 |
|
176 |
|
177 | def CTypeIsManaged(c_type: str) -> bool:
|
178 | """For rooting and field masks."""
|
179 | assert c_type != 'void'
|
180 |
|
181 | if util.SMALL_STR:
|
182 | if c_type == 'Str':
|
183 | return True
|
184 |
|
185 | # int, double, bool, scope_t enums, etc. are not managed
|
186 | return c_type.endswith('*')
|
187 |
|
188 |
|
189 | def GetCType(t: Type) -> str:
|
190 | """Recursively translate MyPy type to C++ type."""
|
191 | is_pointer = False
|
192 |
|
193 | if isinstance(t, UninhabitedType):
|
194 | # UninhabitedType is used by def e_usage() -> NoReturn
|
195 | # TODO: we could add [[noreturn]] here!
|
196 | c_type = 'void'
|
197 |
|
198 | elif isinstance(t, PartialType):
|
199 | # I removed the last instance of this! It was dead code in comp_ui.py.
|
200 | raise AssertionError()
|
201 | #c_type = 'void'
|
202 | #is_pointer = True
|
203 |
|
204 | elif isinstance(t,
|
205 | NoneTyp): # e.g. a function that doesn't return anything
|
206 | return 'void'
|
207 |
|
208 | elif isinstance(t, AnyType):
|
209 | # 'any' in ASDL becomes void*
|
210 | # It's useful for value::BuiltinFunc(void* f) which is a vm::_Callable*
|
211 | c_type = 'void'
|
212 | is_pointer = True
|
213 |
|
214 | elif isinstance(t, CallableType):
|
215 | # Function types are expanded
|
216 | # Callable[[Parser, Token, int], arith_expr_t]
|
217 | # -> arith_expr_t* (*f)(Parser*, Token*, int) nud;
|
218 |
|
219 | ret_type = GetCType(t.ret_type)
|
220 | arg_types = [GetCType(typ) for typ in t.arg_types]
|
221 | c_type = '%s (*f)(%s)' % (ret_type, ', '.join(arg_types))
|
222 |
|
223 | elif isinstance(t, TypeAliasType):
|
224 | if 0:
|
225 | log('***')
|
226 | log('%s', t)
|
227 | log('%s', dir(t))
|
228 | log('%s', t.alias)
|
229 | log('%s', dir(t.alias))
|
230 | log('%s', t.alias.target)
|
231 | log('***')
|
232 | return GetCType(t.alias.target)
|
233 |
|
234 | elif isinstance(t, Instance):
|
235 | type_name = t.type.fullname
|
236 | #log('** TYPE NAME %s', type_name)
|
237 |
|
238 | if type_name == 'builtins.int':
|
239 | c_type = 'int'
|
240 |
|
241 | elif type_name == 'builtins.float':
|
242 | c_type = 'double'
|
243 |
|
244 | elif type_name == 'builtins.bool':
|
245 | c_type = 'bool'
|
246 |
|
247 | elif type_name == 'builtins.str':
|
248 | if util.SMALL_STR:
|
249 | c_type = 'Str'
|
250 | is_pointer = False
|
251 | else:
|
252 | c_type = 'BigStr'
|
253 | is_pointer = True
|
254 |
|
255 | elif 'BigInt' in type_name:
|
256 | # also spelled mycpp.mylib.BigInt
|
257 |
|
258 | c_type = 'mops::BigInt'
|
259 | # Not a pointer!
|
260 |
|
261 | elif type_name == 'typing.IO':
|
262 | c_type = 'mylib::File'
|
263 | is_pointer = True
|
264 |
|
265 | # Parameterized types: List, Dict, Iterator
|
266 | elif type_name == 'builtins.list':
|
267 | assert len(t.args) == 1, t.args
|
268 | type_param = t.args[0]
|
269 | inner_c_type = GetCType(type_param)
|
270 | c_type = 'List<%s>' % inner_c_type
|
271 | is_pointer = True
|
272 |
|
273 | elif type_name == 'builtins.dict':
|
274 | params = []
|
275 | for type_param in t.args:
|
276 | params.append(GetCType(type_param))
|
277 | c_type = 'Dict<%s>' % ', '.join(params)
|
278 | is_pointer = True
|
279 |
|
280 | elif type_name == 'typing.Iterator':
|
281 | assert len(t.args) == 1, t.args
|
282 | type_param = t.args[0]
|
283 | inner_c_type = GetCType(type_param)
|
284 | c_type = 'ListIter<%s>' % inner_c_type
|
285 |
|
286 | else:
|
287 | parts = t.type.fullname.split('.')
|
288 | c_type = '%s::%s' % (parts[-2], parts[-1])
|
289 |
|
290 | # note: fullname => 'parse.Lexer'; name => 'Lexer'
|
291 | base_class_names = [b.type.fullname for b in t.type.bases]
|
292 |
|
293 | # Check base class for pybase.SimpleObj so we can output
|
294 | # expr_asdl::tok_t instead of expr_asdl::tok_t*. That is a enum, while
|
295 | # expr_t is a "regular base class".
|
296 | # NOTE: Could we avoid the typedef? If it's SimpleObj, just generate
|
297 | # tok_e instead?
|
298 |
|
299 | if 'asdl.pybase.SimpleObj' not in base_class_names:
|
300 | is_pointer = True
|
301 |
|
302 | elif isinstance(t, TupleType):
|
303 | inner_c_types = [GetCType(inner) for inner in t.items]
|
304 | c_type = 'Tuple%d<%s>' % (len(t.items), ', '.join(inner_c_types))
|
305 | is_pointer = True
|
306 |
|
307 | elif isinstance(t, UnionType): # Optional[T]
|
308 |
|
309 | num_items = len(t.items)
|
310 |
|
311 | if num_items == 3:
|
312 | # Special case for Optional[IOError_OSError] ==
|
313 | # Union[IOError, # OSError, None]
|
314 | t0 = t.items[0]
|
315 | t1 = t.items[1]
|
316 | t2 = t.items[2]
|
317 |
|
318 | assert isinstance(t0, Instance), t0
|
319 | assert isinstance(t1, Instance), t1
|
320 | t0_name = t0.type.fullname
|
321 | t1_name = t1.type.fullname
|
322 |
|
323 | if t0_name != 'builtins.IOError':
|
324 | raise NotImplementedError(
|
325 | 'Expected Union[IOError, OSError, None]: t0 = %s' %
|
326 | t0_name)
|
327 |
|
328 | if t1_name != 'builtins.OSError':
|
329 | raise NotImplementedError(
|
330 | 'Expected Union[IOError, OSError, None]: t1 = %s' %
|
331 | t1_name)
|
332 |
|
333 | if not isinstance(t2, NoneTyp):
|
334 | raise NotImplementedError(
|
335 | 'Expected Union[IOError, OSError, None]')
|
336 |
|
337 | c_type = 'IOError_OSError'
|
338 | is_pointer = True
|
339 |
|
340 | elif num_items == 2:
|
341 | # Optional[T]
|
342 |
|
343 | t0 = t.items[0]
|
344 | t1 = t.items[1]
|
345 |
|
346 | c_type = None
|
347 | if isinstance(t1, NoneTyp):
|
348 | c_type = GetCType(t.items[0])
|
349 | else:
|
350 | assert isinstance(t0, Instance), t0
|
351 | assert isinstance(t1, Instance), t1
|
352 |
|
353 | # Detect type alias defined in core/error.py
|
354 | # IOError_OSError = Union[IOError, OSError]
|
355 | t0_name = t0.type.fullname
|
356 | t1_name = t1.type.fullname
|
357 | if (t0_name == 'builtins.IOError' and
|
358 | t1_name == 'builtins.OSError'):
|
359 | c_type = 'IOError_OSError'
|
360 | is_pointer = True
|
361 |
|
362 | if c_type is None:
|
363 | raise NotImplementedError('Unexpected Union type %s' % t)
|
364 |
|
365 | else:
|
366 | raise NotImplementedError(
|
367 | 'Expected 2 or 3 items in Union, got %s' % num_items)
|
368 |
|
369 | else:
|
370 | raise NotImplementedError('MyPy type: %s %s' % (type(t), t))
|
371 |
|
372 | if is_pointer:
|
373 | c_type += '*'
|
374 |
|
375 | return c_type
|
376 |
|
377 |
|
378 | def GetCReturnType(t: Type) -> Tuple[str, bool, Optional[str]]:
|
379 | """
|
380 | Returns a C string, whether the tuple-by-value optimization was applied,
|
381 | and the C type of an extra output param if the function is a generator.
|
382 | """
|
383 |
|
384 | c_ret_type = GetCType(t)
|
385 |
|
386 | # Optimization: Return tuples BY VALUE
|
387 | if isinstance(t, TupleType):
|
388 | assert c_ret_type.endswith('*')
|
389 | return c_ret_type[:-1], True, None
|
390 | elif c_ret_type.startswith('ListIter<'):
|
391 | assert len(t.args) == 1, t.args
|
392 | inner_c_type = GetCType(t.args[0])
|
393 | return 'void', False, 'List<%s>*' % inner_c_type
|
394 | else:
|
395 | return c_ret_type, False, None
|
396 |
|
397 |
|
398 | def PythonStringLiteral(s: str) -> str:
|
399 | """
|
400 | Returns a properly quoted string.
|
401 | """
|
402 | # MyPy does bad escaping. Decode and push through json to get something
|
403 | # workable in C++.
|
404 | return json.dumps(format_strings.DecodeMyPyString(s))
|
405 |
|
406 |
|
407 | def _GetNoReturn(func_name: str) -> str:
|
408 | # Avoid C++ warnings by prepending [[noreturn]]
|
409 | if func_name in ('e_die', 'e_die_status', 'e_strict', 'e_usage', 'p_die'):
|
410 | return '[[noreturn]] '
|
411 | else:
|
412 | return ''
|
413 |
|
414 |
|
415 | # name, type, is_param
|
416 | LocalVar = Tuple[str, Type, bool]
|
417 |
|
418 | # lval_type, c_type, is_managed
|
419 | MemberVar = Tuple[Type, str, bool]
|
420 |
|
421 | AllMemberVars = Dict[ClassDef, Dict[str, MemberVar]]
|
422 |
|
423 | AllLocalVars = Dict[FuncDef, List[Tuple[str, Type]]]
|
424 |
|
425 |
|
426 | class _Shared(visitor.TypedVisitor):
|
427 |
|
428 | def __init__(
|
429 | self,
|
430 | types: Dict[Expression, Type],
|
431 | global_strings: 'const_pass.GlobalStrings',
|
432 | yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
|
433 | dunder_exit_special: Dict[ClassDef, bool], # input
|
434 | # all_member_vars:
|
435 | # - Decl for declaring members in class { }
|
436 | # - Impl for rooting context managers
|
437 | all_member_vars: Optional[AllMemberVars] = None,
|
438 | ) -> None:
|
439 | visitor.TypedVisitor.__init__(self, types)
|
440 | self.global_strings = global_strings
|
441 | self.yield_out_params = yield_out_params
|
442 | self.dunder_exit_special = dunder_exit_special
|
443 | self.all_member_vars = all_member_vars # for class def, and rooting
|
444 |
|
445 | # Primitives shared for default values
|
446 |
|
447 | def visit_int_expr(self, o: 'mypy.nodes.IntExpr') -> None:
|
448 | self.write(str(o.value))
|
449 |
|
450 | def visit_float_expr(self, o: 'mypy.nodes.FloatExpr') -> None:
|
451 | # e.g. for arg.t > 0.0
|
452 | self.write(str(o.value))
|
453 |
|
454 | def visit_str_expr(self, o: 'mypy.nodes.StrExpr') -> None:
|
455 | self.write(self.global_strings.GetVarName(o))
|
456 |
|
457 | def oils_visit_name_expr(self, o: 'mypy.nodes.NameExpr') -> None:
|
458 | if o.name == 'None':
|
459 | self.write('nullptr')
|
460 | return
|
461 | if o.name == 'True':
|
462 | self.write('true')
|
463 | return
|
464 | if o.name == 'False':
|
465 | self.write('false')
|
466 | return
|
467 | if o.name == 'self':
|
468 | self.write('this')
|
469 | return
|
470 |
|
471 | self.write(o.name)
|
472 |
|
473 | def visit_unary_expr(self, o: 'mypy.nodes.UnaryExpr') -> None:
|
474 | # e.g. a[-1] or 'not x'
|
475 | if o.op == 'not':
|
476 | op_str = '!'
|
477 | else:
|
478 | op_str = o.op
|
479 | self.write(op_str)
|
480 | self.accept(o.expr)
|
481 |
|
482 | def _NamespaceComment(self) -> str:
|
483 | # abstract method
|
484 | raise NotImplementedError()
|
485 |
|
486 | def oils_visit_mypy_file(self, o: 'mypy.nodes.MypyFile') -> None:
|
487 | mod_parts = o.fullname.split('.')
|
488 | comment = self._NamespaceComment()
|
489 |
|
490 | self.write_ind('namespace %s { // %s\n', mod_parts[-1], comment)
|
491 | self.write('\n')
|
492 |
|
493 | #self.log('defs %s', o.defs)
|
494 | for node in o.defs:
|
495 | self.accept(node)
|
496 |
|
497 | self.write('\n')
|
498 | self.write_ind('} // %s namespace %s\n', comment, mod_parts[-1])
|
499 | self.write('\n')
|
500 |
|
501 | def _WriteFuncParams(self,
|
502 | func_def: FuncDef,
|
503 | write_defaults: bool = False) -> None:
|
504 | """Write params for function/method signatures."""
|
505 | arg_types = func_def.type.arg_types
|
506 | arguments = func_def.arguments
|
507 |
|
508 | is_first = True # EXCLUDING 'self'
|
509 | for arg_type, arg in zip(arg_types, arguments):
|
510 | if not is_first:
|
511 | self.write(', ')
|
512 |
|
513 | c_type = GetCType(arg_type)
|
514 |
|
515 | arg_name = arg.variable.name
|
516 |
|
517 | # C++ has implicit 'this'
|
518 | if arg_name == 'self':
|
519 | continue
|
520 |
|
521 | # int foo
|
522 | self.write('%s %s', c_type, arg_name)
|
523 |
|
524 | if write_defaults and arg.initializer: # int foo = 42
|
525 | self.write(' = ')
|
526 | self.accept(arg.initializer)
|
527 |
|
528 | is_first = False
|
529 |
|
530 | if 0:
|
531 | self.log('Argument %s', arg.variable)
|
532 | self.log(' type_annotation %s', arg.type_annotation)
|
533 | # I think these are for default values
|
534 | self.log(' initializer %s', arg.initializer)
|
535 | self.log(' kind %s', arg.kind)
|
536 |
|
537 | # Is the function we're writing params for an iterator?
|
538 | if func_def in self.yield_out_params:
|
539 | self.write(', ')
|
540 |
|
541 | arg_name, c_type = self.yield_out_params[func_def]
|
542 | self.write('%s %s', c_type, arg_name)
|
543 |
|
544 |
|
545 | class Decl(_Shared):
|
546 |
|
547 | def __init__(
|
548 | self,
|
549 | types: Dict[Expression, Type],
|
550 | global_strings: 'const_pass.GlobalStrings',
|
551 | yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
|
552 | dunder_exit_special: Dict[ClassDef, bool],
|
553 | virtual: pass_state.Virtual = None,
|
554 | all_member_vars: Optional[AllMemberVars] = None,
|
555 | ) -> None:
|
556 | _Shared.__init__(
|
557 | self,
|
558 | types,
|
559 | global_strings,
|
560 | yield_out_params,
|
561 | dunder_exit_special,
|
562 | all_member_vars=all_member_vars,
|
563 | )
|
564 | self.virtual = virtual
|
565 |
|
566 | def _NamespaceComment(self) -> str:
|
567 | # abstract method
|
568 | return 'declare'
|
569 |
|
570 | def oils_visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
|
571 | # Avoid C++ warnings by prepending [[noreturn]]
|
572 | noreturn = _GetNoReturn(o.name)
|
573 |
|
574 | virtual = ''
|
575 | if self.virtual.IsVirtual(self.current_class_name, o.name):
|
576 | virtual = 'virtual '
|
577 |
|
578 | # declaration inside class { }
|
579 | func_name = o.name
|
580 |
|
581 | # Why can't we get this Type object with self.types[o]?
|
582 | c_ret_type, _, _ = GetCReturnType(o.type.ret_type)
|
583 |
|
584 | self.write_ind('%s%s%s %s(', noreturn, virtual, c_ret_type, func_name)
|
585 |
|
586 | self._WriteFuncParams(o, write_defaults=True)
|
587 | self.write(');\n')
|
588 |
|
589 | def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
|
590 | # In declarations, 'a.b' is only used for default argument
|
591 | # values 'a::b'
|
592 | self.accept(o.expr)
|
593 | # TODO: remove write() in Decl pass
|
594 | self.write('::')
|
595 | self.write(o.name)
|
596 |
|
597 | def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
|
598 | lval: Expression, rval: Expression) -> None:
|
599 | # Declare constant strings. They have to be at the top level.
|
600 |
|
601 | # TODO: self.at_global_scope doesn't work for context managers and so forth
|
602 | if self.indent == 0:
|
603 | # Top level can't have foo.bar = baz
|
604 | assert isinstance(lval, NameExpr), lval
|
605 | if not util.SkipAssignment(lval.name):
|
606 | c_type = GetCType(self.types[lval])
|
607 | self.write('extern %s %s;\n', c_type, lval.name)
|
608 |
|
609 | # TODO: we don't traverse here, so _CheckCondition() isn't called
|
610 | # e.g. x = 'a' if mylist else 'b'
|
611 |
|
612 | def oils_visit_constructor(self, o: ClassDef, stmt: FuncDef,
|
613 | base_class_sym: util.SymbolPath) -> None:
|
614 | self.indent += 1
|
615 | self.write_ind('%s(', o.name)
|
616 | self._WriteFuncParams(stmt, write_defaults=True)
|
617 | self.write(');\n')
|
618 | self.indent -= 1
|
619 |
|
620 | def oils_visit_dunder_exit(self, o: ClassDef, stmt: FuncDef,
|
621 | base_class_sym: util.SymbolPath) -> None:
|
622 | self.indent += 1
|
623 | # Turn it into a destructor with NO ARGS
|
624 | self.write_ind('~%s();\n', o.name)
|
625 |
|
626 | # special method
|
627 | if o in self.dunder_exit_special:
|
628 | self.write_ind('void ctx_EXIT();\n')
|
629 |
|
630 | self.indent -= 1
|
631 |
|
632 | def oils_visit_method(self, o: ClassDef, stmt: FuncDef,
|
633 | base_class_sym: util.SymbolPath) -> None:
|
634 | self.indent += 1
|
635 | self.accept(stmt)
|
636 | self.indent -= 1
|
637 |
|
638 | def oils_visit_class_members(self, o: ClassDef,
|
639 | base_class_sym: util.SymbolPath) -> None:
|
640 | # Write member variables
|
641 | self.indent += 1
|
642 | self._MemberDecl(o, base_class_sym)
|
643 | self.indent -= 1
|
644 |
|
645 | def oils_visit_class_def(
|
646 | self, o: 'mypy.nodes.ClassDef',
|
647 | base_class_sym: Optional[util.SymbolPath]) -> None:
|
648 | self.write_ind('class %s', o.name) # block after this
|
649 |
|
650 | # e.g. class TextOutput : public ColorOutput
|
651 | if base_class_sym:
|
652 | self.write(' : public %s',
|
653 | SymbolToString(base_class_sym, strip_package=True))
|
654 |
|
655 | self.write(' {\n')
|
656 | self.write_ind(' public:\n')
|
657 |
|
658 | # This visits all the methods, with self.indent += 1, param
|
659 | # base_class_sym, self.current_method_name
|
660 |
|
661 | super().oils_visit_class_def(o, base_class_sym)
|
662 |
|
663 | self.write_ind('};\n')
|
664 | self.write('\n')
|
665 |
|
666 | def _GcHeaderDecl(self, o: 'mypy.nodes.ClassDef',
|
667 | field_gc: Tuple[str, str], mask_bits: List[str]) -> None:
|
668 | if mask_bits:
|
669 | self.write_ind('\n')
|
670 | self.write_ind('static constexpr uint32_t field_mask() {\n')
|
671 | self.write_ind(' return ')
|
672 | for i, b in enumerate(mask_bits):
|
673 | if i != 0:
|
674 | self.write('\n')
|
675 | self.write_ind(' | ')
|
676 | self.write(b)
|
677 | self.write(';\n')
|
678 | self.write_ind('}\n')
|
679 |
|
680 | obj_tag, obj_arg = field_gc
|
681 | if obj_tag == 'HeapTag::FixedSize':
|
682 | obj_mask = obj_arg
|
683 | obj_header = 'ObjHeader::ClassFixed(%s, sizeof(%s))' % (obj_mask,
|
684 | o.name)
|
685 | elif obj_tag == 'HeapTag::Scanned':
|
686 | num_pointers = obj_arg
|
687 | obj_header = 'ObjHeader::ClassScanned(%s, sizeof(%s))' % (
|
688 | num_pointers, o.name)
|
689 | else:
|
690 | raise AssertionError(o.name)
|
691 |
|
692 | self.write('\n')
|
693 | self.write_ind('static constexpr ObjHeader obj_header() {\n')
|
694 | self.write_ind(' return %s;\n' % obj_header)
|
695 | self.write_ind('}\n')
|
696 |
|
697 | def _MemberDecl(self, o: 'mypy.nodes.ClassDef',
|
698 | base_class_sym: util.SymbolPath) -> None:
|
699 | member_vars = self.all_member_vars[o]
|
700 |
|
701 | # List of field mask expressions
|
702 | mask_bits = []
|
703 | if self.virtual.CanReorderFields(SplitPyName(o.fullname)):
|
704 | # No inheritance, so we are free to REORDER member vars, putting
|
705 | # pointers at the front.
|
706 |
|
707 | pointer_members = []
|
708 | non_pointer_members = []
|
709 |
|
710 | for name in member_vars:
|
711 | _, c_type, is_managed = member_vars[name]
|
712 | if is_managed:
|
713 | pointer_members.append(name)
|
714 | else:
|
715 | non_pointer_members.append(name)
|
716 |
|
717 | # So we declare them in the right order
|
718 | sorted_member_names = pointer_members + non_pointer_members
|
719 |
|
720 | field_gc = ('HeapTag::Scanned', str(len(pointer_members)))
|
721 | else:
|
722 | # Has inheritance
|
723 |
|
724 | # The field mask of a derived class is unioned with its base's
|
725 | # field mask.
|
726 | if base_class_sym:
|
727 | mask_bits.append(
|
728 | '%s::field_mask()' %
|
729 | SymbolToString(base_class_sym, strip_package=True))
|
730 |
|
731 | for name in sorted(member_vars):
|
732 | _, c_type, is_managed = member_vars[name]
|
733 | if is_managed:
|
734 | mask_bits.append('maskbit(offsetof(%s, %s))' %
|
735 | (o.name, name))
|
736 |
|
737 | # A base class with no fields has kZeroMask.
|
738 | if not base_class_sym and not mask_bits:
|
739 | mask_bits.append('kZeroMask')
|
740 |
|
741 | sorted_member_names = sorted(member_vars)
|
742 |
|
743 | field_gc = ('HeapTag::FixedSize', 'field_mask()')
|
744 |
|
745 | # Write member variables
|
746 |
|
747 | #log('MEMBERS for %s: %s', o.name, list(self.member_vars.keys()))
|
748 | if len(member_vars):
|
749 | if base_class_sym:
|
750 | self.write('\n') # separate from functions
|
751 |
|
752 | for name in sorted_member_names:
|
753 | _, c_type, _ = member_vars[name]
|
754 | # use default zero initialization for all members
|
755 | # (context managers may be on the stack)
|
756 | self.write_ind('%s %s{};\n', c_type, name)
|
757 |
|
758 | # Context managers aren't GC objects
|
759 | if not _IsContextManager(self.current_class_name):
|
760 | self._GcHeaderDecl(o, field_gc, mask_bits)
|
761 |
|
762 | self.write('\n')
|
763 | self.write_ind('DISALLOW_COPY_AND_ASSIGN(%s)\n', o.name)
|
764 |
|
765 |
|
766 | class Impl(_Shared):
|
767 |
|
768 | def __init__(
|
769 | self,
|
770 | types: Dict[Expression, Type],
|
771 | global_strings: 'const_pass.GlobalStrings',
|
772 | yield_out_params: Dict[FuncDef, Tuple[str, str]], # input
|
773 | dunder_exit_special: Dict[ClassDef, bool],
|
774 | all_member_vars: Optional[AllMemberVars] = None,
|
775 | local_vars: Optional[AllLocalVars] = None,
|
776 | dot_exprs: Optional['conversion_pass.DotExprs'] = None,
|
777 | stack_roots_warn: Optional[int] = None,
|
778 | stack_roots: Optional[pass_state.StackRoots] = None) -> None:
|
779 | _Shared.__init__(self,
|
780 | types,
|
781 | global_strings,
|
782 | yield_out_params,
|
783 | dunder_exit_special,
|
784 | all_member_vars=all_member_vars)
|
785 | self.local_vars = local_vars
|
786 |
|
787 | # Computed in previous passes
|
788 | self.dot_exprs = dot_exprs
|
789 | self.stack_roots_warn = stack_roots_warn
|
790 | self.stack_roots = stack_roots
|
791 |
|
792 | # Traversal state used to to create an EAGER List<T>
|
793 | self.yield_eager_assign: Dict[AssignmentStmt, Tuple[str, str]] = {}
|
794 | self.yield_eager_for: Dict[ForStmt, Tuple[str, str]] = {}
|
795 |
|
796 | self.yield_assign_node: Optional[AssignmentStmt] = None
|
797 | self.yield_for_node: Optional[ForStmt] = None
|
798 |
|
799 | # More Traversal state
|
800 | self.current_func_node: Optional[FuncDef] = None
|
801 |
|
802 | self.unique_id = 0
|
803 |
|
804 | def _NamespaceComment(self) -> str:
|
805 | # abstract method
|
806 | return 'define'
|
807 |
|
808 | def oils_visit_func_def(self, o: 'mypy.nodes.FuncDef') -> None:
|
809 | if self.current_class_name:
|
810 | # definition looks like
|
811 | # void Class::method(...);
|
812 | func_name = SymbolToString((self.current_class_name[-1], o.name))
|
813 | noreturn = ''
|
814 | else:
|
815 | func_name = o.name
|
816 | noreturn = _GetNoReturn(o.name)
|
817 |
|
818 | self.write('\n')
|
819 |
|
820 | # Why can't we get this Type object with self.types[o]?
|
821 | c_ret_type, _, _ = GetCReturnType(o.type.ret_type)
|
822 |
|
823 | self.write_ind('%s%s %s(', noreturn, c_ret_type, func_name)
|
824 |
|
825 | self.current_func_node = o
|
826 | self._WriteFuncParams(o, write_defaults=False)
|
827 |
|
828 | self.write(') ')
|
829 | arg_names = [arg.variable.name for arg in o.arguments]
|
830 | #log('arg_names %s', arg_names)
|
831 | #log('local_vars %s', self.local_vars[o])
|
832 | local_var_list: List[LocalVar] = []
|
833 | for (lval_name, lval_type) in self.local_vars[o]:
|
834 | local_var_list.append((lval_name, lval_type, lval_name
|
835 | in arg_names))
|
836 |
|
837 | self.write('{\n')
|
838 |
|
839 | self.indent += 1
|
840 | self._WriteLocals(local_var_list)
|
841 | self._WriteBody(o.body.body)
|
842 | self.indent -= 1
|
843 |
|
844 | self.write('}\n')
|
845 |
|
846 | self.current_func_node = None
|
847 |
|
848 | def visit_yield_expr(self, o: 'mypy.nodes.YieldExpr') -> None:
|
849 | assert self.current_func_node in self.yield_out_params
|
850 | self.write('%s->append(',
|
851 | self.yield_out_params[self.current_func_node][0])
|
852 | self.accept(o.expr)
|
853 | self.write(')')
|
854 |
|
855 | def _WriteArgList(self, args: List[Expression]) -> None:
|
856 | self.write('(')
|
857 | for i, arg in enumerate(args):
|
858 | if i != 0:
|
859 | self.write(', ')
|
860 | self.accept(arg)
|
861 |
|
862 | # Pass an extra arg like my_generator(42, &accum)
|
863 | #
|
864 | # Two cases:
|
865 | # ForStmt: for y in generator(42): =>
|
866 | # generator(42, &y)
|
867 | # AssignmentStmt: it = generator(42) =>
|
868 | # List<int> _iter_buf_it;
|
869 | # generator(42, &iter_buf_it); # eagerly append
|
870 |
|
871 | eager_pair = (self.yield_eager_assign.get(self.yield_assign_node) or
|
872 | self.yield_eager_for.get(self.yield_for_node))
|
873 |
|
874 | if eager_pair:
|
875 | if len(args) > 0:
|
876 | self.write(', ')
|
877 |
|
878 | eager_list_name, _ = eager_pair
|
879 | self.write('&%s', eager_list_name)
|
880 |
|
881 | self.write(')')
|
882 |
|
883 | def oils_visit_member_expr(self, o: 'mypy.nodes.MemberExpr') -> None:
|
884 | dot_expr = self.dot_exprs[o]
|
885 |
|
886 | if isinstance(dot_expr, pass_state.StackObjectMember):
|
887 | op = '.'
|
888 |
|
889 | elif (isinstance(dot_expr, pass_state.StaticClassMember) or
|
890 | isinstance(dot_expr, pass_state.ModuleMember)):
|
891 | op = '::'
|
892 |
|
893 | elif isinstance(dot_expr, pass_state.HeapObjectMember):
|
894 | op = '->'
|
895 |
|
896 | else:
|
897 | raise AssertionError()
|
898 |
|
899 | self.accept(o.expr)
|
900 | self.write(op)
|
901 |
|
902 | if o.name == 'errno':
|
903 | # e->errno -> e->errno_ to avoid conflict with C macro
|
904 | self.write('errno_')
|
905 | else:
|
906 | self.write('%s', o.name)
|
907 |
|
908 | def _IsInstantiation(self, o: 'mypy.nodes.CallExpr') -> bool:
|
909 | callee_name = o.callee.name
|
910 | callee_type = self.types[o.callee]
|
911 |
|
912 | # e.g. int() takes str, float, etc. It doesn't matter for translation.
|
913 | if isinstance(callee_type, Overloaded):
|
914 | if 0:
|
915 | for item in callee_type.items():
|
916 | self.log('item: %s', item)
|
917 |
|
918 | if isinstance(callee_type, CallableType):
|
919 | # If the function name is the same as the return type, then add
|
920 | # 'Alloc<>'. f = Foo() => f = Alloc<Foo>().
|
921 | ret_type = callee_type.ret_type
|
922 |
|
923 | # e.g. str(i) is a free function
|
924 | if (callee_name not in ('str', 'bool', 'float') and
|
925 | 'BigInt' not in callee_name and
|
926 | isinstance(ret_type, Instance)):
|
927 |
|
928 | ret_type_name = ret_type.type.name
|
929 |
|
930 | # HACK: Const is the callee; expr__Const is the return type
|
931 | if (ret_type_name == callee_name or
|
932 | ret_type_name.endswith('__' + callee_name)):
|
933 | return True
|
934 |
|
935 | return False
|
936 |
|
937 | def oils_visit_probe_call(self, o: 'mypy.nodes.CallExpr') -> None:
|
938 | assert len(o.args) >= 2 and len(o.args) < 13, o.args
|
939 | assert isinstance(o.args[0], mypy.nodes.StrExpr), o.args[0]
|
940 | assert isinstance(o.args[1], mypy.nodes.StrExpr), o.args[1]
|
941 | arity = len(o.args) - 2
|
942 | macro = 'DTRACE_PROBE'
|
943 | if arity > 0:
|
944 | macro = 'DTRACE_PROBE%d' % arity
|
945 |
|
946 | self.write('%s(%s, %s', macro, o.args[0].value, o.args[1].value)
|
947 |
|
948 | for arg in o.args[2:]:
|
949 | arg_type = self.types[arg]
|
950 | self.write(', ')
|
951 | if util.IsStr(arg_type): # TODO: doesn't know it's an Instance
|
952 | self.write('%s->data()' % arg.name)
|
953 | else:
|
954 | self.accept(arg)
|
955 |
|
956 | self.write(')')
|
957 |
|
958 | def oils_visit_log_call(self, fmt: StrExpr,
|
959 | args: List[Expression]) -> None:
|
960 | if len(args) == 0: # log('const') -> print_stderr(S_xyz)
|
961 | # This is a GC string
|
962 | self.write('mylib::print_stderr(')
|
963 | self.accept(fmt)
|
964 | self.write(')')
|
965 | return
|
966 |
|
967 | # log('const %s', a) -> print_stderr(StrFormat("const %s", a))
|
968 | quoted_fmt = PythonStringLiteral(fmt.value)
|
969 | self.write('mylib::print_stderr(StrFormat(%s, ' % quoted_fmt)
|
970 |
|
971 | for i, arg in enumerate(args):
|
972 | if i != 0:
|
973 | self.write(', ')
|
974 | self.accept(arg)
|
975 | self.write('))')
|
976 |
|
977 | def oils_visit_call_expr(self, o: 'mypy.nodes.CallExpr') -> None:
|
978 | callee_name = o.callee.name
|
979 |
|
980 | # return cast(YshArrayLiteral, tok)
|
981 | # -> return static_cast<YshArrayLiteral*>(tok)
|
982 |
|
983 | # TODO: Consolidate this with AssignmentExpr logic.
|
984 | if callee_name == 'cast':
|
985 | call = o
|
986 | type_expr = call.args[0]
|
987 |
|
988 | subtype_name = _GetCTypeForCast(type_expr)
|
989 | cast_kind = _GetCastKind(self.module_path, subtype_name)
|
990 | self.write('%s<%s>(', cast_kind, subtype_name)
|
991 | self.accept(call.args[1]) # variable being casted
|
992 | self.write(')')
|
993 | return
|
994 |
|
995 | if isinstance(o.callee, MemberExpr) and callee_name == 'next':
|
996 | self.accept(o.callee.expr)
|
997 | self.write('.iterNext')
|
998 | self._WriteArgList(o.args)
|
999 | return
|
1000 |
|
1001 | if self._IsInstantiation(o):
|
1002 | self.write('Alloc<')
|
1003 | self.accept(o.callee)
|
1004 | self.write('>')
|
1005 | self._WriteArgList(o.args)
|
1006 | return
|
1007 |
|
1008 | # Namespace.
|
1009 | if callee_name == 'int': # int('foo') in Python conflicts with keyword
|
1010 | self.write('to_int')
|
1011 | elif callee_name == 'float':
|
1012 | self.write('to_float')
|
1013 | elif callee_name == 'bool':
|
1014 | self.write('to_bool')
|
1015 | else:
|
1016 | self.accept(o.callee) # could be f() or obj.method()
|
1017 |
|
1018 | self._WriteArgList(o.args)
|
1019 |
|
1020 | # TODO: we could check that keyword arguments are passed as named args?
|
1021 | #self.log(' arg_kinds %s', o.arg_kinds)
|
1022 | #self.log(' arg_names %s', o.arg_names)
|
1023 |
|
1024 | def oils_visit_format_expr(self, left: Expression,
|
1025 | right: Expression) -> None:
|
1026 | self.write('StrFormat(')
|
1027 | if isinstance(left, StrExpr):
|
1028 | self.write(PythonStringLiteral(left.value))
|
1029 | else:
|
1030 | self.accept(left)
|
1031 | #log('right_type %s', right_type)
|
1032 |
|
1033 | right_type = self.types[right]
|
1034 |
|
1035 | # TODO: Can we restore some type checking?
|
1036 | if 0:
|
1037 | if isinstance(right_type, Instance):
|
1038 | fmt_types: List[Type] = [right_type]
|
1039 | elif isinstance(right_type, TupleType):
|
1040 | fmt_types = right_type.items
|
1041 | # Handle Optional[str]
|
1042 | elif (isinstance(right_type, UnionType) and
|
1043 | len(right_type.items) == 2 and
|
1044 | isinstance(right_type.items[1], NoneTyp)):
|
1045 | fmt_types = [right_type.items[0]]
|
1046 | else:
|
1047 | raise AssertionError(right_type)
|
1048 |
|
1049 | # In the definition pass, write the call site.
|
1050 | if isinstance(right_type, TupleType):
|
1051 | assert isinstance(right, TupleExpr), right
|
1052 | for i, item in enumerate(right.items):
|
1053 | self.write(', ')
|
1054 | self.accept(item)
|
1055 |
|
1056 | else: # '[%s]' % x
|
1057 | self.write(', ')
|
1058 | self.accept(right)
|
1059 |
|
1060 | self.write(')')
|
1061 |
|
1062 | def oils_visit_op_expr(self, o: 'mypy.nodes.OpExpr') -> None:
|
1063 | # a + b when a and b are strings. (Can't use operator overloading
|
1064 | # because they're pointers.)
|
1065 | left_type = self.types[o.left]
|
1066 | right_type = self.types[o.right]
|
1067 |
|
1068 | # NOTE: Need GetCType to handle Optional[BigStr*] in ASDL schemas.
|
1069 | # Could tighten it up later.
|
1070 | left_ctype = GetCType(left_type)
|
1071 | right_ctype = GetCType(right_type)
|
1072 |
|
1073 | c_op = o.op
|
1074 | if left_ctype == right_ctype == 'int' and c_op == '//':
|
1075 | # integer division // -> /
|
1076 | c_op = '/'
|
1077 |
|
1078 | # 'abc' + 'def'
|
1079 | if left_ctype == right_ctype == 'BigStr*' and c_op == '+':
|
1080 | self.write('str_concat(')
|
1081 | self.accept(o.left)
|
1082 | self.write(', ')
|
1083 | self.accept(o.right)
|
1084 | self.write(')')
|
1085 | return
|
1086 |
|
1087 | # 'abc' * 3
|
1088 | if left_ctype == 'BigStr*' and right_ctype == 'int' and c_op == '*':
|
1089 | self.write('str_repeat(')
|
1090 | self.accept(o.left)
|
1091 | self.write(', ')
|
1092 | self.accept(o.right)
|
1093 | self.write(')')
|
1094 | return
|
1095 |
|
1096 | # [None] * 3 => list_repeat(None, 3)
|
1097 | if (left_ctype.startswith('List<') and right_ctype == 'int' and
|
1098 | c_op == '*'):
|
1099 | self.write('list_repeat(')
|
1100 | self.accept(o.left.items[0])
|
1101 | self.write(', ')
|
1102 | self.accept(o.right)
|
1103 | self.write(')')
|
1104 | return
|
1105 |
|
1106 | # These parens are sometimes extra, but sometimes required. Example:
|
1107 | #
|
1108 | # if ((a and (false or true))) { # right
|
1109 | # vs.
|
1110 | # if (a and false or true)) { # wrong
|
1111 | self.write('(')
|
1112 | self.accept(o.left)
|
1113 | self.write(' %s ', c_op)
|
1114 | self.accept(o.right)
|
1115 | self.write(')')
|
1116 |
|
1117 | def visit_comparison_expr(self, o: 'mypy.nodes.ComparisonExpr') -> None:
|
1118 | # Make sure it's binary
|
1119 | assert len(o.operators) == 1, o.operators
|
1120 | assert len(o.operands) == 2, o.operands
|
1121 |
|
1122 | operator = o.operators[0]
|
1123 | left = o.operands[0]
|
1124 | right = o.operands[1]
|
1125 |
|
1126 | # Assume is and is not are for None / nullptr comparison.
|
1127 | if operator == 'is': # foo is None => foo == nullptr
|
1128 | self.accept(o.operands[0])
|
1129 | self.write(' == ')
|
1130 | self.accept(o.operands[1])
|
1131 | return
|
1132 |
|
1133 | if operator == 'is not': # foo is not None => foo != nullptr
|
1134 | self.accept(o.operands[0])
|
1135 | self.write(' != ')
|
1136 | self.accept(o.operands[1])
|
1137 | return
|
1138 |
|
1139 | t0 = self.types[left]
|
1140 | t1 = self.types[right]
|
1141 |
|
1142 | # 0: not a special case
|
1143 | # 1: str
|
1144 | # 2: Optional[str] which is Union[str, None]
|
1145 | left_type_i = 0 # not a special case
|
1146 | right_type_i = 0 # not a special case
|
1147 |
|
1148 | if util.IsStr(t0):
|
1149 | left_type_i = 1
|
1150 | elif (isinstance(t0, UnionType) and len(t0.items) == 2 and
|
1151 | util.IsStr(t0.items[0]) and isinstance(t0.items[1], NoneTyp)):
|
1152 | left_type_i = 2
|
1153 |
|
1154 | if util.IsStr(t1):
|
1155 | right_type_i = 1
|
1156 | elif (isinstance(t1, UnionType) and len(t1.items) == 2 and
|
1157 | util.IsStr(t1.items[0]) and isinstance(t1.items[1], NoneTyp)):
|
1158 | right_type_i = 2
|
1159 |
|
1160 | #self.log('left_type_i %s right_type_i %s', left_type, right_type)
|
1161 |
|
1162 | if left_type_i > 0 and right_type_i > 0 and operator in ('==', '!='):
|
1163 | if operator == '!=':
|
1164 | self.write('!(')
|
1165 |
|
1166 | # NOTE: This could also be str_equals(left, right)? Does it make a
|
1167 | # difference?
|
1168 | if left_type_i > 1 or right_type_i > 1:
|
1169 | self.write('maybe_str_equals(')
|
1170 | else:
|
1171 | self.write('str_equals(')
|
1172 | self.accept(left)
|
1173 | self.write(', ')
|
1174 | self.accept(right)
|
1175 | self.write(')')
|
1176 |
|
1177 | if operator == '!=':
|
1178 | self.write(')')
|
1179 | return
|
1180 |
|
1181 | # Note: we could get rid of this altogether and rely on C++ function
|
1182 | # overloading. But somehow I like it more explicit, closer to C (even
|
1183 | # though we use templates).
|
1184 | contains_func = _ContainsFunc(t1)
|
1185 |
|
1186 | if operator == 'in':
|
1187 | if isinstance(right, TupleExpr):
|
1188 | left_type = self.types[left]
|
1189 |
|
1190 | equals_func = _EqualsFunc(left_type)
|
1191 |
|
1192 | # x in (1, 2, 3) => (x == 1 || x == 2 || x == 3)
|
1193 | self.write('(')
|
1194 |
|
1195 | for i, item in enumerate(right.items):
|
1196 | if i != 0:
|
1197 | self.write(' || ')
|
1198 |
|
1199 | if equals_func:
|
1200 | self.write('%s(' % equals_func)
|
1201 | self.accept(left)
|
1202 | self.write(', ')
|
1203 | self.accept(item)
|
1204 | self.write(')')
|
1205 | else:
|
1206 | self.accept(left)
|
1207 | self.write(' == ')
|
1208 | self.accept(item)
|
1209 |
|
1210 | self.write(')')
|
1211 | return
|
1212 |
|
1213 | assert contains_func, "RHS of 'in' has type %r" % t1
|
1214 | # x in mylist => list_contains(mylist, x)
|
1215 | self.write('%s(', contains_func)
|
1216 | self.accept(right)
|
1217 | self.write(', ')
|
1218 | self.accept(left)
|
1219 | self.write(')')
|
1220 | return
|
1221 |
|
1222 | if operator == 'not in':
|
1223 | if isinstance(right, TupleExpr):
|
1224 | left_type = self.types[left]
|
1225 | equals_func = _EqualsFunc(left_type)
|
1226 |
|
1227 | # x not in (1, 2, 3) => (x != 1 && x != 2 && x != 3)
|
1228 | self.write('(')
|
1229 |
|
1230 | for i, item in enumerate(right.items):
|
1231 | if i != 0:
|
1232 | self.write(' && ')
|
1233 |
|
1234 | if equals_func:
|
1235 | self.write('!%s(' % equals_func)
|
1236 | self.accept(left)
|
1237 | self.write(', ')
|
1238 | self.accept(item)
|
1239 | self.write(')')
|
1240 | else:
|
1241 | self.accept(left)
|
1242 | self.write(' != ')
|
1243 | self.accept(item)
|
1244 |
|
1245 | self.write(')')
|
1246 | return
|
1247 |
|
1248 | assert contains_func, t1
|
1249 |
|
1250 | # x not in mylist => !list_contains(mylist, x)
|
1251 | self.write('!%s(', contains_func)
|
1252 | self.accept(right)
|
1253 | self.write(', ')
|
1254 | self.accept(left)
|
1255 | self.write(')')
|
1256 | return
|
1257 |
|
1258 | # Default case
|
1259 | self.accept(o.operands[0])
|
1260 | self.write(' %s ', o.operators[0])
|
1261 | self.accept(o.operands[1])
|
1262 |
|
1263 | def _WriteListElements(self,
|
1264 | items: List[Expression],
|
1265 | sep: str = ', ') -> None:
|
1266 | # sep may be 'COMMA' for a macro
|
1267 | self.write('{')
|
1268 | for i, item in enumerate(items):
|
1269 | if i != 0:
|
1270 | self.write(sep)
|
1271 | self.accept(item)
|
1272 | self.write('}')
|
1273 |
|
1274 | def visit_list_expr(self, o: 'mypy.nodes.ListExpr') -> None:
|
1275 | list_type = self.types[o]
|
1276 | # Note: need a lookup function that understands ListExpr -> Instance
|
1277 | assert isinstance(list_type, Instance), list_type
|
1278 |
|
1279 | #self.log('**** list_type = %s', list_type)
|
1280 | c_type = GetCType(list_type)
|
1281 |
|
1282 | item_type = list_type.args[0] # int for List[int]
|
1283 | item_c_type = GetCType(item_type)
|
1284 |
|
1285 | assert c_type.endswith('*'), c_type
|
1286 | c_type = c_type[:-1] # HACK TO CLEAN UP
|
1287 |
|
1288 | if len(o.items) == 0:
|
1289 | self.write('Alloc<%s>()' % c_type)
|
1290 | else:
|
1291 | self.write('NewList<%s>(std::initializer_list<%s>' %
|
1292 | (item_c_type, item_c_type))
|
1293 | self._WriteListElements(o.items)
|
1294 | self.write(')')
|
1295 |
|
1296 | def visit_dict_expr(self, o: 'mypy.nodes.DictExpr') -> None:
|
1297 | dict_type = self.types[o]
|
1298 | # Note: need a lookup function that understands DictExpr -> Instance
|
1299 | assert isinstance(dict_type, Instance), dict_type
|
1300 |
|
1301 | c_type = GetCType(dict_type)
|
1302 | assert c_type.endswith('*'), c_type
|
1303 | c_type = c_type[:-1] # HACK TO CLEAN UP
|
1304 |
|
1305 | key_type, val_type = dict_type.args
|
1306 | key_c_type = GetCType(key_type)
|
1307 | val_c_type = GetCType(val_type)
|
1308 |
|
1309 | self.write('Alloc<%s>(' % c_type)
|
1310 | #self.write('NewDict<%s, %s>(' % (key_c_type, val_c_type))
|
1311 | if o.items:
|
1312 | keys = [k for k, _ in o.items]
|
1313 | values = [v for _, v in o.items]
|
1314 |
|
1315 | self.write('std::initializer_list<%s>' % key_c_type)
|
1316 | self._WriteListElements(keys)
|
1317 | self.write(', ')
|
1318 |
|
1319 | self.write('std::initializer_list<%s>' % val_c_type)
|
1320 | self._WriteListElements(values)
|
1321 |
|
1322 | self.write(')')
|
1323 |
|
1324 | def visit_tuple_expr(self, o: 'mypy.nodes.TupleExpr') -> None:
|
1325 | tuple_type = self.types[o]
|
1326 | c_type = GetCType(tuple_type)
|
1327 | assert c_type.endswith('*'), c_type
|
1328 | c_type = c_type[:-1] # HACK TO CLEAN UP
|
1329 |
|
1330 | self.write('(Alloc<%s>(' % c_type)
|
1331 | for i, item in enumerate(o.items):
|
1332 | if i != 0:
|
1333 | self.write(', ')
|
1334 | self.accept(item)
|
1335 | self.write('))')
|
1336 |
|
1337 | def visit_index_expr(self, o: 'mypy.nodes.IndexExpr') -> None:
|
1338 | self.accept(o.base)
|
1339 |
|
1340 | #base_type = self.types[o.base]
|
1341 | #self.log('*** BASE TYPE %s', base_type)
|
1342 |
|
1343 | if isinstance(o.index, SliceExpr):
|
1344 | self.accept(o.index) # method call
|
1345 | else:
|
1346 | # it's hard syntactically to do (*a)[0], so do it this way.
|
1347 | if util.SMALL_STR:
|
1348 | self.write('.at(')
|
1349 | else:
|
1350 | self.write('->at(')
|
1351 |
|
1352 | self.accept(o.index)
|
1353 | self.write(')')
|
1354 |
|
1355 | def visit_slice_expr(self, o: 'mypy.nodes.SliceExpr') -> None:
|
1356 | self.write('->slice(')
|
1357 | if o.begin_index:
|
1358 | self.accept(o.begin_index)
|
1359 | else:
|
1360 | self.write('0') # implicit beginning
|
1361 |
|
1362 | if o.end_index:
|
1363 | self.write(', ')
|
1364 | self.accept(o.end_index)
|
1365 |
|
1366 | if o.stride:
|
1367 | if not o.begin_index or not o.end_index:
|
1368 | raise AssertionError(
|
1369 | 'Stride only supported with beginning and ending index')
|
1370 |
|
1371 | self.write(', ')
|
1372 | self.accept(o.stride)
|
1373 |
|
1374 | self.write(')')
|
1375 |
|
1376 | def visit_conditional_expr(self, o: 'mypy.nodes.ConditionalExpr') -> None:
|
1377 | if not _CheckCondition(o.cond, self.types):
|
1378 | self.report_error(
|
1379 | o,
|
1380 | "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict"
|
1381 | )
|
1382 | return
|
1383 |
|
1384 | # 0 if b else 1 -> b ? 0 : 1
|
1385 | self.accept(o.cond)
|
1386 | self.write(' ? ')
|
1387 | self.accept(o.if_expr)
|
1388 | self.write(' : ')
|
1389 | self.accept(o.else_expr)
|
1390 |
|
1391 | def _WriteTupleUnpacking(self,
|
1392 | temp_name: str,
|
1393 | lval_items: List[Expression],
|
1394 | item_types: List[Type],
|
1395 | is_return: bool = False) -> None:
|
1396 | """Used by assignment and for loops.
|
1397 |
|
1398 | is_return is a special case for:
|
1399 |
|
1400 | # return Tuple2<A, B> by VALUE, not Tuple2<A, B>* pointer
|
1401 | a, b = myfunc()
|
1402 | """
|
1403 | for i, (lval_item, item_type) in enumerate(zip(lval_items,
|
1404 | item_types)):
|
1405 | if isinstance(lval_item, NameExpr):
|
1406 | if util.SkipAssignment(lval_item.name):
|
1407 | continue
|
1408 | self.write_ind('%s', lval_item.name)
|
1409 | else:
|
1410 | # Could be MemberExpr like self.foo, self.bar = baz
|
1411 | self.write_ind('')
|
1412 | self.accept(lval_item)
|
1413 |
|
1414 | # Tuples that are return values aren't pointers
|
1415 | op = '.' if is_return else '->'
|
1416 | self.write(' = %s%sat%d();\n', temp_name, op, i) # RHS
|
1417 |
|
1418 | def _WriteTupleUnpackingInLoop(self, temp_name: str,
|
1419 | lval_items: List[Expression],
|
1420 | item_types: List[Type]) -> None:
|
1421 | for i, (lval_item, item_type) in enumerate(zip(lval_items,
|
1422 | item_types)):
|
1423 | c_item_type = GetCType(item_type)
|
1424 |
|
1425 | if isinstance(lval_item, NameExpr):
|
1426 | if util.SkipAssignment(lval_item.name):
|
1427 | continue
|
1428 |
|
1429 | self.write_ind('%s %s', c_item_type, lval_item.name)
|
1430 | else:
|
1431 | # Could be MemberExpr like self.foo, self.bar = baz
|
1432 | self.write_ind('')
|
1433 | self.accept(lval_item)
|
1434 |
|
1435 | op = '->'
|
1436 | self.write(' = %s%sat%d();\n', temp_name, op, i) # RHS
|
1437 |
|
1438 | # Note: it would be nice to eliminate these roots, just like
|
1439 | # StackRoots _for() below
|
1440 | if isinstance(lval_item, NameExpr):
|
1441 | if CTypeIsManaged(c_item_type) and not self.stack_roots:
|
1442 | self.write_ind('StackRoot _unpack_%d(&%s);\n' %
|
1443 | (i, lval_item.name))
|
1444 |
|
1445 | def _AssignNewDictImpl(self, lval: Expression, prefix: str = '') -> None:
|
1446 | """Translate NewDict() -> Alloc<Dict<K, V>>
|
1447 |
|
1448 | This function is a specal case because the RHS need TYPES from the LHS.
|
1449 |
|
1450 | e.g. here is how we make ORDERED dictionaries, which can't be done with {}:
|
1451 |
|
1452 | d = NewDict() # type: Dict[int, int]
|
1453 |
|
1454 | -> one of
|
1455 |
|
1456 | auto* d = Alloc<Dict<int, int>>(); # declare
|
1457 | d = Alloc<Dict<int, int>>(); # mutate
|
1458 |
|
1459 | We also have:
|
1460 |
|
1461 | self.d = NewDict()
|
1462 | ->
|
1463 | this->d = Alloc<Dict<int, int>)();
|
1464 | """
|
1465 | lval_type = self.types[lval]
|
1466 | #self.log('lval type %s', lval_type)
|
1467 |
|
1468 | # Fix for Dict[str, value]? in ASDL
|
1469 | if (isinstance(lval_type, UnionType) and len(lval_type.items) == 2 and
|
1470 | isinstance(lval_type.items[1], NoneTyp)):
|
1471 | lval_type = lval_type.items[0]
|
1472 |
|
1473 | c_type = GetCType(lval_type)
|
1474 | assert c_type.endswith('*')
|
1475 | self.write('Alloc<%s>()', c_type[:-1])
|
1476 |
|
1477 | def _AssignCastImpl(self, lval: Expression, rval: CallExpr) -> None:
|
1478 | """
|
1479 | is_downcast_and_shadow idiom:
|
1480 |
|
1481 | src = cast(source__SourcedFile, UP_src)
|
1482 | -> source__SourcedFile* src = static_cast<source__SourcedFile>(UP_src)
|
1483 | """
|
1484 | assert isinstance(lval, NameExpr)
|
1485 | type_expr = rval.args[0]
|
1486 | subtype_name = _GetCTypeForCast(type_expr)
|
1487 |
|
1488 | cast_kind = _GetCastKind(self.module_path, subtype_name)
|
1489 |
|
1490 | is_downcast_and_shadow = False
|
1491 | to_cast = rval.args[1]
|
1492 | if isinstance(to_cast, NameExpr):
|
1493 | if to_cast.name.startswith('UP_'):
|
1494 | is_downcast_and_shadow = True
|
1495 |
|
1496 | if is_downcast_and_shadow:
|
1497 | # Declare NEW local variable inside case, which shadows it
|
1498 | self.write_ind('%s %s = %s<%s>(', subtype_name, lval.name,
|
1499 | cast_kind, subtype_name)
|
1500 | else:
|
1501 | # Normal variable
|
1502 | self.write_ind('%s = %s<%s>(', lval.name, cast_kind, subtype_name)
|
1503 |
|
1504 | self.accept(rval.args[1]) # variable being casted
|
1505 | self.write(');\n')
|
1506 |
|
1507 | def _AssignToGenerator(self, o: 'mypy.nodes.AssignmentStmt',
|
1508 | lval: Expression, rval_type: Instance) -> None:
|
1509 | """
|
1510 | it_f = f(42)
|
1511 |
|
1512 | translates to
|
1513 |
|
1514 | List<int> _iter_buf_it;
|
1515 | f(42, &_iter_buf_it);
|
1516 | """
|
1517 | # We're calling a generator. Create a temporary List<T> on the stack
|
1518 | # to accumulate the results in one big batch, then wrap it in
|
1519 | # ListIter<T>.
|
1520 | assert len(rval_type.args) == 1, rval_type.args
|
1521 | c_type = GetCType(rval_type)
|
1522 |
|
1523 | type_param = rval_type.args[0]
|
1524 | inner_c_type = GetCType(type_param)
|
1525 |
|
1526 | assert isinstance(lval, NameExpr), lval
|
1527 | eager_list_name = 'YIELD_%s' % lval.name
|
1528 | eager_list_type = 'List<%s>*' % inner_c_type
|
1529 |
|
1530 | # write the variable to accumulate into
|
1531 | self.write_ind('List<%s> %s;\n', inner_c_type, eager_list_name)
|
1532 |
|
1533 | # AssignmentStmt key, like:
|
1534 | # it_f = f()
|
1535 | # maybe call them self.generator_func, generator_assign
|
1536 | # In MyPy, the type is Iterator though
|
1537 | self.yield_eager_assign[o] = (eager_list_name, eager_list_type)
|
1538 | self.write_ind('')
|
1539 |
|
1540 | self.yield_assign_node = o # AssignmentStmt
|
1541 | self.accept(o.rvalue)
|
1542 | self.yield_assign_node = None
|
1543 |
|
1544 | self.write(';\n')
|
1545 |
|
1546 | self.write_ind('%s %s(&%s);\n', c_type, lval.name, eager_list_name)
|
1547 |
|
1548 | def oils_visit_assign_to_listcomp(self, lval: NameExpr,
|
1549 | left_expr: Expression,
|
1550 | index_expr: Expression, seq: Expression,
|
1551 | cond: Expression) -> None:
|
1552 | """
|
1553 | Special case for list comprehensions. Note that the LHS MUST be on the
|
1554 | LHS, so we can append to it.
|
1555 |
|
1556 | y = [i+1 for i in x[1:] if i]
|
1557 | =>
|
1558 | y = []
|
1559 | for i in x[1:]:
|
1560 | if i:
|
1561 | y.append(i+1)
|
1562 | (but in C++)
|
1563 | """
|
1564 | self.write_ind('%s = ', lval.name)
|
1565 |
|
1566 | # BUG: can't use this to filter
|
1567 | # results = [x for x in results]
|
1568 | if isinstance(seq, NameExpr) and seq.name == lval.name:
|
1569 | raise AssertionError(
|
1570 | "Can't use var %r in list comprehension because it would "
|
1571 | "be overwritten" % lval.name)
|
1572 |
|
1573 | c_type = GetCType(self.types[lval])
|
1574 | # Write empty container as initialization.
|
1575 | assert c_type.endswith('*'), c_type # Hack
|
1576 | self.write('Alloc<%s>();\n' % c_type[:-1])
|
1577 |
|
1578 | over_type = self.types[seq]
|
1579 | assert isinstance(over_type, Instance), over_type
|
1580 |
|
1581 | if over_type.type.fullname == 'builtins.list':
|
1582 | c_type = GetCType(over_type)
|
1583 | # remove *
|
1584 | assert c_type.endswith('*'), c_type
|
1585 | c_iter_type = c_type.replace('List', 'ListIter', 1)[:-1]
|
1586 | else:
|
1587 | # List comprehension over dictionary not implemented
|
1588 | c_iter_type = 'TODO_DICT'
|
1589 |
|
1590 | self.write_ind('for (%s it(', c_iter_type)
|
1591 | self.accept(seq)
|
1592 | self.write('); !it.Done(); it.Next()) {\n')
|
1593 |
|
1594 | item_type = over_type.args[0] # get 'int' from 'List<int>'
|
1595 |
|
1596 | if isinstance(item_type, Instance):
|
1597 | self.write_ind(' %s ', GetCType(item_type))
|
1598 | # TODO(StackRoots): for ch in 'abc'
|
1599 | self.accept(index_expr)
|
1600 | self.write(' = it.Value();\n')
|
1601 |
|
1602 | elif isinstance(item_type, TupleType): # [x for x, y in pairs]
|
1603 | c_item_type = GetCType(item_type)
|
1604 |
|
1605 | if isinstance(index_expr, TupleExpr):
|
1606 | temp_name = 'tup%d' % self.unique_id
|
1607 | self.unique_id += 1
|
1608 | self.write_ind(' %s %s = it.Value();\n', c_item_type,
|
1609 | temp_name)
|
1610 |
|
1611 | self.indent += 1
|
1612 |
|
1613 | # list comp
|
1614 | self._WriteTupleUnpackingInLoop(temp_name, index_expr.items,
|
1615 | item_type.items)
|
1616 |
|
1617 | self.indent -= 1
|
1618 | else:
|
1619 | raise AssertionError()
|
1620 |
|
1621 | else:
|
1622 | raise AssertionError('Unexpected type %s' % item_type)
|
1623 |
|
1624 | if cond is not None:
|
1625 | self.indent += 1
|
1626 | self.write_ind('if (')
|
1627 | self.accept(cond)
|
1628 | self.write(') {\n')
|
1629 |
|
1630 | self.write_ind(' %s->append(', lval.name)
|
1631 | self.accept(left_expr)
|
1632 | self.write(');\n')
|
1633 |
|
1634 | if cond:
|
1635 | self.write_ind('}\n')
|
1636 | self.indent -= 1
|
1637 |
|
1638 | self.write_ind('}\n')
|
1639 |
|
1640 | def oils_visit_assignment_stmt(self, o: 'mypy.nodes.AssignmentStmt',
|
1641 | lval: Expression, rval: Expression) -> None:
|
1642 |
|
1643 | # GLOBAL CONSTANTS - Avoid Alloc<T>, since that can't be done until main().
|
1644 | if self.indent == 0:
|
1645 | assert isinstance(lval, NameExpr), lval
|
1646 | if util.SkipAssignment(lval.name):
|
1647 | return
|
1648 | #self.log(' GLOBAL: %s', lval.name)
|
1649 |
|
1650 | lval_type = self.types[lval]
|
1651 |
|
1652 | # Global
|
1653 | # L = [1, 2] # type: List[int]
|
1654 | if isinstance(rval, ListExpr):
|
1655 | assert isinstance(lval_type, Instance), lval_type
|
1656 |
|
1657 | item_type = lval_type.args[0]
|
1658 | item_c_type = GetCType(item_type)
|
1659 |
|
1660 | # Any constant strings will have already been written
|
1661 | # TODO: Assert that every item is a constant?
|
1662 | self.write('GLOBAL_LIST(%s, %s, %d, ', lval.name, item_c_type,
|
1663 | len(rval.items))
|
1664 |
|
1665 | self._WriteListElements(rval.items, sep=' COMMA ')
|
1666 |
|
1667 | self.write(');\n')
|
1668 | return
|
1669 |
|
1670 | # Global
|
1671 | # D = {"foo": "bar"} # type: Dict[str, str]
|
1672 | if isinstance(rval, DictExpr):
|
1673 | assert isinstance(lval_type, Instance), lval_type
|
1674 |
|
1675 | key_type, val_type = lval_type.args
|
1676 |
|
1677 | key_c_type = GetCType(key_type)
|
1678 | val_c_type = GetCType(val_type)
|
1679 |
|
1680 | dict_expr = rval
|
1681 | self.write('GLOBAL_DICT(%s, %s, %s, %d, ', lval.name,
|
1682 | key_c_type, val_c_type, len(dict_expr.items))
|
1683 |
|
1684 | keys = [k for k, _ in dict_expr.items]
|
1685 | values = [v for _, v in dict_expr.items]
|
1686 |
|
1687 | self._WriteListElements(keys, sep=' COMMA ')
|
1688 | self.write(', ')
|
1689 | self._WriteListElements(values, sep=' COMMA ')
|
1690 |
|
1691 | self.write(');\n')
|
1692 | return
|
1693 |
|
1694 | # We could do GcGlobal<> for ASDL classes, but Oils doesn't use them
|
1695 | if isinstance(rval, CallExpr):
|
1696 | self.report_error(
|
1697 | o,
|
1698 | "Can't initialize objects at the top level, only BigStr List Dict"
|
1699 | )
|
1700 | return
|
1701 |
|
1702 | # myconst = 1 << 3 => myconst = 1 << 3 is currently allowed
|
1703 |
|
1704 | #
|
1705 | # Non-top-level
|
1706 | #
|
1707 |
|
1708 | if isinstance(rval, CallExpr):
|
1709 | callee = rval.callee
|
1710 | callee_name = callee.name
|
1711 |
|
1712 | if callee_name == 'NewDict':
|
1713 | self.write_ind('')
|
1714 |
|
1715 | # Hack for non-members - why does this work?
|
1716 | # Tests cases in mycpp/examples/containers.py
|
1717 | if (not isinstance(lval, MemberExpr) and
|
1718 | self.current_func_node is None):
|
1719 | self.write('auto* ')
|
1720 |
|
1721 | self.accept(lval)
|
1722 | self.write(' = ')
|
1723 | self._AssignNewDictImpl(lval) # uses lval, not rval
|
1724 | self.write(';\n')
|
1725 | return
|
1726 |
|
1727 | if callee_name == 'cast':
|
1728 | self._AssignCastImpl(lval, rval)
|
1729 | return
|
1730 |
|
1731 | rval_type = self.types[rval]
|
1732 | if (isinstance(rval_type, Instance) and
|
1733 | rval_type.type.fullname == 'typing.Iterator'):
|
1734 | self._AssignToGenerator(o, lval, rval_type)
|
1735 | return
|
1736 |
|
1737 | if isinstance(lval, NameExpr):
|
1738 | lval_type = self.types[lval]
|
1739 | #c_type = GetCType(lval_type, local=self.indent != 0)
|
1740 | c_type = GetCType(lval_type)
|
1741 |
|
1742 | if self.at_global_scope:
|
1743 | # globals always get a type -- they're not mutated
|
1744 | self.write_ind('%s %s = ', c_type, lval.name)
|
1745 | else:
|
1746 | # local declarations are "hoisted" to the top of the function
|
1747 | self.write_ind('%s = ', lval.name)
|
1748 |
|
1749 | self.accept(rval)
|
1750 | self.write(';\n')
|
1751 | return
|
1752 |
|
1753 | if isinstance(lval, MemberExpr): # self.x = foo
|
1754 | self.write_ind('')
|
1755 | self.accept(lval)
|
1756 | self.write(' = ')
|
1757 | self.accept(rval)
|
1758 | self.write(';\n')
|
1759 | return
|
1760 |
|
1761 | if isinstance(lval, IndexExpr): # a[x] = 1
|
1762 | # d->set(x, 1) for both List and Dict
|
1763 | self.write_ind('')
|
1764 | self.accept(lval.base)
|
1765 | self.write('->set(')
|
1766 | self.accept(lval.index)
|
1767 | self.write(', ')
|
1768 | self.accept(rval)
|
1769 | self.write(');\n')
|
1770 | return
|
1771 |
|
1772 | if isinstance(lval, TupleExpr):
|
1773 | # An assignment to an n-tuple turns into n+1 statements. Example:
|
1774 | #
|
1775 | # x, y = mytuple
|
1776 | #
|
1777 | # Tuple2<int, BigStr*> tup1 = mytuple
|
1778 | # int x = tup1->at0()
|
1779 | # BigStr* y = tup1->at1()
|
1780 |
|
1781 | rvalue_type = self.types[rval]
|
1782 |
|
1783 | # type alias upgrade for MyPy 0.780
|
1784 | if isinstance(rvalue_type, TypeAliasType):
|
1785 | rvalue_type = rvalue_type.alias.target
|
1786 |
|
1787 | assert isinstance(rvalue_type, TupleType), rvalue_type
|
1788 |
|
1789 | c_type = GetCType(rvalue_type)
|
1790 |
|
1791 | is_return = (isinstance(rval, CallExpr) and
|
1792 | rval.callee.name != "next")
|
1793 | if is_return:
|
1794 | assert c_type.endswith('*')
|
1795 | c_type = c_type[:-1]
|
1796 |
|
1797 | temp_name = 'tup%d' % self.unique_id
|
1798 | self.unique_id += 1
|
1799 | self.write_ind('%s %s = ', c_type, temp_name)
|
1800 |
|
1801 | self.accept(rval)
|
1802 | self.write(';\n')
|
1803 |
|
1804 | # assignment
|
1805 | self._WriteTupleUnpacking(temp_name,
|
1806 | lval.items,
|
1807 | rvalue_type.items,
|
1808 | is_return=is_return)
|
1809 | return
|
1810 |
|
1811 | raise AssertionError(lval)
|
1812 |
|
1813 | def _WriteBody(self, body: List[Statement]) -> None:
|
1814 | """Write a block without the { }."""
|
1815 | for stmt in body:
|
1816 | self.accept(stmt)
|
1817 |
|
1818 | def oils_visit_for_stmt(self, o: 'mypy.nodes.ForStmt',
|
1819 | func_name: Optional[str]) -> None:
|
1820 | if 0:
|
1821 | self.log('ForStmt')
|
1822 | self.log(' index_type %s', o.index_type)
|
1823 | self.log(' inferred_item_type %s', o.inferred_item_type)
|
1824 | self.log(' inferred_iterator_type %s', o.inferred_iterator_type)
|
1825 |
|
1826 | if func_name:
|
1827 | assert isinstance(o.expr, CallExpr), o.expr # caller ensured it
|
1828 | args = o.expr.args
|
1829 |
|
1830 | # special case: 'for i in xrange(3)'
|
1831 | if func_name == 'xrange':
|
1832 | assert isinstance(o.index, NameExpr), o.index
|
1833 | index_name = o.index.name
|
1834 |
|
1835 | assert isinstance(o.expr, CallExpr), o.expr # caller ensured it
|
1836 | num_args = len(args)
|
1837 |
|
1838 | if num_args == 1: # xrange(end)
|
1839 | self.write_ind('for (int %s = 0; %s < ', index_name,
|
1840 | index_name)
|
1841 | self.accept(args[0])
|
1842 | self.write('; ++%s) ', index_name)
|
1843 |
|
1844 | elif num_args == 2: # xrange(being, end)
|
1845 | self.write_ind('for (int %s = ', index_name)
|
1846 | self.accept(args[0])
|
1847 | self.write('; %s < ', index_name)
|
1848 | self.accept(args[1])
|
1849 | self.write('; ++%s) ', index_name)
|
1850 |
|
1851 | elif num_args == 3: # xrange(being, end, step)
|
1852 | # Special case to detect a step of -1. This is a static
|
1853 | # heuristic, because it could be negative dynamically.
|
1854 | # TODO: could add an API like mylib.reverse_xrange()
|
1855 | step = args[2]
|
1856 | if isinstance(step, UnaryExpr) and step.op == '-':
|
1857 | comparison_op = '>'
|
1858 | else:
|
1859 | comparison_op = '<'
|
1860 |
|
1861 | self.write_ind('for (int %s = ', index_name)
|
1862 | self.accept(args[0])
|
1863 | self.write('; %s %s ', index_name, comparison_op)
|
1864 | self.accept(args[1])
|
1865 | self.write('; %s += ', index_name)
|
1866 | self.accept(step)
|
1867 | self.write(') ')
|
1868 |
|
1869 | else:
|
1870 | raise AssertionError()
|
1871 |
|
1872 | self.accept(o.body)
|
1873 | return
|
1874 |
|
1875 | reverse = False
|
1876 |
|
1877 | # for i, x in enumerate(...):
|
1878 | index0_name = None
|
1879 | if func_name == 'enumerate':
|
1880 | assert isinstance(o.index, TupleExpr), o.index
|
1881 | index0 = o.index.items[0]
|
1882 |
|
1883 | assert isinstance(index0, NameExpr), index0
|
1884 | index0_name = index0.name # generate int i = 0; ; ++i
|
1885 |
|
1886 | # Get type of 'x' in 'for i, x in enumerate(...)'
|
1887 | assert isinstance(o.inferred_item_type,
|
1888 | TupleType), o.inferred_item_type
|
1889 | item_type = o.inferred_item_type.items[1]
|
1890 | index_expr = o.index.items[1]
|
1891 |
|
1892 | assert isinstance(o.expr, CallExpr), o.expr # caller ensured
|
1893 | # enumerate(mylist) turns into iteration over mylist with variable i
|
1894 | assert len(args) == 1, args
|
1895 | iterated_over = args[0]
|
1896 |
|
1897 | elif func_name == 'reversed':
|
1898 | # NOTE: enumerate() and reversed() can't be mixed yet. But you CAN
|
1899 | # reverse iter over tuples.
|
1900 | item_type = o.inferred_item_type
|
1901 | index_expr = o.index
|
1902 |
|
1903 | assert len(args) == 1, args
|
1904 | iterated_over = args[0]
|
1905 |
|
1906 | reverse = True # use different iterate
|
1907 |
|
1908 | elif func_name == 'iteritems':
|
1909 | item_type = o.inferred_item_type
|
1910 | index_expr = o.index
|
1911 |
|
1912 | assert len(args) == 1, args
|
1913 | # This should be a dict
|
1914 | iterated_over = args[0]
|
1915 |
|
1916 | #log('------------ ITERITEMS OVER %s', iterated_over)
|
1917 |
|
1918 | else:
|
1919 | item_type = o.inferred_item_type
|
1920 | index_expr = o.index
|
1921 | iterated_over = o.expr
|
1922 |
|
1923 | over_type = self.types[iterated_over]
|
1924 |
|
1925 | if isinstance(over_type, TypeAliasType):
|
1926 | over_type = over_type.alias.target
|
1927 |
|
1928 | assert isinstance(over_type, Instance), over_type
|
1929 |
|
1930 | if 0:
|
1931 | log("***** OVER %s %s", over_type, dir(over_type))
|
1932 | t = over_type.type
|
1933 | log("***** t %s %s", t, dir(t))
|
1934 | bases = t.bases
|
1935 | # Look for string and dict!
|
1936 | log("=== bases %s %s", bases, dir(bases))
|
1937 |
|
1938 | #self.log(' iterating over type %s', over_type)
|
1939 | #self.log(' iterating over type %s', over_type.type.fullname)
|
1940 |
|
1941 | eager_list_name: Optional[str] = None
|
1942 |
|
1943 | over_list = False
|
1944 | over_dict = False
|
1945 |
|
1946 | if over_type.type.fullname == 'builtins.list':
|
1947 | over_list = True
|
1948 | container_base_type = over_type
|
1949 |
|
1950 | if over_type.type.fullname == 'builtins.dict':
|
1951 | over_dict = True
|
1952 | container_base_type = over_type
|
1953 |
|
1954 | # now check base classes
|
1955 | for base_type in over_type.type.bases:
|
1956 | n = base_type.type.fullname
|
1957 | if n == 'builtins.list':
|
1958 | over_list = True
|
1959 | container_base_type = base_type
|
1960 | elif n == 'builtins.dict':
|
1961 | over_dict = True
|
1962 | container_base_type = base_type
|
1963 |
|
1964 | assert not (over_dict and over_list)
|
1965 |
|
1966 | if over_list:
|
1967 | c_type = GetCType(over_type)
|
1968 | assert c_type.endswith('*'), c_type
|
1969 | inner_c_type = GetCType(container_base_type.args[0])
|
1970 | c_iter_type = 'ListIter<%s>' % inner_c_type
|
1971 |
|
1972 | # ReverseListIter!
|
1973 | if reverse:
|
1974 | c_iter_type = 'Reverse' + c_iter_type
|
1975 |
|
1976 | elif over_dict:
|
1977 | key_c_type = GetCType(container_base_type.args[0])
|
1978 | val_c_type = GetCType(container_base_type.args[1])
|
1979 | c_iter_type = 'DictIter<%s, %s>' % (key_c_type, val_c_type)
|
1980 | assert not reverse
|
1981 |
|
1982 | elif over_type.type.fullname == 'builtins.str':
|
1983 | c_iter_type = 'StrIter'
|
1984 | assert not reverse # can't reverse iterate over string yet
|
1985 |
|
1986 | elif over_type.type.fullname == 'typing.Iterator':
|
1987 | # We're iterating over a generator. Create a temporary List<T> on
|
1988 | # the stack to accumulate the results in one big batch.
|
1989 | c_iter_type = GetCType(over_type)
|
1990 |
|
1991 | assert len(over_type.args) == 1, over_type.args
|
1992 | inner_c_type = GetCType(over_type.args[0])
|
1993 |
|
1994 | # eager_list_name is used below
|
1995 | eager_list_name = 'YIELD_for_%d' % self.unique_id
|
1996 | eager_list_type = 'List<%s>*' % inner_c_type
|
1997 | self.unique_id += 1
|
1998 |
|
1999 | self.write_ind('List<%s> %s;\n', inner_c_type, eager_list_name)
|
2000 | self.write_ind('')
|
2001 |
|
2002 | # ForStmt - could be self.generator_for_stmt
|
2003 | #
|
2004 | # for x in my_generator(42):
|
2005 | # log('x = %s', x)
|
2006 | #
|
2007 | # Turns into
|
2008 | # List<T> _for_yield_acc3;
|
2009 | # my_generator(42, &_for_yield_acc3);
|
2010 | # for (ListIter it(_for_yield_acc3) ...)
|
2011 |
|
2012 | self.yield_eager_for[o] = (eager_list_name, eager_list_type)
|
2013 |
|
2014 | self.yield_for_node = o # ForStmt
|
2015 | self.accept(iterated_over)
|
2016 | self.yield_for_node = None
|
2017 |
|
2018 | self.write(';\n')
|
2019 |
|
2020 | else: # assume it's like d.iteritems()? Iterator type
|
2021 | assert False, over_type
|
2022 |
|
2023 | if index0_name:
|
2024 | # can't initialize two things in a for loop, so do it on a separate line
|
2025 | self.write_ind('%s = 0;\n', index0_name)
|
2026 | index_update = ', ++%s' % index0_name
|
2027 | else:
|
2028 | index_update = ''
|
2029 |
|
2030 | self.write_ind('for (%s it(', c_iter_type)
|
2031 | if eager_list_name:
|
2032 | self.write('&%s', eager_list_name)
|
2033 | else:
|
2034 | self.accept(iterated_over) # the thing being iterated over
|
2035 | self.write('); !it.Done(); it.Next()%s) {\n', index_update)
|
2036 |
|
2037 | # for x in it: ...
|
2038 | # for i, x in enumerate(pairs): ...
|
2039 |
|
2040 | if isinstance(item_type, Instance) or index0_name:
|
2041 | c_item_type = GetCType(item_type)
|
2042 | self.write_ind(' %s ', c_item_type)
|
2043 | self.accept(index_expr)
|
2044 | if over_dict:
|
2045 | self.write(' = it.Key();\n')
|
2046 | else:
|
2047 | self.write(' = it.Value();\n')
|
2048 |
|
2049 | # Register loop variable as a stack root.
|
2050 | # Note we have mylib.Collect() in CommandEvaluator::_Execute(), and
|
2051 | # it's called in a loop by _ExecuteList(). Although the 'child'
|
2052 | # variable is already live by other means.
|
2053 | # TODO: Test how much this affects performance.
|
2054 | if CTypeIsManaged(c_item_type) and not self.stack_roots:
|
2055 | self.write_ind(' StackRoot _for(&')
|
2056 | self.accept(index_expr)
|
2057 | self.write_ind(');\n')
|
2058 |
|
2059 | elif isinstance(item_type, TupleType): # for x, y in pairs
|
2060 | if over_dict:
|
2061 | assert isinstance(o.index, TupleExpr), o.index
|
2062 | index_items = o.index.items
|
2063 | assert len(index_items) == 2, index_items
|
2064 | assert len(item_type.items) == 2, item_type.items
|
2065 |
|
2066 | key_type = GetCType(item_type.items[0])
|
2067 | val_type = GetCType(item_type.items[1])
|
2068 |
|
2069 | #log('** %s key_type %s', item_type.items[0], key_type)
|
2070 | #log('** %s val_type %s', item_type.items[1], val_type)
|
2071 |
|
2072 | assert isinstance(index_items[0], NameExpr), index_items[0]
|
2073 | assert isinstance(index_items[1], NameExpr), index_items[1]
|
2074 |
|
2075 | # TODO(StackRoots): k, v
|
2076 | self.write_ind(' %s %s = it.Key();\n', key_type,
|
2077 | index_items[0].name)
|
2078 | self.write_ind(' %s %s = it.Value();\n', val_type,
|
2079 | index_items[1].name)
|
2080 |
|
2081 | else:
|
2082 | # Example:
|
2083 | # for (ListIter it(mylist); !it.Done(); it.Next()) {
|
2084 | # Tuple2<int, BigStr*> tup1 = it.Value();
|
2085 | # int i = tup1->at0();
|
2086 | # BigStr* s = tup1->at1();
|
2087 | # log("%d %s", i, s);
|
2088 | # }
|
2089 |
|
2090 | c_item_type = GetCType(item_type)
|
2091 |
|
2092 | if isinstance(o.index, TupleExpr):
|
2093 | # TODO(StackRoots)
|
2094 | temp_name = 'tup%d' % self.unique_id
|
2095 | self.unique_id += 1
|
2096 | self.write_ind(' %s %s = it.Value();\n', c_item_type,
|
2097 | temp_name)
|
2098 |
|
2099 | # loop - for x, y in other:
|
2100 | self.indent += 1
|
2101 | self._WriteTupleUnpackingInLoop(temp_name, o.index.items,
|
2102 | item_type.items)
|
2103 | self.indent -= 1
|
2104 |
|
2105 | elif isinstance(o.index, NameExpr):
|
2106 | self.write_ind(' %s %s = it.Value();\n', c_item_type,
|
2107 | o.index.name)
|
2108 | #self.write_ind(' StackRoots _for(&%s)\n;', o.index.name)
|
2109 |
|
2110 | else:
|
2111 | raise AssertionError()
|
2112 |
|
2113 | else:
|
2114 | raise AssertionError('Unexpected type %s' % item_type)
|
2115 |
|
2116 | # Copy of visit_block, without opening {
|
2117 | self.indent += 1
|
2118 | block = o.body
|
2119 | self._WriteBody(block.body)
|
2120 | self.indent -= 1
|
2121 | self.write_ind('}\n')
|
2122 |
|
2123 | if o.else_body:
|
2124 | raise AssertionError("can't translate for-else")
|
2125 |
|
2126 | def _WriteCases(self, switch_expr: Expression, cases: util.CaseList,
|
2127 | default_block: Union['mypy.nodes.Block', int]) -> None:
|
2128 | """ Write a list of (expr, block) pairs """
|
2129 |
|
2130 | for expr, body in cases:
|
2131 | assert expr is not None, expr
|
2132 | if not isinstance(expr, CallExpr):
|
2133 | self.report_error(expr,
|
2134 | 'Expected call like case(x), got %s' % expr)
|
2135 | return
|
2136 |
|
2137 | for i, arg in enumerate(expr.args):
|
2138 | if i != 0:
|
2139 | self.write('\n')
|
2140 | self.write_ind('case ')
|
2141 | self.accept(arg)
|
2142 | self.write(': ')
|
2143 |
|
2144 | self.accept(body)
|
2145 | self.write_ind(' break;\n')
|
2146 |
|
2147 | if default_block == -1:
|
2148 | # an error occurred
|
2149 | return
|
2150 | if default_block == -2:
|
2151 | # This is too restrictive
|
2152 | #self.report_error(switch_expr,
|
2153 | # 'switch got no else: for default block')
|
2154 | return
|
2155 |
|
2156 | # Narrow the type
|
2157 | assert not isinstance(default_block, int), default_block
|
2158 |
|
2159 | self.write_ind('default: ')
|
2160 | self.accept(default_block)
|
2161 | # don't write 'break'
|
2162 |
|
2163 | def _WriteSwitch(self, expr: CallExpr, o: 'mypy.nodes.WithStmt') -> None:
|
2164 | """Write a switch statement over integers."""
|
2165 | assert len(expr.args) == 1, expr.args
|
2166 |
|
2167 | self.write_ind('switch (')
|
2168 | self.accept(expr.args[0])
|
2169 | self.write(') {\n')
|
2170 |
|
2171 | assert len(o.body.body) == 1, o.body.body
|
2172 | if_node = o.body.body[0]
|
2173 | assert isinstance(if_node, IfStmt), if_node
|
2174 |
|
2175 | self.indent += 1
|
2176 | cases: util.CaseList = []
|
2177 | default_block = util.CollectSwitchCases(self.module_path,
|
2178 | if_node,
|
2179 | cases,
|
2180 | errors=self.errors_keep_going)
|
2181 | self._WriteCases(expr, cases, default_block)
|
2182 |
|
2183 | self.indent -= 1
|
2184 | self.write_ind('}\n')
|
2185 |
|
2186 | def _WriteTagSwitch(self, expr: CallExpr,
|
2187 | o: 'mypy.nodes.WithStmt') -> None:
|
2188 | """Write a switch statement over ASDL types."""
|
2189 | assert len(expr.args) == 1, expr.args
|
2190 |
|
2191 | self.write_ind('switch (')
|
2192 | self.accept(expr.args[0])
|
2193 | self.write('->tag()) {\n')
|
2194 |
|
2195 | assert len(o.body.body) == 1, o.body.body
|
2196 | if_node = o.body.body[0]
|
2197 | assert isinstance(if_node, IfStmt), if_node
|
2198 |
|
2199 | self.indent += 1
|
2200 | cases: util.CaseList = []
|
2201 | default_block = util.CollectSwitchCases(self.module_path,
|
2202 | if_node,
|
2203 | cases,
|
2204 | errors=self.errors_keep_going)
|
2205 | self._WriteCases(expr, cases, default_block)
|
2206 |
|
2207 | self.indent -= 1
|
2208 | self.write_ind('}\n')
|
2209 |
|
2210 | def _StrSwitchCases(self, cases: util.CaseList) -> Any:
|
2211 | cases2: List[Tuple[int, str, 'mypy.nodes.Block']] = []
|
2212 | for expr, body in cases:
|
2213 | if not isinstance(expr, CallExpr):
|
2214 | # non-fatal check from CollectSwitchCases
|
2215 | break
|
2216 |
|
2217 | args = expr.args
|
2218 | if len(args) != 1:
|
2219 | self.report_error(
|
2220 | expr,
|
2221 | 'str_switch can only have case("x"), not case("x", "y"): got %r'
|
2222 | % args)
|
2223 | break
|
2224 |
|
2225 | if not isinstance(args[0], StrExpr):
|
2226 | self.report_error(
|
2227 | expr,
|
2228 | 'str_switch can only be used with constant strings, got %s'
|
2229 | % args[0])
|
2230 | break
|
2231 |
|
2232 | s = args[0].value
|
2233 | cases2.append((len(s), s, body))
|
2234 |
|
2235 | # Sort by string length
|
2236 | cases2.sort(key=lambda pair: pair[0])
|
2237 | grouped = itertools.groupby(cases2, key=lambda pair: pair[0])
|
2238 | return grouped
|
2239 |
|
2240 | def _WriteStrSwitch(self, expr: CallExpr,
|
2241 | o: 'mypy.nodes.WithStmt') -> None:
|
2242 | """Write a switch statement over strings."""
|
2243 | assert len(expr.args) == 1, expr.args
|
2244 |
|
2245 | switch_expr = expr # for later error
|
2246 |
|
2247 | switch_var = expr.args[0]
|
2248 | if not isinstance(switch_var, NameExpr):
|
2249 | self.report_error(
|
2250 | expr.args[0],
|
2251 | 'str_switch(x) accepts only a variable name, got %s' %
|
2252 | switch_var)
|
2253 | return
|
2254 |
|
2255 | self.write_ind('switch (len(%s)) {\n' % switch_var.name)
|
2256 |
|
2257 | # There can only be one thing under 'with str_switch'
|
2258 | assert len(o.body.body) == 1, o.body.body
|
2259 | if_node = o.body.body[0]
|
2260 | assert isinstance(if_node, IfStmt), if_node
|
2261 |
|
2262 | self.indent += 1
|
2263 |
|
2264 | cases: util.CaseList = []
|
2265 | default_block = util.CollectSwitchCases(self.module_path,
|
2266 | if_node,
|
2267 | cases,
|
2268 | errors=self.errors_keep_going)
|
2269 |
|
2270 | grouped_cases = self._StrSwitchCases(cases)
|
2271 | # Warning: this consumes internal iterator
|
2272 | #self.log('grouped %s', list(grouped_cases))
|
2273 |
|
2274 | for str_len, group in grouped_cases:
|
2275 | self.write_ind('case %s: {\n' % str_len)
|
2276 | if_num = 0
|
2277 | for _, case_str, block in group:
|
2278 | self.indent += 1
|
2279 |
|
2280 | else_str = '' if if_num == 0 else 'else '
|
2281 | self.write_ind('%sif (str_equals_c(%s, %s, %d)) ' %
|
2282 | (else_str, switch_var.name,
|
2283 | PythonStringLiteral(case_str), str_len))
|
2284 | self.accept(block)
|
2285 |
|
2286 | self.indent -= 1
|
2287 | if_num += 1
|
2288 |
|
2289 | self.indent += 1
|
2290 | self.write_ind('else {\n')
|
2291 | self.write_ind(' goto str_switch_default;\n')
|
2292 | self.write_ind('}\n')
|
2293 | self.indent -= 1
|
2294 |
|
2295 | self.write_ind('}\n')
|
2296 | self.write_ind(' break;\n')
|
2297 |
|
2298 | if default_block == -1:
|
2299 | # an error occurred
|
2300 | return
|
2301 | if default_block == -2:
|
2302 | self.report_error(switch_expr,
|
2303 | 'str_switch got no else: for default block')
|
2304 | return
|
2305 |
|
2306 | # Narrow the type
|
2307 | assert not isinstance(default_block, int), default_block
|
2308 |
|
2309 | self.write('\n')
|
2310 | self.write_ind('str_switch_default:\n')
|
2311 | self.write_ind('default: ')
|
2312 | self.accept(default_block)
|
2313 |
|
2314 | self.indent -= 1
|
2315 | self.write_ind('}\n')
|
2316 |
|
2317 | def visit_with_stmt(self, o: 'mypy.nodes.WithStmt') -> None:
|
2318 | """
|
2319 | Translate only blocks of this form:
|
2320 |
|
2321 | with switch(x) as case:
|
2322 | if case(0):
|
2323 | print('zero')
|
2324 | elif case(1, 2, 3):
|
2325 | print('low')
|
2326 | else:
|
2327 | print('other')
|
2328 |
|
2329 | switch(x) {
|
2330 | case 0:
|
2331 | print('zero')
|
2332 | break;
|
2333 | case 1:
|
2334 | case 2:
|
2335 | case 3:
|
2336 | print('low')
|
2337 | break;
|
2338 | default:
|
2339 | print('other')
|
2340 | break;
|
2341 | }
|
2342 |
|
2343 | Or:
|
2344 |
|
2345 | with ctx_Bar(bar, x, y):
|
2346 | x()
|
2347 |
|
2348 | {
|
2349 | ctx_Bar(bar, x, y)
|
2350 | x();
|
2351 | }
|
2352 | """
|
2353 | #log('WITH')
|
2354 | #log('expr %s', o.expr)
|
2355 | #log('target %s', o.target)
|
2356 |
|
2357 | assert len(o.expr) == 1, o.expr
|
2358 | expr = o.expr[0]
|
2359 | assert isinstance(expr, CallExpr), expr
|
2360 |
|
2361 | # There is no 'with mylib.tagswitch(x)', only 'with tagswitch(x)'
|
2362 | # But we have with alloc.ctx_SourceCode
|
2363 | #assert isinstance(expr.callee, NameExpr), expr.callee
|
2364 |
|
2365 | callee_name = expr.callee.name
|
2366 | if callee_name == 'switch':
|
2367 | self._WriteSwitch(expr, o)
|
2368 | elif callee_name == 'str_switch':
|
2369 | self._WriteStrSwitch(expr, o)
|
2370 | elif callee_name == 'tagswitch':
|
2371 | self._WriteTagSwitch(expr, o)
|
2372 | else:
|
2373 | assert isinstance(expr, CallExpr), expr
|
2374 | self.write_ind('{ // with\n')
|
2375 | self.indent += 1
|
2376 |
|
2377 | self.write_ind('')
|
2378 | self.accept(expr.callee)
|
2379 |
|
2380 | # FIX: Use braced initialization to avoid most-vexing parse when
|
2381 | # there are 0 args!
|
2382 | self.write(' ctx{')
|
2383 | for i, arg in enumerate(expr.args):
|
2384 | if i != 0:
|
2385 | self.write(', ')
|
2386 | self.accept(arg)
|
2387 | self.write('};\n\n')
|
2388 |
|
2389 | self._WriteBody(o.body.body)
|
2390 |
|
2391 | self.indent -= 1
|
2392 | self.write_ind('}\n')
|
2393 |
|
2394 | def visit_del_stmt(self, o: 'mypy.nodes.DelStmt') -> None:
|
2395 |
|
2396 | d = o.expr
|
2397 | if isinstance(d, IndexExpr):
|
2398 | self.write_ind('')
|
2399 | self.accept(d.base)
|
2400 |
|
2401 | if isinstance(d.index, SliceExpr):
|
2402 | # del mylist[:] -> mylist->clear()
|
2403 |
|
2404 | sl = d.index
|
2405 | assert sl.begin_index is None, sl
|
2406 | assert sl.end_index is None, sl
|
2407 | self.write('->clear()')
|
2408 | else:
|
2409 | # del mydict[mykey] raises KeyError, which we don't want
|
2410 | raise AssertionError(
|
2411 | 'Use mylib.dict_erase(d, key) instead of del d[key]')
|
2412 |
|
2413 | self.write(';\n')
|
2414 |
|
2415 | def oils_visit_constructor(self, o: ClassDef, stmt: FuncDef,
|
2416 | base_class_sym: util.SymbolPath) -> None:
|
2417 | self.write('\n')
|
2418 | self.write('%s::%s(', o.name, o.name)
|
2419 | self._WriteFuncParams(stmt, write_defaults=False)
|
2420 | self.write(')')
|
2421 |
|
2422 | first_index = 0
|
2423 |
|
2424 | # Skip docstring
|
2425 | maybe_skip_stmt = stmt.body.body[0]
|
2426 | if (isinstance(maybe_skip_stmt, ExpressionStmt) and
|
2427 | isinstance(maybe_skip_stmt.expr, StrExpr)):
|
2428 | first_index += 1
|
2429 |
|
2430 | # Check for Base.__init__(self, ...) and move that to the initializer list.
|
2431 | first_stmt = stmt.body.body[first_index]
|
2432 | if (isinstance(first_stmt, ExpressionStmt) and
|
2433 | isinstance(first_stmt.expr, CallExpr)):
|
2434 | expr = first_stmt.expr
|
2435 | #log('expr %s', expr)
|
2436 | callee = first_stmt.expr.callee
|
2437 |
|
2438 | # TextOutput() : ColorOutput(f), ... {
|
2439 | if (isinstance(callee, MemberExpr) and callee.name == '__init__'):
|
2440 | base_constructor_args = expr.args
|
2441 | #log('ARGS %s', base_constructor_args)
|
2442 | self.write(' : %s(',
|
2443 | SymbolToString(base_class_sym, strip_package=True))
|
2444 | for i, arg in enumerate(base_constructor_args):
|
2445 | if i == 0:
|
2446 | continue # Skip 'this'
|
2447 | if i != 1:
|
2448 | self.write(', ')
|
2449 | self.accept(arg)
|
2450 | self.write(')')
|
2451 |
|
2452 | first_index += 1
|
2453 |
|
2454 | self.write(' {\n')
|
2455 |
|
2456 | # Now visit the rest of the statements
|
2457 | self.indent += 1
|
2458 |
|
2459 | if _IsContextManager(self.current_class_name):
|
2460 | # For ctx_* classes only, do gHeap.PushRoot() for all the pointer
|
2461 | # members
|
2462 | member_vars = self.all_member_vars[o]
|
2463 | for name in sorted(member_vars):
|
2464 | _, c_type, is_managed = member_vars[name]
|
2465 | if is_managed:
|
2466 | # VALIDATE_ROOTS doesn't complain even if it's not
|
2467 | # initialized? Should be initialized after PushRoot().
|
2468 | #self.write_ind('this->%s = nullptr;\n' % name)
|
2469 | self.write_ind(
|
2470 | 'gHeap.PushRoot(reinterpret_cast<RawObject**>(&(this->%s)));\n'
|
2471 | % name)
|
2472 |
|
2473 | for node in stmt.body.body[first_index:]:
|
2474 | self.accept(node)
|
2475 | self.indent -= 1
|
2476 | self.write('}\n')
|
2477 |
|
2478 | def _WritePopRoots(self, member_vars):
|
2479 | # gHeap.PopRoot() for all the pointer members
|
2480 |
|
2481 | for name in sorted(member_vars):
|
2482 | _, c_type, is_managed = member_vars[name]
|
2483 | if is_managed:
|
2484 | self.write_ind('gHeap.PopRoot();\n')
|
2485 |
|
2486 | def oils_visit_dunder_exit(self, o: ClassDef, stmt: FuncDef,
|
2487 | base_class_sym: util.SymbolPath) -> None:
|
2488 | self.write('\n')
|
2489 |
|
2490 | member_vars = self.all_member_vars[o]
|
2491 |
|
2492 | if o in self.dunder_exit_special: # EARLY RETURN from destructor
|
2493 | # Write ctx_exit()
|
2494 | self.write_ind('void %s::ctx_EXIT() {\n', o.name)
|
2495 | self.indent += 1
|
2496 | for node in stmt.body.body:
|
2497 | self.accept(node)
|
2498 | self.indent -= 1
|
2499 | self.write('}\n')
|
2500 | self.write('\n')
|
2501 |
|
2502 | # Write the actual destructor, which calls ctx_exit()
|
2503 | self.write_ind('%s::~%s() {\n', o.name, o.name)
|
2504 | self.indent += 1
|
2505 | self.write_ind('ctx_EXIT();\n')
|
2506 | self._WritePopRoots(member_vars)
|
2507 | self.indent -= 1
|
2508 | self.write('}\n')
|
2509 | else:
|
2510 | # Write as one function
|
2511 | self.write_ind('%s::~%s() {\n', o.name, o.name)
|
2512 |
|
2513 | self.indent += 1
|
2514 | for node in stmt.body.body:
|
2515 | self.accept(node)
|
2516 | self._WritePopRoots(member_vars)
|
2517 | self.indent -= 1
|
2518 |
|
2519 | self.write('}\n')
|
2520 |
|
2521 | def oils_visit_method(self, o: ClassDef, stmt: FuncDef,
|
2522 | base_class_sym: util.SymbolPath) -> None:
|
2523 | self.accept(stmt)
|
2524 |
|
2525 | # Module structure
|
2526 |
|
2527 | def visit_import(self, o: 'mypy.nodes.Import') -> None:
|
2528 | pass
|
2529 |
|
2530 | def visit_import_from(self, o: 'mypy.nodes.ImportFrom') -> None:
|
2531 | """
|
2532 | Write C++ namespace aliases and 'using' for imports.
|
2533 | We need them in the 'decl' phase for default arguments like
|
2534 | runtime_asdl::scope_e -> scope_e
|
2535 | """
|
2536 | if o.id in ('__future__', 'typing'):
|
2537 | return # do nothing
|
2538 |
|
2539 | for name, alias in o.names:
|
2540 | #self.log('ImportFrom id: %s name: %s alias: %s', o.id, name, alias)
|
2541 |
|
2542 | if name == 'log': # varargs translation
|
2543 | continue
|
2544 |
|
2545 | if o.id == 'mycpp.mylib':
|
2546 | # These mylib functions are translated in a special way
|
2547 | if name in ('switch', 'tagswitch', 'str_switch', 'iteritems',
|
2548 | 'NewDict', 'probe'):
|
2549 | continue
|
2550 | # STDIN_FILENO is #included
|
2551 | if name == 'STDIN_FILENO':
|
2552 | continue
|
2553 |
|
2554 | # A heuristic that works for the Oils import style.
|
2555 | if '.' in o.id:
|
2556 | # from mycpp.mylib import log => using mylib::log
|
2557 | translate_import = True
|
2558 | else:
|
2559 | # from core import util => NOT translated
|
2560 | # We just rely on 'util' being defined.
|
2561 | translate_import = False
|
2562 |
|
2563 | if translate_import:
|
2564 | dotted_parts = o.id.split('.')
|
2565 | last_dotted = dotted_parts[-1]
|
2566 |
|
2567 | # Omit these:
|
2568 | # from _gen.ysh import grammar_nt
|
2569 | if last_dotted == 'ysh':
|
2570 | return
|
2571 | # from _devbuild.gen import syntax_asdl
|
2572 | if last_dotted == 'gen':
|
2573 | return
|
2574 |
|
2575 | # Problem:
|
2576 | # - The decl stage has to return yaks_asdl::mod_def, so imports should go there
|
2577 | # - But if you change this to decl_write() instead of
|
2578 | # write(), you end up 'using error::e_usage' in say
|
2579 | # 'assign_osh', and it hasn't been defined yet.
|
2580 |
|
2581 | if alias:
|
2582 | # using runtime_asdl::emit_e = EMIT;
|
2583 | self.write_ind('using %s = %s::%s;\n', alias, last_dotted,
|
2584 | name)
|
2585 | else:
|
2586 | # from _devbuild.gen.id_kind_asdl import Id
|
2587 | # -> using id_kind_asdl::Id.
|
2588 | using_str = 'using %s::%s;\n' % (last_dotted, name)
|
2589 | self.write_ind(using_str)
|
2590 |
|
2591 | # Fully qualified:
|
2592 | # self.write_ind('using %s::%s;\n', '::'.join(dotted_parts), name)
|
2593 |
|
2594 | else:
|
2595 | # If we're importing a module without an alias, we don't need to do
|
2596 | # anything. 'namespace cmd_eval' is already defined.
|
2597 | if not alias:
|
2598 | return
|
2599 |
|
2600 | # from asdl import format as fmt
|
2601 | # -> namespace fmt = format;
|
2602 | self.write_ind('namespace %s = %s;\n', alias, name)
|
2603 |
|
2604 | # Statements
|
2605 |
|
2606 | def _WriteLocals(self, local_var_list: List[LocalVar]) -> None:
|
2607 | # TODO: put the pointers first, and then register a single StackRoots
|
2608 | # record.
|
2609 |
|
2610 | # Initialize local vars, e.g. to nullptr
|
2611 | done = set() # track duplicates? why?
|
2612 | for lval_name, lval_type, is_param in local_var_list:
|
2613 | c_type = GetCType(lval_type)
|
2614 | if not is_param and lval_name not in done:
|
2615 | if util.SMALL_STR and c_type == 'Str':
|
2616 | self.write_ind('%s %s(nullptr);\n', c_type, lval_name)
|
2617 | else:
|
2618 | rhs = ' = nullptr' if CTypeIsManaged(c_type) else ''
|
2619 | self.write_ind('%s %s%s;\n', c_type, lval_name, rhs)
|
2620 |
|
2621 | # TODO: we're not skipping the assignment, because of
|
2622 | # the RHS
|
2623 | if util.IsUnusedVar(lval_name):
|
2624 | # suppress C++ unused var compiler warnings!
|
2625 | self.write_ind('(void)%s;\n' % lval_name)
|
2626 |
|
2627 | done.add(lval_name)
|
2628 |
|
2629 | # Figure out if we have any roots to write with StackRoots
|
2630 | full_func_name = None
|
2631 | if self.current_func_node:
|
2632 | full_func_name = SplitPyName(self.current_func_node.fullname)
|
2633 |
|
2634 | roots = [] # keep it sorted
|
2635 | for lval_name, lval_type, is_param in local_var_list:
|
2636 | if lval_name in roots: # skip duplicates
|
2637 | continue
|
2638 |
|
2639 | #self.log('%s %s %s', lval_name, c_type, is_param)
|
2640 | c_type = GetCType(lval_type)
|
2641 | if not CTypeIsManaged(c_type):
|
2642 | continue
|
2643 |
|
2644 | # COUPLING to Oils - PNode are never GC objects. Instead we use
|
2645 | # PNodeAllocator in cpp/pgen2.h.
|
2646 | if c_type == 'pnode::PNode*':
|
2647 | #self.log('Not rooting PNode %s', lval_name)
|
2648 | continue
|
2649 |
|
2650 | if (not self.stack_roots or self.stack_roots.needs_root(
|
2651 | full_func_name, SplitPyName(lval_name))):
|
2652 | roots.append(lval_name)
|
2653 |
|
2654 | #self.log('roots %s', roots)
|
2655 |
|
2656 | if len(roots):
|
2657 | if (self.stack_roots_warn and len(roots) > self.stack_roots_warn):
|
2658 | log('WARNING: %s() has %d stack roots. Consider refactoring this function.'
|
2659 | % (self.current_func_node.fullname, len(roots)))
|
2660 |
|
2661 | for i, r in enumerate(roots):
|
2662 | self.write_ind('StackRoot _root%d(&%s);\n' % (i, r))
|
2663 |
|
2664 | self.write('\n')
|
2665 |
|
2666 | def visit_block(self, block: 'mypy.nodes.Block') -> None:
|
2667 | self.write('{\n') # not indented to use same line as while/if
|
2668 |
|
2669 | self.indent += 1
|
2670 | self._WriteBody(block.body)
|
2671 | self.indent -= 1
|
2672 |
|
2673 | self.write_ind('}\n')
|
2674 |
|
2675 | def oils_visit_expression_stmt(self,
|
2676 | o: 'mypy.nodes.ExpressionStmt') -> None:
|
2677 | self.write_ind('')
|
2678 | self.accept(o.expr)
|
2679 | self.write(';\n')
|
2680 |
|
2681 | def visit_operator_assignment_stmt(
|
2682 | self, o: 'mypy.nodes.OperatorAssignmentStmt') -> None:
|
2683 | self.write_ind('')
|
2684 | self.accept(o.lvalue)
|
2685 | self.write(' %s= ', o.op) # + to +=
|
2686 | self.accept(o.rvalue)
|
2687 | self.write(';\n')
|
2688 |
|
2689 | def visit_while_stmt(self, o: 'mypy.nodes.WhileStmt') -> None:
|
2690 | self.write_ind('while (')
|
2691 | self.accept(o.expr)
|
2692 | self.write(') ')
|
2693 | self.accept(o.body)
|
2694 |
|
2695 | def visit_return_stmt(self, o: 'mypy.nodes.ReturnStmt') -> None:
|
2696 | # Examples:
|
2697 | # return
|
2698 | # return None
|
2699 | # return my_int + 3;
|
2700 | self.write_ind('return ')
|
2701 | if o.expr:
|
2702 | if not (isinstance(o.expr, NameExpr) and o.expr.name == 'None'):
|
2703 |
|
2704 | # Note: the type of the return expression (self.types[o.expr])
|
2705 | # and the return type of the FUNCTION are different. Use the
|
2706 | # latter.
|
2707 | ret_type = self.current_func_node.type.ret_type
|
2708 |
|
2709 | c_ret_type, returning_tuple, _ = GetCReturnType(ret_type)
|
2710 |
|
2711 | # return '', None # tuple literal
|
2712 | # but NOT
|
2713 | # return tuple_func()
|
2714 | if returning_tuple and isinstance(o.expr, TupleExpr):
|
2715 | self.write('%s(' % c_ret_type)
|
2716 | for i, item in enumerate(o.expr.items):
|
2717 | if i != 0:
|
2718 | self.write(', ')
|
2719 | self.accept(item)
|
2720 | self.write(');\n')
|
2721 | return
|
2722 |
|
2723 | # Not returning tuple
|
2724 | self.accept(o.expr)
|
2725 |
|
2726 | self.write(';\n')
|
2727 |
|
2728 | def visit_if_stmt(self, o: 'mypy.nodes.IfStmt') -> None:
|
2729 | # Not sure why this wouldn't be true
|
2730 | assert len(o.expr) == 1, o.expr
|
2731 |
|
2732 | condition = o.expr[0]
|
2733 |
|
2734 | if not _CheckCondition(condition, self.types):
|
2735 | self.report_error(
|
2736 | o,
|
2737 | "Use explicit len(obj) or 'obj is not None' for mystr, mylist, mydict"
|
2738 | )
|
2739 | return
|
2740 |
|
2741 | if util.ShouldVisitIfExpr(o):
|
2742 | self.write_ind('if (')
|
2743 | for e in o.expr:
|
2744 | self.accept(e)
|
2745 | self.write(') ')
|
2746 |
|
2747 | if util.ShouldVisitIfBody(o):
|
2748 | cond = util.GetSpecialIfCondition(o)
|
2749 | if cond == 'CPP':
|
2750 | self.write_ind('// if MYCPP\n')
|
2751 | self.write_ind('')
|
2752 |
|
2753 | for body in o.body:
|
2754 | self.accept(body)
|
2755 |
|
2756 | if cond == 'CPP':
|
2757 | self.write_ind('// endif MYCPP\n')
|
2758 |
|
2759 | if util.ShouldVisitElseBody(o):
|
2760 | cond = util.GetSpecialIfCondition(o)
|
2761 | if cond == 'PYTHON':
|
2762 | self.write_ind('// if not PYTHON\n')
|
2763 | self.write_ind('')
|
2764 |
|
2765 | if util.ShouldVisitIfBody(o):
|
2766 | self.write_ind('else ')
|
2767 |
|
2768 | self.accept(o.else_body)
|
2769 |
|
2770 | if cond == 'PYTHON':
|
2771 | self.write_ind('// endif MYCPP\n')
|
2772 |
|
2773 | def visit_break_stmt(self, o: 'mypy.nodes.BreakStmt') -> None:
|
2774 | self.write_ind('break;\n')
|
2775 |
|
2776 | def visit_continue_stmt(self, o: 'mypy.nodes.ContinueStmt') -> None:
|
2777 | self.write_ind('continue;\n')
|
2778 |
|
2779 | def visit_pass_stmt(self, o: 'mypy.nodes.PassStmt') -> None:
|
2780 | self.write_ind('; // pass\n')
|
2781 |
|
2782 | def visit_raise_stmt(self, o: 'mypy.nodes.RaiseStmt') -> None:
|
2783 | to_raise = o.expr
|
2784 |
|
2785 | if to_raise:
|
2786 | if isinstance(to_raise, CallExpr) and isinstance(
|
2787 | to_raise.callee, NameExpr):
|
2788 | callee_name = to_raise.callee.name
|
2789 | if callee_name == 'AssertionError':
|
2790 | # C++ compiler is aware of assert(0) for unreachable code
|
2791 | self.write_ind('assert(0); // AssertionError\n')
|
2792 | return
|
2793 | if callee_name == 'NotImplementedError':
|
2794 | self.write_ind(
|
2795 | 'FAIL(kNotImplemented); // Python NotImplementedError\n'
|
2796 | )
|
2797 | return
|
2798 | self.write_ind('throw ')
|
2799 | self.accept(to_raise)
|
2800 | self.write(';\n')
|
2801 | else:
|
2802 | # raise without arg
|
2803 | self.write_ind('throw;\n')
|
2804 |
|
2805 | def visit_try_stmt(self, o: 'mypy.nodes.TryStmt') -> None:
|
2806 | self.write_ind('try ')
|
2807 | self.accept(o.body)
|
2808 | caught = False
|
2809 |
|
2810 | for t, v, handler in zip(o.types, o.vars, o.handlers):
|
2811 | c_type = None
|
2812 |
|
2813 | if isinstance(t, NameExpr):
|
2814 | if t.name in ('IOError', 'OSError'):
|
2815 | self.report_error(
|
2816 | handler,
|
2817 | 'Use except (IOError, OSError) rather than catching just one'
|
2818 | )
|
2819 | c_type = '%s*' % t.name
|
2820 |
|
2821 | elif isinstance(t, MemberExpr):
|
2822 | # We never use 'except foo.bar.T', only `foo.T'
|
2823 | assert isinstance(t.expr, NameExpr), t.expr
|
2824 | c_type = '%s::%s*' % (t.expr.name, t.name)
|
2825 |
|
2826 | elif isinstance(t, TupleExpr):
|
2827 | if len(t.items) == 2:
|
2828 | e1 = t.items[0]
|
2829 | e2 = t.items[1]
|
2830 | if isinstance(e1, NameExpr) and isinstance(e2, NameExpr):
|
2831 | names = [e1.name, e2.name]
|
2832 | names.sort()
|
2833 | if names == ['IOError', 'OSError']:
|
2834 | c_type = 'IOError_OSError*' # Base class in mylib
|
2835 |
|
2836 | else:
|
2837 | raise AssertionError()
|
2838 |
|
2839 | if c_type is None:
|
2840 | self.report_error(o, "try couldn't determine c_type")
|
2841 | return
|
2842 |
|
2843 | if v:
|
2844 | self.write_ind('catch (%s %s) ', c_type, v.name)
|
2845 | else:
|
2846 | self.write_ind('catch (%s) ', c_type)
|
2847 | self.accept(handler)
|
2848 |
|
2849 | caught = True
|
2850 |
|
2851 | if not caught:
|
2852 | self.report_error(o, 'try should have an except')
|
2853 |
|
2854 | if o.else_body:
|
2855 | self.report_error(o, 'try/else not supported')
|
2856 |
|
2857 | if o.finally_body:
|
2858 | self.report_error(o, 'try/finally not supported')
|