github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-tools-src/grumpy_tools/compiler/block.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  """Classes for analyzing and storing the state of Python code blocks."""
    18  
    19  from __future__ import unicode_literals
    20  
    21  import abc
    22  import collections
    23  import re
    24  
    25  from grumpy_tools.compiler import expr
    26  from grumpy_tools.compiler import util
    27  from pythonparser import algorithm
    28  from pythonparser import ast
    29  from pythonparser import source
    30  
    31  
    32  _non_word_re = re.compile('[^A-Za-z0-9_]')
    33  
    34  
    35  class Package(object):
    36    """A Go package import."""
    37  
    38    def __init__(self, name, alias=None):
    39      self.name = name
    40        # Use Γ as a separator since it provides readability with a low
    41        # probability of name collisions.
    42      self.alias = alias or 'π_' + name.replace('/', 'Γ').replace('.', 'Γ')
    43  
    44  
    45  class Loop(object):
    46    """Represents a for or while loop within a particular block."""
    47  
    48    def __init__(self, breakvar):
    49      self.breakvar = breakvar
    50  
    51  
    52  class Block(object):
    53    """Represents a Python block such as a function or class definition."""
    54  
    55    __metaclass__ = abc.ABCMeta
    56  
    57    def __init__(self, parent, name):
    58      self.root = parent.root if parent else self
    59      self.parent = parent
    60      self.name = name
    61      self.free_temps = set()
    62      self.used_temps = set()
    63      self.temp_index = 0
    64      self.label_count = 0
    65      self.checkpoints = set()
    66      self.loop_stack = []
    67      self.is_generator = False
    68  
    69    @abc.abstractmethod
    70    def bind_var(self, writer, name, value):
    71      """Writes Go statements for assigning value to named var in this block.
    72  
    73      This is overridden in the different concrete block types since in Python,
    74      binding a variable in, e.g. a function is quite different than binding at
    75      global block.
    76  
    77      Args:
    78        writer: The Writer object where statements will be written.
    79        name: The name of the Python variable.
    80        value: A Go expression to assign to the variable.
    81      """
    82      pass
    83  
    84    @abc.abstractmethod
    85    def del_var(self, writer, name):
    86      pass
    87  
    88    @abc.abstractmethod
    89    def resolve_name(self, writer, name):
    90      """Returns a GeneratedExpr object for accessing the named var in this block.
    91  
    92      This is overridden in the different concrete block types since name
    93      resolution in Python behaves differently depending on where in what kind of
    94      block its happening within, e.g. local vars are different than globals.
    95  
    96      Args:
    97        writer: Writer object where intermediate calculations will be printed.
    98        name: The name of the Python variable.
    99      """
   100      pass
   101  
   102    def genlabel(self, is_checkpoint=False):
   103      self.label_count += 1
   104      if is_checkpoint:
   105        self.checkpoints.add(self.label_count)
   106      return self.label_count
   107  
   108    def alloc_temp(self, type_='*πg.Object'):
   109      """Create a new temporary Go variable having type type_ for this block."""
   110      for v in sorted(self.free_temps, key=lambda k: k.name):
   111        if v.type_ == type_:
   112          self.free_temps.remove(v)
   113          self.used_temps.add(v)
   114          return v
   115      self.temp_index += 1
   116      name = 'πTemp{:03d}'.format(self.temp_index)
   117      v = expr.GeneratedTempVar(self, name, type_)
   118      self.used_temps.add(v)
   119      return v
   120  
   121    def free_temp(self, v):
   122      """Release the GeneratedTempVar v so it can be reused."""
   123      self.used_temps.remove(v)
   124      self.free_temps.add(v)
   125  
   126    def push_loop(self, breakvar):
   127      loop = Loop(breakvar)
   128      self.loop_stack.append(loop)
   129      return loop
   130  
   131    def pop_loop(self):
   132      self.loop_stack.pop()
   133  
   134    def top_loop(self):
   135      return self.loop_stack[-1]
   136  
   137    def _resolve_global(self, writer, name):
   138      result = self.alloc_temp()
   139      writer.write_checked_call2(
   140          result, 'πg.ResolveGlobal(πF, {})', self.root.intern(name))
   141      return result
   142  
   143  
   144  class ModuleBlock(Block):
   145    """Python block for a module."""
   146  
   147    def __init__(self, importer, full_package_name,
   148                 filename, src, future_features):
   149      Block.__init__(self, None, '<module>')
   150      self.importer = importer
   151      self.full_package_name = full_package_name
   152      self.filename = filename
   153      self.buffer = source.Buffer(src)
   154      self.strings = set()
   155      self.future_features = future_features
   156  
   157    def bind_var(self, writer, name, value):
   158      writer.write_checked_call1(
   159          'πF.Globals().SetItem(πF, {}.ToObject(), {})',
   160          self.intern(name), value)
   161  
   162    def del_var(self, writer, name):
   163      writer.write_checked_call1('πg.DelVar(πF, πF.Globals(), {})',
   164                                 self.intern(name))
   165  
   166    def resolve_name(self, writer, name):
   167      return self._resolve_global(writer, name)
   168  
   169    def intern(self, s):
   170      if len(s) > 64 or _non_word_re.search(s):
   171        return 'πg.NewStr({})'.format(util.go_str(s))
   172      self.strings.add(s)
   173      return 'ß' + s
   174  
   175  
   176  class ClassBlock(Block):
   177    """Python block for a class definition."""
   178  
   179    def __init__(self, parent, name, global_vars):
   180      Block.__init__(self, parent, name)
   181      self.global_vars = global_vars
   182  
   183    def bind_var(self, writer, name, value):
   184      if name in self.global_vars:
   185        return self.root.bind_var(writer, name, value)
   186      writer.write_checked_call1('πClass.SetItem(πF, {}.ToObject(), {})',
   187                                 self.root.intern(name), value)
   188  
   189    def del_var(self, writer, name):
   190      if name in self.global_vars:
   191        return self.root.del_var(writer, name)
   192      writer.write_checked_call1('πg.DelVar(πF, πClass, {})',
   193                                 self.root.intern(name))
   194  
   195    def resolve_name(self, writer, name):
   196      local = 'nil'
   197      if name not in self.global_vars:
   198        # Only look for a local in an outer block when name hasn't been declared
   199        # global in this block. If it has been declared global then we fallback
   200        # straight to the global dict.
   201        block = self.parent
   202        while not isinstance(block, ModuleBlock):
   203          if isinstance(block, FunctionBlock) and name in block.vars:
   204            var = block.vars[name]
   205            if var.type != Var.TYPE_GLOBAL:
   206              local = util.adjust_local_name(name)
   207            # When it is declared global, prefer it to anything in outer blocks.
   208            break
   209          block = block.parent
   210      result = self.alloc_temp()
   211      writer.write_checked_call2(
   212          result, 'πg.ResolveClass(πF, πClass, {}, {})',
   213          local, self.root.intern(name))
   214      return result
   215  
   216  
   217  class FunctionBlock(Block):
   218    """Python block for a function definition."""
   219  
   220    def __init__(self, parent, name, block_vars, is_generator):
   221      Block.__init__(self, parent, name)
   222      self.vars = block_vars
   223      self.parent = parent
   224      self.is_generator = is_generator
   225  
   226    def bind_var(self, writer, name, value):
   227      if self.vars[name].type == Var.TYPE_GLOBAL:
   228        return self.root.bind_var(writer, name, value)
   229      writer.write('{} = {}'.format(util.adjust_local_name(name), value))
   230  
   231    def del_var(self, writer, name):
   232      var = self.vars.get(name)
   233      if not var:
   234        raise util.ParseError(
   235            None, 'cannot delete nonexistent local: {}'.format(name))
   236      if var.type == Var.TYPE_GLOBAL:
   237        return self.root.del_var(writer, name)
   238      adjusted_name = util.adjust_local_name(name)
   239      # Resolve local first to ensure the variable is already bound.
   240      writer.write_checked_call1('πg.CheckLocal(πF, {}, {})',
   241                                 adjusted_name, util.go_str(name))
   242      writer.write('{} = πg.UnboundLocal'.format(adjusted_name))
   243  
   244    def resolve_name(self, writer, name):
   245      block = self
   246      while not isinstance(block, ModuleBlock):
   247        if isinstance(block, FunctionBlock):
   248          var = block.vars.get(name)
   249          if var:
   250            if var.type == Var.TYPE_GLOBAL:
   251              return self._resolve_global(writer, name)
   252            if var.type == Var.TYPE_TUPLE_PARAM:
   253              return expr.GeneratedLocalVar(name)
   254            writer.write_checked_call1('πg.CheckLocal(πF, {}, {})',
   255                                       util.adjust_local_name(name),
   256                                       util.go_str(name))
   257            return expr.GeneratedLocalVar(name)
   258        block = block.parent
   259      return self._resolve_global(writer, name)
   260  
   261  
   262  class Var(object):
   263    """A Python variable used within a particular block."""
   264  
   265    TYPE_LOCAL = 0
   266    TYPE_PARAM = 1
   267    TYPE_GLOBAL = 2
   268    TYPE_TUPLE_PARAM = 3
   269  
   270    def __init__(self, name, var_type, arg_index=None):
   271      self.name = name
   272      self.type = var_type
   273      if var_type == Var.TYPE_LOCAL:
   274        assert arg_index is None
   275        self.init_expr = 'πg.UnboundLocal'
   276      elif var_type == Var.TYPE_PARAM:
   277        assert arg_index is not None
   278        self.init_expr = 'πArgs[{}]'.format(arg_index)
   279      elif var_type == Var.TYPE_TUPLE_PARAM:
   280        assert arg_index is None
   281        self.init_expr = 'nil'
   282      else:
   283        assert arg_index is None
   284        self.init_expr = None
   285  
   286  
   287  class BlockVisitor(algorithm.Visitor):
   288    """Visits nodes in a function or class to determine block variables."""
   289  
   290    # pylint: disable=invalid-name,missing-docstring
   291  
   292    def __init__(self):
   293      self.vars = collections.OrderedDict()
   294  
   295    def visit_Assign(self, node):
   296      for target in node.targets:
   297        self._assign_target(target)
   298      self.visit(node.value)
   299  
   300    def visit_AugAssign(self, node):
   301      self._assign_target(node.target)
   302      self.visit(node.value)
   303  
   304    def visit_ClassDef(self, node):
   305      self._register_local(node.name)
   306  
   307    def visit_ExceptHandler(self, node):
   308      if node.name:
   309        self._register_local(node.name.id)
   310      self.generic_visit(node)
   311  
   312    def visit_For(self, node):
   313      self._assign_target(node.target)
   314      self.generic_visit(node)
   315  
   316    def visit_FunctionDef(self, node):
   317      # The function being defined is local to this block, i.e. is nested within
   318      # another function. Note that further nested symbols are not traversed
   319      # because we don't explicitly visit the function body.
   320      self._register_local(node.name)
   321  
   322    def visit_Global(self, node):
   323      for name in node.names:
   324        self._register_global(node, name)
   325  
   326    def visit_Import(self, node):
   327      for alias in node.names:
   328        self._register_local(alias.asname or alias.name.split('.')[0])
   329  
   330    def visit_ImportFrom(self, node):
   331      for alias in node.names:
   332        self._register_local(alias.asname or alias.name)
   333  
   334    def visit_With(self, node):
   335      for item in node.items:
   336        if item.optional_vars:
   337          self._assign_target(item.optional_vars)
   338      self.generic_visit(node)
   339  
   340    def _assign_target(self, target):
   341      if isinstance(target, ast.Name):
   342        self._register_local(target.id)
   343      elif isinstance(target, (ast.Tuple, ast.List)):
   344        for elt in target.elts:
   345          self._assign_target(elt)
   346  
   347    def _register_global(self, node, name):
   348      var = self.vars.get(name)
   349      if var:
   350        if var.type == Var.TYPE_PARAM:
   351          msg = "name '{}' is parameter and global"
   352          raise util.ParseError(node, msg.format(name))
   353        if var.type == Var.TYPE_LOCAL:
   354          msg = "name '{}' is used prior to global declaration"
   355          raise util.ParseError(node, msg.format(name))
   356      else:
   357        self.vars[name] = Var(name, Var.TYPE_GLOBAL)
   358  
   359    def _register_local(self, name):
   360      if not self.vars.get(name):
   361        self.vars[name] = Var(name, Var.TYPE_LOCAL)
   362  
   363  
   364  class FunctionBlockVisitor(BlockVisitor):
   365    """Visits function nodes to determine variables and generator state."""
   366  
   367    # pylint: disable=invalid-name,missing-docstring
   368  
   369    def __init__(self, node):
   370      BlockVisitor.__init__(self)
   371      self.is_generator = False
   372      node_args = node.args
   373      args = []
   374      for arg in node_args.args:
   375        if isinstance(arg, ast.Tuple):
   376          args.append(arg.elts)
   377        else:
   378          args.append(arg.arg)
   379       # args = [a.arg for a in node_args.args]
   380  
   381      if node_args.vararg:
   382        args.append(node_args.vararg.arg)
   383      if node_args.kwarg:
   384        args.append(node_args.kwarg.arg)
   385      for i, name in enumerate(args):
   386        if isinstance(name, list):
   387          arg_name = 'τ{}'.format(id(name))
   388          for el in name:
   389            self._parse_tuple(el, node)
   390          self.vars[arg_name] = Var(arg_name, Var.TYPE_PARAM, i)
   391        else:
   392          self._check_duplicate_args(name, node)
   393          self.vars[name] = Var(name, Var.TYPE_PARAM, arg_index=i)
   394  
   395    def _parse_tuple(self, el, node):
   396      if isinstance(el, ast.Tuple):
   397        for x in el.elts:
   398          self._parse_tuple(x, node)
   399      else:
   400        self._check_duplicate_args(el.arg, node)
   401        self.vars[el.arg] = Var(el.arg, Var.TYPE_TUPLE_PARAM)
   402  
   403    def _check_duplicate_args(self, name, node):
   404      if name in self.vars:
   405        msg = "duplicate argument '{}' in function definition".format(name)
   406        raise util.ParseError(node, msg)
   407  
   408    def visit_Yield(self, unused_node): # pylint: disable=unused-argument
   409      self.is_generator = True