Skip to content

Commit

Permalink
added support for local aliasing of some attributes. the technique us…
Browse files Browse the repository at this point in the history
…ed is not very good but covers at least some of the more common use cases

--HG--
branch : trunk
  • Loading branch information
mitsuhiko committed May 15, 2008
1 parent 9d42abf commit b3a1fcf
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 3 deletions.
29 changes: 29 additions & 0 deletions docs/api.rst
Expand Up @@ -369,3 +369,32 @@ context. This is the place where you can put variables and functions
that should be available all the time. Additionally :attr:`Template.globals`
exist that are variables available to a specific template that are available
to all :meth:`~Template.render` calls.


Jinja2 Semantics
----------------

Jinja2 behaves like regular Python code in the runtime with some additional
restrictions that allow the template engine to optimize some common idoms.
The context is semi immutable. Only Jinja2 itself may modify it, if other
code (such as :func:`contextfunction`\s) modify it the changes won't show up
as expected.

Another change to regular Python is that Jinja2 sees attributes as immutable.
It assumes that every time it accesses `foo.bar`, bar will look the same as
long as `foo` was not overriden. In some situations Jinja will take advantage
of that to internally substitute continuous lookups to the same attributes
with a copy of the attribute.

For example in the following template the `post.user` attribute is looked up
once:

.. sourcecode:: html+jinja

{% if post.user %}
<h3>{{ post.user.username }}</h3>
<p>Location: {{ post.user.location }}</p>
{% endif %}

That said, the objects passed to the template may not generate different
values every time a property is accessed.
68 changes: 67 additions & 1 deletion jinja2/compiler.py
Expand Up @@ -107,6 +107,9 @@ def __init__(self):
# names that are declared by parameters
self.declared_parameter = set()

# static subscribes
self.static_subscribes = {}

def add_special(self, name):
"""Register a special name like `loop`."""
self.undeclared.discard(name)
Expand Down Expand Up @@ -149,14 +152,18 @@ def __init__(self, parent=None):
# the name of the block we're in, otherwise None.
self.block = parent and parent.block or None

# node overlays. see `CodeGenerator.overlay` for more details
self.overlays = {}

# the parent of this frame
self.parent = parent

if parent is not None:
self.identifiers.declared.update(
parent.identifiers.declared |
parent.identifiers.declared_locally |
parent.identifiers.declared_parameter
parent.identifiers.declared_parameter |
parent.identifiers.undeclared
)
self.identifiers.outer_undeclared.update(
parent.identifiers.undeclared -
Expand Down Expand Up @@ -253,6 +260,25 @@ def visit_Name(self, node):
self.identifiers.is_declared(node.name, self.hard_scope):
self.identifiers.undeclared.add(node.name)

def visit_Subscript(self, node):
"""Under some circumstances subscripts are aliased with local names:
- the subscript node is either already aliased or a name that was not
reassigned.
- and the subscription argument is a constant string.
"""
self.generic_visit(node)
if isinstance(node.arg, nodes.Const) and \
isinstance(node.arg.value, basestring) and \
(isinstance(node.node, nodes.Name) and
node.node.name not in (self.identifiers.declared_locally |
self.identifiers.declared_parameter)) or \
node.node in self.identifiers.static_subscribes:
if node in self.identifiers.static_subscribes:
self.identifiers.static_subscribes[node] += 1
else:
self.identifiers.static_subscribes[node] = 1

def visit_Macro(self, node):
self.generic_visit(node)
self.identifiers.declared_locally.add(node.name)
Expand Down Expand Up @@ -468,11 +494,47 @@ def touch_comma():
self.write('**')
self.visit(node.dyn_kwargs, frame)

def overlay(self, node, frame):
"""Visit an overlay and return `True` or `False` if the overlay
does not exist or is exhausted. An overlay is used to replace a
node with another one (or an identifier) N times. This is for
example used to replace static subscribes before reassignments
of a name that occour more than one time. If a node has overlays
it's important that this method is called, otherwise the count
will be out of sync and the generated code is broken.
"""
if node not in frame.overlays:
return False
overlay, count = frame.overlays[node]
if count is not None and count <= 0:
return False
if isinstance(overlay, basestring):
self.write(overlay)
else:
self.visit(overlay, frame)
frame.overlays[node] = (overlay, count - 1)
return True

def pull_locals(self, frame):
"""Pull all the references identifiers into the local scope."""
for name in frame.identifiers.undeclared:
self.writeline('l_%s = context.resolve(%r)' % (name, name))

# find all the static subscribes with a count > 2 and
# order them properly so that subscribes depending on other
# subscribes are handled later.
static_subscribes = [(node, count) for node, count in
frame.identifiers.static_subscribes.items() if
count >= 2]
if static_subscribes:
static_subscribes.sort(key=lambda x: type(x[0].node)
is nodes.Subscript)
for node, count in static_subscribes:
ident = self.temporary_identifier()
self.writeline(ident + ' = ')
self.visit(node, frame)
frame.overlays[node] = (ident, count)

def pull_dependencies(self, nodes):
"""Pull all the dependencies."""
visitor = DependencyFinderVisitor()
Expand Down Expand Up @@ -1264,6 +1326,10 @@ def visit_Operand(self, node, frame):
self.visit(node.expr, frame)

def visit_Subscript(self, node, frame):
# subscripts can have overlays
if self.overlay(node, frame):
return

# slices or integer subscriptions bypass the subscribe
# method if we can determine that at compile time.
if isinstance(node.arg, nodes.Slice) or \
Expand Down
5 changes: 3 additions & 2 deletions jinja2/nodes.py
Expand Up @@ -234,15 +234,16 @@ def freeze(self):
todo.extend(node.iter_child_nodes())

def __eq__(self, other):
return type(self) is type(other) and self.__dict__ == other.__dict__
return type(self) is type(other) and \
tuple(self.iter_fields()) == tuple(other.iter_fields())

def __ne__(self, other):
return not self.__eq__(other)

def __hash__(self):
if not self.frozen:
raise TypeError('unfrozen nodes are unhashable')
return hash(tuple(self.__dict__.items()))
return hash(tuple(self.iter_fields()))

def __repr__(self):
return '%s(%s)' % (
Expand Down

0 comments on commit b3a1fcf

Please sign in to comment.