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