From: Armin Ronacher Date: Mon, 31 Mar 2008 18:01:08 +0000 (+0200) Subject: first version of new parser X-Git-Tag: 2.0rc1~215 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=82b3f3d512eb3f8fe75f49f8dcd0ef2c07740860;p=jinja2.git first version of new parser --HG-- branch : trunk --- diff --git a/jinja2/__init__.py b/jinja2/__init__.py index 94949c1..b54c504 100644 --- a/jinja2/__init__.py +++ b/jinja2/__init__.py @@ -56,3 +56,4 @@ :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +from jinja2.environment import Environment diff --git a/jinja2/datastructure.py b/jinja2/datastructure.py index b20acd1..2777df5 100644 --- a/jinja2/datastructure.py +++ b/jinja2/datastructure.py @@ -9,7 +9,8 @@ :license: BSD, see LICENSE for more details. """ from operator import itemgetter -from jinja.exceptions import TemplateSyntaxError, TemplateRuntimeError +from collections import deque +from jinja2.exceptions import TemplateSyntaxError, TemplateRuntimeError _missing = object() @@ -20,10 +21,10 @@ class Token(tuple): Token class. """ __slots__ = () - lineno, type, value = map(itemgetter, range(3)) + lineno, type, value = (property(itemgetter(x)) for x in range(3)) def __new__(cls, lineno, type, value): - return tuple.__new__(cls, (lineno, type, value)) + return tuple.__new__(cls, (lineno, intern(str(type)), value)) def __str__(self): from jinja.lexer import keywords, reverse_operators @@ -75,7 +76,7 @@ class TokenStream(object): def __init__(self, generator, filename): self._next = generator.next - self._pushed = [] + self._pushed = deque() self.current = Token(1, 'initial', '') self.filename = filename self.next() @@ -83,11 +84,6 @@ class TokenStream(object): def __iter__(self): return TokenStreamIterator(self) - def lineno(self): - """The current line number.""" - return self.current.lineno - lineno = property(lineno, doc=lineno.__doc__) - def __nonzero__(self): """Are we at the end of the tokenstream?""" return bool(self._pushed) or self.current.type != 'eof' @@ -98,6 +94,14 @@ class TokenStream(object): """Push a token back to the stream.""" self._pushed.append(token) + def look(self): + """Look at the next token.""" + old_token = self.current + next = self.next() + self.push(old_token) + self.push(next) + return next + def skip(self, n): """Got n tokens ahead.""" for x in xrange(n): @@ -106,7 +110,7 @@ class TokenStream(object): def next(self): """Go one token ahead.""" if self._pushed: - self.current = self._pushed.pop() + self.current = self._pushed.popleft() elif self.current.type != 'eof': try: self.current = self._next() @@ -120,7 +124,7 @@ class TokenStream(object): def expect(self, token_type, token_value=_missing): """Expect a given token type and return it""" - if self.current.type != token_type: + if self.current.type is not token_type: raise TemplateSyntaxError("expected token %r, got %r" % (token_type, self.current.type), self.current.lineno, diff --git a/jinja2/environment.py b/jinja2/environment.py index a387ffa..77f6047 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -1,28 +1,16 @@ # -*- coding: utf-8 -*- """ - jinja.environment - ~~~~~~~~~~~~~~~~~ + jinja2.environment + ~~~~~~~~~~~~~~~~~~ Provides a class that holds runtime and parsing time options. :copyright: 2007 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from jinja.lexer import Lexer -from jinja.parser import Parser -from jinja.loaders import LoaderWrapper -from jinja.datastructure import SilentUndefined, Markup, Context, FakeTranslator -from jinja.utils import collect_translations, get_attribute -from jinja.exceptions import FilterNotFound, TestNotFound, \ - SecurityException, TemplateSyntaxError -from jinja.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE - - -__all__ = ['Environment'] - - -#: minor speedup -_getattr = getattr +from jinja2.lexer import Lexer +from jinja2.parser import Parser +from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE class Environment(object): @@ -42,7 +30,7 @@ class Environment(object): comment_start_string='{#', comment_end_string='#}', trim_blocks=False, - loader=None): + template_charset='utf-8'): """ Here the possible initialization parameters: @@ -60,7 +48,7 @@ class Environment(object): `trim_blocks` If this is set to ``True`` the first newline after a block is removed (block, not variable tag!). Defaults to ``False``. - `loader` The loader for this environment. + `template_charset` the charset of the templates. ========================= ============================================ """ @@ -72,10 +60,7 @@ class Environment(object): self.comment_start_string = comment_start_string self.comment_end_string = comment_end_string self.trim_blocks = trim_blocks - - # other stuff self.template_charset = template_charset - self.loader = loader # defaults self.filters = DEFAULT_FILTERS.copy() @@ -85,13 +70,6 @@ class Environment(object): # create lexer self.lexer = Lexer(self) - def loader(self, value): - """ - Get or set the template loader. - """ - self._loader = LoaderWrapper(self, value) - loader = property(lambda s: s._loader, loader, doc=loader.__doc__) - def parse(self, source, filename=None): """ Parse the sourcecode and return the abstract syntax tree. This tree diff --git a/jinja2/lexer.py b/jinja2/lexer.py index 7752bee..515508b 100644 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@ -16,8 +16,8 @@ """ import re import unicodedata -from jinja.datastructure import TokenStream, Token -from jinja.exceptions import TemplateSyntaxError +from jinja2.datastructure import TokenStream, Token +from jinja2.exceptions import TemplateSyntaxError from weakref import WeakValueDictionary @@ -232,7 +232,7 @@ class Lexer(object): (whitespace_re, None, None), (float_re, 'float', None), (integer_re, 'integer', None), - ('%s' % '|'.join(sorted(keywords, key=lambda x: -len(x))), + (c('%s' % '|'.join(sorted(keywords, key=lambda x: -len(x)))), 'keyword', None), (name_re, 'name', None), (string_re, 'string', None), @@ -343,7 +343,7 @@ class Lexer(object): except UnicodeError: pass elif token == 'keyword': - token = str(value) + token = value elif token == 'name': value = str(value) elif token == 'string': diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 1e20096..974b4ed 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -14,6 +14,7 @@ """ import operator from itertools import chain, izip +from collections import deque from copy import copy @@ -34,6 +35,21 @@ _uaop_to_func = { } +def set_ctx(node, ctx): + """ + Reset the context of a node and all child nodes. Per default the parser + will all generate nodes that have a 'load' context as it's the most common + one. This method is used in the parser to set assignment targets and + other nodes to a store context. + """ + todo = deque([node]) + while todo: + node = todo.popleft() + if 'ctx' in node._fields: + node.ctx = ctx + todo.extend(node.iter_child_nodes()) + + class Impossible(Exception): """ Raised if the node could not perform a requested action. @@ -120,7 +136,7 @@ class Template(Node): """ Node that represents a template. """ - _fields = ('extends', 'body') + _fields = ('body',) class Output(Stmt): @@ -142,7 +158,7 @@ class For(Stmt): """ A node that represents a for loop """ - _fields = ('item', 'seq', 'body', 'else_', 'recursive') + _fields = ('target', 'iter', 'body', 'else_', 'recursive') class If(Stmt): @@ -208,6 +224,13 @@ class ExprStmt(Stmt): _fields = ('node',) +class Assign(Stmt): + """ + Assigns an expression to a target. + """ + _fields = ('target', 'node') + + class Expr(Node): """ Baseclass for all expressions. @@ -262,7 +285,7 @@ class Name(Expr): """ any name such as {{ foo }} """ - _fields = ('name',) + _fields = ('name', 'ctx') def can_assign(self): return True @@ -289,7 +312,7 @@ class Tuple(Literal): For loop unpacking and some other things like multiple arguments for subscripts. """ - _fields = ('items',) + _fields = ('items', 'ctx') def as_const(self): return tuple(x.as_const() for x in self.items) @@ -350,11 +373,18 @@ class Filter(Expr): _fields = ('node', 'filters') +class FilterCall(Expr): + """ + {{ |bar() }} + """ + _fields = ('name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') + + class Test(Expr): """ {{ foo is lower }} """ - _fields = ('node', 'name', 'args') + _fields = ('name', 'args', 'kwargs', 'dyn_args', 'dyn_kwargs') class Call(Expr): @@ -368,7 +398,7 @@ class Subscript(Expr): """ {{ foo.bar }} and {{ foo['bar'] }} etc. """ - _fields = ('node', 'arg') + _fields = ('node', 'arg', 'ctx') def as_const(self): try: @@ -404,6 +434,13 @@ class Compare(Expr): _fields = ('expr', 'ops') +class Operand(Helper): + """ + Operator + expression. + """ + _fields = ('op', 'expr') + + class Mul(BinExpr): """ {{ foo * bar }} diff --git a/jinja2/parser.py b/jinja2/parser.py index d981c75..0c4edfc 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -8,12 +8,20 @@ :copyright: 2008 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ -from jinja import nodes -from jinja.exceptions import TemplateSyntaxError +from jinja2 import nodes +from jinja2.exceptions import TemplateSyntaxError __all__ = ['Parser'] +_statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'include']) +_compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq', 'in']) +_tuple_edge_tokens = set(['rparen', 'block_end', 'variable_end', 'in', + 'semicolon', 'recursive']) +_statement_end_tokens = set(['elif', 'else', 'endblock', 'endfilter', + 'endfor', 'endif', 'endmacro', + 'endcall', 'block_end']) + class Parser(object): """ @@ -35,5 +43,527 @@ class Parser(object): self.no_variable_block = self.environment.lexer.no_variable_block self.stream = environment.lexer.tokenize(source, filename) - def parse(self): + def end_statement(self): + """Make sure that the statement ends properly.""" + if self.stream.current.type is 'semicolon': + self.stream.next() + elif self.stream.current.type not in _statement_end_tokens: + raise TemplateSyntaxError('ambigous end of statement', + self.stream.current.lineno, + self.filename) + + def parse_statement(self): + """Parse a single statement.""" + token_type = self.stream.current.type + if token_type in _statement_keywords: + return getattr(self, 'parse_' + token_type)() + elif token_type is 'call': + self.stream.next() + return self.parse_call_block() + lineno = self.stream.current.lineno + expr = self.parse_expression() + if self.stream.current.type == 'assign': + return self.parse_assign(expr) + self.end_statement() + return nodes.ExprStmt(expr, lineno=lineno) + + def parse_assign(self, target): + """Parse an assign statement.""" + lineno = self.stream.expect('assign').lineno + if not target.can_assign(): + raise TemplateSyntaxError("can't assign to '%s'" % + target, target.lineno, + self.filename) + expr = self.parse_tuple() + self.end_statement() + nodes.set_ctx(target, 'store') + return nodes.Assign(target, expr, lineno=lineno) + + def parse_statements(self, end_tokens, drop_needle=False): + """ + Parse multiple statements into a list until one of the end tokens + is reached. This is used to parse the body of statements as it + also parses template data if appropriate. + """ + # the first token may be a colon for python compatibility + if self.stream.current.type is 'colon': + self.stream.next() + + if self.stream.current.type is 'block_end': + self.stream.next() + result = self.subparse(end_tokens) + else: + result = [] + while self.stream.current.type not in end_tokens: + result.append(self.parse_statement()) + if drop_needle: + self.stream.next() + return result + + def parse_for(self): + """Parse a for loop.""" + lineno = self.stream.expect('for').lineno + target = self.parse_tuple(simplified=True) + nodes.set_ctx(target, 'store') + self.stream.expect('in') + iter = self.parse_tuple() + if self.stream.current.type is 'recursive': + self.stream.next() + recursive = True + else: + recursive = False + body = self.parse_statements(('endfor', 'else')) + token_type = self.stream.current.type + self.stream.next() + if token_type is 'endfor': + else_ = [] + else: + else_ = self.parse_statements(('endfor',), drop_needle=True) + return nodes.For(target, iter, body, else_, False, lineno=lineno) + + def parse_if(self): + pass + + def parse_block(self): + pass + + def parse_extends(self): + pass + + def parse_include(self): pass + + def parse_call_block(self): + pass + + def parse_expression(self): + """Parse an expression.""" + return self.parse_condexpr() + + def parse_condexpr(self): + lineno = self.stream.current.lineno + expr1 = self.parse_or() + while self.stream.current.type is 'if': + self.stream.next() + expr2 = self.parse_or() + self.stream.expect('else') + expr3 = self.parse_condexpr() + expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno) + lineno = self.stream.current.lineno + return expr1 + + def parse_or(self): + lineno = self.stream.current.lineno + left = self.parse_and() + while self.stream.current.type is 'or': + self.stream.next() + right = self.parse_and() + left = nodes.Or(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_and(self): + lineno = self.stream.current.lineno + left = self.parse_compare() + while self.stream.current.type is 'and': + self.stream.next() + right = self.parse_compare() + left = nodes.And(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_compare(self): + lineno = self.stream.current.lineno + expr = self.parse_add() + ops = [] + while 1: + token_type = self.stream.current.type + if token_type in _compare_operators: + self.stream.next() + ops.append(nodes.Operand(token_type, self.parse_add())) + elif token_type is 'not' and self.stream.look().type is 'in': + self.stream.skip(2) + ops.append(nodes.Operand('notin', self.parse_add())) + else: + break + lineno = self.stream.current.lineno + if not ops: + return expr + return nodes.Compare(expr, ops, lineno=lineno) + + def parse_add(self): + lineno = self.stream.current.lineno + left = self.parse_sub() + while self.stream.current.type is 'add': + self.stream.next() + right = self.parse_sub() + left = nodes.Add(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_sub(self): + lineno = self.stream.current.lineno + left = self.parse_concat() + while self.stream.current.type is 'sub': + self.stream.next() + right = self.parse_concat() + left = nodes.Sub(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_concat(self): + lineno = self.stream.current.lineno + args = [self.parse_mul()] + while self.stream.current.type is 'tilde': + self.stream.next() + args.append(self.parse_mul()) + if len(args) == 1: + return args[0] + return nodes.Concat(args, lineno=lineno) + + def parse_mul(self): + lineno = self.stream.current.lineno + left = self.parse_div() + while self.stream.current.type is 'mul': + self.stream.next() + right = self.parse_div() + left = nodes.Mul(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_div(self): + lineno = self.stream.current.lineno + left = self.parse_floordiv() + while self.stream.current.type is 'div': + self.stream.next() + right = self.parse_floordiv() + left = nodes.Floor(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_floordiv(self): + lineno = self.stream.current.lineno + left = self.parse_mod() + while self.stream.current.type is 'floordiv': + self.stream.next() + right = self.parse_mod() + left = nodes.FloorDiv(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_mod(self): + lineno = self.stream.current.lineno + left = self.parse_pow() + while self.stream.current.type is 'mod': + self.stream.next() + right = self.parse_pow() + left = nodes.Mod(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_pow(self): + lineno = self.stream.current.lineno + left = self.parse_unary() + while self.stream.current.type is 'pow': + self.stream.next() + right = self.parse_unary() + left = nodes.Pow(left, right, lineno=lineno) + lineno = self.stream.current.lineno + return left + + def parse_unary(self): + token_type = self.stream.current.type + lineno = self.stream.current.lineno + if token_type is 'not': + self.stream.next() + node = self.parse_unary() + return nodes.Neg(node, lineno=lineno) + if token_type is 'sub': + self.stream.next() + node = self.parse_unary() + return nodes.Sub(node, lineno=lineno) + if token_type is 'add': + self.stream.next() + node = self.parse_unary() + return nodes.Pos(node, lineno=lineno) + return self.parse_primary() + + def parse_primary(self, parse_postfix=True): + token = self.stream.current + if token.type is 'name': + if token.value in ('true', 'false'): + node = nodes.Const(token.value == 'true', lineno=token.lineno) + elif token.value == 'none': + node = nodes.Const(None, lineno=token.lineno) + else: + node = nodes.Name(token.value, 'load', lineno=token.lineno) + self.stream.next() + elif token.type in ('integer', 'float', 'string'): + self.stream.next() + node = nodes.Const(token.value, lineno=token.lineno) + elif token.type is 'lparen': + self.stream.next() + node = self.parse_tuple() + self.stream.expect('rparen') + elif token.type is 'lbracket': + node = self.parse_list() + elif token.type is 'lbrace': + node = self.parse_dict() + else: + raise TemplateSyntaxError("unexpected token '%s'" % + (token,), token.lineno, + self.filename) + if parse_postfix: + node = self.parse_postfix(node) + return node + + def parse_tuple(self, enforce=False, simplified=False): + """ + Parse multiple expressions into a tuple. This can also return + just one expression which is not a tuple. If you want to enforce + a tuple, pass it enforce=True (currently unused). + """ + lineno = self.stream.current.lineno + parse = simplified and self.parse_primary or self.parse_expression + args = [] + is_tuple = False + while 1: + if args: + self.stream.expect('comma') + if self.stream.current.type in _tuple_edge_tokens: + break + args.append(parse()) + if self.stream.current.type is not 'comma': + break + is_tuple = True + lineno = self.stream.current.lineno + if not is_tuple and args: + if enforce: + raise TemplateSyntaxError('tuple expected', lineno, + self.filename) + return args[0] + return nodes.Tuple(args, 'load', lineno=lineno) + + def parse_list(self): + token = self.stream.expect('lbracket') + items = [] + while self.stream.current.type is not 'rbracket': + if items: + self.stream.expect('comma') + if self.stream.current.type == 'rbracket': + break + items.append(self.parse_expression()) + self.stream.expect('rbracket') + return nodes.List(items, lineno=token.lineno) + + def parse_dict(self): + token = self.stream.expect('lbrace') + items = [] + while self.stream.current.type is not 'rbrace': + if items: + self.stream.expect('comma') + if self.stream.current.type == 'rbrace': + break + key = self.parse_expression() + self.stream.expect('colon') + value = self.parse_expression() + items.append(nodes.Pair(key, value, lineno=key.lineno)) + self.stream.expect('rbrace') + return nodes.Dict(items, token.lineno, self.filename) + + def parse_postfix(self, node): + while 1: + token_type = self.stream.current.type + if token_type is 'dot' or token_type is 'lbracket': + node = self.parse_subscript(node) + elif token_type is 'lparen': + node = self.parse_call(node) + elif token_type is 'pipe': + node = self.parse_filter(node) + elif token_type is 'is': + node = self.parse_test(node) + else: + break + return node + + def parse_subscript(self, node): + token = self.stream.next() + if token.type is 'dot': + if token.type not in ('name', 'integer'): + raise TemplateSyntaxError('expected name or number', + token.lineno, self.filename) + arg = nodes.Const(token.value, lineno=token.lineno) + self.stream.next() + elif token.type is 'lbracket': + args = [] + while self.stream.current.type is not 'rbracket': + if args: + self.stream.expect('comma') + args.append(self.parse_subscribed()) + self.stream.expect('rbracket') + if len(args) == 1: + arg = args[0] + else: + arg = nodes.Tuple(args, lineno, self.filename) + else: + raise TemplateSyntaxError('expected subscript expression', + self.lineno, self.filename) + return nodes.Subscript(node, arg, 'load', lineno=token.lineno) + + def parse_subscribed(self): + lineno = self.stream.current.lineno + + if self.stream.current.type is 'colon': + self.stream.next() + args = [None] + else: + node = self.parse_expression() + if self.stream.current.type is not 'colon': + return node + self.stream.next() + args = [node] + + if self.stream.current.type is 'colon': + args.append(None) + elif self.stream.current.type not in ('rbracket', 'comma'): + args.append(self.parse_expression()) + else: + args.append(None) + + if self.stream.current.type is 'colon': + self.stream.next() + if self.stream.current.type not in ('rbracket', 'comma'): + args.append(self.parse_expression()) + else: + args.append(None) + else: + args.append(None) + + return nodes.Slice(lineno=lineno, *args) + + def parse_call(self, node): + token = self.stream.expect('lparen') + args = [] + kwargs = [] + dyn_args = dyn_kwargs = None + require_comma = False + + def ensure(expr): + if not expr: + raise TemplateSyntaxError('invalid syntax for function ' + 'call expression', token.lineno, + self.filename) + + while self.stream.current.type is not 'rparen': + if require_comma: + self.stream.expect('comma') + # support for trailing comma + if self.stream.current.type is 'rparen': + break + if self.stream.current.type is 'mul': + ensure(dyn_args is None and dyn_kwargs is None) + self.stream.next() + dyn_args = self.parse_expression() + elif self.stream.current.type is 'pow': + ensure(dyn_kwargs is None) + self.stream.next() + dyn_kwargs = self.parse_expression() + else: + ensure(dyn_args is None and dyn_kwargs is None) + if self.stream.current.type is 'name' and \ + self.stream.look().type is 'assign': + key = self.stream.current.value + self.stream.skip(2) + kwargs.append(nodes.Pair(key, self.parse_expression(), + lineno=key.lineno)) + else: + ensure(not kwargs) + args.append(self.parse_expression()) + + require_comma = True + self.stream.expect('rparen') + + if node is None: + return args, kwargs, dyn_args, dyn_kwargs + return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, + lineno=token.lineno) + + def parse_filter(self, node): + lineno = self.stream.current.type + filters = [] + while self.stream.current.type == 'pipe': + self.stream.next() + token = self.stream.expect('name') + if self.stream.current.type is 'lparen': + args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) + else: + args = [] + kwargs = [] + dyn_args = dyn_kwargs = None + filters.append(nodes.FilterCall(token.value, args, kwargs, + dyn_args, dyn_kwargs, + lineno=token.lineno)) + return nodes.Filter(node, filters) + + def parse_test(self, node): + token = self.stream.expect('is') + if self.stream.current.type is 'not': + self.stream.next() + negated = True + else: + negated = False + name = self.stream.expect('name').value + if self.stream.current.type is 'lparen': + args, kwargs, dyn_args, dyn_kwargs = self.parse_call(None) + elif self.stream.current.type in ('name', 'string', 'integer', + 'float', 'lparen', 'lbracket', + 'lbrace', 'regex'): + args = [self.parse_expression()] + else: + args = [] + kwargs = [] + dyn_args = dyn_kwargs = None + node = nodes.Test(node, name, args, kwargs, dyn_args, + dyn_kwargs, lineno=token.lineno) + if negated: + node = nodes.NotExpression(node, lineno=token.lineno) + return node + + def subparse(self, end_tokens=None): + body = [] + data_buffer = [] + add_data = data_buffer.append + + def flush_data(): + if data_buffer: + lineno = data_buffer[0].lineno + body.append(nodes.Output(data_buffer[:], lineno=lineno)) + del data_buffer[:] + + while self.stream: + token = self.stream.current + if token.type is 'data': + add_data(nodes.Const(token.value, lineno=token.lineno)) + self.stream.next() + elif token.type is 'variable_begin': + self.stream.next() + add_data(self.parse_tuple()) + self.stream.expect('variable_end') + elif token.type is 'block_begin': + flush_data() + self.stream.next() + if end_tokens is not None and \ + self.stream.current.type in end_tokens: + return body + while self.stream.current.type is not 'block_end': + body.append(self.parse_statement()) + self.stream.expect('block_end') + else: + raise AssertionError('internal parsing error') + + flush_data() + return body + + def parse(self): + """Parse the whole template into a `Template` node.""" + return nodes.Template(self.subparse(), lineno=1) diff --git a/jinja2/translators/__init__.py b/jinja2/translators/__init__.py deleted file mode 100644 index ba55148..0000000 --- a/jinja2/translators/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja.translators - ~~~~~~~~~~~~~~~~~ - - The submodules of this module provide translators for the jinja ast. - - :copyright: 2007 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" - - -class Translator(object): - """ - Base class of all translators. - """ - - def process(environment, tree, source=None): - """ - Process the given ast with the rules defined in - environment and return a translated version of it. - The translated object can be anything. The python - translator for example outputs Template instances, - a javascript translator would probably output strings. - - This is a static function. - """ - pass - process = staticmethod(process) diff --git a/jinja2/translators/python.py b/jinja2/translators/python.py deleted file mode 100644 index 3e7b942..0000000 --- a/jinja2/translators/python.py +++ /dev/null @@ -1,1115 +0,0 @@ -# -*- coding: utf-8 -*- -""" - jinja.translators.python - ~~~~~~~~~~~~~~~~~~~~~~~~ - - This module translates a jinja ast into python code. - - This translator tries hard to keep Jinja sandboxed. All security - relevant calls are wrapped by methods defined in the environment. - This affects: - - - method calls - - attribute access - - name resolution - - It also adds debug symbols used by the traceback toolkit implemented - in `jinja.utils`. - - Implementation Details - ====================== - - It might sound strange but the translator tries to keep the generated - code readable as much as possible. This simplifies debugging the Jinja - core a lot. The additional processing overhead is just relevant for - the translation process, the additional comments and whitespace won't - appear in the saved bytecode. - - :copyright: 2007 by Armin Ronacher. - :license: BSD, see LICENSE for more details. -""" -import re -import sys -from jinja import nodes -from jinja.nodes import get_nodes -from jinja.parser import Parser -from jinja.exceptions import TemplateSyntaxError -from jinja.translators import Translator -from jinja.datastructure import TemplateStream -from jinja.utils import set, capture_generator - - -#: regular expression for the debug symbols -_debug_re = re.compile(r'^\s*\# DEBUG\(filename=(?P.*?), ' - r'lineno=(?P\d+)\)$') - -# For Python versions without generator exit exceptions -try: - GeneratorExit = GeneratorExit -except NameError: - class GeneratorExit(Exception): - pass - -# For Pythons without conditional expressions -try: - exec '0 if 0 else 0' - have_conditional_expr = True -except SyntaxError: - have_conditional_expr = False - - -class Template(object): - """ - Represents a finished template. - """ - - def __init__(self, environment, code): - self.environment = environment - self.code = code - self.generate_func = None - - def dump(self, stream=None): - """Dump the template into python bytecode.""" - if stream is not None: - from marshal import dump - dump(self.code, stream) - else: - from marshal import dumps - return dumps(self.code) - - def load(environment, data): - """Load the template from python bytecode.""" - if isinstance(data, basestring): - from marshal import loads - code = loads(data) - else: - from marshal import load - code = load(data) - return Template(environment, code) - load = staticmethod(load) - - def render(self, *args, **kwargs): - """Render a template.""" - __traceback_hide__ = True - ctx = self._prepare(*args, **kwargs) - try: - return capture_generator(self.generate_func(ctx)) - except: - self._debug(ctx, *sys.exc_info()) - - def stream(self, *args, **kwargs): - """Render a template as stream.""" - def proxy(ctx): - try: - for item in self.generate_func(ctx): - yield item - except GeneratorExit: - return - except: - self._debug(ctx, *sys.exc_info()) - return TemplateStream(proxy(self._prepare(*args, **kwargs))) - - def _prepare(self, *args, **kwargs): - """Prepare the template execution.""" - # if there is no generation function we execute the code - # in a new namespace and save the generation function and - # debug information. - env = self.environment - if self.generate_func is None: - ns = {'environment': env} - exec self.code in ns - self.generate_func = ns['generate'] - return env.context_class(env, *args, **kwargs) - - def _debug(self, ctx, exc_type, exc_value, traceback): - """Debugging Helper""" - # just modify traceback if we have that feature enabled - from traceback import print_exception - print_exception(exc_type, exc_value, traceback) - - if self.environment.friendly_traceback: - # hook the debugger in - from jinja.debugger import translate_exception - exc_type, exc_value, traceback = translate_exception( - self, ctx, exc_type, exc_value, traceback) - print_exception(exc_type, exc_value, traceback) - - raise exc_type, exc_value, traceback - - -class TranslationOperator(object): - """ - A translation operator has a single string representing the operation - """ - def __init__(self, operator, translator): - self.operator = operator - self.translator = translator - -class UnaryOperator(TranslationOperator): - """ - A unary operator has one side to the operation. - """ - def __call__(self, node): - """ - Apply operator. - """ - return '( %s %s)' % ( - self.operator, - self.translator.handle_node(node.node), - ) - -class BinaryOperator(TranslationOperator): - """ - A binary operator has two sides to the operation. - """ - def __call__(self, node): - """ - Apply operator. - """ - return '(%s %s %s)' % ( - self.translator.handle_node(node.left), - self.operator, - self.translator.handle_node(node.right), - ) - -class PythonTranslator(Translator): - """ - Pass this translator a ast tree to get valid python code. - """ - - def __init__(self, environment, node, source): - self.environment = environment - self.loader = environment.loader.get_controlled_loader() - self.node = node - self.source = source - self.closed = False - - #: current level of indention - self.indention = 0 - #: each {% cycle %} tag has a unique ID which increments - #: automatically for each tag. - self.last_cycle_id = 0 - #: set of used shortcuts jinja has to make local automatically - self.used_shortcuts = set(['undefined_singleton']) - #: set of used datastructures jinja has to import - self.used_data_structures = set() - #: set of used utils jinja has to import - self.used_utils = set() - #: flags for runtime error - self.require_runtime_error = False - #: do wee need a "set" object? - self.need_set_import = False - #: flag for regular expressions - self.compiled_regular_expressions = {} - - #: bind the nodes to the callback functions. There are - #: some missing! A few are specified in the `unhandled` - #: mapping in order to disallow their usage, some of them - #: will not appear in the jinja parser output because - #: they are filtered out. - self.handlers = { - # block nodes - nodes.Template: self.handle_template, - nodes.Text: self.handle_template_text, - nodes.NodeList: self.handle_node_list, - nodes.ForLoop: self.handle_for_loop, - nodes.IfCondition: self.handle_if_condition, - nodes.Cycle: self.handle_cycle, - nodes.Print: self.handle_print, - nodes.Macro: self.handle_macro, - nodes.Call: self.handle_call, - nodes.Set: self.handle_set, - nodes.Filter: self.handle_filter, - nodes.Block: self.handle_block, - nodes.Include: self.handle_include, - nodes.Trans: self.handle_trans, - - # expression nodes - nodes.NameExpression: self.handle_name, - nodes.CompareExpression: self.handle_compare, - nodes.TestExpression: self.handle_test, - nodes.ConstantExpression: self.handle_const, - nodes.RegexExpression: self.handle_regex, - nodes.SubscriptExpression: self.handle_subscript, - nodes.FilterExpression: self.handle_filter_expr, - nodes.CallExpression: self.handle_call_expr, - nodes.AddExpression: BinaryOperator('+', self), - nodes.SubExpression: BinaryOperator('-', self), - nodes.ConcatExpression: self.handle_concat, - nodes.DivExpression: BinaryOperator('/', self), - nodes.FloorDivExpression: BinaryOperator('//', self), - nodes.MulExpression: BinaryOperator('*', self), - nodes.ModExpression: BinaryOperator('%', self), - nodes.PosExpression: UnaryOperator('+', self), - nodes.NegExpression: UnaryOperator('-', self), - nodes.PowExpression: BinaryOperator('**', self), - nodes.DictExpression: self.handle_dict, - nodes.SetExpression: self.handle_set_expr, - nodes.ListExpression: self.handle_list, - nodes.TupleExpression: self.handle_tuple, - nodes.UndefinedExpression: self.handle_undefined, - nodes.AndExpression: BinaryOperator(' and ', self), - nodes.OrExpression: BinaryOperator(' or ', self), - nodes.NotExpression: UnaryOperator(' not ', self), - nodes.SliceExpression: self.handle_slice, - nodes.ConditionalExpression: self.handle_conditional_expr - } - - # -- public methods - - def process(environment, node, source=None): - """ - The only public method. Creates a translator instance, - translates the code and returns it in form of an - `Template` instance. - """ - translator = PythonTranslator(environment, node, source) - filename = node.filename or '