1*635a8641SAndroid Build Coastguard Worker# -*- coding: utf-8 -*- 2*635a8641SAndroid Build Coastguard Worker""" 3*635a8641SAndroid Build Coastguard Worker jinja2.lexer 4*635a8641SAndroid Build Coastguard Worker ~~~~~~~~~~~~ 5*635a8641SAndroid Build Coastguard Worker 6*635a8641SAndroid Build Coastguard Worker This module implements a Jinja / Python combination lexer. The 7*635a8641SAndroid Build Coastguard Worker `Lexer` class provided by this module is used to do some preprocessing 8*635a8641SAndroid Build Coastguard Worker for Jinja. 9*635a8641SAndroid Build Coastguard Worker 10*635a8641SAndroid Build Coastguard Worker On the one hand it filters out invalid operators like the bitshift 11*635a8641SAndroid Build Coastguard Worker operators we don't allow in templates. On the other hand it separates 12*635a8641SAndroid Build Coastguard Worker template code and python code in expressions. 13*635a8641SAndroid Build Coastguard Worker 14*635a8641SAndroid Build Coastguard Worker :copyright: (c) 2017 by the Jinja Team. 15*635a8641SAndroid Build Coastguard Worker :license: BSD, see LICENSE for more details. 16*635a8641SAndroid Build Coastguard Worker""" 17*635a8641SAndroid Build Coastguard Workerimport re 18*635a8641SAndroid Build Coastguard Workerfrom collections import deque 19*635a8641SAndroid Build Coastguard Workerfrom operator import itemgetter 20*635a8641SAndroid Build Coastguard Worker 21*635a8641SAndroid Build Coastguard Workerfrom jinja2._compat import implements_iterator, intern, iteritems, text_type 22*635a8641SAndroid Build Coastguard Workerfrom jinja2.exceptions import TemplateSyntaxError 23*635a8641SAndroid Build Coastguard Workerfrom jinja2.utils import LRUCache 24*635a8641SAndroid Build Coastguard Worker 25*635a8641SAndroid Build Coastguard Worker# cache for the lexers. Exists in order to be able to have multiple 26*635a8641SAndroid Build Coastguard Worker# environments with the same lexer 27*635a8641SAndroid Build Coastguard Worker_lexer_cache = LRUCache(50) 28*635a8641SAndroid Build Coastguard Worker 29*635a8641SAndroid Build Coastguard Worker# static regular expressions 30*635a8641SAndroid Build Coastguard Workerwhitespace_re = re.compile(r'\s+', re.U) 31*635a8641SAndroid Build Coastguard Workerstring_re = re.compile(r"('([^'\\]*(?:\\.[^'\\]*)*)'" 32*635a8641SAndroid Build Coastguard Worker r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) 33*635a8641SAndroid Build Coastguard Workerinteger_re = re.compile(r'\d+') 34*635a8641SAndroid Build Coastguard Worker 35*635a8641SAndroid Build Coastguard Workertry: 36*635a8641SAndroid Build Coastguard Worker # check if this Python supports Unicode identifiers 37*635a8641SAndroid Build Coastguard Worker compile('föö', '<unknown>', 'eval') 38*635a8641SAndroid Build Coastguard Workerexcept SyntaxError: 39*635a8641SAndroid Build Coastguard Worker # no Unicode support, use ASCII identifiers 40*635a8641SAndroid Build Coastguard Worker name_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') 41*635a8641SAndroid Build Coastguard Worker check_ident = False 42*635a8641SAndroid Build Coastguard Workerelse: 43*635a8641SAndroid Build Coastguard Worker # Unicode support, build a pattern to match valid characters, and set flag 44*635a8641SAndroid Build Coastguard Worker # to use str.isidentifier to validate during lexing 45*635a8641SAndroid Build Coastguard Worker from jinja2 import _identifier 46*635a8641SAndroid Build Coastguard Worker name_re = re.compile(r'[\w{0}]+'.format(_identifier.pattern)) 47*635a8641SAndroid Build Coastguard Worker check_ident = True 48*635a8641SAndroid Build Coastguard Worker # remove the pattern from memory after building the regex 49*635a8641SAndroid Build Coastguard Worker import sys 50*635a8641SAndroid Build Coastguard Worker del sys.modules['jinja2._identifier'] 51*635a8641SAndroid Build Coastguard Worker import jinja2 52*635a8641SAndroid Build Coastguard Worker del jinja2._identifier 53*635a8641SAndroid Build Coastguard Worker del _identifier 54*635a8641SAndroid Build Coastguard Worker 55*635a8641SAndroid Build Coastguard Workerfloat_re = re.compile(r'(?<!\.)\d+\.\d+') 56*635a8641SAndroid Build Coastguard Workernewline_re = re.compile(r'(\r\n|\r|\n)') 57*635a8641SAndroid Build Coastguard Worker 58*635a8641SAndroid Build Coastguard Worker# internal the tokens and keep references to them 59*635a8641SAndroid Build Coastguard WorkerTOKEN_ADD = intern('add') 60*635a8641SAndroid Build Coastguard WorkerTOKEN_ASSIGN = intern('assign') 61*635a8641SAndroid Build Coastguard WorkerTOKEN_COLON = intern('colon') 62*635a8641SAndroid Build Coastguard WorkerTOKEN_COMMA = intern('comma') 63*635a8641SAndroid Build Coastguard WorkerTOKEN_DIV = intern('div') 64*635a8641SAndroid Build Coastguard WorkerTOKEN_DOT = intern('dot') 65*635a8641SAndroid Build Coastguard WorkerTOKEN_EQ = intern('eq') 66*635a8641SAndroid Build Coastguard WorkerTOKEN_FLOORDIV = intern('floordiv') 67*635a8641SAndroid Build Coastguard WorkerTOKEN_GT = intern('gt') 68*635a8641SAndroid Build Coastguard WorkerTOKEN_GTEQ = intern('gteq') 69*635a8641SAndroid Build Coastguard WorkerTOKEN_LBRACE = intern('lbrace') 70*635a8641SAndroid Build Coastguard WorkerTOKEN_LBRACKET = intern('lbracket') 71*635a8641SAndroid Build Coastguard WorkerTOKEN_LPAREN = intern('lparen') 72*635a8641SAndroid Build Coastguard WorkerTOKEN_LT = intern('lt') 73*635a8641SAndroid Build Coastguard WorkerTOKEN_LTEQ = intern('lteq') 74*635a8641SAndroid Build Coastguard WorkerTOKEN_MOD = intern('mod') 75*635a8641SAndroid Build Coastguard WorkerTOKEN_MUL = intern('mul') 76*635a8641SAndroid Build Coastguard WorkerTOKEN_NE = intern('ne') 77*635a8641SAndroid Build Coastguard WorkerTOKEN_PIPE = intern('pipe') 78*635a8641SAndroid Build Coastguard WorkerTOKEN_POW = intern('pow') 79*635a8641SAndroid Build Coastguard WorkerTOKEN_RBRACE = intern('rbrace') 80*635a8641SAndroid Build Coastguard WorkerTOKEN_RBRACKET = intern('rbracket') 81*635a8641SAndroid Build Coastguard WorkerTOKEN_RPAREN = intern('rparen') 82*635a8641SAndroid Build Coastguard WorkerTOKEN_SEMICOLON = intern('semicolon') 83*635a8641SAndroid Build Coastguard WorkerTOKEN_SUB = intern('sub') 84*635a8641SAndroid Build Coastguard WorkerTOKEN_TILDE = intern('tilde') 85*635a8641SAndroid Build Coastguard WorkerTOKEN_WHITESPACE = intern('whitespace') 86*635a8641SAndroid Build Coastguard WorkerTOKEN_FLOAT = intern('float') 87*635a8641SAndroid Build Coastguard WorkerTOKEN_INTEGER = intern('integer') 88*635a8641SAndroid Build Coastguard WorkerTOKEN_NAME = intern('name') 89*635a8641SAndroid Build Coastguard WorkerTOKEN_STRING = intern('string') 90*635a8641SAndroid Build Coastguard WorkerTOKEN_OPERATOR = intern('operator') 91*635a8641SAndroid Build Coastguard WorkerTOKEN_BLOCK_BEGIN = intern('block_begin') 92*635a8641SAndroid Build Coastguard WorkerTOKEN_BLOCK_END = intern('block_end') 93*635a8641SAndroid Build Coastguard WorkerTOKEN_VARIABLE_BEGIN = intern('variable_begin') 94*635a8641SAndroid Build Coastguard WorkerTOKEN_VARIABLE_END = intern('variable_end') 95*635a8641SAndroid Build Coastguard WorkerTOKEN_RAW_BEGIN = intern('raw_begin') 96*635a8641SAndroid Build Coastguard WorkerTOKEN_RAW_END = intern('raw_end') 97*635a8641SAndroid Build Coastguard WorkerTOKEN_COMMENT_BEGIN = intern('comment_begin') 98*635a8641SAndroid Build Coastguard WorkerTOKEN_COMMENT_END = intern('comment_end') 99*635a8641SAndroid Build Coastguard WorkerTOKEN_COMMENT = intern('comment') 100*635a8641SAndroid Build Coastguard WorkerTOKEN_LINESTATEMENT_BEGIN = intern('linestatement_begin') 101*635a8641SAndroid Build Coastguard WorkerTOKEN_LINESTATEMENT_END = intern('linestatement_end') 102*635a8641SAndroid Build Coastguard WorkerTOKEN_LINECOMMENT_BEGIN = intern('linecomment_begin') 103*635a8641SAndroid Build Coastguard WorkerTOKEN_LINECOMMENT_END = intern('linecomment_end') 104*635a8641SAndroid Build Coastguard WorkerTOKEN_LINECOMMENT = intern('linecomment') 105*635a8641SAndroid Build Coastguard WorkerTOKEN_DATA = intern('data') 106*635a8641SAndroid Build Coastguard WorkerTOKEN_INITIAL = intern('initial') 107*635a8641SAndroid Build Coastguard WorkerTOKEN_EOF = intern('eof') 108*635a8641SAndroid Build Coastguard Worker 109*635a8641SAndroid Build Coastguard Worker# bind operators to token types 110*635a8641SAndroid Build Coastguard Workeroperators = { 111*635a8641SAndroid Build Coastguard Worker '+': TOKEN_ADD, 112*635a8641SAndroid Build Coastguard Worker '-': TOKEN_SUB, 113*635a8641SAndroid Build Coastguard Worker '/': TOKEN_DIV, 114*635a8641SAndroid Build Coastguard Worker '//': TOKEN_FLOORDIV, 115*635a8641SAndroid Build Coastguard Worker '*': TOKEN_MUL, 116*635a8641SAndroid Build Coastguard Worker '%': TOKEN_MOD, 117*635a8641SAndroid Build Coastguard Worker '**': TOKEN_POW, 118*635a8641SAndroid Build Coastguard Worker '~': TOKEN_TILDE, 119*635a8641SAndroid Build Coastguard Worker '[': TOKEN_LBRACKET, 120*635a8641SAndroid Build Coastguard Worker ']': TOKEN_RBRACKET, 121*635a8641SAndroid Build Coastguard Worker '(': TOKEN_LPAREN, 122*635a8641SAndroid Build Coastguard Worker ')': TOKEN_RPAREN, 123*635a8641SAndroid Build Coastguard Worker '{': TOKEN_LBRACE, 124*635a8641SAndroid Build Coastguard Worker '}': TOKEN_RBRACE, 125*635a8641SAndroid Build Coastguard Worker '==': TOKEN_EQ, 126*635a8641SAndroid Build Coastguard Worker '!=': TOKEN_NE, 127*635a8641SAndroid Build Coastguard Worker '>': TOKEN_GT, 128*635a8641SAndroid Build Coastguard Worker '>=': TOKEN_GTEQ, 129*635a8641SAndroid Build Coastguard Worker '<': TOKEN_LT, 130*635a8641SAndroid Build Coastguard Worker '<=': TOKEN_LTEQ, 131*635a8641SAndroid Build Coastguard Worker '=': TOKEN_ASSIGN, 132*635a8641SAndroid Build Coastguard Worker '.': TOKEN_DOT, 133*635a8641SAndroid Build Coastguard Worker ':': TOKEN_COLON, 134*635a8641SAndroid Build Coastguard Worker '|': TOKEN_PIPE, 135*635a8641SAndroid Build Coastguard Worker ',': TOKEN_COMMA, 136*635a8641SAndroid Build Coastguard Worker ';': TOKEN_SEMICOLON 137*635a8641SAndroid Build Coastguard Worker} 138*635a8641SAndroid Build Coastguard Worker 139*635a8641SAndroid Build Coastguard Workerreverse_operators = dict([(v, k) for k, v in iteritems(operators)]) 140*635a8641SAndroid Build Coastguard Workerassert len(operators) == len(reverse_operators), 'operators dropped' 141*635a8641SAndroid Build Coastguard Workeroperator_re = re.compile('(%s)' % '|'.join(re.escape(x) for x in 142*635a8641SAndroid Build Coastguard Worker sorted(operators, key=lambda x: -len(x)))) 143*635a8641SAndroid Build Coastguard Worker 144*635a8641SAndroid Build Coastguard Workerignored_tokens = frozenset([TOKEN_COMMENT_BEGIN, TOKEN_COMMENT, 145*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT_END, TOKEN_WHITESPACE, 146*635a8641SAndroid Build Coastguard Worker TOKEN_LINECOMMENT_BEGIN, TOKEN_LINECOMMENT_END, 147*635a8641SAndroid Build Coastguard Worker TOKEN_LINECOMMENT]) 148*635a8641SAndroid Build Coastguard Workerignore_if_empty = frozenset([TOKEN_WHITESPACE, TOKEN_DATA, 149*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT, TOKEN_LINECOMMENT]) 150*635a8641SAndroid Build Coastguard Worker 151*635a8641SAndroid Build Coastguard Worker 152*635a8641SAndroid Build Coastguard Workerdef _describe_token_type(token_type): 153*635a8641SAndroid Build Coastguard Worker if token_type in reverse_operators: 154*635a8641SAndroid Build Coastguard Worker return reverse_operators[token_type] 155*635a8641SAndroid Build Coastguard Worker return { 156*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT_BEGIN: 'begin of comment', 157*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT_END: 'end of comment', 158*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT: 'comment', 159*635a8641SAndroid Build Coastguard Worker TOKEN_LINECOMMENT: 'comment', 160*635a8641SAndroid Build Coastguard Worker TOKEN_BLOCK_BEGIN: 'begin of statement block', 161*635a8641SAndroid Build Coastguard Worker TOKEN_BLOCK_END: 'end of statement block', 162*635a8641SAndroid Build Coastguard Worker TOKEN_VARIABLE_BEGIN: 'begin of print statement', 163*635a8641SAndroid Build Coastguard Worker TOKEN_VARIABLE_END: 'end of print statement', 164*635a8641SAndroid Build Coastguard Worker TOKEN_LINESTATEMENT_BEGIN: 'begin of line statement', 165*635a8641SAndroid Build Coastguard Worker TOKEN_LINESTATEMENT_END: 'end of line statement', 166*635a8641SAndroid Build Coastguard Worker TOKEN_DATA: 'template data / text', 167*635a8641SAndroid Build Coastguard Worker TOKEN_EOF: 'end of template' 168*635a8641SAndroid Build Coastguard Worker }.get(token_type, token_type) 169*635a8641SAndroid Build Coastguard Worker 170*635a8641SAndroid Build Coastguard Worker 171*635a8641SAndroid Build Coastguard Workerdef describe_token(token): 172*635a8641SAndroid Build Coastguard Worker """Returns a description of the token.""" 173*635a8641SAndroid Build Coastguard Worker if token.type == 'name': 174*635a8641SAndroid Build Coastguard Worker return token.value 175*635a8641SAndroid Build Coastguard Worker return _describe_token_type(token.type) 176*635a8641SAndroid Build Coastguard Worker 177*635a8641SAndroid Build Coastguard Worker 178*635a8641SAndroid Build Coastguard Workerdef describe_token_expr(expr): 179*635a8641SAndroid Build Coastguard Worker """Like `describe_token` but for token expressions.""" 180*635a8641SAndroid Build Coastguard Worker if ':' in expr: 181*635a8641SAndroid Build Coastguard Worker type, value = expr.split(':', 1) 182*635a8641SAndroid Build Coastguard Worker if type == 'name': 183*635a8641SAndroid Build Coastguard Worker return value 184*635a8641SAndroid Build Coastguard Worker else: 185*635a8641SAndroid Build Coastguard Worker type = expr 186*635a8641SAndroid Build Coastguard Worker return _describe_token_type(type) 187*635a8641SAndroid Build Coastguard Worker 188*635a8641SAndroid Build Coastguard Worker 189*635a8641SAndroid Build Coastguard Workerdef count_newlines(value): 190*635a8641SAndroid Build Coastguard Worker """Count the number of newline characters in the string. This is 191*635a8641SAndroid Build Coastguard Worker useful for extensions that filter a stream. 192*635a8641SAndroid Build Coastguard Worker """ 193*635a8641SAndroid Build Coastguard Worker return len(newline_re.findall(value)) 194*635a8641SAndroid Build Coastguard Worker 195*635a8641SAndroid Build Coastguard Worker 196*635a8641SAndroid Build Coastguard Workerdef compile_rules(environment): 197*635a8641SAndroid Build Coastguard Worker """Compiles all the rules from the environment into a list of rules.""" 198*635a8641SAndroid Build Coastguard Worker e = re.escape 199*635a8641SAndroid Build Coastguard Worker rules = [ 200*635a8641SAndroid Build Coastguard Worker (len(environment.comment_start_string), 'comment', 201*635a8641SAndroid Build Coastguard Worker e(environment.comment_start_string)), 202*635a8641SAndroid Build Coastguard Worker (len(environment.block_start_string), 'block', 203*635a8641SAndroid Build Coastguard Worker e(environment.block_start_string)), 204*635a8641SAndroid Build Coastguard Worker (len(environment.variable_start_string), 'variable', 205*635a8641SAndroid Build Coastguard Worker e(environment.variable_start_string)) 206*635a8641SAndroid Build Coastguard Worker ] 207*635a8641SAndroid Build Coastguard Worker 208*635a8641SAndroid Build Coastguard Worker if environment.line_statement_prefix is not None: 209*635a8641SAndroid Build Coastguard Worker rules.append((len(environment.line_statement_prefix), 'linestatement', 210*635a8641SAndroid Build Coastguard Worker r'^[ \t\v]*' + e(environment.line_statement_prefix))) 211*635a8641SAndroid Build Coastguard Worker if environment.line_comment_prefix is not None: 212*635a8641SAndroid Build Coastguard Worker rules.append((len(environment.line_comment_prefix), 'linecomment', 213*635a8641SAndroid Build Coastguard Worker r'(?:^|(?<=\S))[^\S\r\n]*' + 214*635a8641SAndroid Build Coastguard Worker e(environment.line_comment_prefix))) 215*635a8641SAndroid Build Coastguard Worker 216*635a8641SAndroid Build Coastguard Worker return [x[1:] for x in sorted(rules, reverse=True)] 217*635a8641SAndroid Build Coastguard Worker 218*635a8641SAndroid Build Coastguard Worker 219*635a8641SAndroid Build Coastguard Workerclass Failure(object): 220*635a8641SAndroid Build Coastguard Worker """Class that raises a `TemplateSyntaxError` if called. 221*635a8641SAndroid Build Coastguard Worker Used by the `Lexer` to specify known errors. 222*635a8641SAndroid Build Coastguard Worker """ 223*635a8641SAndroid Build Coastguard Worker 224*635a8641SAndroid Build Coastguard Worker def __init__(self, message, cls=TemplateSyntaxError): 225*635a8641SAndroid Build Coastguard Worker self.message = message 226*635a8641SAndroid Build Coastguard Worker self.error_class = cls 227*635a8641SAndroid Build Coastguard Worker 228*635a8641SAndroid Build Coastguard Worker def __call__(self, lineno, filename): 229*635a8641SAndroid Build Coastguard Worker raise self.error_class(self.message, lineno, filename) 230*635a8641SAndroid Build Coastguard Worker 231*635a8641SAndroid Build Coastguard Worker 232*635a8641SAndroid Build Coastguard Workerclass Token(tuple): 233*635a8641SAndroid Build Coastguard Worker """Token class.""" 234*635a8641SAndroid Build Coastguard Worker __slots__ = () 235*635a8641SAndroid Build Coastguard Worker lineno, type, value = (property(itemgetter(x)) for x in range(3)) 236*635a8641SAndroid Build Coastguard Worker 237*635a8641SAndroid Build Coastguard Worker def __new__(cls, lineno, type, value): 238*635a8641SAndroid Build Coastguard Worker return tuple.__new__(cls, (lineno, intern(str(type)), value)) 239*635a8641SAndroid Build Coastguard Worker 240*635a8641SAndroid Build Coastguard Worker def __str__(self): 241*635a8641SAndroid Build Coastguard Worker if self.type in reverse_operators: 242*635a8641SAndroid Build Coastguard Worker return reverse_operators[self.type] 243*635a8641SAndroid Build Coastguard Worker elif self.type == 'name': 244*635a8641SAndroid Build Coastguard Worker return self.value 245*635a8641SAndroid Build Coastguard Worker return self.type 246*635a8641SAndroid Build Coastguard Worker 247*635a8641SAndroid Build Coastguard Worker def test(self, expr): 248*635a8641SAndroid Build Coastguard Worker """Test a token against a token expression. This can either be a 249*635a8641SAndroid Build Coastguard Worker token type or ``'token_type:token_value'``. This can only test 250*635a8641SAndroid Build Coastguard Worker against string values and types. 251*635a8641SAndroid Build Coastguard Worker """ 252*635a8641SAndroid Build Coastguard Worker # here we do a regular string equality check as test_any is usually 253*635a8641SAndroid Build Coastguard Worker # passed an iterable of not interned strings. 254*635a8641SAndroid Build Coastguard Worker if self.type == expr: 255*635a8641SAndroid Build Coastguard Worker return True 256*635a8641SAndroid Build Coastguard Worker elif ':' in expr: 257*635a8641SAndroid Build Coastguard Worker return expr.split(':', 1) == [self.type, self.value] 258*635a8641SAndroid Build Coastguard Worker return False 259*635a8641SAndroid Build Coastguard Worker 260*635a8641SAndroid Build Coastguard Worker def test_any(self, *iterable): 261*635a8641SAndroid Build Coastguard Worker """Test against multiple token expressions.""" 262*635a8641SAndroid Build Coastguard Worker for expr in iterable: 263*635a8641SAndroid Build Coastguard Worker if self.test(expr): 264*635a8641SAndroid Build Coastguard Worker return True 265*635a8641SAndroid Build Coastguard Worker return False 266*635a8641SAndroid Build Coastguard Worker 267*635a8641SAndroid Build Coastguard Worker def __repr__(self): 268*635a8641SAndroid Build Coastguard Worker return 'Token(%r, %r, %r)' % ( 269*635a8641SAndroid Build Coastguard Worker self.lineno, 270*635a8641SAndroid Build Coastguard Worker self.type, 271*635a8641SAndroid Build Coastguard Worker self.value 272*635a8641SAndroid Build Coastguard Worker ) 273*635a8641SAndroid Build Coastguard Worker 274*635a8641SAndroid Build Coastguard Worker 275*635a8641SAndroid Build Coastguard Worker@implements_iterator 276*635a8641SAndroid Build Coastguard Workerclass TokenStreamIterator(object): 277*635a8641SAndroid Build Coastguard Worker """The iterator for tokenstreams. Iterate over the stream 278*635a8641SAndroid Build Coastguard Worker until the eof token is reached. 279*635a8641SAndroid Build Coastguard Worker """ 280*635a8641SAndroid Build Coastguard Worker 281*635a8641SAndroid Build Coastguard Worker def __init__(self, stream): 282*635a8641SAndroid Build Coastguard Worker self.stream = stream 283*635a8641SAndroid Build Coastguard Worker 284*635a8641SAndroid Build Coastguard Worker def __iter__(self): 285*635a8641SAndroid Build Coastguard Worker return self 286*635a8641SAndroid Build Coastguard Worker 287*635a8641SAndroid Build Coastguard Worker def __next__(self): 288*635a8641SAndroid Build Coastguard Worker token = self.stream.current 289*635a8641SAndroid Build Coastguard Worker if token.type is TOKEN_EOF: 290*635a8641SAndroid Build Coastguard Worker self.stream.close() 291*635a8641SAndroid Build Coastguard Worker raise StopIteration() 292*635a8641SAndroid Build Coastguard Worker next(self.stream) 293*635a8641SAndroid Build Coastguard Worker return token 294*635a8641SAndroid Build Coastguard Worker 295*635a8641SAndroid Build Coastguard Worker 296*635a8641SAndroid Build Coastguard Worker@implements_iterator 297*635a8641SAndroid Build Coastguard Workerclass TokenStream(object): 298*635a8641SAndroid Build Coastguard Worker """A token stream is an iterable that yields :class:`Token`\\s. The 299*635a8641SAndroid Build Coastguard Worker parser however does not iterate over it but calls :meth:`next` to go 300*635a8641SAndroid Build Coastguard Worker one token ahead. The current active token is stored as :attr:`current`. 301*635a8641SAndroid Build Coastguard Worker """ 302*635a8641SAndroid Build Coastguard Worker 303*635a8641SAndroid Build Coastguard Worker def __init__(self, generator, name, filename): 304*635a8641SAndroid Build Coastguard Worker self._iter = iter(generator) 305*635a8641SAndroid Build Coastguard Worker self._pushed = deque() 306*635a8641SAndroid Build Coastguard Worker self.name = name 307*635a8641SAndroid Build Coastguard Worker self.filename = filename 308*635a8641SAndroid Build Coastguard Worker self.closed = False 309*635a8641SAndroid Build Coastguard Worker self.current = Token(1, TOKEN_INITIAL, '') 310*635a8641SAndroid Build Coastguard Worker next(self) 311*635a8641SAndroid Build Coastguard Worker 312*635a8641SAndroid Build Coastguard Worker def __iter__(self): 313*635a8641SAndroid Build Coastguard Worker return TokenStreamIterator(self) 314*635a8641SAndroid Build Coastguard Worker 315*635a8641SAndroid Build Coastguard Worker def __bool__(self): 316*635a8641SAndroid Build Coastguard Worker return bool(self._pushed) or self.current.type is not TOKEN_EOF 317*635a8641SAndroid Build Coastguard Worker __nonzero__ = __bool__ # py2 318*635a8641SAndroid Build Coastguard Worker 319*635a8641SAndroid Build Coastguard Worker eos = property(lambda x: not x, doc="Are we at the end of the stream?") 320*635a8641SAndroid Build Coastguard Worker 321*635a8641SAndroid Build Coastguard Worker def push(self, token): 322*635a8641SAndroid Build Coastguard Worker """Push a token back to the stream.""" 323*635a8641SAndroid Build Coastguard Worker self._pushed.append(token) 324*635a8641SAndroid Build Coastguard Worker 325*635a8641SAndroid Build Coastguard Worker def look(self): 326*635a8641SAndroid Build Coastguard Worker """Look at the next token.""" 327*635a8641SAndroid Build Coastguard Worker old_token = next(self) 328*635a8641SAndroid Build Coastguard Worker result = self.current 329*635a8641SAndroid Build Coastguard Worker self.push(result) 330*635a8641SAndroid Build Coastguard Worker self.current = old_token 331*635a8641SAndroid Build Coastguard Worker return result 332*635a8641SAndroid Build Coastguard Worker 333*635a8641SAndroid Build Coastguard Worker def skip(self, n=1): 334*635a8641SAndroid Build Coastguard Worker """Got n tokens ahead.""" 335*635a8641SAndroid Build Coastguard Worker for x in range(n): 336*635a8641SAndroid Build Coastguard Worker next(self) 337*635a8641SAndroid Build Coastguard Worker 338*635a8641SAndroid Build Coastguard Worker def next_if(self, expr): 339*635a8641SAndroid Build Coastguard Worker """Perform the token test and return the token if it matched. 340*635a8641SAndroid Build Coastguard Worker Otherwise the return value is `None`. 341*635a8641SAndroid Build Coastguard Worker """ 342*635a8641SAndroid Build Coastguard Worker if self.current.test(expr): 343*635a8641SAndroid Build Coastguard Worker return next(self) 344*635a8641SAndroid Build Coastguard Worker 345*635a8641SAndroid Build Coastguard Worker def skip_if(self, expr): 346*635a8641SAndroid Build Coastguard Worker """Like :meth:`next_if` but only returns `True` or `False`.""" 347*635a8641SAndroid Build Coastguard Worker return self.next_if(expr) is not None 348*635a8641SAndroid Build Coastguard Worker 349*635a8641SAndroid Build Coastguard Worker def __next__(self): 350*635a8641SAndroid Build Coastguard Worker """Go one token ahead and return the old one. 351*635a8641SAndroid Build Coastguard Worker 352*635a8641SAndroid Build Coastguard Worker Use the built-in :func:`next` instead of calling this directly. 353*635a8641SAndroid Build Coastguard Worker """ 354*635a8641SAndroid Build Coastguard Worker rv = self.current 355*635a8641SAndroid Build Coastguard Worker if self._pushed: 356*635a8641SAndroid Build Coastguard Worker self.current = self._pushed.popleft() 357*635a8641SAndroid Build Coastguard Worker elif self.current.type is not TOKEN_EOF: 358*635a8641SAndroid Build Coastguard Worker try: 359*635a8641SAndroid Build Coastguard Worker self.current = next(self._iter) 360*635a8641SAndroid Build Coastguard Worker except StopIteration: 361*635a8641SAndroid Build Coastguard Worker self.close() 362*635a8641SAndroid Build Coastguard Worker return rv 363*635a8641SAndroid Build Coastguard Worker 364*635a8641SAndroid Build Coastguard Worker def close(self): 365*635a8641SAndroid Build Coastguard Worker """Close the stream.""" 366*635a8641SAndroid Build Coastguard Worker self.current = Token(self.current.lineno, TOKEN_EOF, '') 367*635a8641SAndroid Build Coastguard Worker self._iter = None 368*635a8641SAndroid Build Coastguard Worker self.closed = True 369*635a8641SAndroid Build Coastguard Worker 370*635a8641SAndroid Build Coastguard Worker def expect(self, expr): 371*635a8641SAndroid Build Coastguard Worker """Expect a given token type and return it. This accepts the same 372*635a8641SAndroid Build Coastguard Worker argument as :meth:`jinja2.lexer.Token.test`. 373*635a8641SAndroid Build Coastguard Worker """ 374*635a8641SAndroid Build Coastguard Worker if not self.current.test(expr): 375*635a8641SAndroid Build Coastguard Worker expr = describe_token_expr(expr) 376*635a8641SAndroid Build Coastguard Worker if self.current.type is TOKEN_EOF: 377*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError('unexpected end of template, ' 378*635a8641SAndroid Build Coastguard Worker 'expected %r.' % expr, 379*635a8641SAndroid Build Coastguard Worker self.current.lineno, 380*635a8641SAndroid Build Coastguard Worker self.name, self.filename) 381*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError("expected token %r, got %r" % 382*635a8641SAndroid Build Coastguard Worker (expr, describe_token(self.current)), 383*635a8641SAndroid Build Coastguard Worker self.current.lineno, 384*635a8641SAndroid Build Coastguard Worker self.name, self.filename) 385*635a8641SAndroid Build Coastguard Worker try: 386*635a8641SAndroid Build Coastguard Worker return self.current 387*635a8641SAndroid Build Coastguard Worker finally: 388*635a8641SAndroid Build Coastguard Worker next(self) 389*635a8641SAndroid Build Coastguard Worker 390*635a8641SAndroid Build Coastguard Worker 391*635a8641SAndroid Build Coastguard Workerdef get_lexer(environment): 392*635a8641SAndroid Build Coastguard Worker """Return a lexer which is probably cached.""" 393*635a8641SAndroid Build Coastguard Worker key = (environment.block_start_string, 394*635a8641SAndroid Build Coastguard Worker environment.block_end_string, 395*635a8641SAndroid Build Coastguard Worker environment.variable_start_string, 396*635a8641SAndroid Build Coastguard Worker environment.variable_end_string, 397*635a8641SAndroid Build Coastguard Worker environment.comment_start_string, 398*635a8641SAndroid Build Coastguard Worker environment.comment_end_string, 399*635a8641SAndroid Build Coastguard Worker environment.line_statement_prefix, 400*635a8641SAndroid Build Coastguard Worker environment.line_comment_prefix, 401*635a8641SAndroid Build Coastguard Worker environment.trim_blocks, 402*635a8641SAndroid Build Coastguard Worker environment.lstrip_blocks, 403*635a8641SAndroid Build Coastguard Worker environment.newline_sequence, 404*635a8641SAndroid Build Coastguard Worker environment.keep_trailing_newline) 405*635a8641SAndroid Build Coastguard Worker lexer = _lexer_cache.get(key) 406*635a8641SAndroid Build Coastguard Worker if lexer is None: 407*635a8641SAndroid Build Coastguard Worker lexer = Lexer(environment) 408*635a8641SAndroid Build Coastguard Worker _lexer_cache[key] = lexer 409*635a8641SAndroid Build Coastguard Worker return lexer 410*635a8641SAndroid Build Coastguard Worker 411*635a8641SAndroid Build Coastguard Worker 412*635a8641SAndroid Build Coastguard Workerclass Lexer(object): 413*635a8641SAndroid Build Coastguard Worker """Class that implements a lexer for a given environment. Automatically 414*635a8641SAndroid Build Coastguard Worker created by the environment class, usually you don't have to do that. 415*635a8641SAndroid Build Coastguard Worker 416*635a8641SAndroid Build Coastguard Worker Note that the lexer is not automatically bound to an environment. 417*635a8641SAndroid Build Coastguard Worker Multiple environments can share the same lexer. 418*635a8641SAndroid Build Coastguard Worker """ 419*635a8641SAndroid Build Coastguard Worker 420*635a8641SAndroid Build Coastguard Worker def __init__(self, environment): 421*635a8641SAndroid Build Coastguard Worker # shortcuts 422*635a8641SAndroid Build Coastguard Worker c = lambda x: re.compile(x, re.M | re.S) 423*635a8641SAndroid Build Coastguard Worker e = re.escape 424*635a8641SAndroid Build Coastguard Worker 425*635a8641SAndroid Build Coastguard Worker # lexing rules for tags 426*635a8641SAndroid Build Coastguard Worker tag_rules = [ 427*635a8641SAndroid Build Coastguard Worker (whitespace_re, TOKEN_WHITESPACE, None), 428*635a8641SAndroid Build Coastguard Worker (float_re, TOKEN_FLOAT, None), 429*635a8641SAndroid Build Coastguard Worker (integer_re, TOKEN_INTEGER, None), 430*635a8641SAndroid Build Coastguard Worker (name_re, TOKEN_NAME, None), 431*635a8641SAndroid Build Coastguard Worker (string_re, TOKEN_STRING, None), 432*635a8641SAndroid Build Coastguard Worker (operator_re, TOKEN_OPERATOR, None) 433*635a8641SAndroid Build Coastguard Worker ] 434*635a8641SAndroid Build Coastguard Worker 435*635a8641SAndroid Build Coastguard Worker # assemble the root lexing rule. because "|" is ungreedy 436*635a8641SAndroid Build Coastguard Worker # we have to sort by length so that the lexer continues working 437*635a8641SAndroid Build Coastguard Worker # as expected when we have parsing rules like <% for block and 438*635a8641SAndroid Build Coastguard Worker # <%= for variables. (if someone wants asp like syntax) 439*635a8641SAndroid Build Coastguard Worker # variables are just part of the rules if variable processing 440*635a8641SAndroid Build Coastguard Worker # is required. 441*635a8641SAndroid Build Coastguard Worker root_tag_rules = compile_rules(environment) 442*635a8641SAndroid Build Coastguard Worker 443*635a8641SAndroid Build Coastguard Worker # block suffix if trimming is enabled 444*635a8641SAndroid Build Coastguard Worker block_suffix_re = environment.trim_blocks and '\\n?' or '' 445*635a8641SAndroid Build Coastguard Worker 446*635a8641SAndroid Build Coastguard Worker # strip leading spaces if lstrip_blocks is enabled 447*635a8641SAndroid Build Coastguard Worker prefix_re = {} 448*635a8641SAndroid Build Coastguard Worker if environment.lstrip_blocks: 449*635a8641SAndroid Build Coastguard Worker # use '{%+' to manually disable lstrip_blocks behavior 450*635a8641SAndroid Build Coastguard Worker no_lstrip_re = e('+') 451*635a8641SAndroid Build Coastguard Worker # detect overlap between block and variable or comment strings 452*635a8641SAndroid Build Coastguard Worker block_diff = c(r'^%s(.*)' % e(environment.block_start_string)) 453*635a8641SAndroid Build Coastguard Worker # make sure we don't mistake a block for a variable or a comment 454*635a8641SAndroid Build Coastguard Worker m = block_diff.match(environment.comment_start_string) 455*635a8641SAndroid Build Coastguard Worker no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' 456*635a8641SAndroid Build Coastguard Worker m = block_diff.match(environment.variable_start_string) 457*635a8641SAndroid Build Coastguard Worker no_lstrip_re += m and r'|%s' % e(m.group(1)) or '' 458*635a8641SAndroid Build Coastguard Worker 459*635a8641SAndroid Build Coastguard Worker # detect overlap between comment and variable strings 460*635a8641SAndroid Build Coastguard Worker comment_diff = c(r'^%s(.*)' % e(environment.comment_start_string)) 461*635a8641SAndroid Build Coastguard Worker m = comment_diff.match(environment.variable_start_string) 462*635a8641SAndroid Build Coastguard Worker no_variable_re = m and r'(?!%s)' % e(m.group(1)) or '' 463*635a8641SAndroid Build Coastguard Worker 464*635a8641SAndroid Build Coastguard Worker lstrip_re = r'^[ \t]*' 465*635a8641SAndroid Build Coastguard Worker block_prefix_re = r'%s%s(?!%s)|%s\+?' % ( 466*635a8641SAndroid Build Coastguard Worker lstrip_re, 467*635a8641SAndroid Build Coastguard Worker e(environment.block_start_string), 468*635a8641SAndroid Build Coastguard Worker no_lstrip_re, 469*635a8641SAndroid Build Coastguard Worker e(environment.block_start_string), 470*635a8641SAndroid Build Coastguard Worker ) 471*635a8641SAndroid Build Coastguard Worker comment_prefix_re = r'%s%s%s|%s\+?' % ( 472*635a8641SAndroid Build Coastguard Worker lstrip_re, 473*635a8641SAndroid Build Coastguard Worker e(environment.comment_start_string), 474*635a8641SAndroid Build Coastguard Worker no_variable_re, 475*635a8641SAndroid Build Coastguard Worker e(environment.comment_start_string), 476*635a8641SAndroid Build Coastguard Worker ) 477*635a8641SAndroid Build Coastguard Worker prefix_re['block'] = block_prefix_re 478*635a8641SAndroid Build Coastguard Worker prefix_re['comment'] = comment_prefix_re 479*635a8641SAndroid Build Coastguard Worker else: 480*635a8641SAndroid Build Coastguard Worker block_prefix_re = '%s' % e(environment.block_start_string) 481*635a8641SAndroid Build Coastguard Worker 482*635a8641SAndroid Build Coastguard Worker self.newline_sequence = environment.newline_sequence 483*635a8641SAndroid Build Coastguard Worker self.keep_trailing_newline = environment.keep_trailing_newline 484*635a8641SAndroid Build Coastguard Worker 485*635a8641SAndroid Build Coastguard Worker # global lexing rules 486*635a8641SAndroid Build Coastguard Worker self.rules = { 487*635a8641SAndroid Build Coastguard Worker 'root': [ 488*635a8641SAndroid Build Coastguard Worker # directives 489*635a8641SAndroid Build Coastguard Worker (c('(.*?)(?:%s)' % '|'.join( 490*635a8641SAndroid Build Coastguard Worker [r'(?P<raw_begin>(?:\s*%s\-|%s)\s*raw\s*(?:\-%s\s*|%s))' % ( 491*635a8641SAndroid Build Coastguard Worker e(environment.block_start_string), 492*635a8641SAndroid Build Coastguard Worker block_prefix_re, 493*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string), 494*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string) 495*635a8641SAndroid Build Coastguard Worker )] + [ 496*635a8641SAndroid Build Coastguard Worker r'(?P<%s_begin>\s*%s\-|%s)' % (n, r, prefix_re.get(n,r)) 497*635a8641SAndroid Build Coastguard Worker for n, r in root_tag_rules 498*635a8641SAndroid Build Coastguard Worker ])), (TOKEN_DATA, '#bygroup'), '#bygroup'), 499*635a8641SAndroid Build Coastguard Worker # data 500*635a8641SAndroid Build Coastguard Worker (c('.+'), TOKEN_DATA, None) 501*635a8641SAndroid Build Coastguard Worker ], 502*635a8641SAndroid Build Coastguard Worker # comments 503*635a8641SAndroid Build Coastguard Worker TOKEN_COMMENT_BEGIN: [ 504*635a8641SAndroid Build Coastguard Worker (c(r'(.*?)((?:\-%s\s*|%s)%s)' % ( 505*635a8641SAndroid Build Coastguard Worker e(environment.comment_end_string), 506*635a8641SAndroid Build Coastguard Worker e(environment.comment_end_string), 507*635a8641SAndroid Build Coastguard Worker block_suffix_re 508*635a8641SAndroid Build Coastguard Worker )), (TOKEN_COMMENT, TOKEN_COMMENT_END), '#pop'), 509*635a8641SAndroid Build Coastguard Worker (c('(.)'), (Failure('Missing end of comment tag'),), None) 510*635a8641SAndroid Build Coastguard Worker ], 511*635a8641SAndroid Build Coastguard Worker # blocks 512*635a8641SAndroid Build Coastguard Worker TOKEN_BLOCK_BEGIN: [ 513*635a8641SAndroid Build Coastguard Worker (c(r'(?:\-%s\s*|%s)%s' % ( 514*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string), 515*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string), 516*635a8641SAndroid Build Coastguard Worker block_suffix_re 517*635a8641SAndroid Build Coastguard Worker )), TOKEN_BLOCK_END, '#pop'), 518*635a8641SAndroid Build Coastguard Worker ] + tag_rules, 519*635a8641SAndroid Build Coastguard Worker # variables 520*635a8641SAndroid Build Coastguard Worker TOKEN_VARIABLE_BEGIN: [ 521*635a8641SAndroid Build Coastguard Worker (c(r'\-%s\s*|%s' % ( 522*635a8641SAndroid Build Coastguard Worker e(environment.variable_end_string), 523*635a8641SAndroid Build Coastguard Worker e(environment.variable_end_string) 524*635a8641SAndroid Build Coastguard Worker )), TOKEN_VARIABLE_END, '#pop') 525*635a8641SAndroid Build Coastguard Worker ] + tag_rules, 526*635a8641SAndroid Build Coastguard Worker # raw block 527*635a8641SAndroid Build Coastguard Worker TOKEN_RAW_BEGIN: [ 528*635a8641SAndroid Build Coastguard Worker (c(r'(.*?)((?:\s*%s\-|%s)\s*endraw\s*(?:\-%s\s*|%s%s))' % ( 529*635a8641SAndroid Build Coastguard Worker e(environment.block_start_string), 530*635a8641SAndroid Build Coastguard Worker block_prefix_re, 531*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string), 532*635a8641SAndroid Build Coastguard Worker e(environment.block_end_string), 533*635a8641SAndroid Build Coastguard Worker block_suffix_re 534*635a8641SAndroid Build Coastguard Worker )), (TOKEN_DATA, TOKEN_RAW_END), '#pop'), 535*635a8641SAndroid Build Coastguard Worker (c('(.)'), (Failure('Missing end of raw directive'),), None) 536*635a8641SAndroid Build Coastguard Worker ], 537*635a8641SAndroid Build Coastguard Worker # line statements 538*635a8641SAndroid Build Coastguard Worker TOKEN_LINESTATEMENT_BEGIN: [ 539*635a8641SAndroid Build Coastguard Worker (c(r'\s*(\n|$)'), TOKEN_LINESTATEMENT_END, '#pop') 540*635a8641SAndroid Build Coastguard Worker ] + tag_rules, 541*635a8641SAndroid Build Coastguard Worker # line comments 542*635a8641SAndroid Build Coastguard Worker TOKEN_LINECOMMENT_BEGIN: [ 543*635a8641SAndroid Build Coastguard Worker (c(r'(.*?)()(?=\n|$)'), (TOKEN_LINECOMMENT, 544*635a8641SAndroid Build Coastguard Worker TOKEN_LINECOMMENT_END), '#pop') 545*635a8641SAndroid Build Coastguard Worker ] 546*635a8641SAndroid Build Coastguard Worker } 547*635a8641SAndroid Build Coastguard Worker 548*635a8641SAndroid Build Coastguard Worker def _normalize_newlines(self, value): 549*635a8641SAndroid Build Coastguard Worker """Called for strings and template data to normalize it to unicode.""" 550*635a8641SAndroid Build Coastguard Worker return newline_re.sub(self.newline_sequence, value) 551*635a8641SAndroid Build Coastguard Worker 552*635a8641SAndroid Build Coastguard Worker def tokenize(self, source, name=None, filename=None, state=None): 553*635a8641SAndroid Build Coastguard Worker """Calls tokeniter + tokenize and wraps it in a token stream. 554*635a8641SAndroid Build Coastguard Worker """ 555*635a8641SAndroid Build Coastguard Worker stream = self.tokeniter(source, name, filename, state) 556*635a8641SAndroid Build Coastguard Worker return TokenStream(self.wrap(stream, name, filename), name, filename) 557*635a8641SAndroid Build Coastguard Worker 558*635a8641SAndroid Build Coastguard Worker def wrap(self, stream, name=None, filename=None): 559*635a8641SAndroid Build Coastguard Worker """This is called with the stream as returned by `tokenize` and wraps 560*635a8641SAndroid Build Coastguard Worker every token in a :class:`Token` and converts the value. 561*635a8641SAndroid Build Coastguard Worker """ 562*635a8641SAndroid Build Coastguard Worker for lineno, token, value in stream: 563*635a8641SAndroid Build Coastguard Worker if token in ignored_tokens: 564*635a8641SAndroid Build Coastguard Worker continue 565*635a8641SAndroid Build Coastguard Worker elif token == 'linestatement_begin': 566*635a8641SAndroid Build Coastguard Worker token = 'block_begin' 567*635a8641SAndroid Build Coastguard Worker elif token == 'linestatement_end': 568*635a8641SAndroid Build Coastguard Worker token = 'block_end' 569*635a8641SAndroid Build Coastguard Worker # we are not interested in those tokens in the parser 570*635a8641SAndroid Build Coastguard Worker elif token in ('raw_begin', 'raw_end'): 571*635a8641SAndroid Build Coastguard Worker continue 572*635a8641SAndroid Build Coastguard Worker elif token == 'data': 573*635a8641SAndroid Build Coastguard Worker value = self._normalize_newlines(value) 574*635a8641SAndroid Build Coastguard Worker elif token == 'keyword': 575*635a8641SAndroid Build Coastguard Worker token = value 576*635a8641SAndroid Build Coastguard Worker elif token == 'name': 577*635a8641SAndroid Build Coastguard Worker value = str(value) 578*635a8641SAndroid Build Coastguard Worker if check_ident and not value.isidentifier(): 579*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError( 580*635a8641SAndroid Build Coastguard Worker 'Invalid character in identifier', 581*635a8641SAndroid Build Coastguard Worker lineno, name, filename) 582*635a8641SAndroid Build Coastguard Worker elif token == 'string': 583*635a8641SAndroid Build Coastguard Worker # try to unescape string 584*635a8641SAndroid Build Coastguard Worker try: 585*635a8641SAndroid Build Coastguard Worker value = self._normalize_newlines(value[1:-1]) \ 586*635a8641SAndroid Build Coastguard Worker .encode('ascii', 'backslashreplace') \ 587*635a8641SAndroid Build Coastguard Worker .decode('unicode-escape') 588*635a8641SAndroid Build Coastguard Worker except Exception as e: 589*635a8641SAndroid Build Coastguard Worker msg = str(e).split(':')[-1].strip() 590*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError(msg, lineno, name, filename) 591*635a8641SAndroid Build Coastguard Worker elif token == 'integer': 592*635a8641SAndroid Build Coastguard Worker value = int(value) 593*635a8641SAndroid Build Coastguard Worker elif token == 'float': 594*635a8641SAndroid Build Coastguard Worker value = float(value) 595*635a8641SAndroid Build Coastguard Worker elif token == 'operator': 596*635a8641SAndroid Build Coastguard Worker token = operators[value] 597*635a8641SAndroid Build Coastguard Worker yield Token(lineno, token, value) 598*635a8641SAndroid Build Coastguard Worker 599*635a8641SAndroid Build Coastguard Worker def tokeniter(self, source, name, filename=None, state=None): 600*635a8641SAndroid Build Coastguard Worker """This method tokenizes the text and returns the tokens in a 601*635a8641SAndroid Build Coastguard Worker generator. Use this method if you just want to tokenize a template. 602*635a8641SAndroid Build Coastguard Worker """ 603*635a8641SAndroid Build Coastguard Worker source = text_type(source) 604*635a8641SAndroid Build Coastguard Worker lines = source.splitlines() 605*635a8641SAndroid Build Coastguard Worker if self.keep_trailing_newline and source: 606*635a8641SAndroid Build Coastguard Worker for newline in ('\r\n', '\r', '\n'): 607*635a8641SAndroid Build Coastguard Worker if source.endswith(newline): 608*635a8641SAndroid Build Coastguard Worker lines.append('') 609*635a8641SAndroid Build Coastguard Worker break 610*635a8641SAndroid Build Coastguard Worker source = '\n'.join(lines) 611*635a8641SAndroid Build Coastguard Worker pos = 0 612*635a8641SAndroid Build Coastguard Worker lineno = 1 613*635a8641SAndroid Build Coastguard Worker stack = ['root'] 614*635a8641SAndroid Build Coastguard Worker if state is not None and state != 'root': 615*635a8641SAndroid Build Coastguard Worker assert state in ('variable', 'block'), 'invalid state' 616*635a8641SAndroid Build Coastguard Worker stack.append(state + '_begin') 617*635a8641SAndroid Build Coastguard Worker else: 618*635a8641SAndroid Build Coastguard Worker state = 'root' 619*635a8641SAndroid Build Coastguard Worker statetokens = self.rules[stack[-1]] 620*635a8641SAndroid Build Coastguard Worker source_length = len(source) 621*635a8641SAndroid Build Coastguard Worker 622*635a8641SAndroid Build Coastguard Worker balancing_stack = [] 623*635a8641SAndroid Build Coastguard Worker 624*635a8641SAndroid Build Coastguard Worker while 1: 625*635a8641SAndroid Build Coastguard Worker # tokenizer loop 626*635a8641SAndroid Build Coastguard Worker for regex, tokens, new_state in statetokens: 627*635a8641SAndroid Build Coastguard Worker m = regex.match(source, pos) 628*635a8641SAndroid Build Coastguard Worker # if no match we try again with the next rule 629*635a8641SAndroid Build Coastguard Worker if m is None: 630*635a8641SAndroid Build Coastguard Worker continue 631*635a8641SAndroid Build Coastguard Worker 632*635a8641SAndroid Build Coastguard Worker # we only match blocks and variables if braces / parentheses 633*635a8641SAndroid Build Coastguard Worker # are balanced. continue parsing with the lower rule which 634*635a8641SAndroid Build Coastguard Worker # is the operator rule. do this only if the end tags look 635*635a8641SAndroid Build Coastguard Worker # like operators 636*635a8641SAndroid Build Coastguard Worker if balancing_stack and \ 637*635a8641SAndroid Build Coastguard Worker tokens in ('variable_end', 'block_end', 638*635a8641SAndroid Build Coastguard Worker 'linestatement_end'): 639*635a8641SAndroid Build Coastguard Worker continue 640*635a8641SAndroid Build Coastguard Worker 641*635a8641SAndroid Build Coastguard Worker # tuples support more options 642*635a8641SAndroid Build Coastguard Worker if isinstance(tokens, tuple): 643*635a8641SAndroid Build Coastguard Worker for idx, token in enumerate(tokens): 644*635a8641SAndroid Build Coastguard Worker # failure group 645*635a8641SAndroid Build Coastguard Worker if token.__class__ is Failure: 646*635a8641SAndroid Build Coastguard Worker raise token(lineno, filename) 647*635a8641SAndroid Build Coastguard Worker # bygroup is a bit more complex, in that case we 648*635a8641SAndroid Build Coastguard Worker # yield for the current token the first named 649*635a8641SAndroid Build Coastguard Worker # group that matched 650*635a8641SAndroid Build Coastguard Worker elif token == '#bygroup': 651*635a8641SAndroid Build Coastguard Worker for key, value in iteritems(m.groupdict()): 652*635a8641SAndroid Build Coastguard Worker if value is not None: 653*635a8641SAndroid Build Coastguard Worker yield lineno, key, value 654*635a8641SAndroid Build Coastguard Worker lineno += value.count('\n') 655*635a8641SAndroid Build Coastguard Worker break 656*635a8641SAndroid Build Coastguard Worker else: 657*635a8641SAndroid Build Coastguard Worker raise RuntimeError('%r wanted to resolve ' 658*635a8641SAndroid Build Coastguard Worker 'the token dynamically' 659*635a8641SAndroid Build Coastguard Worker ' but no group matched' 660*635a8641SAndroid Build Coastguard Worker % regex) 661*635a8641SAndroid Build Coastguard Worker # normal group 662*635a8641SAndroid Build Coastguard Worker else: 663*635a8641SAndroid Build Coastguard Worker data = m.group(idx + 1) 664*635a8641SAndroid Build Coastguard Worker if data or token not in ignore_if_empty: 665*635a8641SAndroid Build Coastguard Worker yield lineno, token, data 666*635a8641SAndroid Build Coastguard Worker lineno += data.count('\n') 667*635a8641SAndroid Build Coastguard Worker 668*635a8641SAndroid Build Coastguard Worker # strings as token just are yielded as it. 669*635a8641SAndroid Build Coastguard Worker else: 670*635a8641SAndroid Build Coastguard Worker data = m.group() 671*635a8641SAndroid Build Coastguard Worker # update brace/parentheses balance 672*635a8641SAndroid Build Coastguard Worker if tokens == 'operator': 673*635a8641SAndroid Build Coastguard Worker if data == '{': 674*635a8641SAndroid Build Coastguard Worker balancing_stack.append('}') 675*635a8641SAndroid Build Coastguard Worker elif data == '(': 676*635a8641SAndroid Build Coastguard Worker balancing_stack.append(')') 677*635a8641SAndroid Build Coastguard Worker elif data == '[': 678*635a8641SAndroid Build Coastguard Worker balancing_stack.append(']') 679*635a8641SAndroid Build Coastguard Worker elif data in ('}', ')', ']'): 680*635a8641SAndroid Build Coastguard Worker if not balancing_stack: 681*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError('unexpected \'%s\'' % 682*635a8641SAndroid Build Coastguard Worker data, lineno, name, 683*635a8641SAndroid Build Coastguard Worker filename) 684*635a8641SAndroid Build Coastguard Worker expected_op = balancing_stack.pop() 685*635a8641SAndroid Build Coastguard Worker if expected_op != data: 686*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError('unexpected \'%s\', ' 687*635a8641SAndroid Build Coastguard Worker 'expected \'%s\'' % 688*635a8641SAndroid Build Coastguard Worker (data, expected_op), 689*635a8641SAndroid Build Coastguard Worker lineno, name, 690*635a8641SAndroid Build Coastguard Worker filename) 691*635a8641SAndroid Build Coastguard Worker # yield items 692*635a8641SAndroid Build Coastguard Worker if data or tokens not in ignore_if_empty: 693*635a8641SAndroid Build Coastguard Worker yield lineno, tokens, data 694*635a8641SAndroid Build Coastguard Worker lineno += data.count('\n') 695*635a8641SAndroid Build Coastguard Worker 696*635a8641SAndroid Build Coastguard Worker # fetch new position into new variable so that we can check 697*635a8641SAndroid Build Coastguard Worker # if there is a internal parsing error which would result 698*635a8641SAndroid Build Coastguard Worker # in an infinite loop 699*635a8641SAndroid Build Coastguard Worker pos2 = m.end() 700*635a8641SAndroid Build Coastguard Worker 701*635a8641SAndroid Build Coastguard Worker # handle state changes 702*635a8641SAndroid Build Coastguard Worker if new_state is not None: 703*635a8641SAndroid Build Coastguard Worker # remove the uppermost state 704*635a8641SAndroid Build Coastguard Worker if new_state == '#pop': 705*635a8641SAndroid Build Coastguard Worker stack.pop() 706*635a8641SAndroid Build Coastguard Worker # resolve the new state by group checking 707*635a8641SAndroid Build Coastguard Worker elif new_state == '#bygroup': 708*635a8641SAndroid Build Coastguard Worker for key, value in iteritems(m.groupdict()): 709*635a8641SAndroid Build Coastguard Worker if value is not None: 710*635a8641SAndroid Build Coastguard Worker stack.append(key) 711*635a8641SAndroid Build Coastguard Worker break 712*635a8641SAndroid Build Coastguard Worker else: 713*635a8641SAndroid Build Coastguard Worker raise RuntimeError('%r wanted to resolve the ' 714*635a8641SAndroid Build Coastguard Worker 'new state dynamically but' 715*635a8641SAndroid Build Coastguard Worker ' no group matched' % 716*635a8641SAndroid Build Coastguard Worker regex) 717*635a8641SAndroid Build Coastguard Worker # direct state name given 718*635a8641SAndroid Build Coastguard Worker else: 719*635a8641SAndroid Build Coastguard Worker stack.append(new_state) 720*635a8641SAndroid Build Coastguard Worker statetokens = self.rules[stack[-1]] 721*635a8641SAndroid Build Coastguard Worker # we are still at the same position and no stack change. 722*635a8641SAndroid Build Coastguard Worker # this means a loop without break condition, avoid that and 723*635a8641SAndroid Build Coastguard Worker # raise error 724*635a8641SAndroid Build Coastguard Worker elif pos2 == pos: 725*635a8641SAndroid Build Coastguard Worker raise RuntimeError('%r yielded empty string without ' 726*635a8641SAndroid Build Coastguard Worker 'stack change' % regex) 727*635a8641SAndroid Build Coastguard Worker # publish new function and start again 728*635a8641SAndroid Build Coastguard Worker pos = pos2 729*635a8641SAndroid Build Coastguard Worker break 730*635a8641SAndroid Build Coastguard Worker # if loop terminated without break we haven't found a single match 731*635a8641SAndroid Build Coastguard Worker # either we are at the end of the file or we have a problem 732*635a8641SAndroid Build Coastguard Worker else: 733*635a8641SAndroid Build Coastguard Worker # end of text 734*635a8641SAndroid Build Coastguard Worker if pos >= source_length: 735*635a8641SAndroid Build Coastguard Worker return 736*635a8641SAndroid Build Coastguard Worker # something went wrong 737*635a8641SAndroid Build Coastguard Worker raise TemplateSyntaxError('unexpected char %r at %d' % 738*635a8641SAndroid Build Coastguard Worker (source[pos], pos), lineno, 739*635a8641SAndroid Build Coastguard Worker name, filename) 740