summaryrefslogtreecommitdiff
path: root/cheetah/Parser.py
diff options
context:
space:
mode:
Diffstat (limited to 'cheetah/Parser.py')
-rw-r--r--cheetah/Parser.py2662
1 files changed, 2662 insertions, 0 deletions
diff --git a/cheetah/Parser.py b/cheetah/Parser.py
new file mode 100644
index 0000000..7436e9c
--- /dev/null
+++ b/cheetah/Parser.py
@@ -0,0 +1,2662 @@
+# $Id: Parser.py,v 1.137 2008/03/10 05:25:13 tavis_rudd Exp $
+"""Parser classes for Cheetah's Compiler
+
+Classes:
+ ParseError( Exception )
+ _LowLevelParser( Cheetah.SourceReader.SourceReader ), basically a lexer
+ _HighLevelParser( _LowLevelParser )
+ Parser === _HighLevelParser (an alias)
+
+Meta-Data
+================================================================================
+Author: Tavis Rudd <tavis@damnsimple.com>
+Version: $Revision: 1.137 $
+Start Date: 2001/08/01
+Last Revision Date: $Date: 2008/03/10 05:25:13 $
+"""
+__author__ = "Tavis Rudd <tavis@damnsimple.com>"
+__revision__ = "$Revision: 1.137 $"[11:-2]
+
+import os
+import sys
+import re
+from re import DOTALL, MULTILINE
+from types import StringType, ListType, TupleType, ClassType, TypeType
+import time
+from tokenize import pseudoprog
+import inspect
+import new
+import traceback
+
+from Cheetah.SourceReader import SourceReader
+from Cheetah import Filters
+from Cheetah import ErrorCatchers
+from Cheetah.Unspecified import Unspecified
+from Cheetah.Macros.I18n import I18n
+
+# re tools
+_regexCache = {}
+def cachedRegex(pattern):
+ if pattern not in _regexCache:
+ _regexCache[pattern] = re.compile(pattern)
+ return _regexCache[pattern]
+
+def escapeRegexChars(txt,
+ escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
+
+ """Return a txt with all special regular expressions chars escaped."""
+
+ return escapeRE.sub(r'\\\1' , txt)
+
+def group(*choices): return '(' + '|'.join(choices) + ')'
+def nongroup(*choices): return '(?:' + '|'.join(choices) + ')'
+def namedGroup(name, *choices): return '(P:<' + name +'>' + '|'.join(choices) + ')'
+def any(*choices): return apply(group, choices) + '*'
+def maybe(*choices): return apply(group, choices) + '?'
+
+##################################################
+## CONSTANTS & GLOBALS ##
+
+NO_CACHE = 0
+STATIC_CACHE = 1
+REFRESH_CACHE = 2
+
+SET_LOCAL = 0
+SET_GLOBAL = 1
+SET_MODULE = 2
+
+##################################################
+## Tokens for the parser ##
+
+#generic
+identchars = "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
+namechars = identchars + "0123456789"
+
+#operators
+powerOp = '**'
+unaryArithOps = ('+', '-', '~')
+binaryArithOps = ('+', '-', '/', '//','%')
+shiftOps = ('>>','<<')
+bitwiseOps = ('&','|','^')
+assignOp = '='
+augAssignOps = ('+=','-=','/=','*=', '**=','^=','%=',
+ '>>=','<<=','&=','|=', )
+assignmentOps = (assignOp,) + augAssignOps
+
+compOps = ('<','>','==','!=','<=','>=', '<>', 'is', 'in',)
+booleanOps = ('and','or','not')
+operators = (powerOp,) + unaryArithOps + binaryArithOps \
+ + shiftOps + bitwiseOps + assignmentOps \
+ + compOps + booleanOps
+
+delimeters = ('(',')','{','}','[',']',
+ ',','.',':',';','=','`') + augAssignOps
+
+
+keywords = ('and', 'del', 'for', 'is', 'raise',
+ 'assert', 'elif', 'from', 'lambda', 'return',
+ 'break', 'else', 'global', 'not', 'try',
+ 'class', 'except', 'if', 'or', 'while',
+ 'continue', 'exec', 'import', 'pass',
+ 'def', 'finally', 'in', 'print',
+ )
+
+single3 = "'''"
+double3 = '"""'
+
+tripleQuotedStringStarts = ("'''", '"""',
+ "r'''", 'r"""', "R'''", 'R"""',
+ "u'''", 'u"""', "U'''", 'U"""',
+ "ur'''", 'ur"""', "Ur'''", 'Ur"""',
+ "uR'''", 'uR"""', "UR'''", 'UR"""')
+
+tripleQuotedStringPairs = {"'''": single3, '"""': double3,
+ "r'''": single3, 'r"""': double3,
+ "u'''": single3, 'u"""': double3,
+ "ur'''": single3, 'ur"""': double3,
+ "R'''": single3, 'R"""': double3,
+ "U'''": single3, 'U"""': double3,
+ "uR'''": single3, 'uR"""': double3,
+ "Ur'''": single3, 'Ur"""': double3,
+ "UR'''": single3, 'UR"""': double3,
+ }
+
+closurePairs= {')':'(',']':'[','}':'{'}
+closurePairsRev= {'(':')','[':']','{':'}'}
+
+##################################################
+## Regex chunks for the parser ##
+
+tripleQuotedStringREs = {}
+def makeTripleQuoteRe(start, end):
+ start = escapeRegexChars(start)
+ end = escapeRegexChars(end)
+ return re.compile(r'(?:' + start + r').*?' + r'(?:' + end + r')', re.DOTALL)
+
+for start, end in tripleQuotedStringPairs.items():
+ tripleQuotedStringREs[start] = makeTripleQuoteRe(start, end)
+
+WS = r'[ \f\t]*'
+EOL = r'\r\n|\n|\r'
+EOLZ = EOL + r'|\Z'
+escCharLookBehind = nongroup(r'(?<=\A)',r'(?<!\\)')
+nameCharLookAhead = r'(?=[A-Za-z_])'
+identRE=re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
+EOLre=re.compile(r'(?:\r\n|\r|\n)')
+
+specialVarRE=re.compile(r'([a-zA-z_]+)@') # for matching specialVar comments
+# e.g. ##author@ Tavis Rudd
+
+unicodeDirectiveRE = re.compile(
+ r'(?:^|\r\n|\r|\n)\s*#\s{0,5}unicode[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re.MULTILINE)
+encodingDirectiveRE = re.compile(
+ r'(?:^|\r\n|\r|\n)\s*#\s{0,5}encoding[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re.MULTILINE)
+
+escapedNewlineRE = re.compile(r'(?<!\\)\\n')
+
+directiveNamesAndParsers = {
+ # importing and inheritance
+ 'import':None,
+ 'from':None,
+ 'extends': 'eatExtends',
+ 'implements': 'eatImplements',
+ 'super': 'eatSuper',
+
+ # output, filtering, and caching
+ 'slurp': 'eatSlurp',
+ 'raw': 'eatRaw',
+ 'include': 'eatInclude',
+ 'cache': 'eatCache',
+ 'filter': 'eatFilter',
+ 'echo': None,
+ 'silent': None,
+ 'transform' : 'eatTransform',
+
+ 'call': 'eatCall',
+ 'arg': 'eatCallArg',
+
+ 'capture': 'eatCapture',
+
+ # declaration, assignment, and deletion
+ 'attr': 'eatAttr',
+ 'def': 'eatDef',
+ 'block': 'eatBlock',
+ '@': 'eatDecorator',
+ 'defmacro': 'eatDefMacro',
+
+ 'closure': 'eatClosure',
+
+ 'set': 'eatSet',
+ 'del': None,
+
+ # flow control
+ 'if': 'eatIf',
+ 'while': None,
+ 'for': None,
+ 'else': None,
+ 'elif': None,
+ 'pass': None,
+ 'break': None,
+ 'continue': None,
+ 'stop': None,
+ 'return': None,
+ 'yield': None,
+
+ # little wrappers
+ 'repeat': None,
+ 'unless': None,
+
+ # error handling
+ 'assert': None,
+ 'raise': None,
+ 'try': None,
+ 'except': None,
+ 'finally': None,
+ 'errorCatcher': 'eatErrorCatcher',
+
+ # intructions to the parser and compiler
+ 'breakpoint': 'eatBreakPoint',
+ 'compiler': 'eatCompiler',
+ 'compiler-settings': 'eatCompilerSettings',
+
+ # misc
+ 'shBang': 'eatShbang',
+ 'encoding': 'eatEncoding',
+
+ 'end': 'eatEndDirective',
+ }
+
+endDirectiveNamesAndHandlers = {
+ 'def': 'handleEndDef', # has short-form
+ 'block': None, # has short-form
+ 'closure': None, # has short-form
+ 'cache': None, # has short-form
+ 'call': None, # has short-form
+ 'capture': None, # has short-form
+ 'filter': None,
+ 'errorCatcher':None,
+ 'while': None, # has short-form
+ 'for': None, # has short-form
+ 'if': None, # has short-form
+ 'try': None, # has short-form
+ 'repeat': None, # has short-form
+ 'unless': None, # has short-form
+ }
+
+##################################################
+## CLASSES ##
+
+# @@TR: SyntaxError doesn't call exception.__str__ for some reason!
+#class ParseError(SyntaxError):
+class ParseError(ValueError):
+ def __init__(self, stream, msg='Invalid Syntax', extMsg='', lineno=None, col=None):
+ self.stream = stream
+ if stream.pos() >= len(stream):
+ stream.setPos(len(stream) -1)
+ self.msg = msg
+ self.extMsg = extMsg
+ self.lineno = lineno
+ self.col = col
+
+ def __str__(self):
+ return self.report()
+
+ def report(self):
+ stream = self.stream
+ if stream.filename():
+ f = " in file %s" % stream.filename()
+ else:
+ f = ''
+ report = ''
+ if self.lineno:
+ lineno = self.lineno
+ row, col, line = (lineno, (self.col or 0),
+ self.stream.splitlines()[lineno-1])
+ else:
+ row, col, line = self.stream.getRowColLine()
+
+ ## get the surrounding lines
+ lines = stream.splitlines()
+ prevLines = [] # (rowNum, content)
+ for i in range(1,4):
+ if row-1-i <=0:
+ break
+ prevLines.append( (row-i,lines[row-1-i]) )
+
+ nextLines = [] # (rowNum, content)
+ for i in range(1,4):
+ if not row-1+i < len(lines):
+ break
+ nextLines.append( (row+i,lines[row-1+i]) )
+ nextLines.reverse()
+
+ ## print the main message
+ report += "\n\n%s\n" %self.msg
+ report += "Line %i, column %i%s\n\n" % (row, col, f)
+ report += 'Line|Cheetah Code\n'
+ report += '----|-------------------------------------------------------------\n'
+ while prevLines:
+ lineInfo = prevLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+ report += "%(row)-4d|%(line)s\n"% {'row':row, 'line':line}
+ report += ' '*5 +' '*(col-1) + "^\n"
+
+ while nextLines:
+ lineInfo = nextLines.pop()
+ report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
+ ## add the extra msg
+ if self.extMsg:
+ report += self.extMsg + '\n'
+
+ return report
+
+class ForbiddenSyntax(ParseError): pass
+class ForbiddenExpression(ForbiddenSyntax): pass
+class ForbiddenDirective(ForbiddenSyntax): pass
+
+class CheetahVariable:
+ def __init__(self, nameChunks, useNameMapper=True, cacheToken=None,
+ rawSource=None):
+ self.nameChunks = nameChunks
+ self.useNameMapper = useNameMapper
+ self.cacheToken = cacheToken
+ self.rawSource = rawSource
+
+class Placeholder(CheetahVariable): pass
+
+class ArgList:
+ """Used by _LowLevelParser.getArgList()"""
+
+ def __init__(self):
+ self.argNames = []
+ self.defVals = []
+ self.i = 0
+
+ def addArgName(self, name):
+ self.argNames.append( name )
+ self.defVals.append( None )
+
+ def next(self):
+ self.i += 1
+
+ def addToDefVal(self, token):
+ i = self.i
+ if self.defVals[i] == None:
+ self.defVals[i] = ''
+ self.defVals[i] += token
+
+ def merge(self):
+ defVals = self.defVals
+ for i in range(len(defVals)):
+ if type(defVals[i]) == StringType:
+ defVals[i] = defVals[i].strip()
+
+ return map(None, [i.strip() for i in self.argNames], defVals)
+
+ def __str__(self):
+ return str(self.merge())
+
+class _LowLevelParser(SourceReader):
+ """This class implements the methods to match or extract ('get*') the basic
+ elements of Cheetah's grammar. It does NOT handle any code generation or
+ state management.
+ """
+
+ _settingsManager = None
+
+ def setSettingsManager(self, settingsManager):
+ self._settingsManager = settingsManager
+
+ def setting(self, key, default=Unspecified):
+ if default is Unspecified:
+ return self._settingsManager.setting(key)
+ else:
+ return self._settingsManager.setting(key, default=default)
+
+ def setSetting(self, key, val):
+ self._settingsManager.setSetting(key, val)
+
+ def settings(self):
+ return self._settingsManager.settings()
+
+ def updateSettings(self, settings):
+ self._settingsManager.updateSettings(settings)
+
+ def _initializeSettings(self):
+ self._settingsManager._initializeSettings()
+
+ def configureParser(self):
+ """Is called by the Compiler instance after the parser has had a
+ settingsManager assigned with self.setSettingsManager()
+ """
+ self._makeCheetahVarREs()
+ self._makeCommentREs()
+ self._makeDirectiveREs()
+ self._makePspREs()
+ self._possibleNonStrConstantChars = (
+ self.setting('commentStartToken')[0] +
+ self.setting('multiLineCommentStartToken')[0] +
+ self.setting('cheetahVarStartToken')[0] +
+ self.setting('directiveStartToken')[0] +
+ self.setting('PSPStartToken')[0])
+ self._nonStrConstMatchers = [
+ self.matchCommentStartToken,
+ self.matchMultiLineCommentStartToken,
+ self.matchVariablePlaceholderStart,
+ self.matchExpressionPlaceholderStart,
+ self.matchDirective,
+ self.matchPSPStartToken,
+ self.matchEOLSlurpToken,
+ ]
+
+ ## regex setup ##
+
+ def _makeCheetahVarREs(self):
+
+ """Setup the regexs for Cheetah $var parsing."""
+
+ num = r'[0-9\.]+'
+ interval = (r'(?P<interval>' +
+ num + r's|' +
+ num + r'm|' +
+ num + r'h|' +
+ num + r'd|' +
+ num + r'w|' +
+ num + ')'
+ )
+
+ cacheToken = (r'(?:' +
+ r'(?P<REFRESH_CACHE>\*' + interval + '\*)'+
+ '|' +
+ r'(?P<STATIC_CACHE>\*)' +
+ '|' +
+ r'(?P<NO_CACHE>)' +
+ ')')
+ self.cacheTokenRE = cachedRegex(cacheToken)
+
+ silentPlaceholderToken = (r'(?:' +
+ r'(?P<SILENT>' +escapeRegexChars('!')+')'+
+ '|' +
+ r'(?P<NOT_SILENT>)' +
+ ')')
+ self.silentPlaceholderTokenRE = cachedRegex(silentPlaceholderToken)
+
+ self.cheetahVarStartRE = cachedRegex(
+ escCharLookBehind +
+ r'(?P<startToken>'+escapeRegexChars(self.setting('cheetahVarStartToken'))+')'+
+ r'(?P<silenceToken>'+silentPlaceholderToken+')'+
+ r'(?P<cacheToken>'+cacheToken+')'+
+ r'(?P<enclosure>|(?:(?:\{|\(|\[)[ \t\f]*))' + # allow WS after enclosure
+ r'(?=[A-Za-z_])')
+ validCharsLookAhead = r'(?=[A-Za-z_\*!\{\(\[])'
+ self.cheetahVarStartToken = self.setting('cheetahVarStartToken')
+ self.cheetahVarStartTokenRE = cachedRegex(
+ escCharLookBehind +
+ escapeRegexChars(self.setting('cheetahVarStartToken'))
+ +validCharsLookAhead
+ )
+
+ self.cheetahVarInExpressionStartTokenRE = cachedRegex(
+ escapeRegexChars(self.setting('cheetahVarStartToken'))
+ +r'(?=[A-Za-z_])'
+ )
+
+ self.expressionPlaceholderStartRE = cachedRegex(
+ escCharLookBehind +
+ r'(?P<startToken>' + escapeRegexChars(self.setting('cheetahVarStartToken')) + ')' +
+ r'(?P<cacheToken>' + cacheToken + ')' +
+ #r'\[[ \t\f]*'
+ r'(?:\{|\(|\[)[ \t\f]*'
+ + r'(?=[^\)\}\]])'
+ )
+
+ if self.setting('EOLSlurpToken'):
+ self.EOLSlurpRE = cachedRegex(
+ escapeRegexChars(self.setting('EOLSlurpToken'))
+ + r'[ \t\f]*'
+ + r'(?:'+EOL+')'
+ )
+ else:
+ self.EOLSlurpRE = None
+
+
+ def _makeCommentREs(self):
+ """Construct the regex bits that are used in comment parsing."""
+ startTokenEsc = escapeRegexChars(self.setting('commentStartToken'))
+ self.commentStartTokenRE = cachedRegex(escCharLookBehind + startTokenEsc)
+ del startTokenEsc
+
+ startTokenEsc = escapeRegexChars(
+ self.setting('multiLineCommentStartToken'))
+ endTokenEsc = escapeRegexChars(
+ self.setting('multiLineCommentEndToken'))
+ self.multiLineCommentTokenStartRE = cachedRegex(escCharLookBehind +
+ startTokenEsc)
+ self.multiLineCommentEndTokenRE = cachedRegex(escCharLookBehind +
+ endTokenEsc)
+
+ def _makeDirectiveREs(self):
+ """Construct the regexs that are used in directive parsing."""
+ startToken = self.setting('directiveStartToken')
+ endToken = self.setting('directiveEndToken')
+ startTokenEsc = escapeRegexChars(startToken)
+ endTokenEsc = escapeRegexChars(endToken)
+ validSecondCharsLookAhead = r'(?=[A-Za-z_@])'
+ reParts = [escCharLookBehind, startTokenEsc]
+ if self.setting('allowWhitespaceAfterDirectiveStartToken'):
+ reParts.append('[ \t]*')
+ reParts.append(validSecondCharsLookAhead)
+ self.directiveStartTokenRE = cachedRegex(''.join(reParts))
+ self.directiveEndTokenRE = cachedRegex(escCharLookBehind + endTokenEsc)
+
+ def _makePspREs(self):
+ """Setup the regexs for PSP parsing."""
+ startToken = self.setting('PSPStartToken')
+ startTokenEsc = escapeRegexChars(startToken)
+ self.PSPStartTokenRE = cachedRegex(escCharLookBehind + startTokenEsc)
+ endToken = self.setting('PSPEndToken')
+ endTokenEsc = escapeRegexChars(endToken)
+ self.PSPEndTokenRE = cachedRegex(escCharLookBehind + endTokenEsc)
+
+
+ def isLineClearToStartToken(self, pos=None):
+ return self.isLineClearToPos(pos)
+
+ def matchTopLevelToken(self):
+ """Returns the first match found from the following methods:
+ self.matchCommentStartToken
+ self.matchMultiLineCommentStartToken
+ self.matchVariablePlaceholderStart
+ self.matchExpressionPlaceholderStart
+ self.matchDirective
+ self.matchPSPStartToken
+ self.matchEOLSlurpToken
+
+ Returns None if no match.
+ """
+ match = None
+ if self.peek() in self._possibleNonStrConstantChars:
+ for matcher in self._nonStrConstMatchers:
+ match = matcher()
+ if match:
+ break
+ return match
+
+ def matchPyToken(self):
+ match = pseudoprog.match(self.src(), self.pos())
+
+ if match and match.group() in tripleQuotedStringStarts:
+ TQSmatch = tripleQuotedStringREs[match.group()].match(self.src(), self.pos())
+ if TQSmatch:
+ return TQSmatch
+ return match
+
+ def getPyToken(self):
+ match = self.matchPyToken()
+ if match is None:
+ raise ParseError(self)
+ elif match.group() in tripleQuotedStringStarts:
+ raise ParseError(self, msg='Malformed triple-quoted string')
+ return self.readTo(match.end())
+
+ def matchEOLSlurpToken(self):
+ if self.EOLSlurpRE:
+ return self.EOLSlurpRE.match(self.src(), self.pos())
+
+ def getEOLSlurpToken(self):
+ match = self.matchEOLSlurpToken()
+ if not match:
+ raise ParseError(self, msg='Invalid EOL slurp token')
+ return self.readTo(match.end())
+
+ def matchCommentStartToken(self):
+ return self.commentStartTokenRE.match(self.src(), self.pos())
+
+ def getCommentStartToken(self):
+ match = self.matchCommentStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid single-line comment start token')
+ return self.readTo(match.end())
+
+ def matchMultiLineCommentStartToken(self):
+ return self.multiLineCommentTokenStartRE.match(self.src(), self.pos())
+
+ def getMultiLineCommentStartToken(self):
+ match = self.matchMultiLineCommentStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid multi-line comment start token')
+ return self.readTo(match.end())
+
+ def matchMultiLineCommentEndToken(self):
+ return self.multiLineCommentEndTokenRE.match(self.src(), self.pos())
+
+ def getMultiLineCommentEndToken(self):
+ match = self.matchMultiLineCommentEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid multi-line comment end token')
+ return self.readTo(match.end())
+
+ def getCommaSeparatedSymbols(self):
+ """
+ Loosely based on getDottedName to pull out comma separated
+ named chunks
+ """
+ srcLen = len(self)
+ pieces = []
+ nameChunks = []
+
+ if not self.peek() in identchars:
+ raise ParseError(self)
+
+ while self.pos() < srcLen:
+ c = self.peek()
+ if c in namechars:
+ nameChunk = self.getIdentifier()
+ nameChunks.append(nameChunk)
+ elif c == '.':
+ if self.pos()+1 <srcLen and self.peek(1) in identchars:
+ nameChunks.append(self.getc())
+ else:
+ break
+ elif c == ',':
+ self.getc()
+ pieces.append(''.join(nameChunks))
+ nameChunks = []
+ elif c in (' ', '\t'):
+ self.getc()
+ else:
+ break
+
+ if nameChunks:
+ pieces.append(''.join(nameChunks))
+
+ return pieces
+
+ def getDottedName(self):
+ srcLen = len(self)
+ nameChunks = []
+
+ if not self.peek() in identchars:
+ raise ParseError(self)
+
+ while self.pos() < srcLen:
+ c = self.peek()
+ if c in namechars:
+ nameChunk = self.getIdentifier()
+ nameChunks.append(nameChunk)
+ elif c == '.':
+ if self.pos()+1 <srcLen and self.peek(1) in identchars:
+ nameChunks.append(self.getc())
+ else:
+ break
+ else:
+ break
+
+ return ''.join(nameChunks)
+
+ def matchIdentifier(self):
+ return identRE.match(self.src(), self.pos())
+
+ def getIdentifier(self):
+ match = self.matchIdentifier()
+ if not match:
+ raise ParseError(self, msg='Invalid identifier')
+ return self.readTo(match.end())
+
+ def matchOperator(self):
+ match = self.matchPyToken()
+ if match and match.group() not in operators:
+ match = None
+ return match
+
+ def getOperator(self):
+ match = self.matchOperator()
+ if not match:
+ raise ParseError(self, msg='Expected operator')
+ return self.readTo( match.end() )
+
+ def matchAssignmentOperator(self):
+ match = self.matchPyToken()
+ if match and match.group() not in assignmentOps:
+ match = None
+ return match
+
+ def getAssignmentOperator(self):
+ match = self.matchAssignmentOperator()
+ if not match:
+ raise ParseError(self, msg='Expected assignment operator')
+ return self.readTo( match.end() )
+
+ def matchDirective(self):
+ """Returns False or the name of the directive matched.
+ """
+ startPos = self.pos()
+ if not self.matchDirectiveStartToken():
+ return False
+ self.getDirectiveStartToken()
+ directiveName = self.matchDirectiveName()
+ self.setPos(startPos)
+ return directiveName
+
+ def matchDirectiveName(self, directiveNameChars=identchars+'0123456789-@'):
+ startPos = self.pos()
+ possibleMatches = self._directiveNamesAndParsers.keys()
+ name = ''
+ match = None
+
+ while not self.atEnd():
+ c = self.getc()
+ if not c in directiveNameChars:
+ break
+ name += c
+ if name == '@':
+ if not self.atEnd() and self.peek() in identchars:
+ match = '@'
+ break
+ possibleMatches = [dn for dn in possibleMatches if dn.startswith(name)]
+ if not possibleMatches:
+ break
+ elif (name in possibleMatches and (self.atEnd() or self.peek() not in directiveNameChars)):
+ match = name
+ break
+
+ self.setPos(startPos)
+ return match
+
+ def matchDirectiveStartToken(self):
+ return self.directiveStartTokenRE.match(self.src(), self.pos())
+
+ def getDirectiveStartToken(self):
+ match = self.matchDirectiveStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid directive start token')
+ return self.readTo(match.end())
+
+ def matchDirectiveEndToken(self):
+ return self.directiveEndTokenRE.match(self.src(), self.pos())
+
+ def getDirectiveEndToken(self):
+ match = self.matchDirectiveEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid directive end token')
+ return self.readTo(match.end())
+
+
+ def matchColonForSingleLineShortFormDirective(self):
+ if not self.atEnd() and self.peek()==':':
+ restOfLine = self[self.pos()+1:self.findEOL()]
+ restOfLine = restOfLine.strip()
+ if not restOfLine:
+ return False
+ elif self.commentStartTokenRE.match(restOfLine):
+ return False
+ else: # non-whitespace, non-commment chars found
+ return True
+ return False
+
+ def matchPSPStartToken(self):
+ return self.PSPStartTokenRE.match(self.src(), self.pos())
+
+ def matchPSPEndToken(self):
+ return self.PSPEndTokenRE.match(self.src(), self.pos())
+
+ def getPSPStartToken(self):
+ match = self.matchPSPStartToken()
+ if not match:
+ raise ParseError(self, msg='Invalid psp start token')
+ return self.readTo(match.end())
+
+ def getPSPEndToken(self):
+ match = self.matchPSPEndToken()
+ if not match:
+ raise ParseError(self, msg='Invalid psp end token')
+ return self.readTo(match.end())
+
+ def matchCheetahVarStart(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartRE.match(self.src(), self.pos())
+
+ def matchCheetahVarStartToken(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartTokenRE.match(self.src(), self.pos())
+
+ def matchCheetahVarInExpressionStartToken(self):
+ """no enclosures or cache tokens allowed"""
+ return self.cheetahVarInExpressionStartTokenRE.match(self.src(), self.pos())
+
+ def matchVariablePlaceholderStart(self):
+ """includes the enclosure and cache token"""
+ return self.cheetahVarStartRE.match(self.src(), self.pos())
+
+ def matchExpressionPlaceholderStart(self):
+ """includes the enclosure and cache token"""
+ return self.expressionPlaceholderStartRE.match(self.src(), self.pos())
+
+ def getCheetahVarStartToken(self):
+ """just the start token, not the enclosure or cache token"""
+ match = self.matchCheetahVarStartToken()
+ if not match:
+ raise ParseError(self, msg='Expected Cheetah $var start token')
+ return self.readTo( match.end() )
+
+
+ def getCacheToken(self):
+ try:
+ token = self.cacheTokenRE.match(self.src(), self.pos())
+ self.setPos( token.end() )
+ return token.group()
+ except:
+ raise ParseError(self, msg='Expected cache token')
+
+ def getSilentPlaceholderToken(self):
+ try:
+ token = self.silentPlaceholderTokenRE.match(self.src(), self.pos())
+ self.setPos( token.end() )
+ return token.group()
+ except:
+ raise ParseError(self, msg='Expected silent placeholder token')
+
+
+
+ def getTargetVarsList(self):
+ varnames = []
+ while not self.atEnd():
+ if self.peek() in ' \t\f':
+ self.getWhiteSpace()
+ elif self.peek() in '\r\n':
+ break
+ elif self.startswith(','):
+ self.advance()
+ elif self.startswith('in ') or self.startswith('in\t'):
+ break
+ #elif self.matchCheetahVarStart():
+ elif self.matchCheetahVarInExpressionStartToken():
+ self.getCheetahVarStartToken()
+ self.getSilentPlaceholderToken()
+ self.getCacheToken()
+ varnames.append( self.getDottedName() )
+ elif self.matchIdentifier():
+ varnames.append( self.getDottedName() )
+ else:
+ break
+ return varnames
+
+ def getCheetahVar(self, plain=False, skipStartToken=False):
+ """This is called when parsing inside expressions. Cache tokens are only
+ valid in placeholders so this method discards any cache tokens found.
+ """
+ if not skipStartToken:
+ self.getCheetahVarStartToken()
+ self.getSilentPlaceholderToken()
+ self.getCacheToken()
+ return self.getCheetahVarBody(plain=plain)
+
+ def getCheetahVarBody(self, plain=False):
+ # @@TR: this should be in the compiler
+ return self._compiler.genCheetahVar(self.getCheetahVarNameChunks(), plain=plain)
+
+ def getCheetahVarNameChunks(self):
+
+ """
+ nameChunks = list of Cheetah $var subcomponents represented as tuples
+ [ (namemapperPart,autoCall,restOfName),
+ ]
+ where:
+ namemapperPart = the dottedName base
+ autocall = where NameMapper should use autocalling on namemapperPart
+ restOfName = any arglist, index, or slice
+
+ If restOfName contains a call arglist (e.g. '(1234)') then autocall is
+ False, otherwise it defaults to True.
+
+ EXAMPLE
+ ------------------------------------------------------------------------
+
+ if the raw CheetahVar is
+ $a.b.c[1].d().x.y.z
+
+ nameChunks is the list
+ [ ('a.b.c',True,'[1]'),
+ ('d',False,'()'),
+ ('x.y.z',True,''),
+ ]
+
+ """
+
+ chunks = []
+ while self.pos() < len(self):
+ rest = ''
+ autoCall = True
+ if not self.peek() in identchars + '.':
+ break
+ elif self.peek() == '.':
+
+ if self.pos()+1 < len(self) and self.peek(1) in identchars:
+ self.advance() # discard the period as it isn't needed with NameMapper
+ else:
+ break
+
+ dottedName = self.getDottedName()
+ if not self.atEnd() and self.peek() in '([':
+ if self.peek() == '(':
+ rest = self.getCallArgString()
+ else:
+ rest = self.getExpression(enclosed=True)
+
+ period = max(dottedName.rfind('.'), 0)
+ if period:
+ chunks.append( (dottedName[:period], autoCall, '') )
+ dottedName = dottedName[period+1:]
+ if rest and rest[0]=='(':
+ autoCall = False
+ chunks.append( (dottedName, autoCall, rest) )
+
+ return chunks
+
+
+ def getCallArgString(self,
+ enclosures=[], # list of tuples (char, pos), where char is ({ or [
+ useNameMapper=Unspecified):
+
+ """ Get a method/function call argument string.
+
+ This method understands *arg, and **kw
+ """
+
+ # @@TR: this settings mangling should be removed
+ if useNameMapper is not Unspecified:
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ if enclosures:
+ pass
+ else:
+ if not self.peek() == '(':
+ raise ParseError(self, msg="Expected '('")
+ startPos = self.pos()
+ self.getc()
+ enclosures = [('(', startPos),
+ ]
+
+ argStringBits = ['(']
+ addBit = argStringBits.append
+
+ while 1:
+ if self.atEnd():
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg="EOF was reached before a matching '" + close +
+ "' was found for the '" + open + "'")
+
+ c = self.peek()
+ if c in ")}]": # get the ending enclosure and break
+ if not enclosures:
+ raise ParseError(self)
+ c = self.getc()
+ open = closurePairs[c]
+ if enclosures[-1][0] == open:
+ enclosures.pop()
+ addBit(')')
+ break
+ else:
+ raise ParseError(self)
+ elif c in " \t\f\r\n":
+ addBit(self.getc())
+ elif self.matchCheetahVarInExpressionStartToken():
+ startPos = self.pos()
+ codeFor1stToken = self.getCheetahVar()
+ WS = self.getWhiteSpace()
+ if not self.atEnd() and self.peek() == '=':
+ nextToken = self.getPyToken()
+ if nextToken == '=':
+ endPos = self.pos()
+ self.setPos(startPos)
+ codeFor1stToken = self.getCheetahVar(plain=True)
+ self.setPos(endPos)
+
+ ## finally
+ addBit( codeFor1stToken + WS + nextToken )
+ else:
+ addBit( codeFor1stToken + WS)
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if token in ('{','(','['):
+ self.rev()
+ token = self.getExpression(enclosed=True)
+ token = self.transformToken(token, beforeTokenPos)
+ addBit(token)
+
+ if useNameMapper is not Unspecified:
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+
+ return ''.join(argStringBits)
+
+ def getDefArgList(self, exitPos=None, useNameMapper=False):
+
+ """ Get an argument list. Can be used for method/function definition
+ argument lists or for #directive argument lists. Returns a list of
+ tuples in the form (argName, defVal=None) with one tuple for each arg
+ name.
+
+ These defVals are always strings, so (argName, defVal=None) is safe even
+ with a case like (arg1, arg2=None, arg3=1234*2), which would be returned as
+ [('arg1', None),
+ ('arg2', 'None'),
+ ('arg3', '1234*2'),
+ ]
+
+ This method understands *arg, and **kw
+
+ """
+
+ if self.peek() == '(':
+ self.advance()
+ else:
+ exitPos = self.findEOL() # it's a directive so break at the EOL
+ argList = ArgList()
+ onDefVal = False
+
+ # @@TR: this settings mangling should be removed
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ while 1:
+ if self.atEnd():
+ raise ParseError(
+ self, msg="EOF was reached before a matching ')'"+
+ " was found for the '('")
+
+ if self.pos() == exitPos:
+ break
+
+ c = self.peek()
+ if c == ")" or self.matchDirectiveEndToken():
+ break
+ elif c == ":":
+ break
+ elif c in " \t\f\r\n":
+ if onDefVal:
+ argList.addToDefVal(c)
+ self.advance()
+ elif c == '=':
+ onDefVal = True
+ self.advance()
+ elif c == ",":
+ argList.next()
+ onDefVal = False
+ self.advance()
+ elif self.startswith(self.cheetahVarStartToken) and not onDefVal:
+ self.advance(len(self.cheetahVarStartToken))
+ elif self.matchIdentifier() and not onDefVal:
+ argList.addArgName( self.getIdentifier() )
+ elif onDefVal:
+ if self.matchCheetahVarInExpressionStartToken():
+ token = self.getCheetahVar()
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if token in ('{','(','['):
+ self.rev()
+ token = self.getExpression(enclosed=True)
+ token = self.transformToken(token, beforeTokenPos)
+ argList.addToDefVal(token)
+ elif c == '*' and not onDefVal:
+ varName = self.getc()
+ if self.peek() == '*':
+ varName += self.getc()
+ if not self.matchIdentifier():
+ raise ParseError(self)
+ varName += self.getIdentifier()
+ argList.addArgName(varName)
+ else:
+ raise ParseError(self)
+
+
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+ return argList.merge()
+
+ def getExpressionParts(self,
+ enclosed=False,
+ enclosures=None, # list of tuples (char, pos), where char is ({ or [
+ pyTokensToBreakAt=None, # only works if not enclosed
+ useNameMapper=Unspecified,
+ ):
+
+ """ Get a Cheetah expression that includes $CheetahVars and break at
+ directive end tokens, the end of an enclosure, or at a specified
+ pyToken.
+ """
+
+ if useNameMapper is not Unspecified:
+ useNameMapper_orig = self.setting('useNameMapper')
+ self.setSetting('useNameMapper', useNameMapper)
+
+ if enclosures is None:
+ enclosures = []
+
+ srcLen = len(self)
+ exprBits = []
+ while 1:
+ if self.atEnd():
+ if enclosures:
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg="EOF was reached before a matching '" + close +
+ "' was found for the '" + open + "'")
+ else:
+ break
+
+ c = self.peek()
+ if c in "{([":
+ exprBits.append(c)
+ enclosures.append( (c, self.pos()) )
+ self.advance()
+ elif enclosed and not enclosures:
+ break
+ elif c in "])}":
+ if not enclosures:
+ raise ParseError(self)
+ open = closurePairs[c]
+ if enclosures[-1][0] == open:
+ enclosures.pop()
+ exprBits.append(c)
+ else:
+ open = enclosures[-1][0]
+ close = closurePairsRev[open]
+ row, col = self.getRowCol()
+ self.setPos(enclosures[-1][1])
+ raise ParseError(
+ self, msg= "A '" + c + "' was found at line " + str(row) +
+ ", col " + str(col) +
+ " before a matching '" + close +
+ "' was found\nfor the '" + open + "'")
+ self.advance()
+
+ elif c in " \f\t":
+ exprBits.append(self.getWhiteSpace())
+ elif self.matchDirectiveEndToken() and not enclosures:
+ break
+ elif c == "\\" and self.pos()+1 < srcLen:
+ eolMatch = EOLre.match(self.src(), self.pos()+1)
+ if not eolMatch:
+ self.advance()
+ raise ParseError(self, msg='Line ending expected')
+ self.setPos( eolMatch.end() )
+ elif c in '\r\n':
+ if enclosures:
+ self.advance()
+ else:
+ break
+ elif self.matchCheetahVarInExpressionStartToken():
+ expr = self.getCheetahVar()
+ exprBits.append(expr)
+ elif self.matchCheetahVarStart():
+ # it has syntax that is only valid at the top level
+ self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
+ else:
+ beforeTokenPos = self.pos()
+ token = self.getPyToken()
+ if (not enclosures
+ and pyTokensToBreakAt
+ and token in pyTokensToBreakAt):
+
+ self.setPos(beforeTokenPos)
+ break
+
+ token = self.transformToken(token, beforeTokenPos)
+
+ exprBits.append(token)
+ if identRE.match(token):
+ if token == 'for':
+ expr = self.getExpression(useNameMapper=False, pyTokensToBreakAt=['in'])
+ exprBits.append(expr)
+ else:
+ exprBits.append(self.getWhiteSpace())
+ if not self.atEnd() and self.peek() == '(':
+ exprBits.append(self.getCallArgString())
+ ##
+ if useNameMapper is not Unspecified:
+ self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
+ return exprBits
+
+ def getExpression(self,
+ enclosed=False,
+ enclosures=None, # list of tuples (char, pos), where # char is ({ or [
+ pyTokensToBreakAt=None,
+ useNameMapper=Unspecified,
+ ):
+ """Returns the output of self.getExpressionParts() as a concatenated
+ string rather than as a list.
+ """
+ return ''.join(self.getExpressionParts(
+ enclosed=enclosed, enclosures=enclosures,
+ pyTokensToBreakAt=pyTokensToBreakAt,
+ useNameMapper=useNameMapper))
+
+
+ def transformToken(self, token, beforeTokenPos):
+ """Takes a token from the expression being parsed and performs and
+ special transformations required by Cheetah.
+
+ At the moment only Cheetah's c'$placeholder strings' are transformed.
+ """
+ if token=='c' and not self.atEnd() and self.peek() in '\'"':
+ nextToken = self.getPyToken()
+ token = nextToken.upper()
+ theStr = eval(token)
+ endPos = self.pos()
+ if not theStr:
+ return
+
+ if token.startswith(single3) or token.startswith(double3):
+ startPosIdx = 3
+ else:
+ startPosIdx = 1
+ #print 'CHEETAH STRING', nextToken, theStr, startPosIdx
+ self.setPos(beforeTokenPos+startPosIdx+1)
+ outputExprs = []
+ strConst = ''
+ while self.pos() < (endPos-startPosIdx):
+ if self.matchCheetahVarStart() or self.matchExpressionPlaceholderStart():
+ if strConst:
+ outputExprs.append(repr(strConst))
+ strConst = ''
+ placeholderExpr = self.getPlaceholder()
+ outputExprs.append('str('+placeholderExpr+')')
+ else:
+ strConst += self.getc()
+ self.setPos(endPos)
+ if strConst:
+ outputExprs.append(repr(strConst))
+ #if not self.atEnd() and self.matches('.join('):
+ # print 'DEBUG***'
+ token = "''.join(["+','.join(outputExprs)+"])"
+ return token
+
+ def _raiseErrorAboutInvalidCheetahVarSyntaxInExpr(self):
+ match = self.matchCheetahVarStart()
+ groupdict = match.groupdict()
+ if groupdict.get('cacheToken'):
+ raise ParseError(
+ self,
+ msg='Cache tokens are not valid inside expressions. '
+ 'Use them in top-level $placeholders only.')
+ elif groupdict.get('enclosure'):
+ raise ParseError(
+ self,
+ msg='Long-form placeholders - ${}, $(), $[], etc. are not valid inside expressions. '
+ 'Use them in top-level $placeholders only.')
+ else:
+ raise ParseError(
+ self,
+ msg='This form of $placeholder syntax is not valid here.')
+
+
+ def getPlaceholder(self, allowCacheTokens=False, plain=False, returnEverything=False):
+ # filtered
+ for callback in self.setting('preparsePlaceholderHooks'):
+ callback(parser=self)
+
+ startPos = self.pos()
+ lineCol = self.getRowCol(startPos)
+ startToken = self.getCheetahVarStartToken()
+ silentPlaceholderToken = self.getSilentPlaceholderToken()
+ if silentPlaceholderToken:
+ isSilentPlaceholder = True
+ else:
+ isSilentPlaceholder = False
+
+
+ if allowCacheTokens:
+ cacheToken = self.getCacheToken()
+ cacheTokenParts = self.cacheTokenRE.match(cacheToken).groupdict()
+ else:
+ cacheTokenParts = {}
+
+ if self.peek() in '({[':
+ pos = self.pos()
+ enclosureOpenChar = self.getc()
+ enclosures = [ (enclosureOpenChar, pos) ]
+ self.getWhiteSpace()
+ else:
+ enclosures = []
+
+ filterArgs = None
+ if self.matchIdentifier():
+ nameChunks = self.getCheetahVarNameChunks()
+ expr = self._compiler.genCheetahVar(nameChunks[:], plain=plain)
+ restOfExpr = None
+ if enclosures:
+ WS = self.getWhiteSpace()
+ expr += WS
+ if self.setting('allowPlaceholderFilterArgs') and self.peek()==',':
+ filterArgs = self.getCallArgString(enclosures=enclosures)[1:-1]
+ else:
+ if self.peek()==closurePairsRev[enclosureOpenChar]:
+ self.getc()
+ else:
+ restOfExpr = self.getExpression(enclosed=True, enclosures=enclosures)
+ if restOfExpr[-1] == closurePairsRev[enclosureOpenChar]:
+ restOfExpr = restOfExpr[:-1]
+ expr += restOfExpr
+ rawPlaceholder = self[startPos: self.pos()]
+ else:
+ expr = self.getExpression(enclosed=True, enclosures=enclosures)
+ if expr[-1] == closurePairsRev[enclosureOpenChar]:
+ expr = expr[:-1]
+ rawPlaceholder=self[startPos: self.pos()]
+
+ expr = self._applyExpressionFilters(expr,'placeholder',
+ rawExpr=rawPlaceholder,startPos=startPos)
+ for callback in self.setting('postparsePlaceholderHooks'):
+ callback(parser=self)
+
+ if returnEverything:
+ return (expr, rawPlaceholder, lineCol, cacheTokenParts,
+ filterArgs, isSilentPlaceholder)
+ else:
+ return expr
+
+
+class _HighLevelParser(_LowLevelParser):
+ """This class is a StateMachine for parsing Cheetah source and
+ sending state dependent code generation commands to
+ Cheetah.Compiler.Compiler.
+ """
+ def __init__(self, src, filename=None, breakPoint=None, compiler=None):
+ _LowLevelParser.__init__(self, src, filename=filename, breakPoint=breakPoint)
+ self.setSettingsManager(compiler)
+ self._compiler = compiler
+ self.setupState()
+ self.configureParser()
+
+ def setupState(self):
+ self._macros = {}
+ self._macroDetails = {}
+ self._openDirectivesStack = []
+
+ def cleanup(self):
+ """Cleanup to remove any possible reference cycles
+ """
+ self._macros.clear()
+ for macroname, macroDetails in self._macroDetails.items():
+ macroDetails.template.shutdown()
+ del macroDetails.template
+ self._macroDetails.clear()
+
+ def configureParser(self):
+ _LowLevelParser.configureParser(self)
+ self._initDirectives()
+
+ def _initDirectives(self):
+ def normalizeParserVal(val):
+ if isinstance(val, (str,unicode)):
+ handler = getattr(self, val)
+ elif type(val) in (ClassType, TypeType):
+ handler = val(self)
+ elif callable(val):
+ handler = val
+ elif val is None:
+ handler = val
+ else:
+ raise Exception('Invalid parser/handler value %r for %s'%(val, name))
+ return handler
+
+ normalizeHandlerVal = normalizeParserVal
+
+ _directiveNamesAndParsers = directiveNamesAndParsers.copy()
+ customNamesAndParsers = self.setting('directiveNamesAndParsers',{})
+ _directiveNamesAndParsers.update(customNamesAndParsers)
+
+ _endDirectiveNamesAndHandlers = endDirectiveNamesAndHandlers.copy()
+ customNamesAndHandlers = self.setting('endDirectiveNamesAndHandlers',{})
+ _endDirectiveNamesAndHandlers.update(customNamesAndHandlers)
+
+ self._directiveNamesAndParsers = {}
+ for name, val in _directiveNamesAndParsers.items():
+ if val in (False, 0):
+ continue
+ self._directiveNamesAndParsers[name] = normalizeParserVal(val)
+
+ self._endDirectiveNamesAndHandlers = {}
+ for name, val in _endDirectiveNamesAndHandlers.items():
+ if val in (False, 0):
+ continue
+ self._endDirectiveNamesAndHandlers[name] = normalizeHandlerVal(val)
+
+ self._closeableDirectives = ['def','block','closure','defmacro',
+ 'call',
+ 'capture',
+ 'cache',
+ 'filter',
+ 'if','unless',
+ 'for','while','repeat',
+ 'try',
+ ]
+ for directiveName in self.setting('closeableDirectives',[]):
+ self._closeableDirectives.append(directiveName)
+
+
+
+ macroDirectives = self.setting('macroDirectives',{})
+ macroDirectives['i18n'] = I18n
+
+
+ for macroName, callback in macroDirectives.items():
+ if type(callback) in (ClassType, TypeType):
+ callback = callback(parser=self)
+ assert callback
+ self._macros[macroName] = callback
+ self._directiveNamesAndParsers[macroName] = self.eatMacroCall
+
+ def _applyExpressionFilters(self, expr, exprType, rawExpr=None, startPos=None):
+ """Pipes cheetah expressions through a set of optional filter hooks.
+
+ The filters are functions which may modify the expressions or raise
+ a ForbiddenExpression exception if the expression is not allowed. They
+ are defined in the compiler setting 'expressionFilterHooks'.
+
+ Some intended use cases:
+
+ - to implement 'restricted execution' safeguards in cases where you
+ can't trust the author of the template.
+
+ - to enforce style guidelines
+
+ filter call signature: (parser, expr, exprType, rawExpr=None, startPos=None)
+ - parser is the Cheetah parser
+ - expr is the expression to filter. In some cases the parser will have
+ already modified it from the original source code form. For example,
+ placeholders will have been translated into namemapper calls. If you
+ need to work with the original source, see rawExpr.
+ - exprType is the name of the directive, 'psp', or 'placeholder'. All
+ lowercase. @@TR: These will eventually be replaced with a set of
+ constants.
+ - rawExpr is the original source string that Cheetah parsed. This
+ might be None in some cases.
+ - startPos is the character position in the source string/file
+ where the parser started parsing the current expression.
+
+ @@TR: I realize this use of the term 'expression' is a bit wonky as many
+ of the 'expressions' are actually statements, but I haven't thought of
+ a better name yet. Suggestions?
+ """
+ for callback in self.setting('expressionFilterHooks'):
+ expr = callback(parser=self, expr=expr, exprType=exprType,
+ rawExpr=rawExpr, startPos=startPos)
+ return expr
+
+ def _filterDisabledDirectives(self, directiveName):
+ directiveName = directiveName.lower()
+ if (directiveName in self.setting('disabledDirectives')
+ or (self.setting('enabledDirectives')
+ and directiveName not in self.setting('enabledDirectives'))):
+ for callback in self.setting('disabledDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+ raise ForbiddenDirective(self, msg='This %r directive is disabled'%directiveName)
+
+ ## main parse loop
+
+ def parse(self, breakPoint=None, assertEmptyStack=True):
+ if breakPoint:
+ origBP = self.breakPoint()
+ self.setBreakPoint(breakPoint)
+ assertEmptyStack = False
+
+ while not self.atEnd():
+ if self.matchCommentStartToken():
+ self.eatComment()
+ elif self.matchMultiLineCommentStartToken():
+ self.eatMultiLineComment()
+ elif self.matchVariablePlaceholderStart():
+ self.eatPlaceholder()
+ elif self.matchExpressionPlaceholderStart():
+ self.eatPlaceholder()
+ elif self.matchDirective():
+ self.eatDirective()
+ elif self.matchPSPStartToken():
+ self.eatPSP()
+ elif self.matchEOLSlurpToken():
+ self.eatEOLSlurpToken()
+ else:
+ self.eatPlainText()
+ if assertEmptyStack:
+ self.assertEmptyOpenDirectivesStack()
+ if breakPoint:
+ self.setBreakPoint(origBP)
+
+ ## non-directive eat methods
+
+ def eatPlainText(self):
+ startPos = self.pos()
+ match = None
+ while not self.atEnd():
+ match = self.matchTopLevelToken()
+ if match:
+ break
+ else:
+ self.advance()
+ strConst = self.readTo(self.pos(), start=startPos)
+ self._compiler.addStrConst(strConst)
+ return match
+
+ def eatComment(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ if isLineClearToStartToken:
+ self._compiler.handleWSBeforeDirective()
+ self.getCommentStartToken()
+ comm = self.readToEOL(gobble=isLineClearToStartToken)
+ self._compiler.addComment(comm)
+
+ def eatMultiLineComment(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+
+ self.getMultiLineCommentStartToken()
+ endPos = startPos = self.pos()
+ level = 1
+ while 1:
+ endPos = self.pos()
+ if self.atEnd():
+ break
+ if self.matchMultiLineCommentStartToken():
+ self.getMultiLineCommentStartToken()
+ level += 1
+ elif self.matchMultiLineCommentEndToken():
+ self.getMultiLineCommentEndToken()
+ level -= 1
+ if not level:
+ break
+ self.advance()
+ comm = self.readTo(endPos, start=startPos)
+
+ if not self.atEnd():
+ self.getMultiLineCommentEndToken()
+
+ if (not self.atEnd()) and self.setting('gobbleWhitespaceAroundMultiLineComments'):
+ restOfLine = self[self.pos():self.findEOL()]
+ if not restOfLine.strip(): # WS only to EOL
+ self.readToEOL(gobble=isLineClearToStartToken)
+
+ if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLine):
+ self._compiler.handleWSBeforeDirective()
+
+ self._compiler.addComment(comm)
+
+ def eatPlaceholder(self):
+ (expr, rawPlaceholder,
+ lineCol, cacheTokenParts,
+ filterArgs, isSilentPlaceholder) = self.getPlaceholder(
+ allowCacheTokens=True, returnEverything=True)
+
+ self._compiler.addPlaceholder(
+ expr,
+ filterArgs=filterArgs,
+ rawPlaceholder=rawPlaceholder,
+ cacheTokenParts=cacheTokenParts,
+ lineCol=lineCol,
+ silentMode=isSilentPlaceholder)
+ return
+
+ def eatPSP(self):
+ # filtered
+ self._filterDisabledDirectives(directiveName='psp')
+ self.getPSPStartToken()
+ endToken = self.setting('PSPEndToken')
+ startPos = self.pos()
+ while not self.atEnd():
+ if self.peek() == endToken[0]:
+ if self.matchPSPEndToken():
+ break
+ self.advance()
+ pspString = self.readTo(self.pos(), start=startPos).strip()
+ pspString = self._applyExpressionFilters(pspString, 'psp', startPos=startPos)
+ self._compiler.addPSP(pspString)
+ self.getPSPEndToken()
+
+ ## generic directive eat methods
+ _simpleIndentingDirectives = '''
+ else elif for while repeat unless try except finally'''.split()
+ _simpleExprDirectives = '''
+ pass continue stop return yield break
+ del assert raise
+ silent echo
+ import from'''.split()
+ _directiveHandlerNames = {'import':'addImportStatement',
+ 'from':'addImportStatement', }
+ def eatDirective(self):
+ directiveName = self.matchDirective()
+ self._filterDisabledDirectives(directiveName)
+
+ for callback in self.setting('preparseDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+
+ # subclasses can override the default behaviours here by providing an
+ # eater method in self._directiveNamesAndParsers[directiveName]
+ directiveParser = self._directiveNamesAndParsers.get(directiveName)
+ if directiveParser:
+ directiveParser()
+ elif directiveName in self._simpleIndentingDirectives:
+ handlerName = self._directiveHandlerNames.get(directiveName)
+ if not handlerName:
+ handlerName = 'add'+directiveName.capitalize()
+ handler = getattr(self._compiler, handlerName)
+ self.eatSimpleIndentingDirective(directiveName, callback=handler)
+ elif directiveName in self._simpleExprDirectives:
+ handlerName = self._directiveHandlerNames.get(directiveName)
+ if not handlerName:
+ handlerName = 'add'+directiveName.capitalize()
+ handler = getattr(self._compiler, handlerName)
+ if directiveName in ('silent', 'echo'):
+ includeDirectiveNameInExpr = False
+ else:
+ includeDirectiveNameInExpr = True
+ expr = self.eatSimpleExprDirective(
+ directiveName,
+ includeDirectiveNameInExpr=includeDirectiveNameInExpr)
+ handler(expr)
+ ##
+ for callback in self.setting('postparseDirectiveHooks'):
+ callback(parser=self, directiveName=directiveName)
+
+ def _eatRestOfDirectiveTag(self, isLineClearToStartToken, endOfFirstLinePos):
+ foundComment = False
+ if self.matchCommentStartToken():
+ pos = self.pos()
+ self.advance()
+ if not self.matchDirective():
+ self.setPos(pos)
+ foundComment = True
+ self.eatComment() # this won't gobble the EOL
+ else:
+ self.setPos(pos)
+
+ if not foundComment and self.matchDirectiveEndToken():
+ self.getDirectiveEndToken()
+ elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
+ # still gobble the EOL if a comment was found.
+ self.readToEOL(gobble=True)
+
+ if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLinePos):
+ self._compiler.handleWSBeforeDirective()
+
+ def _eatToThisEndDirective(self, directiveName):
+ finalPos = endRawPos = startPos = self.pos()
+ directiveChar = self.setting('directiveStartToken')[0]
+ isLineClearToStartToken = False
+ while not self.atEnd():
+ if self.peek() == directiveChar:
+ if self.matchDirective() == 'end':
+ endRawPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('end'))
+ self.getWhiteSpace()
+ if self.startswith(directiveName):
+ if self.isLineClearToStartToken(endRawPos):
+ isLineClearToStartToken = True
+ endRawPos = self.findBOL(endRawPos)
+ self.advance(len(directiveName)) # to end of directiveName
+ self.getWhiteSpace()
+ finalPos = self.pos()
+ break
+ self.advance()
+ finalPos = endRawPos = self.pos()
+
+ textEaten = self.readTo(endRawPos, start=startPos)
+ self.setPos(finalPos)
+
+ endOfFirstLinePos = self.findEOL()
+
+ if self.matchDirectiveEndToken():
+ self.getDirectiveEndToken()
+ elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
+ self.readToEOL(gobble=True)
+
+ if isLineClearToStartToken and self.pos() > endOfFirstLinePos:
+ self._compiler.handleWSBeforeDirective()
+ return textEaten
+
+
+ def eatSimpleExprDirective(self, directiveName, includeDirectiveNameInExpr=True):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ if not includeDirectiveNameInExpr:
+ self.advance(len(directiveName))
+ startPos = self.pos()
+ expr = self.getExpression().strip()
+ directiveName = expr.split()[0]
+ expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
+ if directiveName in self._closeableDirectives:
+ self.pushToOpenDirectivesStack(directiveName)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ return expr
+
+ def eatSimpleIndentingDirective(self, directiveName, callback,
+ includeDirectiveNameInExpr=False):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ if directiveName not in 'else elif for while try except finally'.split():
+ self.advance(len(directiveName))
+ startPos = self.pos()
+
+ self.getWhiteSpace()
+
+ expr = self.getExpression(pyTokensToBreakAt=[':'])
+ expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ if directiveName in 'else elif except finally'.split():
+ callback(expr, dedent=False, lineCol=lineCol)
+ else:
+ callback(expr, lineCol=lineCol)
+
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ if directiveName in self._closeableDirectives:
+ self.pushToOpenDirectivesStack(directiveName)
+ callback(expr, lineCol=lineCol)
+
+ def eatEndDirective(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ self.getDirectiveStartToken()
+ self.advance(3) # to end of 'end'
+ self.getWhiteSpace()
+ pos = self.pos()
+ directiveName = False
+ for key in self._endDirectiveNamesAndHandlers.keys():
+ if self.find(key, pos) == pos:
+ directiveName = key
+ break
+ if not directiveName:
+ raise ParseError(self, msg='Invalid end directive')
+
+ endOfFirstLinePos = self.findEOL()
+ self.getExpression() # eat in any extra comment-like crap
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ if directiveName in self._closeableDirectives:
+ self.popFromOpenDirectivesStack(directiveName)
+
+ # subclasses can override the default behaviours here by providing an
+ # end-directive handler in self._endDirectiveNamesAndHandlers[directiveName]
+ if self._endDirectiveNamesAndHandlers.get(directiveName):
+ handler = self._endDirectiveNamesAndHandlers[directiveName]
+ handler()
+ elif directiveName in 'block capture cache call filter errorCatcher'.split():
+ if key == 'block':
+ self._compiler.closeBlock()
+ elif key == 'capture':
+ self._compiler.endCaptureRegion()
+ elif key == 'cache':
+ self._compiler.endCacheRegion()
+ elif key == 'call':
+ self._compiler.endCallRegion()
+ elif key == 'filter':
+ self._compiler.closeFilterBlock()
+ elif key == 'errorCatcher':
+ self._compiler.turnErrorCatcherOff()
+ elif directiveName in 'while for if try repeat unless'.split():
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ elif directiveName=='closure':
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', self._useSearchList_orig)
+
+ ## specific directive eat methods
+
+ def eatBreakPoint(self):
+ """Tells the parser to stop parsing at this point and completely ignore
+ everything else.
+
+ This is a debugging tool.
+ """
+ self.setBreakPoint(self.pos())
+
+ def eatShbang(self):
+ # filtered
+ self.getDirectiveStartToken()
+ self.advance(len('shBang'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ shBang = self.readToEOL()
+ shBang = self._applyExpressionFilters(shBang, 'shbang', startPos=startPos)
+ self._compiler.setShBang(shBang.strip())
+
+ def eatEncoding(self):
+ # filtered
+ self.getDirectiveStartToken()
+ self.advance(len('encoding'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ encoding = self.readToEOL()
+ encoding = self._applyExpressionFilters(encoding, 'encoding', startPos=startPos)
+ self._compiler.setModuleEncoding(encoding.strip())
+
+ def eatCompiler(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('compiler')) # to end of 'compiler'
+ self.getWhiteSpace()
+
+ startPos = self.pos()
+ settingName = self.getIdentifier()
+
+ if settingName.lower() == 'reset':
+ self.getExpression() # gobble whitespace & junk
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self._initializeSettings()
+ self.configureParser()
+ return
+
+ self.getWhiteSpace()
+ if self.peek() == '=':
+ self.advance()
+ else:
+ raise ParseError(self)
+ valueExpr = self.getExpression()
+ endPos = self.pos()
+
+ # @@TR: it's unlikely that anyone apply filters would have left this
+ # directive enabled:
+ # @@TR: fix up filtering, regardless
+ self._applyExpressionFilters('%s=%r'%(settingName, valueExpr),
+ 'compiler', startPos=startPos)
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ try:
+ self._compiler.setCompilerSetting(settingName, valueExpr)
+ except:
+ out = sys.stderr
+ print >> out, 'An error occurred while processing the following #compiler directive.'
+ print >> out, '-'*80
+ print >> out, self[startPos:endPos]
+ print >> out, '-'*80
+ print >> out, 'Please check the syntax of these settings.'
+ print >> out, 'A full Python exception traceback follows.'
+ raise
+
+
+ def eatCompilerSettings(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('compiler-settings')) # to end of 'settings'
+
+ keywords = self.getTargetVarsList()
+ self.getExpression() # gobble any garbage
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ if 'reset' in keywords:
+ self._compiler._initializeSettings()
+ self.configureParser()
+ # @@TR: this implies a single-line #compiler-settings directive, and
+ # thus we should parse forward for an end directive.
+ # Subject to change in the future
+ return
+ startPos = self.pos()
+ settingsStr = self._eatToThisEndDirective('compiler-settings')
+ settingsStr = self._applyExpressionFilters(settingsStr, 'compilerSettings',
+ startPos=startPos)
+ try:
+ self._compiler.setCompilerSettings(keywords=keywords, settingsStr=settingsStr)
+ except:
+ out = sys.stderr
+ print >> out, 'An error occurred while processing the following compiler settings.'
+ print >> out, '-'*80
+ print >> out, settingsStr.strip()
+ print >> out, '-'*80
+ print >> out, 'Please check the syntax of these settings.'
+ print >> out, 'A full Python exception traceback follows.'
+ raise
+
+ def eatAttr(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len('attr'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ attribName = self.getIdentifier()
+ self.getWhiteSpace()
+ self.getAssignmentOperator()
+ expr = self.getExpression()
+ expr = self._applyExpressionFilters(expr, 'attr', startPos=startPos)
+ self._compiler.addAttribute(attribName, expr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+
+ def eatDecorator(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ #self.advance() # eat @
+ startPos = self.pos()
+ decoratorExpr = self.getExpression()
+ decoratorExpr = self._applyExpressionFilters(decoratorExpr, 'decorator', startPos=startPos)
+ self._compiler.addDecorator(decoratorExpr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.getWhiteSpace()
+
+ directiveName = self.matchDirective()
+ if not directiveName or directiveName not in ('def', 'block', 'closure', '@'):
+ raise ParseError(
+ self, msg='Expected #def, #block, #closure or another @decorator')
+ self.eatDirective()
+
+ def eatDef(self):
+ # filtered
+ self._eatDefOrBlock('def')
+
+ def eatBlock(self):
+ # filtered
+ startPos = self.pos()
+ methodName, rawSignature = self._eatDefOrBlock('block')
+ self._compiler._blockMetaData[methodName] = {
+ 'raw':rawSignature,
+ 'lineCol':self.getRowCol(startPos),
+ }
+
+ def eatClosure(self):
+ # filtered
+ self._eatDefOrBlock('closure')
+
+ def _eatDefOrBlock(self, directiveName):
+ # filtered
+ assert directiveName in ('def','block','closure')
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ self.advance(len(directiveName))
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ methodName = self.getIdentifier()
+ self.getWhiteSpace()
+ if self.peek() == '(':
+ argsList = self.getDefArgList()
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ def includeBlockMarkers():
+ if self.setting('includeBlockMarkers'):
+ startMarker = self.setting('blockMarkerStart')
+ self._compiler.addStrConst(startMarker[0] + methodName + startMarker[1])
+
+ # @@TR: fix up filtering
+ self._applyExpressionFilters(self[startPos:self.pos()], 'def', startPos=startPos)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ self.getc()
+ rawSignature = self[startPos:endOfFirstLinePos]
+ self._eatSingleLineDef(directiveName=directiveName,
+ methodName=methodName,
+ argsList=argsList,
+ startPos=startPos,
+ endPos=endOfFirstLinePos)
+ if directiveName == 'def' and not isNestedDef:
+ #@@TR: must come before _eatRestOfDirectiveTag ... for some reason
+ self._compiler.closeDef()
+ elif directiveName == 'block':
+ includeBlockMarkers()
+ self._compiler.closeBlock()
+ elif directiveName == 'closure' or isNestedDef:
+ self._compiler.dedent()
+
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ else:
+ if self.peek()==':':
+ self.getc()
+ self.pushToOpenDirectivesStack(directiveName)
+ rawSignature = self[startPos:self.pos()]
+ self._eatMultiLineDef(directiveName=directiveName,
+ methodName=methodName,
+ argsList=argsList,
+ startPos=startPos,
+ isLineClearToStartToken=isLineClearToStartToken)
+ if directiveName == 'block':
+ includeBlockMarkers()
+
+ return methodName, rawSignature
+
+ def _eatMultiLineDef(self, directiveName, methodName, argsList, startPos,
+ isLineClearToStartToken=False):
+ # filtered in calling method
+ self.getExpression() # slurp up any garbage left at the end
+ signature = self[startPos:self.pos()]
+ endOfFirstLinePos = self.findEOL()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ signature = ' '.join([line.strip() for line in signature.splitlines()])
+ parserComment = ('## CHEETAH: generated from ' + signature +
+ ' at line %s, col %s' % self.getRowCol(startPos)
+ + '.')
+
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and len([name for name in self._openDirectivesStack if name=='def'])>1)
+ if directiveName=='block' or (directiveName=='def' and not isNestedDef):
+ self._compiler.startMethodDef(methodName, argsList, parserComment)
+ else: #closure
+ self._useSearchList_orig = self.setting('useSearchList')
+ self.setSetting('useSearchList', False)
+ self._compiler.addClosure(methodName, argsList, parserComment)
+
+ return methodName
+
+ def _eatSingleLineDef(self, directiveName, methodName, argsList, startPos, endPos):
+ # filtered in calling method
+ fullSignature = self[startPos:endPos]
+ parserComment = ('## Generated from ' + fullSignature +
+ ' at line %s, col %s' % self.getRowCol(startPos)
+ + '.')
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ if directiveName=='block' or (directiveName=='def' and not isNestedDef):
+ self._compiler.startMethodDef(methodName, argsList, parserComment)
+ else: #closure
+ # @@TR: temporary hack of useSearchList
+ useSearchList_orig = self.setting('useSearchList')
+ self.setSetting('useSearchList', False)
+ self._compiler.addClosure(methodName, argsList, parserComment)
+
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=endPos)
+ if directiveName=='closure' or isNestedDef: # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', useSearchList_orig)
+
+ def eatExtends(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('extends'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.setting('allowExpressionsInExtendsDirective'):
+ baseName = self.getExpression()
+ else:
+ baseName = self.getCommaSeparatedSymbols()
+ baseName = ', '.join(baseName)
+
+ baseName = self._applyExpressionFilters(baseName, 'extends', startPos=startPos)
+ self._compiler.setBaseClass(baseName) # in compiler
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ def eatImplements(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('implements'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ methodName = self.getIdentifier()
+ if not self.atEnd() and self.peek() == '(':
+ argsList = self.getDefArgList()
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ # @@TR: need to split up filtering of the methodname and the args
+ #methodName = self._applyExpressionFilters(methodName, 'implements', startPos=startPos)
+ self._applyExpressionFilters(self[startPos:self.pos()], 'implements', startPos=startPos)
+
+ self._compiler.setMainMethodName(methodName)
+ self._compiler.setMainMethodArgs(argsList)
+
+ self.getExpression() # throw away and unwanted crap that got added in
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ def eatSuper(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('super'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if not self.atEnd() and self.peek() == '(':
+ argsList = self.getDefArgList()
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ self._applyExpressionFilters(self[startPos:self.pos()], 'super', startPos=startPos)
+
+ #parserComment = ('## CHEETAH: generated from ' + signature +
+ # ' at line %s, col %s' % self.getRowCol(startPos)
+ # + '.')
+
+ self.getExpression() # throw away and unwanted crap that got added in
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self._compiler.addSuper(argsList)
+
+ def eatSet(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(3)
+ self.getWhiteSpace()
+ style = SET_LOCAL
+ if self.startswith('local'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ elif self.startswith('global'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ style = SET_GLOBAL
+ elif self.startswith('module'):
+ self.getIdentifier()
+ self.getWhiteSpace()
+ style = SET_MODULE
+
+ startsWithDollar = self.matchCheetahVarStart()
+ startPos = self.pos()
+ LVALUE = self.getExpression(pyTokensToBreakAt=assignmentOps, useNameMapper=False).strip()
+ OP = self.getAssignmentOperator()
+ RVALUE = self.getExpression()
+ expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
+
+ expr = self._applyExpressionFilters(expr, 'set', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+
+ class Components: pass # used for 'set global'
+ exprComponents = Components()
+ exprComponents.LVALUE = LVALUE
+ exprComponents.OP = OP
+ exprComponents.RVALUE = RVALUE
+ self._compiler.addSet(expr, exprComponents, style)
+
+ def eatSlurp(self):
+ if self.isLineClearToStartToken():
+ self._compiler.handleWSBeforeDirective()
+ self._compiler.commitStrConst()
+ self.readToEOL(gobble=True)
+
+ def eatEOLSlurpToken(self):
+ if self.isLineClearToStartToken():
+ self._compiler.handleWSBeforeDirective()
+ self._compiler.commitStrConst()
+ self.readToEOL(gobble=True)
+
+ def eatRaw(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('raw'))
+ self.getWhiteSpace()
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ rawBlock = self.readToEOL(gobble=False)
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ rawBlock = self._eatToThisEndDirective('raw')
+ self._compiler.addRawText(rawBlock)
+
+ def eatInclude(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('include'))
+
+ self.getWhiteSpace()
+ includeFrom = 'file'
+ isRaw = False
+ if self.startswith('raw'):
+ self.advance(3)
+ isRaw=True
+
+ self.getWhiteSpace()
+ if self.startswith('source'):
+ self.advance(len('source'))
+ includeFrom = 'str'
+ self.getWhiteSpace()
+ if not self.peek() == '=':
+ raise ParseError(self)
+ self.advance()
+ startPos = self.pos()
+ sourceExpr = self.getExpression()
+ sourceExpr = self._applyExpressionFilters(sourceExpr, 'include', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.addInclude(sourceExpr, includeFrom, isRaw)
+
+
+ def eatDefMacro(self):
+ # @@TR: not filtered yet
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('defmacro'))
+
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ self.getCheetahVarStartToken()
+ macroName = self.getIdentifier()
+ self.getWhiteSpace()
+ if self.peek() == '(':
+ argsList = self.getDefArgList(useNameMapper=False)
+ self.advance() # past the closing ')'
+ if argsList and argsList[0][0] == 'self':
+ del argsList[0]
+ else:
+ argsList=[]
+
+ assert not self._directiveNamesAndParsers.has_key(macroName)
+ argsList.insert(0, ('src',None))
+ argsList.append(('parser','None'))
+ argsList.append(('macros','None'))
+ argsList.append(('compilerSettings','None'))
+ argsList.append(('isShortForm','None'))
+ argsList.append(('EOLCharsInShortForm','None'))
+ argsList.append(('startPos','None'))
+ argsList.append(('endPos','None'))
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ macroSrc = self.readToEOL(gobble=False)
+ self.readToEOL(gobble=True)
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ macroSrc = self._eatToThisEndDirective('defmacro')
+
+ #print argsList
+ normalizedMacroSrc = ''.join(
+ ['%def callMacro('+','.join([defv and '%s=%s'%(n,defv) or n
+ for n,defv in argsList])
+ +')\n',
+ macroSrc,
+ '%end def'])
+
+
+ from Cheetah.Template import Template
+ templateAPIClass = self.setting('templateAPIClassForDefMacro', default=Template)
+ compilerSettings = self.setting('compilerSettingsForDefMacro', default={})
+ searchListForMacros = self.setting('searchListForDefMacro', default=[])
+ searchListForMacros = list(searchListForMacros) # copy to avoid mutation bugs
+ searchListForMacros.append({'macros':self._macros,
+ 'parser':self,
+ 'compilerSettings':self.settings(),
+ })
+
+ templateAPIClass._updateSettingsWithPreprocessTokens(
+ compilerSettings, placeholderToken='@', directiveToken='%')
+ macroTemplateClass = templateAPIClass.compile(source=normalizedMacroSrc,
+ compilerSettings=compilerSettings)
+ #print normalizedMacroSrc
+ #t = macroTemplateClass()
+ #print t.callMacro('src')
+ #print t.generatedClassCode()
+
+ class MacroDetails: pass
+ macroDetails = MacroDetails()
+ macroDetails.macroSrc = macroSrc
+ macroDetails.argsList = argsList
+ macroDetails.template = macroTemplateClass(searchList=searchListForMacros)
+
+ self._macroDetails[macroName] = macroDetails
+ self._macros[macroName] = macroDetails.template.callMacro
+ self._directiveNamesAndParsers[macroName] = self.eatMacroCall
+
+ def eatMacroCall(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ startPos = self.pos()
+ self.getDirectiveStartToken()
+ macroName = self.getIdentifier()
+ macro = self._macros[macroName]
+ if hasattr(macro, 'parse'):
+ return macro.parse(parser=self, startPos=startPos)
+
+ if hasattr(macro, 'parseArgs'):
+ args = macro.parseArgs(parser=self, startPos=startPos)
+ else:
+ self.getWhiteSpace()
+ args = self.getExpression(useNameMapper=False,
+ pyTokensToBreakAt=[':']).strip()
+
+ if self.matchColonForSingleLineShortFormDirective():
+ isShortForm = True
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ srcBlock = self.readToEOL(gobble=False)
+ EOLCharsInShortForm = self.readToEOL(gobble=True)
+ #self.readToEOL(gobble=False)
+ else:
+ isShortForm = False
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ srcBlock = self._eatToThisEndDirective(macroName)
+
+
+ if hasattr(macro, 'convertArgStrToDict'):
+ kwArgs = macro.convertArgStrToDict(args, parser=self, startPos=startPos)
+ else:
+ def getArgs(*pargs, **kws):
+ return pargs, kws
+ exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
+
+ assert not kwArgs.has_key('src')
+ kwArgs['src'] = srcBlock
+
+ if type(macro)==new.instancemethod:
+ co = macro.im_func.func_code
+ elif (hasattr(macro, '__call__')
+ and hasattr(macro.__call__, 'im_func')):
+ co = macro.__call__.im_func.func_code
+ else:
+ co = macro.func_code
+ availableKwArgs = inspect.getargs(co)[0]
+
+ if 'parser' in availableKwArgs:
+ kwArgs['parser'] = self
+ if 'macros' in availableKwArgs:
+ kwArgs['macros'] = self._macros
+ if 'compilerSettings' in availableKwArgs:
+ kwArgs['compilerSettings'] = self.settings()
+ if 'isShortForm' in availableKwArgs:
+ kwArgs['isShortForm'] = isShortForm
+ if isShortForm and 'EOLCharsInShortForm' in availableKwArgs:
+ kwArgs['EOLCharsInShortForm'] = EOLCharsInShortForm
+
+ if 'startPos' in availableKwArgs:
+ kwArgs['startPos'] = startPos
+ if 'endPos' in availableKwArgs:
+ kwArgs['endPos'] = self.pos()
+
+ srcFromMacroOutput = macro(**kwArgs)
+
+ origParseSrc = self._src
+ origBreakPoint = self.breakPoint()
+ origPos = self.pos()
+ # add a comment to the output about the macro src that is being parsed
+ # or add a comment prefix to all the comments added by the compiler
+ self._src = srcFromMacroOutput
+ self.setPos(0)
+ self.setBreakPoint(len(srcFromMacroOutput))
+
+ self.parse(assertEmptyStack=False)
+
+ self._src = origParseSrc
+ self.setBreakPoint(origBreakPoint)
+ self.setPos(origPos)
+
+
+ #self._compiler.addRawText('end')
+
+ def eatCache(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ self.advance(len('cache'))
+
+ startPos = self.pos()
+ argList = self.getDefArgList(useNameMapper=True)
+ argList = self._applyExpressionFilters(argList, 'cache', startPos=startPos)
+
+ def startCache():
+ cacheInfo = self._compiler.genCacheInfoFromArgList(argList)
+ self._compiler.startCacheRegion(cacheInfo, lineCol)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ startCache()
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.endCacheRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.pushToOpenDirectivesStack('cache')
+ startCache()
+
+ def eatCall(self):
+ # @@TR: need to enable single line version of this
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ self.advance(len('call'))
+ startPos = self.pos()
+
+ useAutocallingOrig = self.setting('useAutocalling')
+ self.setSetting('useAutocalling', False)
+ self.getWhiteSpace()
+ if self.matchCheetahVarStart():
+ functionName = self.getCheetahVar()
+ else:
+ functionName = self.getCheetahVar(plain=True, skipStartToken=True)
+ self.setSetting('useAutocalling', useAutocallingOrig)
+ # @@TR: fix up filtering
+ self._applyExpressionFilters(self[startPos:self.pos()], 'call', startPos=startPos)
+
+ self.getWhiteSpace()
+ args = self.getExpression(pyTokensToBreakAt=[':']).strip()
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.startCallRegion(functionName, args, lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.endCallRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self.pushToOpenDirectivesStack("call")
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.startCallRegion(functionName, args, lineCol)
+
+ def eatCallArg(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+
+ self.advance(len('arg'))
+ startPos = self.pos()
+ self.getWhiteSpace()
+ argName = self.getIdentifier()
+ self.getWhiteSpace()
+ argName = self._applyExpressionFilters(argName, 'arg', startPos=startPos)
+ self._compiler.setCallArg(argName, lineCol)
+ if self.peek() == ':':
+ self.getc()
+ else:
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+
+ def eatFilter(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+
+ self.getDirectiveStartToken()
+ self.advance(len('filter'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.matchCheetahVarStart():
+ isKlass = True
+ theFilter = self.getExpression(pyTokensToBreakAt=[':'])
+ else:
+ isKlass = False
+ theFilter = self.getIdentifier()
+ self.getWhiteSpace()
+ theFilter = self._applyExpressionFilters(theFilter, 'filter', startPos=startPos)
+
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self.getWhiteSpace(max=1)
+ self._compiler.setFilter(theFilter, isKlass)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.closeFilterBlock()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self.pushToOpenDirectivesStack("filter")
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.setFilter(theFilter, isKlass)
+
+ def eatTransform(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+
+ self.getDirectiveStartToken()
+ self.advance(len('transform'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ if self.matchCheetahVarStart():
+ isKlass = True
+ transformer = self.getExpression(pyTokensToBreakAt=[':'])
+ else:
+ isKlass = False
+ transformer = self.getIdentifier()
+ self.getWhiteSpace()
+ transformer = self._applyExpressionFilters(transformer, 'transform', startPos=startPos)
+
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.setTransform(transformer, isKlass)
+
+
+ def eatErrorCatcher(self):
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ self.getDirectiveStartToken()
+ self.advance(len('errorCatcher'))
+ self.getWhiteSpace()
+ startPos = self.pos()
+ errorCatcherName = self.getIdentifier()
+ errorCatcherName = self._applyExpressionFilters(
+ errorCatcherName, 'errorcatcher', startPos=startPos)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self._compiler.setErrorCatcher(errorCatcherName)
+
+ def eatCapture(self):
+ # @@TR: this could be refactored to use the code in eatSimpleIndentingDirective
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLinePos = self.findEOL()
+ lineCol = self.getRowCol()
+
+ self.getDirectiveStartToken()
+ self.advance(len('capture'))
+ startPos = self.pos()
+ self.getWhiteSpace()
+
+ expr = self.getExpression(pyTokensToBreakAt=[':'])
+ expr = self._applyExpressionFilters(expr, 'capture', startPos=startPos)
+ if self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=False))
+ self._compiler.endCaptureRegion()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
+ self.pushToOpenDirectivesStack("capture")
+ self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
+
+
+ def eatIf(self):
+ # filtered
+ isLineClearToStartToken = self.isLineClearToStartToken()
+ endOfFirstLine = self.findEOL()
+ lineCol = self.getRowCol()
+ self.getDirectiveStartToken()
+ startPos = self.pos()
+
+ expressionParts = self.getExpressionParts(pyTokensToBreakAt=[':'])
+ expr = ''.join(expressionParts).strip()
+ expr = self._applyExpressionFilters(expr, 'if', startPos=startPos)
+
+ isTernaryExpr = ('then' in expressionParts and 'else' in expressionParts)
+ if isTernaryExpr:
+ conditionExpr = []
+ trueExpr = []
+ falseExpr = []
+ currentExpr = conditionExpr
+ for part in expressionParts:
+ if part.strip()=='then':
+ currentExpr = trueExpr
+ elif part.strip()=='else':
+ currentExpr = falseExpr
+ else:
+ currentExpr.append(part)
+
+ conditionExpr = ''.join(conditionExpr)
+ trueExpr = ''.join(trueExpr)
+ falseExpr = ''.join(falseExpr)
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self._compiler.addTernaryExpr(conditionExpr, trueExpr, falseExpr, lineCol=lineCol)
+ elif self.matchColonForSingleLineShortFormDirective():
+ self.advance() # skip over :
+ self._compiler.addIf(expr, lineCol=lineCol)
+ self.getWhiteSpace(max=1)
+ self.parse(breakPoint=self.findEOL(gobble=True))
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ else:
+ if self.peek()==':':
+ self.advance()
+ self.getWhiteSpace()
+ self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
+ self.pushToOpenDirectivesStack('if')
+ self._compiler.addIf(expr, lineCol=lineCol)
+
+ ## end directive handlers
+ def handleEndDef(self):
+ isNestedDef = (self.setting('allowNestedDefScopes')
+ and [name for name in self._openDirectivesStack if name=='def'])
+ if not isNestedDef:
+ self._compiler.closeDef()
+ else:
+ # @@TR: temporary hack of useSearchList
+ self.setSetting('useSearchList', self._useSearchList_orig)
+ self._compiler.commitStrConst()
+ self._compiler.dedent()
+ ###
+
+ def pushToOpenDirectivesStack(self, directiveName):
+ assert directiveName in self._closeableDirectives
+ self._openDirectivesStack.append(directiveName)
+
+ def popFromOpenDirectivesStack(self, directiveName):
+ if not self._openDirectivesStack:
+ raise ParseError(self, msg="#end found, but nothing to end")
+
+ if self._openDirectivesStack[-1] == directiveName:
+ del self._openDirectivesStack[-1]
+ else:
+ raise ParseError(self, msg="#end %s found, expected #end %s" %(
+ directiveName, self._openDirectivesStack[-1]))
+
+ def assertEmptyOpenDirectivesStack(self):
+ if self._openDirectivesStack:
+ errorMsg = (
+ "Some #directives are missing their corresponding #end ___ tag: %s" %(
+ ', '.join(self._openDirectivesStack)))
+ raise ParseError(self, msg=errorMsg)
+
+##################################################
+## Make an alias to export
+Parser = _HighLevelParser