Permalink
Browse files

added support for local aliasing of some attributes. the technique us…

…ed is not very good but covers at least some of the more common use cases

--HG--
branch : trunk
  • Loading branch information...
1 parent 9d42abf commit b3a1fcfa3dd261de54b3b669a1d1fb1a5c859a9b @mitsuhiko mitsuhiko committed May 15, 2008
Showing with 99 additions and 3 deletions.
  1. +29 −0 docs/api.rst
  2. +67 −1 jinja2/compiler.py
  3. +3 −2 jinja2/nodes.py
View
@@ -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.
View
@@ -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)
@@ -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 -
@@ -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)
@@ -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()
@@ -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 \
View
@@ -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)' % (

0 comments on commit b3a1fcf

Please sign in to comment.