'''
Kv Compiler Source Code Generation
====================================
Generates the compiled kv source code.
'''
import ast
from collections import defaultdict
from itertools import chain
from kivy.lang.compiler.ast_parse import generate_source, BindSubGraph, \
ASTBindNodeRef
from kivy.lang.compiler.utils import StringPool
__all__ = ('KvCompiler', )
[docs]class KvCompiler(object):
'''
The compiler that generates all the source code.
This class is not part of the public API.
'''
ref = False
rebind = []
rebind_callback_pool = None
leaf_callback_pool = None
leaf_canvas_callback_pool = None
leaf_clock_callback_pool = None
binding_store_pool = None
temp_var_pool = None
kv_ctx_pool = None
kv_rule_pool = None
used_clock_rule = False
used_canvas_rule = False
used_weak_ref = False
def __init__(self, **kwargs):
super(KvCompiler, self).__init__(**kwargs)
self.rebind_callback_pool = StringPool(prefix='__kv_rebind_callback')
self.leaf_callback_pool = StringPool(prefix='__kv_leaf_callback')
self.leaf_canvas_callback_pool = StringPool(
prefix='__kv_leaf_callback_canvas')
self.leaf_clock_callback_pool = StringPool(
prefix='__kv_leaf_callback_clock')
self.binding_store_pool = StringPool(prefix='__kv_bind_store')
self.temp_var_pool = StringPool(prefix='__kv_temp_val')
self.kv_ctx_pool = StringPool(prefix='__kv_ctx')
self.kv_rule_pool = StringPool(prefix='__kv_rule')
[docs] def gen_base_rebind_node_local_variable(
self, src_code, subgraph, node, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, bind_store_name):
'''generates the source for getting the value from the bind store
on which the rebind function was bound, and saves it as a local
variable to be used by children nodes later in the rebind function.
Should only be called on the nodes of a subgraph and not its
terminal nodes. Also, should only be called on a node of the subgraph,
if this this is at the root of the rebind function. That means
we need to lookup the node's root value from the bind store.
'''
assert len(node.depends) == 1
assert node.leaf_rule is None
assert node.is_attribute
dep = node.depends[0]
# this is a root attr, just read its value into var.
# node_use_count[node] and len(node.depends_on_me)
# may be different because depends_on_me includes
# all graphs, even those in other rebind funcs, the
# former only includes those used in this func
# get temp variable for all deps that depend on this bind store value
var = temp_pool.borrow(node, nodes_use_count[node])
# a rebind function could be bound to multiple attr
# rebind nodes (e.g. `(self.x + other.y).value`) so
# we need to check if the rebind list has been init
i = subgraph.bind_store_rebind_nodes_indices[node] # rebind list index
assert subgraph.n_rebind_deps >= 1
indent = 4
# only if the graph does have deps, otherwise, there's nothing to
# lookup in the bind store as it's a global etc.
if subgraph.n_rebind_deps > 1:
src_code.append(
'{}{} = None'.format(' ' * 4, var))
# make sure the bind store is not None for the required value
src_code.append(
'{0}if {1}[{2}] is not None and {1}[{2}][0] is not None:'.
format(' ' * 4, bind_store_name, i))
indent = 8
# dep_var = temp_pool.borrow(dep, node_use_count[dep] + 1)
# node_value_var[dep] = dep_var
# code output could be optimized, but then we'd need
# to predict which subgraphs this rebind func eats
if dep not in nodes_original_ref:
assert dep not in nodes_original_ref
nodes_original_ref[dep] = dep.ref_node
dep.set_ref_node(ast.Subscript(
value=ast.Subscript(
value=ast.Name(id=bind_store_name, ctx=ast.Load()),
slice=ast.Index(value=ast.Num(n=i)), ctx=ast.Load()),
slice=ast.Index(value=ast.Num(n=0)), ctx=ast.Load()))
# save node to local variable
assignment = '{}{} = {}'.format(
' ' * indent, var, generate_source(node))
src_code.append(assignment)
src_code.append('')
nodes_temp_var_name[node] = var
assert node not in nodes_original_ref
nodes_original_ref[node] = node.ref_node
node.set_ref_node(ast.Name(id=var, ctx=ast.Load()))
[docs] def gen_non_bind_node_local_variable(
self, src_code, node, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, indent):
'''Create the local temp variable when rebinding from a node, provided
the node is neither a leaf nor a rebindable attribute. E.g. a
simple addition operation, or even a attribute, but rebind is False
for that attribute.
'''
assert node.leaf_rule is None
assert node.depends_on_me
assert not node.is_attribute or not node.rebind
# to eval node so we can bind or whatever we need to
# check if the last value(s) is not None for deps
# but only those that are not locals/globals
depends = [dep for dep in node.depends if dep.depends]
# we hit internal rebind node, performs its
# computation and store it in local var
assert nodes_use_count[node] >= 1
var = temp_pool.borrow(node, nodes_use_count[node])
# if it has no deps, it won't be under a `if`
# so no need to init variable
if depends:
src_code.append('{}{} = None'.format(' ' * indent, var))
condition = []
for dep in depends:
name = nodes_temp_var_name[dep]
condition.append('{} is not None'.format(name))
condition = '{}if {}:'.format(
' ' * indent, ' and '.join(condition))
src_code.append(condition)
indent += 4
assignment = '{}{} = {}'.format(
' ' * indent, var, generate_source(node))
src_code.append(assignment)
src_code.append('')
nodes_temp_var_name[node] = var
assert node not in nodes_original_ref
nodes_original_ref[node] = node.ref_node
node.set_ref_node(ast.Name(id=var, ctx=ast.Load()))
# we are done with this dep here for this node
for dep in depends:
temp_pool.return_back(dep)
def gen_unbind_subgraph_bindings(
self, src_code, subgraph, bind_store_name):
for node in subgraph.terminal_nodes:
# we don't need too check for double visits because
# each graph contains *all* deps of terminal attrs
for i in node.bind_store_indices:
src_code.append(
'{}__kv_bind_ref = {}[{}]'.format(
' ' * 4, bind_store_name, i))
src_code.append(
'{}if __kv_bind_ref is not None:'.format(' ' * 4))
src_code.append(
'{}__kv_obj, __kv_attr, _, __kv_uid, _, _ = __kv_bind_ref'.
format(' ' * 8))
src_code.append(
'{}if __kv_obj is not None and __kv_uid:'.format(' ' * 8))
src_code.append(
'{}__kv_obj.unbind_uid(__kv_attr, __kv_uid)'.
format(' ' * 12))
src_code.append('{}__kv_bind_ref[0] = None'.format(' ' * 12))
src_code.append('')
def gen_fbind_for_subgraph_nodes_in_rebind_callback(
self, src_code, nodes, indent, dep_name, bind_store_name, proxy):
indent2 = indent + 4
indent3 = indent + 8
bind_callback = '__kv_bind_element[2]'
if proxy:
bind_callback = '__kv_ref(__kv_bind_element[2])'
for node in nodes:
obj_attr = node.ref_node.attr
for i in node.bind_store_indices:
src_code.append(
'{}__kv_bind_element = {}[{}]'.format(
' ' * indent2, bind_store_name, i))
src_code.append(
'{}if __kv_bind_element is not None:'.
format(' ' * indent2))
bind = '__kv__fbind("{}", {})'.format(obj_attr, bind_callback)
src_code.append(
'{}__kv_bind_element[0] = {}'.
format(' ' * indent3, dep_name))
src_code.append(
'{}__kv_bind_element[3] = {}'.format(' ' * indent3, bind))
src_code.append('')
def gen_fbind_for_subgraph_nodes_for_initial_bindings(
self, src_code, nodes, indent, dep_name, parent_nodes_of_leaves,
store_indices_count, var_none, bind_store_name, proxy):
indent2 = indent + 4
for node in nodes:
obj_attr = node.ref_node.attr
assert node.count == sum(
store_indices_count[j] for j in node.bind_store_indices)
for i, callback_name in zip(
node.bind_store_indices, node.callback_names):
bind_callback = callback_name
if proxy:
bind_callback = \
'__kv_ref({})'.format(callback_name)
bind = '__kv__fbind("{}", {})'.format(obj_attr, bind_callback)
if var_none:
bind = 'None'
if node.leaf_rule is None:
indices = '()'
else:
indices = '({}, )'.format(', '.join(map(str, sorted(
parent_nodes_of_leaves[node]))))
src_code.append(
'{}{}[{}] = [{}, "{}", {}, {}, {}, {}]'.format(
' ' * indent2, bind_store_name, i, dep_name,
obj_attr, callback_name, bind, store_indices_count[i],
indices
))
src_code.append('')
[docs] def gen_subgraph_bindings(
self, src_code, subgraph, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, init_bindings,
bind_store_name, parent_nodes_of_leaves=None,
store_indices_count=None):
'''Generates the code that binds all the callbacks associated with this
subgraph.
'''
terminal_nodes_by_dep = defaultdict(list)
# terminal nodes are attributes so by definition they can and must have
# exactly one dep.
for node in subgraph.terminal_nodes:
assert len(node.depends) == 1
terminal_nodes_by_dep[node.depends[0]].append(node)
for dep, nodes in terminal_nodes_by_dep.items():
# create forward temp variables for the nodes, if used later
for node in nodes:
assert node.leaf_rule is not None or node.rebind
if node.leaf_rule is None:
nodes_temp_var_name[node] = \
temp_pool.borrow(node, nodes_use_count[node])
indent = 0 if init_bindings else 4
dep_name = nodes_temp_var_name[dep]
# check if the deps are not None
if dep.depends:
# no need to init if not under an if
for node in nodes:
if node.leaf_rule is None:
src_code.append(
'{}{} = None'.
format(' ' * indent, nodes_temp_var_name[node]))
src_code.append(
'{}if {} is not None:'.format(' ' * indent, dep_name))
indent += 4
# do we need to create a proxy for the callback?
self.used_weak_ref = self.used_weak_ref or dep.proxy
fbind_name = 'fbind_proxy' if dep.proxy else 'fbind'
src_code.append('{}__kv__fbind = getattr({}, "{}", None)'.
format(' ' * indent, dep_name, fbind_name))
src_code.append(
'{}if __kv__fbind is not None:'.format(' ' * indent))
else_bind = []
if init_bindings:
self.gen_fbind_for_subgraph_nodes_for_initial_bindings(
src_code, nodes, indent, dep_name, parent_nodes_of_leaves,
store_indices_count, False, bind_store_name, dep.proxy)
src_code.append(
'{}else:'.format(' ' * indent))
self.gen_fbind_for_subgraph_nodes_for_initial_bindings(
src_code, nodes, indent, dep_name, parent_nodes_of_leaves,
store_indices_count, True, bind_store_name, dep.proxy)
if dep.depends:
else_bind.append(
'{}else:'.format(' ' * 0))
self.gen_fbind_for_subgraph_nodes_for_initial_bindings(
else_bind, nodes, 0, dep_name, parent_nodes_of_leaves,
store_indices_count, True, bind_store_name, dep.proxy)
else:
self.gen_fbind_for_subgraph_nodes_in_rebind_callback(
src_code, nodes, indent, dep_name, bind_store_name,
dep.proxy)
# create the values of the nodes for use later, using the temp vars
for node in nodes:
if node.leaf_rule is None:
var = nodes_temp_var_name[node]
assignment = '{}{} = {}'.format(
' ' * indent, var, generate_source(node))
src_code.append(assignment)
src_code.append('')
assert node not in nodes_original_ref
nodes_original_ref[node] = node.ref_node
node.set_ref_node(ast.Name(id=var, ctx=ast.Load()))
src_code.extend(else_bind)
if dep.depends:
for _ in nodes:
temp_pool.return_back(dep)
[docs] def gen_subgraph_and_children_rebind_callback(
self, src_code, subgraph, bind_store_name):
'''
The subgraph is composed of root nodes and leaf nodes and is
of course a directed graph because the containing graph is directed.
All the leaf nodes (called terminal nodes here) are attribute nodes.
They are either rebind nodes (to which we bind a rebind function, e.g.
for `self.widget.x`, it is the `self` node with property `widget`),
or are leaf nodes (to which we bind a rule, e.g. `self.widget` with `x`
property in the example above).
All the root nodes, called simply nodes, are either attribute nodes
that are rebind, or are literals or captured global/local/nonlocal
variables. This means, that we can compute the value of any of the
terminal nodes using just the nodes of the subgraph. We don't need
anything else!
**Task**: Given a subgraph, find all the nodes in the larger graph that
depend (are children) of the terminal nodes in the subgraph. We then
bind a single rebind function, that updates all the nodes in the
dependency graph of the subgraph, whenever any of the root nodes
of the subgraph is changed.
This means we unbind, and then rebind to any new values discovered.
At the end of the rebind callback, we also executed all (once) the
rules that contain any of the nodes encountered along the way. This
ensures the rule(s) are properly executed when any of the rebind
node change.
**Algorithm**: The overall algorithm is pretty simple, we start with
the subgraph added to a queue. Then we (1) remove the first subgraph
from the queue (2) add all the subgraphs that depend on (are children
of) this subgraph to the queue, (3) use the subgraph if it has not
already been used in a previous iteration of the loop.
The exact details of how each subgraph is "used", is defined in the
function.
'''
# we keep track of all attrs we visit in the subgraph. Because
# deps are always explored before the children, if we encounter
# a attr dep that has not been visited, it means its at the root
# level in the rebind function stored in the rebind list
terminal_nodes_visited = set()
nodes_visited = set()
nodes_temp_var_name = {}
nodes_original_ref = {}
capturing_vars = set()
temp_pool = self.temp_var_pool
func_def_i = len(src_code)
src_code.append(None)
assert subgraph.terminal_nodes
assert subgraph.n_rebind_deps
# it's the same for all subgraphs in a graph
capturing_vars.add(bind_store_name)
leaf_callbacks = defaultdict(set)
subgraphs = subgraph.get_subgraph_and_children_subgraphs()
nodes_use_count = BindSubGraph.\
compute_nodes_dependency_count_for_subgraphs(subgraphs)
for current_subgraph in subgraphs:
terminal_nodes_visited.update(current_subgraph.terminal_nodes)
for node in current_subgraph.nodes:
if node in nodes_visited:
continue
nodes_visited.add(node)
# we always store stuff in local variables - it's faster
if not node.depends:
assert not node.is_attribute
# we found a global or local variable - add its name
src = nodes_temp_var_name[node] = node.src
if isinstance(node.ref_node, ast.Name):
capturing_vars.add(src)
elif node.rebind and node not in terminal_nodes_visited:
# there are no cycles, so any node from `nodes` will be
# seen only once
self.gen_base_rebind_node_local_variable(
src_code, current_subgraph, node, temp_pool,
nodes_use_count, nodes_original_ref,
nodes_temp_var_name, bind_store_name)
elif node.rebind:
assert node in nodes_temp_var_name, 'should already ' \
'have been created in terminal node stage'
else:
self.gen_non_bind_node_local_variable(
src_code, node, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, indent=4)
# unbind
self.gen_unbind_subgraph_bindings(
src_code, current_subgraph, bind_store_name)
# collect all the locations where a leaf callback is saved. Then
# we execute the callback at the end of rebind, only if at least
# of the bindings to the leaf callback is alive, i.e. is not None.
for node in current_subgraph.terminal_nodes:
if node.leaf_rule is not None and \
not node.leaf_rule.triggered_only:
assert len(node.bind_store_indices) == 1
assert len(node.callback_names) == 1
i = node.bind_store_indices[0]
f_location = '{}[{}]'.format(bind_store_name, i)
leaf_callbacks[node.callback_names[0]].add(f_location)
self.gen_subgraph_bindings(
src_code, current_subgraph, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, False,
bind_store_name)
current_subgraph.remove_from_count(nodes_use_count)
for node, count in nodes_use_count.items():
assert not count, '{}, {} should be zero'.format(node, count)
for name, locations in leaf_callbacks.items():
src_code.append(
'{}__kv_callback = {}'.format(' ' * 4, ' or '.join(locations)))
src_code.append('{}if __kv_callback is not None:'.format(' ' * 4))
src_code.append('{}__kv_callback[2]()'.format(' ' * 8))
src_code.append('')
for node, origin_node in nodes_original_ref.items():
node.set_ref_node(origin_node)
s = ', '.join(
'{0}={0}'.format(item) for item in sorted(capturing_vars))
s = ', {}'.format(s) if s else ''
func_def = 'def {}(*__kv_largs{}):'.format(
subgraph.rebind_callback_name, s)
src_code[func_def_i] = func_def
def gen_rebind_callbacks(
self, ctx_name, nodes, subgraphs, bind_store_name,
bind_store_size):
if bind_store_name is not None:
src_code = [
'{} = [None, ] * {}'.format(bind_store_name, bind_store_size),
'{}.bind_store = {}'.format(ctx_name, bind_store_name),
'']
else:
src_code = ['{}.bind_store = None'.format(ctx_name), '']
# count the number of places each node is used. E.g. in
# `self.x + self.y`, `self` is used twice. Keep in mind that nodes
# are unique
nodes_use_count = BindSubGraph.compute_nodes_dependency_count(nodes)
for subgraph in subgraphs:
# we create a rebind func for each subgraph that roots in attr
if not subgraph.n_rebind_deps:
# It means that all the deps are not attributes, meaning
# thet are e.g. variables etc, so that we never create a
# rebind func for them
# see further down why len(node.depends_on_me) is correct
subgraph.remove_from_count(nodes_use_count)
continue
func_src_code = []
self.gen_subgraph_and_children_rebind_callback(
func_src_code, subgraph, bind_store_name)
# remove all that depend on the node because either the dep
# is in the same subgraph, or it's in the terminal of the
# subgraph. In the latter case, binding and attr eval happens
# in this subgraph iteration, so this node won't be used again
subgraph.remove_from_count(nodes_use_count)
src_code.extend(func_src_code)
for node, count in nodes_use_count.items():
assert not count, '{}, {} should be zero'.format(node, count)
s = ', '.join(sorted(
subgraph.rebind_callback_name
for subgraph in subgraphs if subgraph.rebind_callback_name))
comma = ', ' if s else ''
src_code.append('{}.rebind_functions = ({}{})'.format(
ctx_name, s, comma))
src_code.append('')
return src_code
def gen_leaf_rules(self, ctx, ctx_name, create_rules, bind_store_name):
rule_creation = []
rule_finalization = []
kv_rule_pool = self.kv_rule_pool
canvas_pool = self.leaf_canvas_callback_pool
clock_pool = self.leaf_clock_callback_pool
for rule_idx, (rule, rule_nodes) in enumerate(
zip(ctx.rules, ctx.transformer.nodes_by_rule)):
name = kv_rule_pool.borrow_persistent()
if create_rules:
delay = rule.delay
if delay is None:
delay_arg = 'None'
elif delay == 'canvas':
delay_arg = '"canvas"'
else:
delay_arg = '{}'.format(rule.delay)
rule_creation.append('{} = __KvRule()'.format(name))
rule_creation.append('{}.delay = {}'.format(name, delay_arg))
if rule.name:
rule_creation.append(
'{}.name = "{}"'.format(name, rule.name))
else:
rule_creation.append('{}.name = None'.format(name))
rule_creation.append('{}.add_rule({})'.format(ctx_name, name))
if rule.with_var_name_ast is not None:
rule.with_var_name_ast.id = name
if rule_nodes:
callback_name = rule.callback_name
if delay is None:
_callback_name = callback_name
elif delay == 'canvas':
_callback_name = canvas_pool.borrow_persistent()
self.used_canvas_rule = True
else:
_callback_name = clock_pool.borrow_persistent()
self.used_clock_rule = True
rule._callback_name = _callback_name
rule_finalization.append(
'{}.callback = {}'.format(name, callback_name))
if callback_name != _callback_name:
rule_finalization.append(
'{}._callback = {}'.format(name, _callback_name))
assert rule.delay is not None
else:
rule_finalization.append(
'{}._callback = None'.format(name))
else:
rule_finalization.append('{}.callback = None'.format(name))
rule_finalization.append(
'{}._callback = None'.format(name))
else:
rule_creation.append(
'{} = {}.rules[{}]'.format(name, ctx_name, rule_idx))
leaf_indices = []
for node in rule_nodes:
if node.leaf_rule is not None:
assert len(node.bind_store_indices) == 1, "it's a leaf"
leaf_indices.append(node.bind_store_indices[0])
if rule_nodes:
assert leaf_indices
assert bind_store_name
rule_finalization.append(
'{}.bind_store = {}'.format(name, bind_store_name))
rule_finalization.append(
'{}.bind_store_leaf_indices = ({}, )'.format(
name, ', '.join(map(str, sorted(leaf_indices)))))
else:
rule_finalization.append('{}.bind_store = None'.format(name))
rule_finalization.append(
'{}.bind_store_leaf_indices = None'.format(name))
rule_creation.append('')
rule_finalization.append('')
return rule_creation, rule_finalization
def gen_leaf_callbacks(self, ctx):
src_code = []
for rule_idx, (rule, rule_nodes) in enumerate(
zip(ctx.rules, ctx.transformer.nodes_by_rule)):
if not rule_nodes:
continue
name = None
if rule.with_var_name_ast is not None:
name = rule.with_var_name_ast.id
rule.captures.add(name)
delay = rule.delay
callback_name = rule.callback_name
_callback_name = rule._callback_name
s = ', '.join(
'{0}={0}'.format(name) for name in sorted(rule.captures))
s = ', {}'.format(s) if s else ''
func_def = 'def {}(*__kv_largs{}):'.format(_callback_name, s)
src_code.append(func_def)
if name is not None:
src_code.append(
'{}{}.largs = __kv_largs'.format(' ' * 4, name))
for line in rule.src.splitlines():
src_code.append('{}{}'.format(' ' * 4, line))
src_code.append('')
if delay is None:
continue
elif delay == 'canvas':
func_def = 'def {}(*__kv_largs, __kv_canvas_item=[{}, ' \
'None, None]):'.format(callback_name, _callback_name)
body = '{}__kv_add_graphics_callback(__kv_canvas_item, ' \
'__kv_largs)'.format(' ' * 4)
src_code.append(func_def)
src_code.append(body)
src_code.append('')
else:
line = '{} = __kv_Clock.create_trigger({}, {})'.\
format(callback_name, _callback_name, delay)
src_code.append(line)
src_code.append('')
return src_code
def gen_initial_bindings(self, ctx, nodes, subgraphs, bind_store_name):
leaf_parents_indices = ASTBindNodeRef.\
get_all_bind_store_indices_for_leaf_and_parents(nodes)
store_indices_count = ASTBindNodeRef.\
get_num_binds_for_bind_store_indices(leaf_parents_indices)
src_code = []
# we keep track of all attrs we visit in the subgraph. Because
# deps are always explored before the children, if we encounter
# a attr dep that has not been visited, it means its at the root
# level in the rebind function stored in the rebind list
terminal_nodes_visited = set()
nodes_visited = set()
nodes_temp_var_name = {}
nodes_original_ref = {}
temp_pool = self.temp_var_pool
# it's the same for all nodes in a graph
assert subgraphs
nodes_use_count = BindSubGraph.compute_nodes_dependency_count(nodes)
for subgraph in subgraphs:
assert subgraph.terminal_nodes
terminal_nodes_visited.update(subgraph.terminal_nodes)
for node in subgraph.nodes:
if node in nodes_visited:
continue
nodes_visited.add(node)
# we always store stuff in local variables - it's faster
if not node.depends:
assert not node.is_attribute
# we found a global or local variable - add its name
nodes_temp_var_name[node] = node.src
elif node.rebind:
assert node in terminal_nodes_visited
assert node in nodes_temp_var_name, 'should already ' \
'have been created in terminal node stage'
else:
self.gen_non_bind_node_local_variable(
src_code, node, temp_pool, nodes_use_count,
nodes_original_ref, nodes_temp_var_name, indent=0)
self.gen_subgraph_bindings(
src_code, subgraph, temp_pool,
nodes_use_count, nodes_original_ref, nodes_temp_var_name,
True, bind_store_name,
parent_nodes_of_leaves=leaf_parents_indices,
store_indices_count=store_indices_count)
subgraph.remove_from_count(nodes_use_count)
for node, count in nodes_use_count.items():
assert not count, '{}, {} should be zero'.format(node, count)
for node, origin_node in nodes_original_ref.items():
node.set_ref_node(origin_node)
src_code.append('')
return src_code
def gen_temp_vars_creation_deletion(self):
# we must create the variables at the root indentation so that we can
# delete it at the root indentation at the end, in case some of the
# variables are only actually used under conditionals, so we make sure
# it always exists.
assert not self.temp_var_pool.get_num_borrowed()
variables = list(sorted(chain(
self.temp_var_pool.get_available_items(),
self.kv_rule_pool.get_all_items())))
if variables:
var_create = ' = '.join(variables)
var_clear = ', '.join(variables)
return ['{} = None'.format(var_create), ''], [
'del {}'.format(var_clear), '']
return [], []
def gen_reinit_after_callbacks(self, nodes, bind_store_name):
src_code = []
leaf_callbacks = defaultdict(set)
for node in nodes:
if node.leaf_rule is None or node.leaf_rule.triggered_only:
continue
assert len(node.bind_store_indices) == 1
assert len(node.callback_names) == 1
i = node.bind_store_indices[0]
f_location = '{}[{}]'.format(bind_store_name, i)
leaf_callbacks[node.callback_names[0]].add(f_location)
for name, locations in leaf_callbacks.items():
src_code.append(
'__kv_callback = {}'.format(' or '.join(locations)))
src_code.append('if __kv_callback is not None:')
src_code.append('{}__kv_callback[2]()'.format(' ' * 4))
src_code.append('')
return src_code
def generate_bindings(self, ctx, ctx_name, create_rules):
if not ctx_name:
ctx_name = self.kv_ctx_pool.borrow_persistent()
subgraphs = BindSubGraph.get_ordered_subgraphs(ctx.transformer)
bind_store_size = BindSubGraph.\
populate_bind_store_size_indices_and_callback_names(
subgraphs, create_rules, ctx.rules, ctx.transformer,
self.rebind_callback_pool, self.leaf_callback_pool)
bind_store_name = None
if bind_store_size:
bind_store_name = self.binding_store_pool.borrow_persistent()
nodes = list(chain(*ctx.transformer.nodes_by_rule))
funcs = self.gen_rebind_callbacks(
ctx_name, nodes, subgraphs, bind_store_name, bind_store_size)
rule_creation, rule_finalization = self.gen_leaf_rules(
ctx, ctx_name, create_rules, bind_store_name)
if create_rules:
res = self.gen_leaf_callbacks(ctx)
funcs.extend(res)
if subgraphs:
assert nodes
else:
assert not nodes
if subgraphs:
res = self.gen_initial_bindings(
ctx, nodes, subgraphs, bind_store_name)
funcs.extend(res)
if ctx.reinit_after:
reinit = self.gen_reinit_after_callbacks(nodes, bind_store_name)
else:
reinit = []
return ctx_name, funcs, rule_creation, rule_finalization, reinit