1# Copyright 2019 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4""" 5The code generator generates code based on a graph of code fragments. Each node 6of the graph is represented with CodeNode and its subclasses. This module 7provides a collection of the classes that represent code nodes independent from 8specific bindings, such as ECMAScript bindings. 9""" 10 11from .codegen_accumulator import CodeGenAccumulator 12from .codegen_format import format_template 13from .mako_renderer import MakoRenderer 14from .mako_renderer import MakoTemplate 15 16 17def render_code_node(code_node): 18 """ 19 Renders |code_node| and turns it into text letting |code_node| apply all 20 necessary changes (side effects). Returns the resulting text. 21 """ 22 assert isinstance(code_node, CodeNode) 23 assert code_node.outer is None 24 25 renderer = code_node.renderer 26 accumulator = code_node.accumulator 27 28 accumulated_size = accumulator.total_size() 29 while True: 30 prev_accumulated_size = accumulated_size 31 renderer.reset() 32 code_node.render(renderer) 33 accumulated_size = accumulator.total_size() 34 if (renderer.is_rendering_complete() 35 and accumulated_size == prev_accumulated_size): 36 break 37 38 return renderer.to_text() 39 40 41class Likeliness(object): 42 """ 43 Represents how much likely a code node will be executed. 44 45 Used in SymbolScopeNode in order to determine where SymbolDefinitionNodes 46 should be inserted. Likeliness level can change only at SymbolScopeNode. 47 48 Relational operators are supported, and it's guaranteed to be: 49 NEVER < UNLIKELY < LIKELY < ALWAYS 50 """ 51 52 class Level(int): 53 pass 54 55 NEVER = Level(0) 56 UNLIKELY = Level(1) 57 LIKELY = Level(2) 58 ALWAYS = Level(3) 59 60 61class CodeNode(object): 62 """ 63 This is the base class of all code fragment nodes. 64 65 - Graph structure 66 CodeNode can be nested and |outer| points to the nesting CodeNode. Also 67 CodeNode can make a sequence and |prev| points to the previous CodeNode. 68 See also |ListNode|. 69 70 - Template rendering 71 CodeNode has template text and template variable bindings. Either of 72 |__str__| or |render| returns a text of generated code. However, these 73 methods have side effects on rendering states, and repeated calls may return 74 different results. 75 """ 76 77 class _RenderState(object): 78 """ 79 Represents a set of per-render states. Every call to CodeNode.render 80 resets all the per-render states. 81 """ 82 83 def __init__(self): 84 # List of SymbolNodes that are defined at this point of rendering. 85 # Used to determine whether a certain symbol is already defined by 86 # this point of rendering. 87 self.defined_code_symbols = [] 88 89 # List of SymbolNodes that are not yet defined at this point of 90 # rendering. SymbolNodes are accumulated in order of their first 91 # appearance. The order affects the insertion order of 92 # SymbolDefinitionNodes. 93 self.undefined_code_symbols = [] 94 95 # Dict from a SymbolNode to a set of tuples of SymbolScopeNodes 96 # where the symbol was used. 97 # 98 # For example, given a code symbol |x|, the following code 99 # structure: 100 # { // Scope1 101 # { // Scope2A 102 # { // Scope3 103 # x; // [1] 104 # } 105 # x; // [2] 106 # } 107 # x; // [3] 108 # { // Scope2B 109 # x; // [4] 110 # } 111 # x; // [5] 112 # } 113 # is translated into an entry of the dict below: 114 # set([ 115 # (Scope1), # [3], [5] 116 # (Scope1, Scope2A), # [2] 117 # (Scope1, Scope2A, Scope3), # [1] 118 # (Scope1, Scope2B), # [4] 119 # ]) 120 self.symbol_to_scope_chains = {} 121 122 _gensym_seq_id = 0 123 124 @classmethod 125 def gensym(cls): 126 """ 127 Creates a new template variable that never conflicts with anything. 128 129 The name 'gensym' came from 'gensym' (generated symbol) in Lisp that 130 exists for exactly the same purpose. 131 132 Note that |gensym| is used to produce a new Mako template variable while 133 SymbolNode is used to represent a code symbol (such as a local variable) 134 in generated code. 135 136 Bad example: 137 template_text = "abc ${tmp} xyz" 138 a = CodeNodeA(template_text='123') 139 b = CodeNodeB(template_text=template_text, {'tmp': a}) 140 |b| expects "abc 123 xyz" but what if 'tmp' were already bound to 141 something else? 142 143 Good example: 144 sym = CodeNode.gensym() 145 template_text = format_template( 146 "abc ${{{node_a}}} xyz", node_a=sym) 147 a = CodeNodeA(template_text='123') 148 b = CodeNodeB(template_text=template_text, {sym: a}) 149 "{{" and "}}" are literal of "{" and "}" themselves, and the innermost 150 "{node_a}" will be replaced with |sym|. The resulting template text 151 will be "abc ${gensym1} xyz" when |sym| is 'gensym1'. 152 """ 153 cls._gensym_seq_id += 1 154 return "gensym{}".format(cls._gensym_seq_id) 155 156 def __init__(self, template_text=None, template_vars=None): 157 assert template_text is None or isinstance(template_text, str) 158 assert template_vars is None or isinstance(template_vars, dict) 159 160 # The outer CodeNode or None iff this is a top-level node 161 self._outer = None 162 # The previous CodeNode if this is a Sequence or None 163 self._prev = None 164 165 # Mako's template text, bindings dict 166 if template_text is None: 167 self._template = None 168 else: 169 self._template = MakoTemplate(template_text) 170 171 # Template variable bindings 172 self._own_template_vars = None 173 self._base_template_vars = None 174 self._cached_template_vars = None 175 176 self._accumulator = None # CodeGenAccumulator 177 self._accumulate_requests = None 178 179 self._renderer = None # MakoRenderer 180 181 self._render_state = CodeNode._RenderState() 182 self._is_rendering = False 183 184 if template_vars: 185 self.add_template_vars(template_vars) 186 187 def __str__(self): 188 """ 189 Renders this CodeNode object directly into the renderer's text buffer 190 and always returns the empty string. This is because it's faster to 191 accumulate the rendering result directly in a single text buffer than 192 making a lot of string pieces and concatenating them. 193 194 This function is supposed to be used in a Mako template as ${code_node}. 195 """ 196 renderer = self.renderer 197 assert renderer 198 199 self.render(renderer) 200 return "" 201 202 def render(self, renderer): 203 """ 204 Renders this CodeNode object as a text string and also propagates 205 updates to related CodeNode objects. As this method has side-effects 206 not only to this object but also other related objects, the resulting 207 text may change on each invocation. 208 """ 209 last_render_state = self._render_state 210 self._render_state = CodeNode._RenderState() 211 self._is_rendering = True 212 213 try: 214 self._render( 215 renderer=renderer, last_render_state=last_render_state) 216 finally: 217 self._is_rendering = False 218 219 if self._accumulate_requests: 220 accumulator = self.accumulator 221 assert accumulator 222 for request in self._accumulate_requests: 223 request(accumulator) 224 self._accumulate_requests = None 225 226 def _render(self, renderer, last_render_state): 227 """ 228 Renders this CodeNode object as a text string and also propagates 229 updates to related CodeNode objects. 230 231 Only limited subclasses may override this method. 232 """ 233 renderer.render( 234 caller=self, 235 template=self._template, 236 template_vars=self.template_vars) 237 238 @property 239 def outer(self): 240 """Returns the outer CodeNode or None iff this is a top-level node.""" 241 return self._outer 242 243 def set_outer(self, outer): 244 assert isinstance(outer, CodeNode) 245 assert self._outer is None 246 self._outer = outer 247 248 def reset_outer(self, outer): 249 assert isinstance(outer, CodeNode) or outer is None 250 self._outer = outer 251 252 @property 253 def prev(self): 254 """Returns the previous CodeNode if this is a Sequence or None.""" 255 return self._prev 256 257 def set_prev(self, prev): 258 assert isinstance(prev, CodeNode) 259 assert self._prev is None 260 self._prev = prev 261 262 def reset_prev(self, prev): 263 assert isinstance(prev, CodeNode) or prev is None 264 self._prev = prev 265 266 def outer_scope(self): 267 """Returns the outer scope closest to this scope or None.""" 268 node = self.outer 269 while node is not None: 270 if isinstance(node, SymbolScopeNode): 271 return node 272 node = node.outer 273 return None 274 275 def outermost(self): 276 """Returns the outermost node, i.e. the node whose |outer| is None.""" 277 node = self 278 while node.outer is not None: 279 node = node.outer 280 return node 281 282 def inclusive_outers(self): 283 """ 284 Returns a list of outer nodes including this node in order from this 285 node to the outermost node. 286 """ 287 outers = [] 288 node = self 289 while node is not None: 290 outers.append(node) 291 node = node.outer 292 return outers 293 294 @property 295 def template_vars(self): 296 """ 297 Returns the template variable bindings available at this point, i.e. 298 bound at this node or outer nodes. 299 300 CAUTION: This accessor caches the result. This accessor must not be 301 called during construction of a code node tree. 302 """ 303 if self._cached_template_vars is not None: 304 return self._cached_template_vars 305 306 outers = self.inclusive_outers() 307 bindings = None 308 309 for node in outers: 310 if node.base_template_vars is not None: 311 bindings = dict(node.base_template_vars) 312 break 313 if bindings is None: 314 bindings = {} 315 316 for node in outers: 317 if node.own_template_vars is None: 318 continue 319 for name, value in node.own_template_vars.items(): 320 assert name not in bindings, ( 321 "Duplicated template variable binding: {}".format(name)) 322 bindings[name] = value 323 324 self._cached_template_vars = bindings 325 return self._cached_template_vars 326 327 @property 328 def own_template_vars(self): 329 """Returns the template variables bound at this code node.""" 330 return self._own_template_vars 331 332 def add_template_var(self, name, value): 333 if self._own_template_vars is None: 334 self._own_template_vars = {} 335 assert isinstance(name, str) 336 assert name not in self._own_template_vars, ( 337 "Duplicated template variable binding: {}".format(name)) 338 if isinstance(value, CodeNode): 339 value.set_outer(self) 340 self._own_template_vars[name] = value 341 342 def add_template_vars(self, template_vars): 343 assert isinstance(template_vars, dict) 344 for name, value in template_vars.items(): 345 self.add_template_var(name, value) 346 347 @property 348 def base_template_vars(self): 349 """ 350 Returns the base template variables if it's set at this code node. 351 352 The base template variables are a set of template variables that of 353 the innermost code node takes effect. It means that the base template 354 variables are layered and shadowable. 355 """ 356 return self._base_template_vars 357 358 def set_base_template_vars(self, template_vars): 359 assert isinstance(template_vars, dict) 360 for name, value in template_vars.items(): 361 assert isinstance(name, str) 362 assert not isinstance(value, CodeNode) 363 assert self._base_template_vars is None 364 self._base_template_vars = template_vars 365 366 @property 367 def accumulator(self): 368 # Always consistently use the accumulator of the root node. 369 if self.outer is None: 370 return self._accumulator 371 return self.outermost().accumulator 372 373 def set_accumulator(self, accumulator): 374 assert isinstance(accumulator, CodeGenAccumulator) 375 assert self._accumulator is None 376 self._accumulator = accumulator 377 378 def accumulate(self, request): 379 """ 380 While rendering the code node, |request| will be called with the 381 argument of self.accumulator. 382 """ 383 assert callable(request) 384 if self._accumulate_requests is None: 385 self._accumulate_requests = [] 386 self._accumulate_requests.append(request) 387 388 @property 389 def renderer(self): 390 # Always consistently use the renderer of the root node. 391 if self.outer is None: 392 return self._renderer 393 return self.outermost().renderer 394 395 def set_renderer(self, renderer): 396 assert isinstance(renderer, MakoRenderer) 397 assert self._renderer is None 398 self._renderer = renderer 399 400 @property 401 def current_render_state(self): 402 assert self._is_rendering 403 return self._render_state 404 405 @property 406 def last_render_state(self): 407 assert not self._is_rendering 408 return self._render_state 409 410 def on_code_symbol_referenced(self, symbol_node, symbol_scope_chain): 411 """Receives a report of use of a symbol node.""" 412 assert isinstance(symbol_node, SymbolNode) 413 assert isinstance(symbol_scope_chain, tuple) 414 assert all( 415 isinstance(scope, SymbolScopeNode) for scope in symbol_scope_chain) 416 self.current_render_state.symbol_to_scope_chains.setdefault( 417 symbol_node, set()).add(symbol_scope_chain) 418 419 420class EmptyNode(CodeNode): 421 """Represents the zero-length text and renders nothing.""" 422 423 def __init__(self): 424 CodeNode.__init__(self) 425 426 def _render(self, renderer, last_render_state): 427 pass 428 429 430class LiteralNode(CodeNode): 431 """ 432 Represents a literal text, which will be rendered as is without any template 433 magic applied. The given literal text object will be stringified on each 434 rendering. 435 """ 436 437 def __init__(self, literal_text): 438 CodeNode.__init__(self) 439 440 self._literal_text = literal_text 441 442 def _render(self, renderer, last_render_state): 443 renderer.push_caller(self) 444 try: 445 renderer.render_text(str(self._literal_text)) 446 finally: 447 renderer.pop_caller() 448 449 450def TextNode(template_text): 451 """ 452 Represents a template text node. 453 454 TextNode is designed to be a leaf node of a code node tree. TextNode 455 represents a template text while LiteralNode represents a literal text. 456 All template magics will be applied to |template_text|. 457 458 This function is pretending to be a CodeNode subclass and instantiates one 459 of text-ish code node subclass depending on the content of |template_text|. 460 """ 461 assert isinstance(template_text, str) 462 463 if "$" in template_text or "%" in template_text: 464 return _TextNode(template_text) 465 elif template_text: 466 return LiteralNode(template_text) 467 else: 468 return EmptyNode() 469 470 471class _TextNode(CodeNode): 472 """ 473 Represents a template text node. 474 475 TextNode is designed to be a leaf node of a code node tree. TextNode 476 represents a template text while LiteralNode represents a literal text. 477 All template magics will be applied to |template_text|. 478 """ 479 480 def __init__(self, template_text): 481 CodeNode.__init__(self, template_text=template_text) 482 483 484class CompositeNode(CodeNode): 485 """ 486 Represents a composition of multiple code nodes. Composition will be done 487 by using |CodeNode.gensym| so that it won't contaminate a namespace of the 488 template variables. 489 """ 490 491 def __init__(self, template_format_str, *args, **kwargs): 492 """ 493 Args: 494 template_format_str: A format string that is used to produce the 495 template text. 496 args: 497 kwargs: Arguments to be passed to |format_template|. Not 498 necessarily be CodeNode, but also anything renderable can be 499 passed in. 500 """ 501 assert isinstance(template_format_str, str) 502 gensym_args = [] 503 gensym_kwargs = {} 504 template_vars = {} 505 for arg in args: 506 assert isinstance(arg, (CodeNode, int, long, str)) 507 gensym = CodeNode.gensym() 508 gensym_args.append("${{{}}}".format(gensym)) 509 template_vars[gensym] = arg 510 for key, value in kwargs.items(): 511 assert isinstance(key, (int, long, str)) 512 assert isinstance(value, (CodeNode, int, long, str)) 513 gensym = CodeNode.gensym() 514 gensym_kwargs[key] = "${{{}}}".format(gensym) 515 template_vars[gensym] = value 516 template_text = format_template(template_format_str, *gensym_args, 517 **gensym_kwargs) 518 519 CodeNode.__init__( 520 self, template_text=template_text, template_vars=template_vars) 521 522 523class ListNode(CodeNode): 524 """ 525 Represents a list of nodes. 526 527 append, extend, insert, and remove work just like built-in list's methods 528 except that addition and removal of None have no effect. 529 """ 530 531 def __init__(self, code_nodes=None, separator="\n", head="", tail=""): 532 """ 533 Args: 534 code_nodes: A list of CodeNode to be rendered. 535 separator: A str inserted between code nodes. 536 head: 537 tail: The head and tail sections that will be rendered iff the 538 content list is not empty. 539 """ 540 assert isinstance(separator, str) 541 assert isinstance(head, str) 542 assert isinstance(tail, str) 543 544 CodeNode.__init__(self) 545 546 self._element_nodes = [] 547 self._separator = separator 548 self._head = head 549 self._tail = tail 550 551 self._will_skip_separator = False 552 553 if code_nodes is not None: 554 self.extend(code_nodes) 555 556 def __getitem__(self, index): 557 return self._element_nodes[index] 558 559 def __iter__(self): 560 return iter(self._element_nodes) 561 562 def __len__(self): 563 return len(self._element_nodes) 564 565 def _render(self, renderer, last_render_state): 566 renderer.push_caller(self) 567 try: 568 if self._element_nodes: 569 renderer.render_text(self._head) 570 self._will_skip_separator = True 571 for node in self._element_nodes: 572 if self._will_skip_separator: 573 self._will_skip_separator = False 574 else: 575 renderer.render_text(self._separator) 576 node.render(renderer) 577 if self._element_nodes: 578 renderer.render_text(self._tail) 579 finally: 580 renderer.pop_caller() 581 582 def skip_separator(self): 583 self._will_skip_separator = True 584 585 def append(self, node): 586 if node is None: 587 return 588 assert isinstance(node, CodeNode) 589 assert node.outer is None and node.prev is None 590 591 if len(self._element_nodes) == 0: 592 self._element_nodes.append(node) 593 else: 594 node.set_prev(self._element_nodes[-1]) 595 self._element_nodes.append(node) 596 node.set_outer(self) 597 598 def extend(self, nodes): 599 for node in nodes: 600 self.append(node) 601 602 def insert(self, index, node): 603 if node is None: 604 return 605 assert isinstance(index, (int, long)) 606 assert isinstance(node, CodeNode) 607 assert node.outer is None and node.prev is None 608 609 if index < 0: 610 index += len(self._element_nodes) 611 index = max(0, min(index, len(self._element_nodes))) 612 613 if (len(self._element_nodes) == 0 614 or index == len(self._element_nodes)): 615 return self.append(node) 616 617 next_node = self._element_nodes[index] 618 if next_node.prev: 619 node.set_prev(next_node.prev) 620 next_node.reset_prev(node) 621 node.set_outer(self) 622 self._element_nodes.insert(index, node) 623 624 def remove(self, node): 625 if node is None: 626 return 627 assert node in self 628 629 index = self._element_nodes.index(node) 630 if index + 1 < len(self._element_nodes): 631 next_node = self._element_nodes[index + 1] 632 prev_node = self._element_nodes[index - 1] if index != 0 else None 633 next_node.reset_prev(prev_node) 634 del self._element_nodes[index] 635 node.reset_outer(None) 636 node.reset_prev(None) 637 638 639class SequenceNode(ListNode): 640 """ 641 Represents a sequence of generated code without introducing any new scope, 642 and provides the points where SymbolDefinitionNodes can be inserted. 643 """ 644 645 def __init__(self, code_nodes=None, separator="\n", head="", tail=""): 646 ListNode.__init__( 647 self, 648 code_nodes=code_nodes, 649 separator=separator, 650 head=head, 651 tail=tail) 652 653 self._to_be_removed = [] 654 655 def _render(self, renderer, last_render_state): 656 if self._to_be_removed: 657 for node in self._to_be_removed: 658 self.remove(node) 659 self._to_be_removed = [] 660 661 super(SequenceNode, self)._render( 662 renderer=renderer, last_render_state=last_render_state) 663 664 def schedule_to_remove(self, node): 665 """Schedules a task to remove the |node| in the next rendering cycle.""" 666 assert node in self 667 self._to_be_removed.append(node) 668 669 670class SymbolScopeNode(SequenceNode): 671 """ 672 Represents a scope of generated code. 673 674 If SymbolNodes are rendered inside this node, this node will attempt to 675 insert corresponding SymbolDefinitionNodes appropriately. 676 """ 677 678 def __init__(self, code_nodes=None, separator="\n", head="", tail=""): 679 SequenceNode.__init__( 680 self, 681 code_nodes=code_nodes, 682 separator=separator, 683 head=head, 684 tail=tail) 685 686 self._likeliness = Likeliness.ALWAYS 687 self._registered_code_symbols = set() 688 689 def _render(self, renderer, last_render_state): 690 for symbol_node in last_render_state.undefined_code_symbols: 691 assert self.is_code_symbol_registered(symbol_node) 692 if not self.is_code_symbol_defined(symbol_node): 693 self._insert_symbol_definition(symbol_node, last_render_state) 694 695 super(SymbolScopeNode, self)._render( 696 renderer=renderer, last_render_state=last_render_state) 697 698 if self.current_render_state.undefined_code_symbols: 699 renderer.invalidate_rendering_result() 700 701 def _insert_symbol_definition(self, symbol_node, last_render_state): 702 DIRECT_USES = "u" 703 DIRECT_CHILD_SCOPES = "s" 704 ANALYSIS_RESULT_KEYS = ( 705 # Number of direct uses in this scope 706 DIRECT_USES, 707 # Number of direct child scopes 708 DIRECT_CHILD_SCOPES, 709 # Number of direct child scopes per likeliness 710 Likeliness.ALWAYS, 711 Likeliness.LIKELY, 712 Likeliness.UNLIKELY, 713 ) 714 715 def analyze_symbol_usage(render_state): 716 counts = dict.fromkeys(ANALYSIS_RESULT_KEYS, 0) 717 718 scope_chains = render_state.symbol_to_scope_chains.get(symbol_node) 719 if not scope_chains: 720 return counts 721 722 self_index = iter(scope_chains).next().index(self) 723 scope_chains = map( 724 lambda scope_chain: scope_chain[self_index + 1:], scope_chains) 725 scope_to_likeliness = {} 726 for scope_chain in scope_chains: 727 if not scope_chain: 728 counts[DIRECT_USES] += 1 729 else: 730 likeliness = min( 731 map(lambda scope: scope.likeliness, scope_chain)) 732 scope = scope_chain[0] 733 scope_to_likeliness[scope] = max( 734 likeliness, scope_to_likeliness.get(scope, likeliness)) 735 for likeliness in scope_to_likeliness.values(): 736 counts[DIRECT_CHILD_SCOPES] += 1 737 counts[likeliness] += 1 738 return counts 739 740 def likeliness_at(render_state): 741 counts = analyze_symbol_usage(render_state) 742 if counts[DIRECT_USES] >= 1: 743 return Likeliness.ALWAYS 744 for likeliness in (Likeliness.ALWAYS, Likeliness.LIKELY, 745 Likeliness.UNLIKELY): 746 if counts[likeliness] > 0: 747 return likeliness 748 return Likeliness.NEVER 749 750 def insert_before_threshold(sequence_node, threshold): 751 for index, node in enumerate(sequence_node): 752 if (isinstance(node, SequenceNode) 753 and not isinstance(node, SymbolScopeNode)): 754 did_insert = insert_before_threshold(node, threshold) 755 if did_insert: 756 return True 757 elif likeliness_at(node.last_render_state) >= threshold: 758 sequence_node.insert(index, 759 symbol_node.create_definition_node()) 760 return True 761 return False 762 763 counts = analyze_symbol_usage(last_render_state) 764 if counts[DIRECT_USES] >= 1: 765 did_insert = insert_before_threshold(self, Likeliness.UNLIKELY) 766 assert did_insert 767 elif counts[DIRECT_CHILD_SCOPES] == 1: 768 pass # Let the child SymbolScopeNode do the work. 769 elif counts[Likeliness.ALWAYS] + counts[Likeliness.LIKELY] >= 2: 770 did_insert = insert_before_threshold(self, Likeliness.LIKELY) 771 assert did_insert 772 else: 773 pass # Let descendant SymbolScopeNodes do the work. 774 775 def is_code_symbol_registered(self, symbol_node): 776 """ 777 Returns True if |symbol_node| is registered and available for use within 778 this scope. 779 """ 780 assert isinstance(symbol_node, SymbolNode) 781 782 if symbol_node in self._registered_code_symbols: 783 return True 784 785 outer = self.outer_scope() 786 if outer is None: 787 return False 788 return outer.is_code_symbol_registered(symbol_node) 789 790 def register_code_symbol(self, symbol_node): 791 """Registers a SymbolNode and makes it available in this scope.""" 792 assert isinstance(symbol_node, SymbolNode) 793 self.add_template_var(symbol_node.name, symbol_node) 794 self._registered_code_symbols.add(symbol_node) 795 796 def register_code_symbols(self, symbol_nodes): 797 for symbol_node in symbol_nodes: 798 self.register_code_symbol(symbol_node) 799 800 @property 801 def likeliness(self): 802 """ 803 Returns how much likely that this SymbolScopeNode will be executed in 804 runtime. The likeliness is relative to the closest outer 805 SymbolScopeNode. 806 """ 807 return self._likeliness 808 809 def set_likeliness(self, likeliness): 810 assert isinstance(likeliness, Likeliness.Level) 811 self._likeliness = likeliness 812 813 def is_code_symbol_defined(self, symbol_node): 814 """ 815 Returns True if |symbol_node| is defined in this scope by the moment 816 when the method is called. 817 """ 818 assert isinstance(symbol_node, SymbolNode) 819 820 if symbol_node in self.current_render_state.defined_code_symbols: 821 return True 822 823 outer = self.outer_scope() 824 if outer is None: 825 return False 826 return outer.is_code_symbol_defined(symbol_node) 827 828 def on_code_symbol_defined(self, symbol_node): 829 """Receives a report that a symbol gets defined.""" 830 assert isinstance(symbol_node, SymbolNode) 831 self.current_render_state.defined_code_symbols.append(symbol_node) 832 833 def on_undefined_code_symbol_found(self, symbol_node): 834 """Receives a report of use of an undefined symbol node.""" 835 assert isinstance(symbol_node, SymbolNode) 836 state = self.current_render_state 837 if symbol_node not in state.undefined_code_symbols: 838 state.undefined_code_symbols.append(symbol_node) 839 840 841class SymbolNode(CodeNode): 842 """ 843 Represents a code symbol such as a local variable of generated code. 844 845 Using a SymbolNode combined with SymbolScopeNode, SymbolDefinitionNode(s) 846 will be automatically inserted iff this symbol is referenced. 847 """ 848 849 def __init__(self, name, template_text=None, definition_constructor=None): 850 """ 851 Args: 852 name: The name of this code symbol. 853 template_text: Template text to be used to define the code symbol. 854 definition_constructor: A callable that creates and returns a new 855 definition node. This SymbolNode will be passed as the 856 argument. 857 Either of |template_text| or |definition_constructor| must be 858 given. 859 """ 860 assert isinstance(name, str) and name 861 862 CodeNode.__init__(self) 863 864 self._name = name 865 866 if template_text is not None: 867 assert isinstance(template_text, str) 868 assert definition_constructor is None 869 870 def constructor(symbol_node): 871 return SymbolDefinitionNode( 872 symbol_node=symbol_node, 873 code_nodes=[TextNode(template_text)]) 874 875 self._definition_constructor = constructor 876 else: 877 assert template_text is None 878 assert callable(definition_constructor) 879 880 self._definition_constructor = definition_constructor 881 882 def _render(self, renderer, last_render_state): 883 self._request_symbol_definition(renderer) 884 885 renderer.render_text(self.name) 886 887 def request_symbol_definition(self): 888 self._request_symbol_definition(self.renderer) 889 890 def _request_symbol_definition(self, renderer): 891 symbol_scope_chain = tuple( 892 filter(lambda node: isinstance(node, SymbolScopeNode), 893 renderer.callers_from_first_to_last)) 894 895 for caller in renderer.callers_from_last_to_first: 896 caller.on_code_symbol_referenced(self, symbol_scope_chain) 897 if caller is self.outer: 898 break 899 900 if not symbol_scope_chain[-1].is_code_symbol_defined(self): 901 for scope in reversed(symbol_scope_chain): 902 scope.on_undefined_code_symbol_found(self) 903 if scope is self.outer: 904 break 905 906 @property 907 def name(self): 908 return self._name 909 910 def create_definition_node(self): 911 """Creates a new definition node.""" 912 node = self._definition_constructor(self) 913 assert isinstance(node, SymbolDefinitionNode) 914 assert node.target_symbol is self 915 return node 916 917 918class SymbolDefinitionNode(SequenceNode): 919 """ 920 Represents a definition of a code symbol. 921 922 It's allowed to define the same code symbol multiple times, and most 923 upstream definition(s) are effective. 924 """ 925 926 def __init__(self, symbol_node, code_nodes=None): 927 assert isinstance(symbol_node, SymbolNode) 928 929 SequenceNode.__init__(self, code_nodes) 930 931 self._symbol_node = symbol_node 932 933 def _render(self, renderer, last_render_state): 934 scope = self.outer_scope() 935 if scope.is_code_symbol_defined(self._symbol_node): 936 assert isinstance(self.outer, SequenceNode) 937 self.outer.schedule_to_remove(self) 938 self.outer.skip_separator() 939 return 940 941 scope.on_code_symbol_defined(self._symbol_node) 942 943 super(SymbolDefinitionNode, self)._render( 944 renderer=renderer, last_render_state=last_render_state) 945 946 @property 947 def target_symbol(self): 948 return self._symbol_node 949