github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-tools-src/grumpy_tools/compiler/stmt.py (about) 1 # coding=utf-8 2 3 # Copyright 2016 Google Inc. All Rights Reserved. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Visitor class for traversing Python statements.""" 18 19 from __future__ import unicode_literals 20 21 import string 22 import textwrap 23 from itertools import ifilter 24 25 from grumpy_tools.compiler import block 26 from grumpy_tools.compiler import expr 27 from grumpy_tools.compiler import expr_visitor 28 from grumpy_tools.compiler import imputil 29 from grumpy_tools.compiler import util 30 from pythonparser import algorithm 31 from pythonparser import ast 32 33 34 _NATIVE_TYPE_PREFIX = 'type_' 35 36 # Partial list of known vcs for go module import 37 # Full list can be found at https://golang.org/src/cmd/go/vcs.go 38 # TODO: Use official vcs.go module instead of partial list 39 _KNOWN_VCS = [ 40 'golang.org', 'github.com', 'bitbucket.org', 'git.apache.org', 41 'git.openstack.org', 'launchpad.net' 42 ] 43 44 _nil_expr = expr.nil_expr 45 46 47 class StatementVisitor(algorithm.Visitor): 48 """Outputs Go statements to a Writer for the given Python nodes.""" 49 50 # pylint: disable=invalid-name,missing-docstring 51 52 def __init__(self, block_, future_node=None): 53 self.block = block_ 54 self.future_node = future_node 55 self.writer = util.Writer() 56 self.expr_visitor = expr_visitor.ExprVisitor(self) 57 self._docstring = None 58 59 def generic_visit(self, node): 60 msg = 'node not yet implemented: {}'.format(type(node).__name__) 61 raise util.ParseError(node, msg) 62 63 def visit_expr(self, node): 64 # Collect the 1st module string as docstring (<module>.__doc__) 65 if self._docstring is None and isinstance(node, ast.Str): 66 if not isinstance(self.block, block.FunctionBlock): 67 self._assign_docstring(node) 68 return self.expr_visitor.visit(node) 69 70 def _assign_docstring(self, node): 71 self._docstring = node 72 doc_assign = ast.Assign(loc=node.loc, targets=[ast.Name(id='__doc__')], value=node) 73 self.visit_Assign(doc_assign) 74 75 def visit_Assert(self, node): 76 self._write_py_context(node.lineno) 77 # TODO: Only evaluate msg if cond is false. 78 with self.visit_expr(node.msg) if node.msg else _nil_expr as msg,\ 79 self.visit_expr(node.test) as cond: 80 self.writer.write_checked_call1( 81 'πg.Assert(πF, {}, {})', cond.expr, msg.expr) 82 83 def visit_AugAssign(self, node): 84 op_type = type(node.op) 85 if op_type not in StatementVisitor._AUG_ASSIGN_TEMPLATES: 86 fmt = 'augmented assignment op not implemented: {}' 87 raise util.ParseError(node, fmt.format(op_type.__name__)) 88 self._write_py_context(node.lineno) 89 with self.visit_expr(node.target) as target,\ 90 self.visit_expr(node.value) as value,\ 91 self.block.alloc_temp() as temp: 92 self.writer.write_checked_call2( 93 temp, StatementVisitor._AUG_ASSIGN_TEMPLATES[op_type], 94 lhs=target.expr, rhs=value.expr) 95 self._assign_target(node.target, temp.expr) 96 97 def visit_Assign(self, node): 98 self._write_py_context(node.lineno) 99 with self.visit_expr(node.value) as value: 100 for target in node.targets: 101 self._tie_target(target, value.expr) 102 103 def visit_Break(self, node): 104 if not self.block.loop_stack: 105 raise util.ParseError(node, "'break' not in loop") 106 self._write_py_context(node.lineno) 107 self.writer.write_tmpl(textwrap.dedent("""\ 108 $breakvar = true 109 continue"""), breakvar=self.block.top_loop().breakvar.name) 110 111 def visit_ClassDef(self, node): 112 # Since we only care about global vars, we end up throwing away the locals 113 # collected by BlockVisitor. But use it anyway since it buys us detection of 114 # assignment to vars that are later declared global. 115 block_visitor = block.BlockVisitor() 116 for child in node.body: 117 block_visitor.visit(child) 118 global_vars = {v.name for v in block_visitor.vars.values() 119 if v.type == block.Var.TYPE_GLOBAL} 120 # Visit all the statements inside body of the class definition. 121 body_visitor = StatementVisitor(block.ClassBlock( 122 self.block, node.name, global_vars), self.future_node) 123 # Indent so that the function body is aligned with the goto labels. 124 with body_visitor.writer.indent_block(): 125 body_visitor._visit_each(node.body) # pylint: disable=protected-access 126 127 self._write_py_context(node.lineno) 128 with self.block.alloc_temp('*πg.Dict') as cls, \ 129 self.block.alloc_temp() as mod_name, \ 130 self.block.alloc_temp('[]*πg.Object') as bases, \ 131 self.block.alloc_temp() as meta: 132 self.writer.write('{} = make([]*πg.Object, {})'.format( 133 bases.expr, len(node.bases))) 134 for i, b in enumerate(node.bases): 135 with self.visit_expr(b) as b: 136 self.writer.write('{}[{}] = {}'.format(bases.expr, i, b.expr)) 137 self.writer.write('{} = πg.NewDict()'.format(cls.name)) 138 self.writer.write_checked_call2( 139 mod_name, 'πF.Globals().GetItem(πF, {}.ToObject())', 140 self.block.root.intern('__name__')) 141 self.writer.write_checked_call1( 142 '{}.SetItem(πF, {}.ToObject(), {})', 143 cls.expr, self.block.root.intern('__module__'), mod_name.expr) 144 tmpl = textwrap.dedent(""" 145 _, πE = πg.NewCode($name, $filename, nil, 0, func(πF *πg.Frame, _ []*πg.Object) (*πg.Object, *πg.BaseException) { 146 \tπClass := $cls 147 \t_ = πClass""") 148 self.writer.write_tmpl(tmpl, name=util.go_str(node.name), 149 filename=util.go_str(self.block.root.filename), 150 cls=cls.expr) 151 with self.writer.indent_block(): 152 self.writer.write_temp_decls(body_visitor.block) 153 self.writer.write_block(body_visitor.block, 154 body_visitor.writer.getvalue()) 155 self.writer.write('return nil, nil') 156 tmpl = textwrap.dedent("""\ 157 }).Eval(πF, πF.Globals(), nil, nil) 158 if πE != nil { 159 \tcontinue 160 } 161 if $meta, πE = $cls.GetItem(πF, $metaclass_str.ToObject()); πE != nil { 162 \tcontinue 163 } 164 if $meta == nil { 165 \t$meta = πg.TypeType.ToObject() 166 }""") 167 self.writer.write_tmpl( 168 tmpl, meta=meta.name, cls=cls.expr, 169 metaclass_str=self.block.root.intern('__metaclass__')) 170 with self.block.alloc_temp() as type_: 171 type_expr = ('{}.Call(πF, []*πg.Object{{πg.NewStr({}).ToObject(), ' 172 'πg.NewTuple({}...).ToObject(), {}.ToObject()}}, nil)') 173 self.writer.write_checked_call2( 174 type_, type_expr, meta.expr, 175 util.go_str(node.name), bases.expr, cls.expr) 176 self.block.bind_var(self.writer, node.name, type_.expr) 177 178 def visit_Continue(self, node): 179 if not self.block.loop_stack: 180 raise util.ParseError(node, "'continue' not in loop") 181 self._write_py_context(node.lineno) 182 self.writer.write('continue') 183 184 def visit_Delete(self, node): 185 self._write_py_context(node.lineno) 186 for target in node.targets: 187 if isinstance(target, ast.Attribute): 188 with self.visit_expr(target.value) as t: 189 self.writer.write_checked_call1( 190 'πg.DelAttr(πF, {}, {})', t.expr, 191 self.block.root.intern(target.attr)) 192 elif isinstance(target, ast.Name): 193 self.block.del_var(self.writer, target.id) 194 elif isinstance(target, ast.Subscript): 195 with self.visit_expr(target.value) as t,\ 196 self.visit_expr(target.slice) as index: 197 self.writer.write_checked_call1('πg.DelItem(πF, {}, {})', 198 t.expr, index.expr) 199 else: 200 msg = 'del target not implemented: {}'.format(type(target).__name__) 201 raise util.ParseError(node, msg) 202 203 def visit_Exec(self, node): 204 self._write_py_context(node.lineno) 205 self.writer.write('πE = πF.RaiseType(πg.NotImplementedErrorType, "exec is not available on Grumpy. Maybe never be.")') 206 self.writer.write('continue') 207 208 def visit_Expr(self, node): 209 self._write_py_context(node.lineno) 210 self.visit_expr(node.value).free() 211 212 def visit_For(self, node): 213 with self.block.alloc_temp() as i: 214 with self.visit_expr(node.iter) as iter_expr: 215 self.writer.write_checked_call2(i, 'πg.Iter(πF, {})', iter_expr.expr) 216 def testfunc(testvar): 217 with self.block.alloc_temp() as n: 218 self.writer.write_tmpl(textwrap.dedent("""\ 219 if $n, πE = πg.Next(πF, $i); πE != nil { 220 \tisStop, exc := πg.IsInstance(πF, πE.ToObject(), πg.StopIterationType.ToObject()) 221 \tif exc != nil { 222 \t\tπE = exc 223 \t} else if isStop { 224 \t\tπE = nil 225 \t\tπF.RestoreExc(nil, nil) 226 \t} 227 \t$testvar = !isStop 228 } else { 229 \t$testvar = true"""), n=n.name, i=i.expr, testvar=testvar.name) 230 with self.writer.indent_block(): 231 self._tie_target(node.target, n.expr) 232 self.writer.write('}') 233 self._visit_loop(testfunc, node) 234 235 def visit_FunctionDef(self, node): 236 self._write_py_context(node.lineno + len(node.decorator_list)) 237 func = self.visit_function_inline(node) 238 self.block.bind_var(self.writer, node.name, func.expr) 239 240 # Docstring should be the 1st statement (expression), if exists 241 first_expr = node.body[0] 242 if isinstance(first_expr, ast.Expr) and isinstance(first_expr.value, ast.Str): 243 docstring = first_expr.value 244 doc_assign = ast.Assign( 245 loc=docstring.loc, 246 targets=[ast.Attribute(value=ast.Name(id=node.name), attr='__doc__')], 247 value=docstring, 248 ) 249 self.visit_Assign(doc_assign) 250 251 while node.decorator_list: 252 decorator = node.decorator_list.pop() 253 wrapped = ast.Name(id=node.name) 254 decorated = ast.Call(func=decorator, args=[wrapped], keywords=[], 255 starargs=None, kwargs=None) 256 target = ast.Assign(targets=[wrapped], value=decorated, loc=node.loc) 257 self.visit_Assign(target) 258 259 def visit_Global(self, node): 260 self._write_py_context(node.lineno) 261 262 def visit_If(self, node): 263 # Collect the nodes for each if/elif/else body and write the dispatching 264 # switch statement. 265 bodies = [] 266 # An elif clause is represented as a single If node within the orelse 267 # section of the previous If node. Thus this loop terminates once we are 268 # done all the elif clauses at which time the orelse var will contain the 269 # nodes (if any) for the else clause. 270 orelse = [node] 271 while len(orelse) == 1 and isinstance(orelse[0], ast.If): 272 ifnode = orelse[0] 273 with self.visit_expr(ifnode.test) as cond: 274 label = self.block.genlabel() 275 # We goto the body of the if statement instead of executing it inline 276 # because the body itself may be a goto target and Go does not support 277 # jumping to targets inside a block. 278 with self.block.alloc_temp('bool') as is_true: 279 self.writer.write_tmpl(textwrap.dedent("""\ 280 if $is_true, πE = πg.IsTrue(πF, $cond); πE != nil { 281 \tcontinue 282 } 283 if $is_true { 284 \tgoto Label$label 285 }"""), is_true=is_true.name, cond=cond.expr, label=label) 286 bodies.append((label, ifnode.body, ifnode.lineno)) 287 orelse = ifnode.orelse 288 default_label = end_label = self.block.genlabel() 289 if orelse: 290 end_label = self.block.genlabel() 291 # The else is not represented by ast and thus there is no lineno. 292 bodies.append((default_label, orelse, None)) 293 self.writer.write('goto Label{}'.format(default_label)) 294 # Write the body of each clause. 295 for label, body, lineno in bodies: 296 if lineno: 297 self._write_py_context(lineno) 298 self.writer.write_label(label) 299 self._visit_each(body) 300 self.writer.write('goto Label{}'.format(end_label)) 301 self.writer.write_label(end_label) 302 303 def visit_Import(self, node): 304 self._write_py_context(node.lineno) 305 for imp in self.block.root.importer.visit(node): 306 self._import_and_bind(imp) 307 308 def visit_ImportFrom(self, node): 309 self._write_py_context(node.lineno) 310 311 if node.module == '__future__' and node != self.future_node: 312 raise util.LateFutureError(node) 313 314 for imp in self.block.root.importer.visit(node): 315 self._import_and_bind(imp) 316 317 def visit_Module(self, node): 318 self._visit_each(node.body) 319 320 def visit_Pass(self, node): 321 self._write_py_context(node.lineno) 322 323 def visit_Print(self, node): 324 if self.block.root.future_features.print_function: 325 raise util.ParseError(node, 'syntax error (print is not a keyword)') 326 self._write_py_context(node.lineno) 327 with self.block.alloc_temp('[]*πg.Object') as args: 328 self.writer.write('{} = make([]*πg.Object, {})'.format( 329 args.expr, len(node.values))) 330 for i, v in enumerate(node.values): 331 with self.visit_expr(v) as arg: 332 self.writer.write('{}[{}] = {}'.format(args.expr, i, arg.expr)) 333 self.writer.write_checked_call1('πg.Print(πF, {}, {})', args.expr, 334 'true' if node.nl else 'false') 335 336 def visit_Raise(self, node): 337 with self.visit_expr(node.exc) if node.exc else _nil_expr as t,\ 338 self.visit_expr(node.inst) if node.inst else _nil_expr as inst,\ 339 self.visit_expr(node.tback) if node.tback else _nil_expr as tb: 340 if node.inst: 341 assert node.exc, 'raise had inst but no type' 342 if node.tback: 343 assert node.inst, 'raise had tback but no inst' 344 self._write_py_context(node.lineno) 345 self.writer.write('πE = πF.Raise({}, {}, {})'.format( 346 t.expr, inst.expr, tb.expr)) 347 self.writer.write('continue') 348 349 def visit_Return(self, node): 350 assert isinstance(self.block, block.FunctionBlock) 351 self._write_py_context(node.lineno) 352 if self.block.is_generator and node.value: 353 raise util.ParseError(node, 'returning a value in a generator function') 354 if node.value: 355 with self.visit_expr(node.value) as value: 356 self.writer.write('πR = {}'.format(value.expr)) 357 else: 358 self.writer.write('πR = πg.None') 359 self.writer.write('continue') 360 361 def visit_Try(self, node): 362 # The general structure generated by this method is shown below: 363 # 364 # checkpoints.Push(Except) 365 # <try body> 366 # Checkpoints.Pop() 367 # <else body> 368 # goto Finally 369 # Except: 370 # <dispatch table> 371 # Handler1: 372 # <handler 1 body> 373 # Checkpoints.Pop() // Finally 374 # goto Finally 375 # Handler2: 376 # <handler 2 body> 377 # Checkpoints.Pop() // Finally 378 # goto Finally 379 # ... 380 # Finally: 381 # <finally body> 382 # 383 # The dispatch table maps the current exception to the appropriate handler 384 # label according to the exception clauses. 385 386 # Write the try body. 387 self._write_py_context(node.lineno) 388 finally_label = self.block.genlabel(is_checkpoint=bool(node.finalbody)) 389 if node.finalbody: 390 self.writer.write('πF.PushCheckpoint({})'.format(finally_label)) 391 except_label = None 392 if node.handlers: 393 except_label = self.block.genlabel(is_checkpoint=True) 394 self.writer.write('πF.PushCheckpoint({})'.format(except_label)) 395 self._visit_each(node.body) 396 if except_label: 397 self.writer.write('πF.PopCheckpoint()') # except_label 398 if node.orelse: 399 self._visit_each(node.orelse) 400 if node.finalbody: 401 self.writer.write('πF.PopCheckpoint()') # finally_label 402 self.writer.write('goto Label{}'.format(finally_label)) 403 404 with self.block.alloc_temp('*πg.BaseException') as exc: 405 if except_label: 406 with self.block.alloc_temp('*πg.Traceback') as tb: 407 self.writer.write_label(except_label) 408 self.writer.write_tmpl(textwrap.dedent("""\ 409 if πE == nil { 410 continue 411 } 412 πE = nil 413 $exc, $tb = πF.ExcInfo()"""), exc=exc.expr, tb=tb.expr) 414 handler_labels = self._write_except_dispatcher( 415 exc.expr, tb.expr, node.handlers) 416 417 # Write the bodies of each of the except handlers. 418 for handler_label, except_node in zip(handler_labels, node.handlers): 419 self._write_except_block(handler_label, exc.expr, except_node) 420 if node.finalbody: 421 self.writer.write('πF.PopCheckpoint()') # finally_label 422 self.writer.write('goto Label{}'.format(finally_label)) 423 424 # Write the finally body. 425 self.writer.write_label(finally_label) 426 if node.finalbody: 427 with self.block.alloc_temp('*πg.Traceback') as tb: 428 self.writer.write('{}, {} = πF.RestoreExc(nil, nil)'.format( 429 exc.expr, tb.expr)) 430 self._visit_each(node.finalbody) 431 self.writer.write_tmpl(textwrap.dedent("""\ 432 if $exc != nil { 433 \tπE = πF.Raise($exc.ToObject(), nil, $tb.ToObject()) 434 \tcontinue 435 } 436 if πR != nil { 437 \tcontinue 438 }"""), exc=exc.expr, tb=tb.expr) 439 440 def visit_While(self, node): 441 self._write_py_context(node.lineno) 442 def testfunc(testvar): 443 with self.visit_expr(node.test) as cond: 444 self.writer.write_checked_call2( 445 testvar, 'πg.IsTrue(πF, {})', cond.expr) 446 self._visit_loop(testfunc, node) 447 448 def visit_With(self, node): 449 self._write_py_context(node.loc.line()) 450 451 # mgr := EXPR 452 ## TODO: Get a way to __enter__ everything using `with` statement instead 453 mgr_list = [self.visit_expr(item.context_expr).__enter__() for item in node.items] 454 exit_funcs = [self.block.alloc_temp().__enter__() for mgr in mgr_list] 455 values = [self.block.alloc_temp().__enter__() for mgr in mgr_list] 456 457 # The code here has a subtle twist: It gets the exit function attribute 458 # from the class, not from the object. This matches the pseudo code from 459 # PEP 343 exactly, and is very close to what CPython actually does. (The 460 # CPython implementation actually uses a special lookup which is performed 461 # on the object, but skips the instance dictionary: see ceval.c and 462 # lookup_maybe in typeobject.c.) 463 464 for exit_func in exit_funcs: 465 # exit := type(mgr).__exit__ 466 self.writer.write_checked_call2( 467 exit_func, 'πg.GetAttr(πF, {}.Type().ToObject(), {}, nil)', 468 mgr.expr, self.block.root.intern('__exit__')) 469 470 for value in values: 471 # value := type(mgr).__enter__(mgr) 472 self.writer.write_checked_call2( 473 value, 'πg.GetAttr(πF, {}.Type().ToObject(), {}, nil)', 474 mgr.expr, self.block.root.intern('__enter__')) 475 self.writer.write_checked_call2( 476 value, '{}.Call(πF, πg.Args{{{}}}, nil)', 477 value.expr, mgr.expr) 478 479 finally_label = self.block.genlabel(is_checkpoint=True) 480 self.writer.write('πF.PushCheckpoint({})'.format(finally_label)) 481 482 for item, value in zip(node.items, values): 483 if item.optional_vars: 484 self._tie_target(item.optional_vars, value.expr) 485 486 self._visit_each(node.body) 487 self.writer.write('πF.PopCheckpoint()') 488 self.writer.write_label(finally_label) 489 490 with self.block.alloc_temp() as swallow_exc,\ 491 self.block.alloc_temp('bool') as swallow_exc_bool,\ 492 self.block.alloc_temp('*πg.BaseException') as exc,\ 493 self.block.alloc_temp('*πg.Traceback') as tb,\ 494 self.block.alloc_temp('*πg.Type') as t: 495 # temp := exit(mgr, *sys.exec_info()) 496 tmpl = """\ 497 $exc, $tb = nil, nil 498 if πE != nil { 499 \t$exc, $tb = πF.ExcInfo() 500 } 501 if $exc != nil { 502 \t$t = $exc.Type() 503 \tif $swallow_exc, πE = $exit_func.Call(πF, πg.Args{$mgr, $t.ToObject(), $exc.ToObject(), $tb.ToObject()}, nil); πE != nil { 504 \t\tcontinue 505 \t} 506 } else { 507 \tif $swallow_exc, πE = $exit_func.Call(πF, πg.Args{$mgr, πg.None, πg.None, πg.None}, nil); πE != nil { 508 \t\tcontinue 509 \t} 510 } 511 """ 512 for exit_func in exit_funcs: 513 self.writer.write_tmpl( 514 textwrap.dedent(tmpl), exc=exc.expr, tb=tb.expr, t=t.name, 515 mgr=mgr.expr, exit_func=exit_func.expr, 516 swallow_exc=swallow_exc.name) 517 518 # if Exc != nil && swallow_exc != true { 519 # Raise(nil, nil) 520 # } 521 self.writer.write_checked_call2( 522 swallow_exc_bool, 'πg.IsTrue(πF, {})', swallow_exc.expr) 523 self.writer.write_tmpl(textwrap.dedent("""\ 524 if $exc != nil && $swallow_exc != true { 525 \tπE = πF.Raise(nil, nil, nil) 526 \tcontinue 527 } 528 if πR != nil { 529 \tcontinue 530 }"""), exc=exc.expr, swallow_exc=swallow_exc_bool.expr) 531 532 ## TODO: Call __exit__() properly on each manually __enter__()ed object 533 # for i in mgr_list + exit_funcs + values: 534 # i.__exit__() 535 536 def visit_function_inline(self, node): 537 """Returns an GeneratedExpr for a function with the given body.""" 538 # First pass collects the names of locals used in this function. Do this in 539 # a separate pass so that we know whether to resolve a name as a local or a 540 # global during the second pass. 541 func_visitor = block.FunctionBlockVisitor(node) 542 for child in node.body: 543 func_visitor.visit(child) 544 func_block = block.FunctionBlock(self.block, node.name, func_visitor.vars, 545 func_visitor.is_generator) 546 visitor = StatementVisitor(func_block, self.future_node) 547 548 for arg in node.args.args: 549 if isinstance(arg, ast.Tuple): 550 arg_name = 'τ{}'.format(id(arg.elts)) 551 with visitor.writer.indent_block(): 552 visitor._tie_target(arg, util.adjust_local_name(arg_name)) # pylint: disable=protected-access 553 554 # Indent so that the function body is aligned with the goto labels. 555 with visitor.writer.indent_block(): 556 visitor._visit_each(node.body) # pylint: disable=protected-access 557 558 result = self.block.alloc_temp() 559 with self.block.alloc_temp('[]πg.Param') as func_args: 560 args = node.args 561 argc = len(args.args) 562 self.writer.write('{} = make([]πg.Param, {})'.format( 563 func_args.expr, argc)) 564 # The list of defaults only contains args for which a default value is 565 # specified so pad it with None to make it the same length as args. 566 defaults = [None] * (argc - len(args.defaults)) + args.defaults 567 for i, (a, d) in enumerate(zip(args.args, defaults)): 568 with self.visit_expr(d) if d else expr.nil_expr as default: 569 if isinstance(a, ast.Tuple): 570 name = util.go_str('τ{}'.format(id(a.elts))) 571 else: 572 name = util.go_str(a.arg) 573 tmpl = '$args[$i] = πg.Param{Name: $name, Def: $default}' 574 self.writer.write_tmpl(tmpl, args=func_args.expr, i=i, 575 name=name, default=default.expr) 576 flags = [] 577 if args.vararg: 578 flags.append('πg.CodeFlagVarArg') 579 if args.kwarg: 580 flags.append('πg.CodeFlagKWArg') 581 # The function object gets written to a temporary writer because we need 582 # it as an expression that we subsequently bind to some variable. 583 self.writer.write_tmpl( 584 '$result = πg.NewFunction(πg.NewCode($name, $filename, $args, ' 585 '$flags, func(πF *πg.Frame, πArgs []*πg.Object) ' 586 '(*πg.Object, *πg.BaseException) {', 587 result=result.name, name=util.go_str(node.name), 588 filename=util.go_str(self.block.root.filename), args=func_args.expr, 589 flags=' | '.join(flags) if flags else 0) 590 with self.writer.indent_block(): 591 for var in func_block.vars.values(): 592 if var.type != block.Var.TYPE_GLOBAL: 593 fmt = 'var {0} *πg.Object = {1}; _ = {0}' 594 self.writer.write(fmt.format( 595 util.adjust_local_name(var.name), var.init_expr)) 596 self.writer.write_temp_decls(func_block) 597 self.writer.write('var πR *πg.Object; _ = πR') 598 self.writer.write('var πE *πg.BaseException; _ = πE') 599 if func_block.is_generator: 600 self.writer.write( 601 'return πg.NewGenerator(πF, func(πSent *πg.Object) ' 602 '(*πg.Object, *πg.BaseException) {') 603 with self.writer.indent_block(): 604 self.writer.write_block(func_block, visitor.writer.getvalue()) 605 self.writer.write('return nil, πE') 606 self.writer.write('}).ToObject(), nil') 607 else: 608 self.writer.write_block(func_block, visitor.writer.getvalue()) 609 self.writer.write(textwrap.dedent("""\ 610 if πE != nil { 611 \tπR = nil 612 } else if πR == nil { 613 \tπR = πg.None 614 } 615 return πR, πE""")) 616 self.writer.write('}), πF.Globals()).ToObject()') 617 return result 618 619 _AUG_ASSIGN_TEMPLATES = { 620 ast.Add: 'πg.IAdd(πF, {lhs}, {rhs})', 621 ast.BitAnd: 'πg.IAnd(πF, {lhs}, {rhs})', 622 ast.Div: 'πg.IDiv(πF, {lhs}, {rhs})', 623 ast.FloorDiv: 'πg.IFloorDiv(πF, {lhs}, {rhs})', 624 ast.LShift: 'πg.ILShift(πF, {lhs}, {rhs})', 625 ast.Mod: 'πg.IMod(πF, {lhs}, {rhs})', 626 ast.Mult: 'πg.IMul(πF, {lhs}, {rhs})', 627 ast.BitOr: 'πg.IOr(πF, {lhs}, {rhs})', 628 ast.Pow: 'πg.IPow(πF, {lhs}, {rhs})', 629 ast.RShift: 'πg.IRShift(πF, {lhs}, {rhs})', 630 ast.Sub: 'πg.ISub(πF, {lhs}, {rhs})', 631 ast.BitXor: 'πg.IXor(πF, {lhs}, {rhs})', 632 } 633 634 def _assign_target(self, target, value): 635 if isinstance(target, ast.Name): 636 self.block.bind_var(self.writer, target.id, value) 637 elif isinstance(target, ast.arg): 638 self.block.bind_var(self.writer, target.arg, value) 639 elif isinstance(target, ast.Attribute): 640 with self.visit_expr(target.value) as obj: 641 self.writer.write_checked_call1( 642 'πg.SetAttr(πF, {}, {}, {})', obj.expr, 643 self.block.root.intern(target.attr), value) 644 elif isinstance(target, ast.Subscript): 645 with self.visit_expr(target.value) as mapping,\ 646 self.visit_expr(target.slice) as index: 647 self.writer.write_checked_call1('πg.SetItem(πF, {}, {}, {})', 648 mapping.expr, index.expr, value) 649 else: 650 msg = 'assignment target not yet implemented: ' + type(target).__name__ 651 raise util.ParseError(target, msg) 652 653 def _build_assign_target(self, target, assigns): 654 if isinstance(target, (ast.Tuple, ast.List)): 655 children = [] 656 for elt in target.elts: 657 children.append(self._build_assign_target(elt, assigns)) 658 tmpl = 'πg.TieTarget{Children: []πg.TieTarget{$children}}' 659 return string.Template(tmpl).substitute(children=', '.join(children)) 660 temp = self.block.alloc_temp() 661 assigns.append((target, temp)) 662 tmpl = 'πg.TieTarget{Target: &$temp}' 663 return string.Template(tmpl).substitute(temp=temp.name) 664 665 def _import_and_bind(self, imp): 666 """Generates code that imports a module and binds it to a variable. 667 668 Args: 669 imp: Import object representing an import of the form "import x.y.z" or 670 "from x.y import z". Expects only a single binding. 671 """ 672 # Acquire handles to the Code objects in each Go package and call 673 # ImportModule to initialize all modules. 674 with self.block.alloc_temp() as mod, \ 675 self.block.alloc_temp('[]*πg.Object') as mod_slice: 676 self.writer.write_checked_call2( 677 mod_slice, 'πg.ImportModule(πF, {})', util.go_str(imp.name)) 678 679 # Bind the imported modules or members to variables in the current scope. 680 for binding in imp.bindings: 681 if binding.bind_type == imputil.Import.MODULE: 682 self.writer.write('{} = {}[{}]'.format( 683 mod.name, mod_slice.expr, binding.value)) 684 self.block.bind_var(self.writer, binding.alias, mod.expr) 685 elif binding.bind_type == imputil.Import.STAR: 686 self.writer.write_checked_call1('πg.LoadMembers(πF, {}[0])', mod_slice.name) 687 else: # Import.MEMBER 688 self.writer.write('{} = {}[{}]'.format( 689 mod.name, mod_slice.expr, imp.name.count('.'))) 690 # Binding a member of the imported module. 691 with self.block.alloc_temp() as member: 692 self.writer.write_checked_call2( 693 member, 'πg.GetAttrImport(πF, {}, {})', 694 mod.expr, self.block.root.intern(binding.value)) 695 self.block.bind_var(self.writer, binding.alias, member.expr) 696 697 def _tie_target(self, target, value): 698 if isinstance(target, ast.Name): 699 self._assign_target(target, value) 700 return 701 702 assigns = [] 703 self.writer.write_checked_call1( 704 'πg.Tie(πF, {}, {})', 705 self._build_assign_target(target, assigns), value) 706 for t, temp in assigns: 707 self._assign_target(t, temp.expr) 708 self.block.free_temp(temp) 709 710 def _visit_each(self, nodes): 711 for node in nodes: 712 self.visit(node) 713 714 def _visit_loop(self, testfunc, node): 715 start_label = self.block.genlabel(is_checkpoint=True) 716 else_label = self.block.genlabel(is_checkpoint=True) 717 end_label = self.block.genlabel() 718 with self.block.alloc_temp('bool') as breakvar: 719 self.block.push_loop(breakvar) 720 self.writer.write('πF.PushCheckpoint({})'.format(else_label)) 721 self.writer.write('{} = false'.format(breakvar.name)) 722 self.writer.write_label(start_label) 723 self.writer.write_tmpl(textwrap.dedent("""\ 724 if πE != nil || πR != nil { 725 \tcontinue 726 } 727 if $breakvar { 728 \tπF.PopCheckpoint() 729 \tgoto Label$end_label 730 }"""), breakvar=breakvar.expr, end_label=end_label) 731 with self.block.alloc_temp('bool') as testvar: 732 testfunc(testvar) 733 self.writer.write_tmpl(textwrap.dedent("""\ 734 if πE != nil || !$testvar { 735 \tcontinue 736 } 737 πF.PushCheckpoint($start_label)\ 738 """), testvar=testvar.name, start_label=start_label) 739 self._visit_each(node.body) 740 self.writer.write('continue') 741 # End the loop so that break applies to an outer loop if present. 742 self.block.pop_loop() 743 self.writer.write_label(else_label) 744 self.writer.write(textwrap.dedent("""\ 745 if πE != nil || πR != nil { 746 \tcontinue 747 }""")) 748 if node.orelse: 749 self._visit_each(node.orelse) 750 self.writer.write_label(end_label) 751 752 def _write_except_block(self, label, exc, except_node): 753 self._write_py_context(except_node.lineno) 754 self.writer.write_label(label) 755 if except_node.name: 756 self.block.bind_var(self.writer, except_node.name.id, 757 '{}.ToObject()'.format(exc)) 758 self._visit_each(except_node.body) 759 self.writer.write('πF.RestoreExc(nil, nil)') 760 761 def _write_except_dispatcher(self, exc, tb, handlers): 762 """Outputs a Go code that jumps to the appropriate except handler. 763 764 Args: 765 exc: Go variable holding the current exception. 766 tb: Go variable holding the current exception's traceback. 767 handlers: A list of ast.ExceptHandler nodes. 768 769 Returns: 770 A list of Go labels indexes corresponding to the exception handlers. 771 772 Raises: 773 ParseError: Except handlers are in an invalid order. 774 """ 775 handler_labels = [] 776 for i, except_node in enumerate(handlers): 777 handler_labels.append(self.block.genlabel()) 778 if except_node.type: 779 with self.visit_expr(except_node.type) as type_,\ 780 self.block.alloc_temp('bool') as is_inst: 781 self.writer.write_checked_call2( 782 is_inst, 'πg.IsInstance(πF, {}.ToObject(), {})', exc, type_.expr) 783 self.writer.write_tmpl(textwrap.dedent("""\ 784 if $is_inst { 785 \tgoto Label$label 786 }"""), is_inst=is_inst.expr, label=handler_labels[-1]) 787 else: 788 # This is a bare except. It should be the last handler. 789 if i != len(handlers) - 1: 790 msg = "default 'except:' must be last" 791 raise util.ParseError(except_node, msg) 792 self.writer.write('goto Label{}'.format(handler_labels[-1])) 793 if handlers[-1].type: 794 # There's no bare except, so the fallback is to re-raise. 795 self.writer.write( 796 'πE = πF.Raise({}.ToObject(), nil, {}.ToObject())'.format(exc, tb)) 797 self.writer.write('continue') 798 return handler_labels 799 800 def _write_py_context(self, lineno): 801 if lineno: 802 line = self.block.root.buffer.source_line(lineno).strip() 803 self.writer.write('// line {}: {}'.format(lineno, line)) 804 self.writer.write('πF.SetLineno({})'.format(lineno))