| # Copyright 2019 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """ |
| The code generator generates code based on a graph of code fragments. Each node |
| of the graph is represented with CodeNode and its subclasses. This module |
| provides a collection of the classes that represent code nodes independent from |
| specific bindings, such as ECMAScript bindings. |
| """ |
| |
| import copy |
| import string |
| |
| from .mako_renderer import MakoRenderer |
| |
| |
| class CodeNode(object): |
| """ |
| This is the base class of all code fragment nodes. |
| |
| - Graph structure |
| CodeNode can be nested and |outer| points to the nesting CodeNode. Also |
| CodeNode can make a sequence and |prev| points to the previous CodeNode. |
| See also |SequenceNode|. |
| |
| - Template rendering |
| CodeNode has template text and template variable bindings. Either of |
| |__str__| or |render| returns a text of generated code. However, these |
| methods have side effects on rendering states, and repeated calls may return |
| different results. |
| """ |
| |
| class _RenderState(object): |
| """ |
| Represents a set of per-render states. Every call to CodeNode.render |
| resets all the per-render states. |
| """ |
| |
| def __init__(self): |
| # Symbols used in generated code, but not yet defined. See also |
| # SymbolNode. |
| self.code_symbols_used = {} |
| |
| class _LooseFormatter(string.Formatter): |
| def get_value(self, key, args, kwargs): |
| if key in kwargs: |
| return kwargs[key] |
| else: |
| return '{' + key + '}' |
| |
| _template_formatter = _LooseFormatter() |
| _gensym_seq_id = 0 |
| |
| @classmethod |
| def format_template(cls, format_string, *args, **kwargs): |
| """ |
| Formats a string like the built-in |format| allowing unbound keys. |
| |
| format_template("${template_var} {format_var}", format_var=42) |
| will produce |
| "${template_var} 42" |
| without raising an exception that |template_var| is unbound. |
| """ |
| return cls._template_formatter.format(format_string, *args, **kwargs) |
| |
| @classmethod |
| def gensym(cls): |
| """ |
| Creates a new template variable that never conflicts with anything. |
| |
| The name 'gensym' came from 'gensym' (generated symbol) in Lisp that |
| exists for exactly the same purpose. |
| |
| Note that |gensym| is used to produce a new Mako template variable while |
| SymbolNode is used to represent a code symbol (such as a local variable) |
| in generated code. |
| |
| Bad example: |
| template_text = "abc ${tmp} xyz" |
| a = CodeNodeA(template_text='123') |
| b = CodeNodeB(template_text=template_text, {'tmp': a}) |
| |b| expects "abc 123 xyz" but what if 'tmp' were already bound to |
| something else? |
| |
| Good example: |
| sym = CodeNode.gensym() |
| template_text = CodeNode.format_template( |
| "abc ${{{node_a}}} xyz", node_a=sym) |
| a = CodeNodeA(template_text='123') |
| b = CodeNodeB(template_text=template_text, {sym: a}) |
| "{{" and "}}" are literal of "{" and "}" themselves, and the innermost |
| "{node_a}" will be replaced with |sym|. The resulting template text |
| will be "abc ${gensym1} xyz" when |sym| is 'gensym1'. |
| """ |
| cls._gensym_seq_id += 1 |
| return 'gensym{}'.format(cls._gensym_seq_id) |
| |
| def __init__(self, |
| outer=None, |
| prev=None, |
| template_text=None, |
| template_vars=None, |
| renderer=None): |
| assert outer is None or isinstance(outer, CodeNode) |
| assert prev is None or isinstance(prev, CodeNode) |
| assert template_text is None or isinstance(template_text, str) |
| assert template_vars is None or isinstance(template_vars, dict) |
| assert renderer is None or isinstance(renderer, MakoRenderer) |
| |
| # The outer CodeNode or None iff this is a top-level node |
| self._outer = outer |
| # The previous CodeNode if this is a Sequence or None |
| self._prev = prev |
| |
| # Mako's template text, bindings dict, and the renderer object |
| self._template_text = template_text |
| self._template_vars = {} |
| self._renderer = renderer |
| |
| self._render_state = CodeNode._RenderState() |
| self._is_rendering = False |
| |
| if template_vars: |
| self.add_template_vars(template_vars) |
| |
| def __str__(self): |
| """ |
| Renders this CodeNode object into a Mako template. This is supposed to |
| be used in a Mako template as ${code_node}. |
| """ |
| return self.render() |
| |
| def render(self): |
| """ |
| Renders this CodeNode object as a text string and also propagates |
| updates to related CodeNode objects. As this method has side-effects |
| not only to this object but also other related objects, the resulting |
| text may change on each invocation. |
| """ |
| assert self.renderer |
| |
| last_render_state = self._render_state |
| self._render_state = CodeNode._RenderState() |
| self._is_rendering = True |
| |
| try: |
| text = self._render( |
| renderer=self.renderer, last_render_state=last_render_state) |
| finally: |
| self._is_rendering = False |
| |
| return text |
| |
| def _render(self, renderer, last_render_state): |
| """ |
| Renders this CodeNode object as a text string and also propagates |
| updates to related CodeNode objects. |
| |
| Only limited subclasses may override this method. |
| """ |
| assert self._template_text |
| return renderer.render( |
| caller=self, |
| template_text=self._template_text, |
| template_vars=self.template_vars) |
| |
| @property |
| def outer(self): |
| """Returns the outer CodeNode or None iff this is a top-level node.""" |
| return self._outer |
| |
| def set_outer(self, outer): |
| assert isinstance(outer, CodeNode) |
| assert self._outer is None |
| self._outer = outer |
| |
| @property |
| def prev(self): |
| """Returns the previous CodeNode if this is a Sequence or None.""" |
| return self._prev |
| |
| def set_prev(self, prev): |
| assert isinstance(prev, CodeNode) |
| assert self._prev is None |
| self._prev = prev |
| |
| def reset_prev(self, prev): |
| assert isinstance(prev, CodeNode) |
| self._prev = prev |
| |
| @property |
| def upstream(self): |
| """ |
| Returns the upstream CodeNode in terms of code flow. |
| |
| The upstream CodeNode is defined as: |
| 1. the previous CodeNode, or |
| 2. the outer CodeNode, or |
| 3. None (as this is a top-level node) |
| """ |
| return self.prev or self.outer |
| |
| @property |
| def template_vars(self): |
| """ |
| Returns the template variable bindings available at this point, i.e. |
| bound at this node or outer nodes. |
| |
| CAUTION: Do not modify the returned dict. This method may return the |
| original dict in a CodeNode. |
| """ |
| if not self.outer: |
| return self._template_vars |
| |
| if not self._template_vars: |
| return self.outer.template_vars |
| |
| binds = copy.copy(self.outer.template_vars) |
| for name, value in self._template_vars.iteritems(): |
| assert name not in binds, ( |
| "Duplicated template variable binding: {}".format(name)) |
| binds[name] = value |
| return binds |
| |
| def add_template_var(self, name, value): |
| assert name not in self._template_vars |
| if isinstance(value, CodeNode): |
| value.set_outer(self) |
| self._template_vars[name] = value |
| |
| def add_template_vars(self, template_vars): |
| assert isinstance(template_vars, dict) |
| for name, value in template_vars.iteritems(): |
| self.add_template_var(name, value) |
| |
| @property |
| def renderer(self): |
| return self._renderer or self.outer.renderer |
| |
| @property |
| def current_render_state(self): |
| assert self._is_rendering |
| return self._render_state |
| |
| @property |
| def last_render_state(self): |
| assert not self._is_rendering |
| return self._render_state |
| |
| def is_code_symbol_defined(self, symbol_node): |
| """ |
| Returns True if |symbol_node| is defined at this point or upstream. |
| """ |
| if self.upstream: |
| return self.upstream.is_code_symbol_defined(symbol_node) |
| return False |
| |
| def on_code_symbol_used(self, symbol_node): |
| """Receives a report of use of an undefined symbol node.""" |
| assert isinstance(symbol_node, SymbolNode) |
| state = self.current_render_state |
| state.code_symbols_used[symbol_node.name] = symbol_node |
| |
| |
| class SimpleNode(CodeNode): |
| """ |
| Represents a simple node that never contains a branch nor sequence. |
| |
| Technically, you can make this node contain branches and sequences with |
| using template text and variable bindings, however, it's not supported. |
| """ |
| |
| def __init__(self, template_text, template_vars=None, renderer=None): |
| CodeNode.__init__( |
| self, |
| template_text=template_text, |
| template_vars=template_vars, |
| renderer=renderer) |
| |
| |
| class SequenceNode(CodeNode): |
| """ |
| Represents a sequence of nodes. |
| |
| If SymbolNodes are used inside this node, this node will attempt to insert |
| corresponding SymbolDefinitionNodes appropriately. |
| """ |
| |
| def __init__(self, renderer=None): |
| element_nodes_symbol = CodeNode.gensym() |
| element_nodes = [] |
| template_text = CodeNode.format_template( |
| """\ |
| % for node in {element_nodes}: |
| ${node} |
| % endfor\ |
| """, |
| element_nodes=element_nodes_symbol) |
| template_vars = {element_nodes_symbol: element_nodes} |
| |
| CodeNode.__init__( |
| self, |
| template_text=template_text, |
| template_vars=template_vars, |
| renderer=renderer) |
| |
| self._element_nodes = element_nodes |
| |
| def __getitem__(self, index): |
| return self._element_nodes[index] |
| |
| def __iter__(self): |
| return iter(self._element_nodes) |
| |
| def __len__(self): |
| return len(self._element_nodes) |
| |
| def _render(self, renderer, last_render_state): |
| # Sort nodes in order to generate reproducible code. |
| symbol_nodes = sorted( |
| last_render_state.code_symbols_used.itervalues(), |
| key=lambda node: node.name) |
| for symbol_node in symbol_nodes: |
| self._insert_symbol_definition(symbol_node) |
| |
| return super(SequenceNode, self)._render( |
| renderer=renderer, last_render_state=last_render_state) |
| |
| def _insert_symbol_definition(self, symbol_node): |
| self.insert(0, symbol_node.create_definition_node()) |
| |
| def append(self, node): |
| assert isinstance(node, CodeNode) |
| assert node.outer is None and node.prev is None |
| |
| if len(self._element_nodes) == 0: |
| self._element_nodes.append(node) |
| else: |
| node.set_prev(self._element_nodes[-1]) |
| self._element_nodes.append(node) |
| node.set_outer(self) |
| |
| def extend(self, nodes): |
| for node in nodes: |
| self.append(node) |
| |
| def insert(self, index, node): |
| assert isinstance(index, (int, long)) |
| assert isinstance(node, CodeNode) |
| assert node.outer is None and node.prev is None |
| |
| if index < 0: |
| index += len(self._element_nodes) |
| index = max(0, min(index, len(self._element_nodes))) |
| |
| if (len(self._element_nodes) == 0 |
| or index == len(self._element_nodes)): |
| return self.append(node) |
| |
| next_node = self._element_nodes[index] |
| if next_node.prev: |
| node.set_prev(next_node.prev) |
| next_node.reset_prev(node) |
| node.set_outer(self) |
| self._element_nodes.insert(index, node) |
| |
| |
| class SymbolNode(CodeNode): |
| """ |
| Represents a code symbol such as a local variable of generated code. |
| |
| Used combined with SequenceNode, SymbolDefinitionNode(s) will be |
| automatically inserted iff this symbol is referenced. |
| """ |
| |
| def __init__(self, name, definition_node_constructor): |
| """ |
| Args: |
| name: The name of this code symbol. |
| definition_node_constructor: A callable that creates and returns a |
| new definition node. This node will be passed as the argument. |
| """ |
| assert isinstance(name, str) and name |
| assert callable(definition_node_constructor) |
| |
| CodeNode.__init__(self) |
| |
| self._name = name |
| self._definition_node_constructor = definition_node_constructor |
| |
| def _render(self, renderer, last_render_state): |
| if not renderer.last_caller.is_code_symbol_defined(self): |
| for caller in renderer.callers: |
| assert isinstance(caller, CodeNode) |
| caller.on_code_symbol_used(self) |
| |
| return self.name |
| |
| @property |
| def name(self): |
| return self._name |
| |
| def create_definition_node(self): |
| """Creates a new definition node.""" |
| node = self._definition_node_constructor(self) |
| assert isinstance(node, SymbolDefinitionNode) |
| return node |
| |
| |
| class SymbolDefinitionNode(CodeNode): |
| """ |
| Represents a definition of a code symbol. |
| |
| It's allowed to define the same code symbol multiple times, and most |
| upstream definition(s) are effective. |
| """ |
| |
| def __init__(self, symbol_node, template_text, template_vars=None): |
| assert isinstance(symbol_node, SymbolNode) |
| |
| CodeNode.__init__( |
| self, template_text=template_text, template_vars=template_vars) |
| |
| self._symbol_node = symbol_node |
| |
| def _render(self, renderer, last_render_state): |
| if (self.upstream |
| and self.upstream.is_code_symbol_defined(self._symbol_node)): |
| return '' |
| |
| return super(SymbolDefinitionNode, self)._render( |
| renderer=renderer, last_render_state=last_render_state) |
| |
| def is_code_symbol_defined(self, symbol_node): |
| if symbol_node is self._symbol_node: |
| return True |
| return super(SymbolDefinitionNode, |
| self).is_code_symbol_defined(symbol_node) |