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