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))