2 # $Id: Parser.py,v 1.135 2007/11/16 18:26:01 tavis_rudd Exp $
3 """Parser classes for Cheetah's Compiler
6 ParseError( Exception )
7 _LowLevelParser( Cheetah.SourceReader.SourceReader ), basically a lexer
8 _HighLevelParser( _LowLevelParser )
9 Parser === _HighLevelParser (an alias)
12 ================================================================================
13 Author: Tavis Rudd <tavis@damnsimple.com>
14 Version: $Revision: 1.135 $
15 Start Date: 2001/08/01
16 Last Revision Date: $Date: 2007/11/16 18:26:01 $
18 __author__
= "Tavis Rudd <tavis@damnsimple.com>"
19 __revision__
= "$Revision: 1.135 $"[11:-2]
24 from re
import DOTALL
, MULTILINE
25 from types
import StringType
, ListType
, TupleType
, ClassType
, TypeType
27 from tokenize
import pseudoprog
32 from Cheetah
.SourceReader
import SourceReader
33 from Cheetah
import Filters
34 from Cheetah
import ErrorCatchers
35 from Cheetah
.Unspecified
import Unspecified
36 from Cheetah
.Macros
.I18n
import I18n
40 def cachedRegex(pattern
):
41 if pattern
not in _regexCache
:
42 _regexCache
[pattern
] = re
.compile(pattern
)
43 return _regexCache
[pattern
]
45 def escapeRegexChars(txt
,
46 escapeRE
=re
.compile(r
'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
48 """Return a txt with all special regular expressions chars escaped."""
50 return escapeRE
.sub(r
'\\\1' , txt
)
52 def group(*choices
): return '(' + '|'.join(choices
) + ')'
53 def nongroup(*choices
): return '(?:' + '|'.join(choices
) + ')'
54 def namedGroup(name
, *choices
): return '(P:<' + name
+'>' + '|'.join(choices
) + ')'
55 def any(*choices
): return apply(group
, choices
) + '*'
56 def maybe(*choices
): return apply(group
, choices
) + '?'
58 ##################################################
59 ## CONSTANTS & GLOBALS ##
69 ##################################################
70 ## Tokens for the parser ##
73 identchars
= "abcdefghijklmnopqrstuvwxyz" \
74 "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
75 namechars
= identchars
+ "0123456789"
79 unaryArithOps
= ('+', '-', '~')
80 binaryArithOps
= ('+', '-', '/', '//','%')
81 shiftOps
= ('>>','<<')
82 bitwiseOps
= ('&','|','^')
84 augAssignOps
= ('+=','-=','/=','*=', '**=','^=','%=',
85 '>>=','<<=','&=','|=', )
86 assignmentOps
= (assignOp
,) + augAssignOps
88 compOps
= ('<','>','==','!=','<=','>=', '<>', 'is', 'in',)
89 booleanOps
= ('and','or','not')
90 operators
= (powerOp
,) + unaryArithOps
+ binaryArithOps \
91 + shiftOps
+ bitwiseOps
+ assignmentOps \
92 + compOps
+ booleanOps
94 delimeters
= ('(',')','{','}','[',']',
95 ',','.',':',';','=','`') + augAssignOps
98 keywords
= ('and', 'del', 'for', 'is', 'raise',
99 'assert', 'elif', 'from', 'lambda', 'return',
100 'break', 'else', 'global', 'not', 'try',
101 'class', 'except', 'if', 'or', 'while',
102 'continue', 'exec', 'import', 'pass',
103 'def', 'finally', 'in', 'print',
109 tripleQuotedStringStarts
= ("'''", '"""',
110 "r'''", 'r"""', "R'''", 'R"""',
111 "u'''", 'u"""', "U'''", 'U"""',
112 "ur'''", 'ur"""', "Ur'''", 'Ur"""',
113 "uR'''", 'uR"""', "UR'''", 'UR"""')
115 tripleQuotedStringPairs
= {"'''": single3
, '"""': double3
,
116 "r'''": single3
, 'r"""': double3
,
117 "u'''": single3
, 'u"""': double3
,
118 "ur'''": single3
, 'ur"""': double3
,
119 "R'''": single3
, 'R"""': double3
,
120 "U'''": single3
, 'U"""': double3
,
121 "uR'''": single3
, 'uR"""': double3
,
122 "Ur'''": single3
, 'Ur"""': double3
,
123 "UR'''": single3
, 'UR"""': double3
,
126 closurePairs
= {')':'(',']':'[','}':'{'}
127 closurePairsRev
= {'(':')','[':']','{':'}'}
129 ##################################################
130 ## Regex chunks for the parser ##
132 tripleQuotedStringREs
= {}
133 def makeTripleQuoteRe(start
, end
):
134 start
= escapeRegexChars(start
)
135 end
= escapeRegexChars(end
)
136 return re
.compile(r
'(?:' + start
+ r
').*?' + r
'(?:' + end
+ r
')', re
.DOTALL
)
138 for start
, end
in tripleQuotedStringPairs
.items():
139 tripleQuotedStringREs
[start
] = makeTripleQuoteRe(start
, end
)
144 escCharLookBehind
= nongroup(r
'(?<=\A)',r
'(?<!\\)')
145 nameCharLookAhead
= r
'(?=[A-Za-z_])'
146 identRE
=re
.compile(r
'[a-zA-Z_][a-zA-Z_0-9]*')
147 EOLre
=re
.compile(r
'(?:\r\n|\r|\n)')
149 specialVarRE
=re
.compile(r
'([a-zA-z_]+)@') # for matching specialVar comments
150 # e.g. ##author@ Tavis Rudd
152 unicodeDirectiveRE
= re
.compile(
153 r
'(?:^|\r\n|\r|\n)\s*#\s{0,5}unicode[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re
.MULTILINE
)
154 encodingDirectiveRE
= re
.compile(
155 r
'(?:^|\r\n|\r|\n)\s*#\s{0,5}encoding[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re
.MULTILINE
)
157 escapedNewlineRE
= re
.compile(r
'(?<!\\)\\n')
159 directiveNamesAndParsers
= {
160 # importing and inheritance
163 'extends': 'eatExtends',
164 'implements': 'eatImplements',
167 # output, filtering, and caching
170 'include': 'eatInclude',
172 'filter': 'eatFilter',
179 'capture': 'eatCapture',
181 # declaration, assignment, and deletion
186 'defmacro': 'eatDefMacro',
188 'closure': 'eatClosure',
216 'errorCatcher': 'eatErrorCatcher',
218 # intructions to the parser and compiler
219 'breakpoint': 'eatBreakPoint',
220 'compiler': 'eatCompiler',
221 'compiler-settings': 'eatCompilerSettings',
224 'shBang': 'eatShbang',
225 'encoding': 'eatEncoding',
227 'end': 'eatEndDirective',
230 endDirectiveNamesAndHandlers
= {
231 'def': 'handleEndDef', # has short-form
232 'block': None, # has short-form
233 'closure': None, # has short-form
234 'cache': None, # has short-form
235 'call': None, # has short-form
236 'capture': None, # has short-form
239 'while': None, # has short-form
240 'for': None, # has short-form
241 'if': None, # has short-form
242 'try': None, # has short-form
243 'repeat': None, # has short-form
244 'unless': None, # has short-form
247 ##################################################
250 # @@TR: SyntaxError doesn't call exception.__str__ for some reason!
251 #class ParseError(SyntaxError):
252 class ParseError(ValueError):
253 def __init__(self
, stream
, msg
='Invalid Syntax', extMsg
='', lineno
=None, col
=None):
255 if stream
.pos() >= len(stream
):
256 stream
.setPos(len(stream
) -1)
267 if stream
.filename():
268 f
= " in file %s" % stream
.filename()
274 row
, col
, line
= (lineno
, (self
.col
or 0),
275 self
.stream
.splitlines()[lineno
-1])
277 row
, col
, line
= self
.stream
.getRowColLine()
279 ## get the surrounding lines
280 lines
= stream
.splitlines()
281 prevLines
= [] # (rowNum, content)
285 prevLines
.append( (row
-i
,lines
[row
-1-i
]) )
287 nextLines
= [] # (rowNum, content)
289 if not row
-1+i
< len(lines
):
291 nextLines
.append( (row
+i
,lines
[row
-1+i
]) )
294 ## print the main message
295 report
+= "\n\n%s\n" %self
.msg
296 report
+= "Line %i, column %i%s\n\n" % (row
, col
, f
)
297 report
+= 'Line|Cheetah Code\n'
298 report
+= '----|-------------------------------------------------------------\n'
300 lineInfo
= prevLines
.pop()
301 report
+= "%(row)-4d|%(line)s\n"% {'row':lineInfo
[0], 'line':lineInfo
[1]}
302 report
+= "%(row)-4d|%(line)s\n"% {'row':row
, 'line':line
}
303 report
+= ' '*5 +' '*(col
-1) + "^\n"
306 lineInfo
= nextLines
.pop()
307 report
+= "%(row)-4d|%(line)s\n"% {'row':lineInfo
[0], 'line':lineInfo
[1]}
310 report
+= self
.extMsg
+ '\n'
314 class ForbiddenSyntax(ParseError
): pass
315 class ForbiddenExpression(ForbiddenSyntax
): pass
316 class ForbiddenDirective(ForbiddenSyntax
): pass
318 class CheetahVariable
:
319 def __init__(self
, nameChunks
, useNameMapper
=True, cacheToken
=None,
321 self
.nameChunks
= nameChunks
322 self
.useNameMapper
= useNameMapper
323 self
.cacheToken
= cacheToken
324 self
.rawSource
= rawSource
326 class Placeholder(CheetahVariable
): pass
329 """Used by _LowLevelParser.getArgList()"""
336 def addArgName(self
, name
):
337 self
.argNames
.append( name
)
338 self
.defVals
.append( None )
343 def addToDefVal(self
, token
):
345 if self
.defVals
[i
] == None:
347 self
.defVals
[i
] += token
350 defVals
= self
.defVals
351 for i
in range(len(defVals
)):
352 if type(defVals
[i
]) == StringType
:
353 defVals
[i
] = defVals
[i
].strip()
355 return map(None, [i
.strip() for i
in self
.argNames
], defVals
)
358 return str(self
.merge())
360 class _LowLevelParser(SourceReader
):
361 """This class implements the methods to match or extract ('get*') the basic
362 elements of Cheetah's grammar. It does NOT handle any code generation or
366 _settingsManager
= None
368 def setSettingsManager(self
, settingsManager
):
369 self
._settingsManager
= settingsManager
371 def setting(self
, key
, default
=Unspecified
):
372 if default
is Unspecified
:
373 return self
._settingsManager
.setting(key
)
375 return self
._settingsManager
.setting(key
, default
=default
)
377 def setSetting(self
, key
, val
):
378 self
._settingsManager
.setSetting(key
, val
)
381 return self
._settingsManager
.settings()
383 def updateSettings(self
, settings
):
384 self
._settingsManager
.updateSettings(settings
)
386 def _initializeSettings(self
):
387 self
._settingsManager
._initializeSettings
()
389 def configureParser(self
):
390 """Is called by the Compiler instance after the parser has had a
391 settingsManager assigned with self.setSettingsManager()
393 self
._makeCheetahVarREs
()
394 self
._makeCommentREs
()
395 self
._makeDirectiveREs
()
397 self
._possibleNonStrConstantChars
= (
398 self
.setting('commentStartToken')[0] +
399 self
.setting('multiLineCommentStartToken')[0] +
400 self
.setting('cheetahVarStartToken')[0] +
401 self
.setting('directiveStartToken')[0] +
402 self
.setting('PSPStartToken')[0])
403 self
._nonStrConstMatchers
= [
404 self
.matchCommentStartToken
,
405 self
.matchMultiLineCommentStartToken
,
406 self
.matchVariablePlaceholderStart
,
407 self
.matchExpressionPlaceholderStart
,
409 self
.matchPSPStartToken
,
410 self
.matchEOLSlurpToken
,
415 def _makeCheetahVarREs(self
):
417 """Setup the regexs for Cheetah $var parsing."""
420 interval
= (r
'(?P<interval>' +
429 cacheToken
= (r
'(?:' +
430 r
'(?P<REFRESH_CACHE>\*' + interval
+ '\*)'+
432 r
'(?P<STATIC_CACHE>\*)' +
436 self
.cacheTokenRE
= cachedRegex(cacheToken
)
438 silentPlaceholderToken
= (r
'(?:' +
439 r
'(?P<SILENT>' +escapeRegexChars('!')+')'+
441 r
'(?P<NOT_SILENT>)' +
443 self
.silentPlaceholderTokenRE
= cachedRegex(silentPlaceholderToken
)
445 self
.cheetahVarStartRE
= cachedRegex(
447 r
'(?P<startToken>'+escapeRegexChars(self
.setting('cheetahVarStartToken'))+')'+
448 r
'(?P<silenceToken>'+silentPlaceholderToken
+')'+
449 r
'(?P<cacheToken>'+cacheToken
+')'+
450 r
'(?P<enclosure>|(?:(?:\{|\(|\[)[ \t\f]*))' + # allow WS after enclosure
452 validCharsLookAhead
= r
'(?=[A-Za-z_\*!\{\(\[])'
453 self
.cheetahVarStartToken
= self
.setting('cheetahVarStartToken')
454 self
.cheetahVarStartTokenRE
= cachedRegex(
456 escapeRegexChars(self
.setting('cheetahVarStartToken'))
460 self
.cheetahVarInExpressionStartTokenRE
= cachedRegex(
461 escapeRegexChars(self
.setting('cheetahVarStartToken'))
465 self
.expressionPlaceholderStartRE
= cachedRegex(
467 r
'(?P<startToken>' + escapeRegexChars(self
.setting('cheetahVarStartToken')) + ')' +
468 r
'(?P<cacheToken>' + cacheToken
+ ')' +
470 r
'(?:\{|\(|\[)[ \t\f]*'
474 if self
.setting('EOLSlurpToken'):
475 self
.EOLSlurpRE
= cachedRegex(
476 escapeRegexChars(self
.setting('EOLSlurpToken'))
481 self
.EOLSlurpRE
= None
484 def _makeCommentREs(self
):
485 """Construct the regex bits that are used in comment parsing."""
486 startTokenEsc
= escapeRegexChars(self
.setting('commentStartToken'))
487 self
.commentStartTokenRE
= cachedRegex(escCharLookBehind
+ startTokenEsc
)
490 startTokenEsc
= escapeRegexChars(
491 self
.setting('multiLineCommentStartToken'))
492 endTokenEsc
= escapeRegexChars(
493 self
.setting('multiLineCommentEndToken'))
494 self
.multiLineCommentTokenStartRE
= cachedRegex(escCharLookBehind
+
496 self
.multiLineCommentEndTokenRE
= cachedRegex(escCharLookBehind
+
499 def _makeDirectiveREs(self
):
500 """Construct the regexs that are used in directive parsing."""
501 startToken
= self
.setting('directiveStartToken')
502 endToken
= self
.setting('directiveEndToken')
503 startTokenEsc
= escapeRegexChars(startToken
)
504 endTokenEsc
= escapeRegexChars(endToken
)
505 validSecondCharsLookAhead
= r
'(?=[A-Za-z_@])'
506 reParts
= [escCharLookBehind
, startTokenEsc
]
507 if self
.setting('allowWhitespaceAfterDirectiveStartToken'):
508 reParts
.append('[ \t]*')
509 reParts
.append(validSecondCharsLookAhead
)
510 self
.directiveStartTokenRE
= cachedRegex(''.join(reParts
))
511 self
.directiveEndTokenRE
= cachedRegex(escCharLookBehind
+ endTokenEsc
)
513 def _makePspREs(self
):
514 """Setup the regexs for PSP parsing."""
515 startToken
= self
.setting('PSPStartToken')
516 startTokenEsc
= escapeRegexChars(startToken
)
517 self
.PSPStartTokenRE
= cachedRegex(escCharLookBehind
+ startTokenEsc
)
518 endToken
= self
.setting('PSPEndToken')
519 endTokenEsc
= escapeRegexChars(endToken
)
520 self
.PSPEndTokenRE
= cachedRegex(escCharLookBehind
+ endTokenEsc
)
523 def isLineClearToStartToken(self
, pos
=None):
524 return self
.isLineClearToPos(pos
)
526 def matchTopLevelToken(self
):
527 """Returns the first match found from the following methods:
528 self.matchCommentStartToken
529 self.matchMultiLineCommentStartToken
530 self.matchVariablePlaceholderStart
531 self.matchExpressionPlaceholderStart
533 self.matchPSPStartToken
534 self.matchEOLSlurpToken
536 Returns None if no match.
539 if self
.peek() in self
._possibleNonStrConstantChars
:
540 for matcher
in self
._nonStrConstMatchers
:
546 def matchPyToken(self
):
547 match
= pseudoprog
.match(self
.src(), self
.pos())
549 if match
and match
.group() in tripleQuotedStringStarts
:
550 TQSmatch
= tripleQuotedStringREs
[match
.group()].match(self
.src(), self
.pos())
555 def getPyToken(self
):
556 match
= self
.matchPyToken()
558 raise ParseError(self
)
559 elif match
.group() in tripleQuotedStringStarts
:
560 raise ParseError(self
, msg
='Malformed triple-quoted string')
561 return self
.readTo(match
.end())
563 def matchEOLSlurpToken(self
):
565 return self
.EOLSlurpRE
.match(self
.src(), self
.pos())
567 def getEOLSlurpToken(self
):
568 match
= self
.matchEOLSlurpToken()
570 raise ParseError(self
, msg
='Invalid EOL slurp token')
571 return self
.readTo(match
.end())
573 def matchCommentStartToken(self
):
574 return self
.commentStartTokenRE
.match(self
.src(), self
.pos())
576 def getCommentStartToken(self
):
577 match
= self
.matchCommentStartToken()
579 raise ParseError(self
, msg
='Invalid single-line comment start token')
580 return self
.readTo(match
.end())
582 def matchMultiLineCommentStartToken(self
):
583 return self
.multiLineCommentTokenStartRE
.match(self
.src(), self
.pos())
585 def getMultiLineCommentStartToken(self
):
586 match
= self
.matchMultiLineCommentStartToken()
588 raise ParseError(self
, msg
='Invalid multi-line comment start token')
589 return self
.readTo(match
.end())
591 def matchMultiLineCommentEndToken(self
):
592 return self
.multiLineCommentEndTokenRE
.match(self
.src(), self
.pos())
594 def getMultiLineCommentEndToken(self
):
595 match
= self
.matchMultiLineCommentEndToken()
597 raise ParseError(self
, msg
='Invalid multi-line comment end token')
598 return self
.readTo(match
.end())
600 def getDottedName(self
):
604 if not self
.peek() in identchars
:
605 raise ParseError(self
)
607 while self
.pos() < srcLen
:
610 nameChunk
= self
.getIdentifier()
611 nameChunks
.append(nameChunk
)
613 if self
.pos()+1 <srcLen
and self
.peek(1) in identchars
:
614 nameChunks
.append(self
.getc())
620 return ''.join(nameChunks
)
622 def matchIdentifier(self
):
623 return identRE
.match(self
.src(), self
.pos())
625 def getIdentifier(self
):
626 match
= self
.matchIdentifier()
628 raise ParseError(self
, msg
='Invalid identifier')
629 return self
.readTo(match
.end())
631 def matchOperator(self
):
632 match
= self
.matchPyToken()
633 if match
and match
.group() not in operators
:
637 def getOperator(self
):
638 match
= self
.matchOperator()
640 raise ParseError(self
, msg
='Expected operator')
641 return self
.readTo( match
.end() )
643 def matchAssignmentOperator(self
):
644 match
= self
.matchPyToken()
645 if match
and match
.group() not in assignmentOps
:
649 def getAssignmentOperator(self
):
650 match
= self
.matchAssignmentOperator()
652 raise ParseError(self
, msg
='Expected assignment operator')
653 return self
.readTo( match
.end() )
655 def matchDirective(self
):
656 """Returns False or the name of the directive matched.
658 startPos
= self
.pos()
659 if not self
.matchDirectiveStartToken():
661 self
.getDirectiveStartToken()
662 directiveName
= self
.matchDirectiveName()
663 self
.setPos(startPos
)
666 def matchDirectiveName(self
, directiveNameChars
=identchars
+'0123456789-@'):
667 startPos
= self
.pos()
668 possibleMatches
= self
._directiveNamesAndParsers
.keys()
672 while not self
.atEnd():
674 if not c
in directiveNameChars
:
678 if not self
.atEnd() and self
.peek() in identchars
:
681 possibleMatches
= [dn
for dn
in possibleMatches
if dn
.startswith(name
)]
682 if not possibleMatches
:
684 elif (name
in possibleMatches
and (self
.atEnd() or self
.peek() not in directiveNameChars
)):
688 self
.setPos(startPos
)
691 def matchDirectiveStartToken(self
):
692 return self
.directiveStartTokenRE
.match(self
.src(), self
.pos())
694 def getDirectiveStartToken(self
):
695 match
= self
.matchDirectiveStartToken()
697 raise ParseError(self
, msg
='Invalid directive start token')
698 return self
.readTo(match
.end())
700 def matchDirectiveEndToken(self
):
701 return self
.directiveEndTokenRE
.match(self
.src(), self
.pos())
703 def getDirectiveEndToken(self
):
704 match
= self
.matchDirectiveEndToken()
706 raise ParseError(self
, msg
='Invalid directive end token')
707 return self
.readTo(match
.end())
710 def matchColonForSingleLineShortFormDirective(self
):
711 if not self
.atEnd() and self
.peek()==':':
712 restOfLine
= self
[self
.pos()+1:self
.findEOL()]
713 restOfLine
= restOfLine
.strip()
716 elif self
.commentStartTokenRE
.match(restOfLine
):
718 else: # non-whitespace, non-commment chars found
722 def matchPSPStartToken(self
):
723 return self
.PSPStartTokenRE
.match(self
.src(), self
.pos())
725 def matchPSPEndToken(self
):
726 return self
.PSPEndTokenRE
.match(self
.src(), self
.pos())
728 def getPSPStartToken(self
):
729 match
= self
.matchPSPStartToken()
731 raise ParseError(self
, msg
='Invalid psp start token')
732 return self
.readTo(match
.end())
734 def getPSPEndToken(self
):
735 match
= self
.matchPSPEndToken()
737 raise ParseError(self
, msg
='Invalid psp end token')
738 return self
.readTo(match
.end())
740 def matchCheetahVarStart(self
):
741 """includes the enclosure and cache token"""
742 return self
.cheetahVarStartRE
.match(self
.src(), self
.pos())
744 def matchCheetahVarStartToken(self
):
745 """includes the enclosure and cache token"""
746 return self
.cheetahVarStartTokenRE
.match(self
.src(), self
.pos())
748 def matchCheetahVarInExpressionStartToken(self
):
749 """no enclosures or cache tokens allowed"""
750 return self
.cheetahVarInExpressionStartTokenRE
.match(self
.src(), self
.pos())
752 def matchVariablePlaceholderStart(self
):
753 """includes the enclosure and cache token"""
754 return self
.cheetahVarStartRE
.match(self
.src(), self
.pos())
756 def matchExpressionPlaceholderStart(self
):
757 """includes the enclosure and cache token"""
758 return self
.expressionPlaceholderStartRE
.match(self
.src(), self
.pos())
760 def getCheetahVarStartToken(self
):
761 """just the start token, not the enclosure or cache token"""
762 match
= self
.matchCheetahVarStartToken()
764 raise ParseError(self
, msg
='Expected Cheetah $var start token')
765 return self
.readTo( match
.end() )
768 def getCacheToken(self
):
770 token
= self
.cacheTokenRE
.match(self
.src(), self
.pos())
771 self
.setPos( token
.end() )
774 raise ParseError(self
, msg
='Expected cache token')
776 def getSilentPlaceholderToken(self
):
778 token
= self
.silentPlaceholderTokenRE
.match(self
.src(), self
.pos())
779 self
.setPos( token
.end() )
782 raise ParseError(self
, msg
='Expected silent placeholder token')
786 def getTargetVarsList(self
):
788 while not self
.atEnd():
789 if self
.peek() in ' \t\f':
791 elif self
.peek() in '\r\n':
793 elif self
.startswith(','):
795 elif self
.startswith('in ') or self
.startswith('in\t'):
797 #elif self.matchCheetahVarStart():
798 elif self
.matchCheetahVarInExpressionStartToken():
799 self
.getCheetahVarStartToken()
800 self
.getSilentPlaceholderToken()
802 varnames
.append( self
.getDottedName() )
803 elif self
.matchIdentifier():
804 varnames
.append( self
.getDottedName() )
809 def getCheetahVar(self
, plain
=False, skipStartToken
=False):
810 """This is called when parsing inside expressions. Cache tokens are only
811 valid in placeholders so this method discards any cache tokens found.
813 if not skipStartToken
:
814 self
.getCheetahVarStartToken()
815 self
.getSilentPlaceholderToken()
817 return self
.getCheetahVarBody(plain
=plain
)
819 def getCheetahVarBody(self
, plain
=False):
820 # @@TR: this should be in the compiler
821 return self
._compiler
.genCheetahVar(self
.getCheetahVarNameChunks(), plain
=plain
)
823 def getCheetahVarNameChunks(self
):
826 nameChunks = list of Cheetah $var subcomponents represented as tuples
827 [ (namemapperPart,autoCall,restOfName),
830 namemapperPart = the dottedName base
831 autocall = where NameMapper should use autocalling on namemapperPart
832 restOfName = any arglist, index, or slice
834 If restOfName contains a call arglist (e.g. '(1234)') then autocall is
835 False, otherwise it defaults to True.
838 ------------------------------------------------------------------------
840 if the raw CheetahVar is
843 nameChunks is the list
844 [ ('a.b.c',True,'[1]'),
852 while self
.pos() < len(self
):
855 if not self
.peek() in identchars
+ '.':
857 elif self
.peek() == '.':
859 if self
.pos()+1 < len(self
) and self
.peek(1) in identchars
:
860 self
.advance() # discard the period as it isn't needed with NameMapper
864 dottedName
= self
.getDottedName()
865 if not self
.atEnd() and self
.peek() in '([':
866 if self
.peek() == '(':
867 rest
= self
.getCallArgString()
869 rest
= self
.getExpression(enclosed
=True)
871 period
= max(dottedName
.rfind('.'), 0)
873 chunks
.append( (dottedName
[:period
], autoCall
, '') )
874 dottedName
= dottedName
[period
+1:]
875 if rest
and rest
[0]=='(':
877 chunks
.append( (dottedName
, autoCall
, rest
) )
882 def getCallArgString(self
,
883 enclosures
=[], # list of tuples (char, pos), where char is ({ or [
884 useNameMapper
=Unspecified
):
886 """ Get a method/function call argument string.
888 This method understands *arg, and **kw
891 # @@TR: this settings mangling should be removed
892 if useNameMapper
is not Unspecified
:
893 useNameMapper_orig
= self
.setting('useNameMapper')
894 self
.setSetting('useNameMapper', useNameMapper
)
899 if not self
.peek() == '(':
900 raise ParseError(self
, msg
="Expected '('")
901 startPos
= self
.pos()
903 enclosures
= [('(', startPos
),
906 argStringBits
= ['(']
907 addBit
= argStringBits
.append
911 open = enclosures
[-1][0]
912 close
= closurePairsRev
[open]
913 self
.setPos(enclosures
[-1][1])
915 self
, msg
="EOF was reached before a matching '" + close
+
916 "' was found for the '" + open + "'")
919 if c
in ")}]": # get the ending enclosure and break
921 raise ParseError(self
)
923 open = closurePairs
[c
]
924 if enclosures
[-1][0] == open:
929 raise ParseError(self
)
930 elif c
in " \t\f\r\n":
932 elif self
.matchCheetahVarInExpressionStartToken():
933 startPos
= self
.pos()
934 codeFor1stToken
= self
.getCheetahVar()
935 WS
= self
.getWhiteSpace()
936 if not self
.atEnd() and self
.peek() == '=':
937 nextToken
= self
.getPyToken()
940 self
.setPos(startPos
)
941 codeFor1stToken
= self
.getCheetahVar(plain
=True)
945 addBit( codeFor1stToken
+ WS
+ nextToken
)
947 addBit( codeFor1stToken
+ WS
)
948 elif self
.matchCheetahVarStart():
949 # it has syntax that is only valid at the top level
950 self
._raiseErrorAboutInvalidCheetahVarSyntaxInExpr
()
952 beforeTokenPos
= self
.pos()
953 token
= self
.getPyToken()
954 if token
in ('{','(','['):
956 token
= self
.getExpression(enclosed
=True)
957 token
= self
.transformToken(token
, beforeTokenPos
)
960 if useNameMapper
is not Unspecified
:
961 self
.setSetting('useNameMapper', useNameMapper_orig
) # @@TR: see comment above
963 return ''.join(argStringBits
)
965 def getDefArgList(self
, exitPos
=None, useNameMapper
=False):
967 """ Get an argument list. Can be used for method/function definition
968 argument lists or for #directive argument lists. Returns a list of
969 tuples in the form (argName, defVal=None) with one tuple for each arg
972 These defVals are always strings, so (argName, defVal=None) is safe even
973 with a case like (arg1, arg2=None, arg3=1234*2), which would be returned as
979 This method understands *arg, and **kw
983 if self
.peek() == '(':
986 exitPos
= self
.findEOL() # it's a directive so break at the EOL
990 # @@TR: this settings mangling should be removed
991 useNameMapper_orig
= self
.setting('useNameMapper')
992 self
.setSetting('useNameMapper', useNameMapper
)
997 self
, msg
="EOF was reached before a matching ')'"+
998 " was found for the '('")
1000 if self
.pos() == exitPos
:
1004 if c
== ")" or self
.matchDirectiveEndToken():
1008 elif c
in " \t\f\r\n":
1010 argList
.addToDefVal(c
)
1019 elif self
.startswith(self
.cheetahVarStartToken
) and not onDefVal
:
1020 self
.advance(len(self
.cheetahVarStartToken
))
1021 elif self
.matchIdentifier() and not onDefVal
:
1022 argList
.addArgName( self
.getIdentifier() )
1024 if self
.matchCheetahVarInExpressionStartToken():
1025 token
= self
.getCheetahVar()
1026 elif self
.matchCheetahVarStart():
1027 # it has syntax that is only valid at the top level
1028 self
._raiseErrorAboutInvalidCheetahVarSyntaxInExpr
()
1030 beforeTokenPos
= self
.pos()
1031 token
= self
.getPyToken()
1032 if token
in ('{','(','['):
1034 token
= self
.getExpression(enclosed
=True)
1035 token
= self
.transformToken(token
, beforeTokenPos
)
1036 argList
.addToDefVal(token
)
1037 elif c
== '*' and not onDefVal
:
1038 varName
= self
.getc()
1039 if self
.peek() == '*':
1040 varName
+= self
.getc()
1041 if not self
.matchIdentifier():
1042 raise ParseError(self
)
1043 varName
+= self
.getIdentifier()
1044 argList
.addArgName(varName
)
1046 raise ParseError(self
)
1049 self
.setSetting('useNameMapper', useNameMapper_orig
) # @@TR: see comment above
1050 return argList
.merge()
1052 def getExpressionParts(self
,
1054 enclosures
=None, # list of tuples (char, pos), where char is ({ or [
1055 pyTokensToBreakAt
=None, # only works if not enclosed
1056 useNameMapper
=Unspecified
,
1059 """ Get a Cheetah expression that includes $CheetahVars and break at
1060 directive end tokens, the end of an enclosure, or at a specified
1064 if useNameMapper
is not Unspecified
:
1065 useNameMapper_orig
= self
.setting('useNameMapper')
1066 self
.setSetting('useNameMapper', useNameMapper
)
1068 if enclosures
is None:
1076 open = enclosures
[-1][0]
1077 close
= closurePairsRev
[open]
1078 self
.setPos(enclosures
[-1][1])
1080 self
, msg
="EOF was reached before a matching '" + close
+
1081 "' was found for the '" + open + "'")
1088 enclosures
.append( (c
, self
.pos()) )
1090 elif enclosed
and not enclosures
:
1094 raise ParseError(self
)
1095 open = closurePairs
[c
]
1096 if enclosures
[-1][0] == open:
1100 open = enclosures
[-1][0]
1101 close
= closurePairsRev
[open]
1102 row
, col
= self
.getRowCol()
1103 self
.setPos(enclosures
[-1][1])
1105 self
, msg
= "A '" + c
+ "' was found at line " + str(row
) +
1106 ", col " + str(col
) +
1107 " before a matching '" + close
+
1108 "' was found\nfor the '" + open + "'")
1112 exprBits
.append(self
.getWhiteSpace())
1113 elif self
.matchDirectiveEndToken() and not enclosures
:
1115 elif c
== "\\" and self
.pos()+1 < srcLen
:
1116 eolMatch
= EOLre
.match(self
.src(), self
.pos()+1)
1119 raise ParseError(self
, msg
='Line ending expected')
1120 self
.setPos( eolMatch
.end() )
1126 elif self
.matchCheetahVarInExpressionStartToken():
1127 expr
= self
.getCheetahVar()
1128 exprBits
.append(expr
)
1129 elif self
.matchCheetahVarStart():
1130 # it has syntax that is only valid at the top level
1131 self
._raiseErrorAboutInvalidCheetahVarSyntaxInExpr
()
1133 beforeTokenPos
= self
.pos()
1134 token
= self
.getPyToken()
1136 and pyTokensToBreakAt
1137 and token
in pyTokensToBreakAt
):
1139 self
.setPos(beforeTokenPos
)
1142 token
= self
.transformToken(token
, beforeTokenPos
)
1144 exprBits
.append(token
)
1145 if identRE
.match(token
):
1147 expr
= self
.getExpression(useNameMapper
=False, pyTokensToBreakAt
=['in'])
1148 exprBits
.append(expr
)
1150 exprBits
.append(self
.getWhiteSpace())
1151 if not self
.atEnd() and self
.peek() == '(':
1152 exprBits
.append(self
.getCallArgString())
1154 if useNameMapper
is not Unspecified
:
1155 self
.setSetting('useNameMapper', useNameMapper_orig
) # @@TR: see comment above
1158 def getExpression(self
,
1160 enclosures
=None, # list of tuples (char, pos), where # char is ({ or [
1161 pyTokensToBreakAt
=None,
1162 useNameMapper
=Unspecified
,
1164 """Returns the output of self.getExpressionParts() as a concatenated
1165 string rather than as a list.
1167 return ''.join(self
.getExpressionParts(
1168 enclosed
=enclosed
, enclosures
=enclosures
,
1169 pyTokensToBreakAt
=pyTokensToBreakAt
,
1170 useNameMapper
=useNameMapper
))
1173 def transformToken(self
, token
, beforeTokenPos
):
1174 """Takes a token from the expression being parsed and performs and
1175 special transformations required by Cheetah.
1177 At the moment only Cheetah's c'$placeholder strings' are transformed.
1179 if token
=='c' and not self
.atEnd() and self
.peek() in '\'"':
1180 nextToken
= self
.getPyToken()
1181 token
= nextToken
.upper()
1182 theStr
= eval(token
)
1187 if token
.startswith(single3
) or token
.startswith(double3
):
1191 #print 'CHEETAH STRING', nextToken, theStr, startPosIdx
1192 self
.setPos(beforeTokenPos
+startPosIdx
+1)
1195 while self
.pos() < (endPos
-startPosIdx
):
1196 if self
.matchCheetahVarStart() or self
.matchExpressionPlaceholderStart():
1198 outputExprs
.append(repr(strConst
))
1200 placeholderExpr
= self
.getPlaceholder()
1201 outputExprs
.append('str('+placeholderExpr
+')')
1203 strConst
+= self
.getc()
1206 outputExprs
.append(repr(strConst
))
1207 #if not self.atEnd() and self.matches('.join('):
1209 token
= "''.join(["+','.join(outputExprs
)+"])"
1212 def _raiseErrorAboutInvalidCheetahVarSyntaxInExpr(self
):
1213 match
= self
.matchCheetahVarStart()
1214 groupdict
= match
.groupdict()
1215 if groupdict
.get('cacheToken'):
1218 msg
='Cache tokens are not valid inside expressions. '
1219 'Use them in top-level $placeholders only.')
1220 elif groupdict
.get('enclosure'):
1223 msg
='Long-form placeholders - ${}, $(), $[], etc. are not valid inside expressions. '
1224 'Use them in top-level $placeholders only.')
1228 msg
='This form of $placeholder syntax is not valid here.')
1231 def getPlaceholder(self
, allowCacheTokens
=False, plain
=False, returnEverything
=False):
1233 for callback
in self
.setting('preparsePlaceholderHooks'):
1234 callback(parser
=self
)
1236 startPos
= self
.pos()
1237 lineCol
= self
.getRowCol(startPos
)
1238 startToken
= self
.getCheetahVarStartToken()
1239 silentPlaceholderToken
= self
.getSilentPlaceholderToken()
1240 if silentPlaceholderToken
:
1241 isSilentPlaceholder
= True
1243 isSilentPlaceholder
= False
1246 if allowCacheTokens
:
1247 cacheToken
= self
.getCacheToken()
1248 cacheTokenParts
= self
.cacheTokenRE
.match(cacheToken
).groupdict()
1250 cacheTokenParts
= {}
1252 if self
.peek() in '({[':
1254 enclosureOpenChar
= self
.getc()
1255 enclosures
= [ (enclosureOpenChar
, pos
) ]
1256 self
.getWhiteSpace()
1261 if self
.matchIdentifier():
1262 nameChunks
= self
.getCheetahVarNameChunks()
1263 expr
= self
._compiler
.genCheetahVar(nameChunks
[:], plain
=plain
)
1266 WS
= self
.getWhiteSpace()
1268 if self
.setting('allowPlaceholderFilterArgs') and self
.peek()==',':
1269 filterArgs
= self
.getCallArgString(enclosures
=enclosures
)[1:-1]
1271 if self
.peek()==closurePairsRev
[enclosureOpenChar
]:
1274 restOfExpr
= self
.getExpression(enclosed
=True, enclosures
=enclosures
)
1275 if restOfExpr
[-1] == closurePairsRev
[enclosureOpenChar
]:
1276 restOfExpr
= restOfExpr
[:-1]
1278 rawPlaceholder
= self
[startPos
: self
.pos()]
1280 expr
= self
.getExpression(enclosed
=True, enclosures
=enclosures
)
1281 if expr
[-1] == closurePairsRev
[enclosureOpenChar
]:
1283 rawPlaceholder
=self
[startPos
: self
.pos()]
1285 expr
= self
._applyExpressionFilters
(expr
,'placeholder',
1286 rawExpr
=rawPlaceholder
,startPos
=startPos
)
1287 for callback
in self
.setting('postparsePlaceholderHooks'):
1288 callback(parser
=self
)
1290 if returnEverything
:
1291 return (expr
, rawPlaceholder
, lineCol
, cacheTokenParts
,
1292 filterArgs
, isSilentPlaceholder
)
1297 class _HighLevelParser(_LowLevelParser
):
1298 """This class is a StateMachine for parsing Cheetah source and
1299 sending state dependent code generation commands to
1300 Cheetah.Compiler.Compiler.
1302 def __init__(self
, src
, filename
=None, breakPoint
=None, compiler
=None):
1303 _LowLevelParser
.__init
__(self
, src
, filename
=filename
, breakPoint
=breakPoint
)
1304 self
.setSettingsManager(compiler
)
1305 self
._compiler
= compiler
1307 self
.configureParser()
1309 def setupState(self
):
1311 self
._macroDetails
= {}
1312 self
._openDirectivesStack
= []
1315 """Cleanup to remove any possible reference cycles
1317 self
._macros
.clear()
1318 for macroname
, macroDetails
in self
._macroDetails
.items():
1319 macroDetails
.template
.shutdown()
1320 del macroDetails
.template
1321 self
._macroDetails
.clear()
1323 def configureParser(self
):
1324 _LowLevelParser
.configureParser(self
)
1325 self
._initDirectives
()
1327 def _initDirectives(self
):
1328 def normalizeParserVal(val
):
1329 if isinstance(val
, (str,unicode)):
1330 handler
= getattr(self
, val
)
1331 elif type(val
) in (ClassType
, TypeType
):
1338 raise Exception('Invalid parser/handler value %r for %s'%(val
, name
))
1341 normalizeHandlerVal
= normalizeParserVal
1343 _directiveNamesAndParsers
= directiveNamesAndParsers
.copy()
1344 customNamesAndParsers
= self
.setting('directiveNamesAndParsers',{})
1345 _directiveNamesAndParsers
.update(customNamesAndParsers
)
1347 _endDirectiveNamesAndHandlers
= endDirectiveNamesAndHandlers
.copy()
1348 customNamesAndHandlers
= self
.setting('endDirectiveNamesAndHandlers',{})
1349 _endDirectiveNamesAndHandlers
.update(customNamesAndHandlers
)
1351 self
._directiveNamesAndParsers
= {}
1352 for name
, val
in _directiveNamesAndParsers
.items():
1353 if val
in (False, 0):
1355 self
._directiveNamesAndParsers
[name
] = normalizeParserVal(val
)
1357 self
._endDirectiveNamesAndHandlers
= {}
1358 for name
, val
in _endDirectiveNamesAndHandlers
.items():
1359 if val
in (False, 0):
1361 self
._endDirectiveNamesAndHandlers
[name
] = normalizeHandlerVal(val
)
1363 self
._closeableDirectives
= ['def','block','closure','defmacro',
1369 'for','while','repeat',
1372 for directiveName
in self
.setting('closeableDirectives',[]):
1373 self
._closeableDirectives
.append(directiveName
)
1377 macroDirectives
= self
.setting('macroDirectives',{})
1378 macroDirectives
['i18n'] = I18n
1381 for macroName
, callback
in macroDirectives
.items():
1382 if type(callback
) in (ClassType
, TypeType
):
1383 callback
= callback(parser
=self
)
1385 self
._macros
[macroName
] = callback
1386 self
._directiveNamesAndParsers
[macroName
] = self
.eatMacroCall
1388 def _applyExpressionFilters(self
, expr
, exprType
, rawExpr
=None, startPos
=None):
1389 """Pipes cheetah expressions through a set of optional filter hooks.
1391 The filters are functions which may modify the expressions or raise
1392 a ForbiddenExpression exception if the expression is not allowed. They
1393 are defined in the compiler setting 'expressionFilterHooks'.
1395 Some intended use cases:
1397 - to implement 'restricted execution' safeguards in cases where you
1398 can't trust the author of the template.
1400 - to enforce style guidelines
1402 filter call signature: (parser, expr, exprType, rawExpr=None, startPos=None)
1403 - parser is the Cheetah parser
1404 - expr is the expression to filter. In some cases the parser will have
1405 already modified it from the original source code form. For example,
1406 placeholders will have been translated into namemapper calls. If you
1407 need to work with the original source, see rawExpr.
1408 - exprType is the name of the directive, 'psp', or 'placeholder'. All
1409 lowercase. @@TR: These will eventually be replaced with a set of
1411 - rawExpr is the original source string that Cheetah parsed. This
1412 might be None in some cases.
1413 - startPos is the character position in the source string/file
1414 where the parser started parsing the current expression.
1416 @@TR: I realize this use of the term 'expression' is a bit wonky as many
1417 of the 'expressions' are actually statements, but I haven't thought of
1418 a better name yet. Suggestions?
1420 for callback
in self
.setting('expressionFilterHooks'):
1421 expr
= callback(parser
=self
, expr
=expr
, exprType
=exprType
,
1422 rawExpr
=rawExpr
, startPos
=startPos
)
1425 def _filterDisabledDirectives(self
, directiveName
):
1426 directiveName
= directiveName
.lower()
1427 if (directiveName
in self
.setting('disabledDirectives')
1428 or (self
.setting('enabledDirectives')
1429 and directiveName
not in self
.setting('enabledDirectives'))):
1430 for callback
in self
.setting('disabledDirectiveHooks'):
1431 callback(parser
=self
, directiveName
=directiveName
)
1432 raise ForbiddenDirective(self
, msg
='This %r directive is disabled'%directiveName
)
1436 def parse(self
, breakPoint
=None, assertEmptyStack
=True):
1438 origBP
= self
.breakPoint()
1439 self
.setBreakPoint(breakPoint
)
1440 assertEmptyStack
= False
1442 while not self
.atEnd():
1443 if self
.matchCommentStartToken():
1445 elif self
.matchMultiLineCommentStartToken():
1446 self
.eatMultiLineComment()
1447 elif self
.matchVariablePlaceholderStart():
1448 self
.eatPlaceholder()
1449 elif self
.matchExpressionPlaceholderStart():
1450 self
.eatPlaceholder()
1451 elif self
.matchDirective():
1453 elif self
.matchPSPStartToken():
1455 elif self
.matchEOLSlurpToken():
1456 self
.eatEOLSlurpToken()
1459 if assertEmptyStack
:
1460 self
.assertEmptyOpenDirectivesStack()
1462 self
.setBreakPoint(origBP
)
1464 ## non-directive eat methods
1466 def eatPlainText(self
):
1467 startPos
= self
.pos()
1469 while not self
.atEnd():
1470 match
= self
.matchTopLevelToken()
1475 strConst
= self
.readTo(self
.pos(), start
=startPos
)
1476 self
._compiler
.addStrConst(strConst
)
1479 def eatComment(self
):
1480 isLineClearToStartToken
= self
.isLineClearToStartToken()
1481 if isLineClearToStartToken
:
1482 self
._compiler
.handleWSBeforeDirective()
1483 self
.getCommentStartToken()
1484 comm
= self
.readToEOL(gobble
=isLineClearToStartToken
)
1485 self
._compiler
.addComment(comm
)
1487 def eatMultiLineComment(self
):
1488 isLineClearToStartToken
= self
.isLineClearToStartToken()
1489 endOfFirstLine
= self
.findEOL()
1491 self
.getMultiLineCommentStartToken()
1492 endPos
= startPos
= self
.pos()
1498 if self
.matchMultiLineCommentStartToken():
1499 self
.getMultiLineCommentStartToken()
1501 elif self
.matchMultiLineCommentEndToken():
1502 self
.getMultiLineCommentEndToken()
1507 comm
= self
.readTo(endPos
, start
=startPos
)
1509 if not self
.atEnd():
1510 self
.getMultiLineCommentEndToken()
1512 if (not self
.atEnd()) and self
.setting('gobbleWhitespaceAroundMultiLineComments'):
1513 restOfLine
= self
[self
.pos():self
.findEOL()]
1514 if not restOfLine
.strip(): # WS only to EOL
1515 self
.readToEOL(gobble
=isLineClearToStartToken
)
1517 if isLineClearToStartToken
and (self
.atEnd() or self
.pos() > endOfFirstLine
):
1518 self
._compiler
.handleWSBeforeDirective()
1520 self
._compiler
.addComment(comm
)
1522 def eatPlaceholder(self
):
1523 (expr
, rawPlaceholder
,
1524 lineCol
, cacheTokenParts
,
1525 filterArgs
, isSilentPlaceholder
) = self
.getPlaceholder(
1526 allowCacheTokens
=True, returnEverything
=True)
1528 self
._compiler
.addPlaceholder(
1530 filterArgs
=filterArgs
,
1531 rawPlaceholder
=rawPlaceholder
,
1532 cacheTokenParts
=cacheTokenParts
,
1534 silentMode
=isSilentPlaceholder
)
1539 self
._filterDisabledDirectives
(directiveName
='psp')
1540 self
.getPSPStartToken()
1541 endToken
= self
.setting('PSPEndToken')
1542 startPos
= self
.pos()
1543 while not self
.atEnd():
1544 if self
.peek() == endToken
[0]:
1545 if self
.matchPSPEndToken():
1548 pspString
= self
.readTo(self
.pos(), start
=startPos
).strip()
1549 pspString
= self
._applyExpressionFilters
(pspString
, 'psp', startPos
=startPos
)
1550 self
._compiler
.addPSP(pspString
)
1551 self
.getPSPEndToken()
1553 ## generic directive eat methods
1554 _simpleIndentingDirectives
= '''
1555 else elif for while repeat unless try except finally'''.split()
1556 _simpleExprDirectives
= '''
1557 pass continue stop return yield break
1560 import from'''.split()
1561 _directiveHandlerNames
= {'import':'addImportStatement',
1562 'from':'addImportStatement', }
1563 def eatDirective(self
):
1564 directiveName
= self
.matchDirective()
1565 self
._filterDisabledDirectives
(directiveName
)
1567 for callback
in self
.setting('preparseDirectiveHooks'):
1568 callback(parser
=self
, directiveName
=directiveName
)
1570 # subclasses can override the default behaviours here by providing an
1571 # eater method in self._directiveNamesAndParsers[directiveName]
1572 directiveParser
= self
._directiveNamesAndParsers
.get(directiveName
)
1575 elif directiveName
in self
._simpleIndentingDirectives
:
1576 handlerName
= self
._directiveHandlerNames
.get(directiveName
)
1578 handlerName
= 'add'+directiveName
.capitalize()
1579 handler
= getattr(self
._compiler
, handlerName
)
1580 self
.eatSimpleIndentingDirective(directiveName
, callback
=handler
)
1581 elif directiveName
in self
._simpleExprDirectives
:
1582 handlerName
= self
._directiveHandlerNames
.get(directiveName
)
1584 handlerName
= 'add'+directiveName
.capitalize()
1585 handler
= getattr(self
._compiler
, handlerName
)
1586 if directiveName
in ('silent', 'echo'):
1587 includeDirectiveNameInExpr
= False
1589 includeDirectiveNameInExpr
= True
1590 expr
= self
.eatSimpleExprDirective(
1592 includeDirectiveNameInExpr
=includeDirectiveNameInExpr
)
1595 for callback
in self
.setting('postparseDirectiveHooks'):
1596 callback(parser
=self
, directiveName
=directiveName
)
1598 def _eatRestOfDirectiveTag(self
, isLineClearToStartToken
, endOfFirstLinePos
):
1599 foundComment
= False
1600 if self
.matchCommentStartToken():
1603 if not self
.matchDirective():
1606 self
.eatComment() # this won't gobble the EOL
1610 if not foundComment
and self
.matchDirectiveEndToken():
1611 self
.getDirectiveEndToken()
1612 elif isLineClearToStartToken
and (not self
.atEnd()) and self
.peek() in '\r\n':
1613 # still gobble the EOL if a comment was found.
1614 self
.readToEOL(gobble
=True)
1616 if isLineClearToStartToken
and (self
.atEnd() or self
.pos() > endOfFirstLinePos
):
1617 self
._compiler
.handleWSBeforeDirective()
1619 def _eatToThisEndDirective(self
, directiveName
):
1620 finalPos
= endRawPos
= startPos
= self
.pos()
1621 directiveChar
= self
.setting('directiveStartToken')[0]
1622 isLineClearToStartToken
= False
1623 while not self
.atEnd():
1624 if self
.peek() == directiveChar
:
1625 if self
.matchDirective() == 'end':
1626 endRawPos
= self
.pos()
1627 self
.getDirectiveStartToken()
1628 self
.advance(len('end'))
1629 self
.getWhiteSpace()
1630 if self
.startswith(directiveName
):
1631 if self
.isLineClearToStartToken(endRawPos
):
1632 isLineClearToStartToken
= True
1633 endRawPos
= self
.findBOL(endRawPos
)
1634 self
.advance(len(directiveName
)) # to end of directiveName
1635 self
.getWhiteSpace()
1636 finalPos
= self
.pos()
1639 finalPos
= endRawPos
= self
.pos()
1641 textEaten
= self
.readTo(endRawPos
, start
=startPos
)
1642 self
.setPos(finalPos
)
1644 endOfFirstLinePos
= self
.findEOL()
1646 if self
.matchDirectiveEndToken():
1647 self
.getDirectiveEndToken()
1648 elif isLineClearToStartToken
and (not self
.atEnd()) and self
.peek() in '\r\n':
1649 self
.readToEOL(gobble
=True)
1651 if isLineClearToStartToken
and self
.pos() > endOfFirstLinePos
:
1652 self
._compiler
.handleWSBeforeDirective()
1656 def eatSimpleExprDirective(self
, directiveName
, includeDirectiveNameInExpr
=True):
1658 isLineClearToStartToken
= self
.isLineClearToStartToken()
1659 endOfFirstLine
= self
.findEOL()
1660 self
.getDirectiveStartToken()
1661 if not includeDirectiveNameInExpr
:
1662 self
.advance(len(directiveName
))
1663 startPos
= self
.pos()
1664 expr
= self
.getExpression().strip()
1665 directiveName
= expr
.split()[0]
1666 expr
= self
._applyExpressionFilters
(expr
, directiveName
, startPos
=startPos
)
1667 if directiveName
in self
._closeableDirectives
:
1668 self
.pushToOpenDirectivesStack(directiveName
)
1669 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
1672 def eatSimpleIndentingDirective(self
, directiveName
, callback
,
1673 includeDirectiveNameInExpr
=False):
1675 isLineClearToStartToken
= self
.isLineClearToStartToken()
1676 endOfFirstLinePos
= self
.findEOL()
1677 lineCol
= self
.getRowCol()
1678 self
.getDirectiveStartToken()
1679 if directiveName
not in 'else elif for while try except finally'.split():
1680 self
.advance(len(directiveName
))
1681 startPos
= self
.pos()
1683 self
.getWhiteSpace()
1685 expr
= self
.getExpression(pyTokensToBreakAt
=[':'])
1686 expr
= self
._applyExpressionFilters
(expr
, directiveName
, startPos
=startPos
)
1687 if self
.matchColonForSingleLineShortFormDirective():
1688 self
.advance() # skip over :
1689 if directiveName
in 'else elif except finally'.split():
1690 callback(expr
, dedent
=False, lineCol
=lineCol
)
1692 callback(expr
, lineCol
=lineCol
)
1694 self
.getWhiteSpace(max=1)
1695 self
.parse(breakPoint
=self
.findEOL(gobble
=True))
1696 self
._compiler
.commitStrConst()
1697 self
._compiler
.dedent()
1699 if self
.peek()==':':
1701 self
.getWhiteSpace()
1702 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1703 if directiveName
in self
._closeableDirectives
:
1704 self
.pushToOpenDirectivesStack(directiveName
)
1705 callback(expr
, lineCol
=lineCol
)
1707 def eatEndDirective(self
):
1708 isLineClearToStartToken
= self
.isLineClearToStartToken()
1709 self
.getDirectiveStartToken()
1710 self
.advance(3) # to end of 'end'
1711 self
.getWhiteSpace()
1713 directiveName
= False
1714 for key
in self
._endDirectiveNamesAndHandlers
.keys():
1715 if self
.find(key
, pos
) == pos
:
1718 if not directiveName
:
1719 raise ParseError(self
, msg
='Invalid end directive')
1721 endOfFirstLinePos
= self
.findEOL()
1722 self
.getExpression() # eat in any extra comment-like crap
1723 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1724 if directiveName
in self
._closeableDirectives
:
1725 self
.popFromOpenDirectivesStack(directiveName
)
1727 # subclasses can override the default behaviours here by providing an
1728 # end-directive handler in self._endDirectiveNamesAndHandlers[directiveName]
1729 if self
._endDirectiveNamesAndHandlers
.get(directiveName
):
1730 handler
= self
._endDirectiveNamesAndHandlers
[directiveName
]
1732 elif directiveName
in 'block capture cache call filter errorCatcher'.split():
1734 self
._compiler
.closeBlock()
1735 elif key
== 'capture':
1736 self
._compiler
.endCaptureRegion()
1737 elif key
== 'cache':
1738 self
._compiler
.endCacheRegion()
1740 self
._compiler
.endCallRegion()
1741 elif key
== 'filter':
1742 self
._compiler
.closeFilterBlock()
1743 elif key
== 'errorCatcher':
1744 self
._compiler
.turnErrorCatcherOff()
1745 elif directiveName
in 'while for if try repeat unless'.split():
1746 self
._compiler
.commitStrConst()
1747 self
._compiler
.dedent()
1748 elif directiveName
=='closure':
1749 self
._compiler
.commitStrConst()
1750 self
._compiler
.dedent()
1751 # @@TR: temporary hack of useSearchList
1752 self
.setSetting('useSearchList', self
._useSearchList
_orig
)
1754 ## specific directive eat methods
1756 def eatBreakPoint(self
):
1757 """Tells the parser to stop parsing at this point and completely ignore
1760 This is a debugging tool.
1762 self
.setBreakPoint(self
.pos())
1764 def eatShbang(self
):
1766 self
.getDirectiveStartToken()
1767 self
.advance(len('shBang'))
1768 self
.getWhiteSpace()
1769 startPos
= self
.pos()
1770 shBang
= self
.readToEOL()
1771 shBang
= self
._applyExpressionFilters
(shBang
, 'shbang', startPos
=startPos
)
1772 self
._compiler
.setShBang(shBang
.strip())
1774 def eatEncoding(self
):
1776 self
.getDirectiveStartToken()
1777 self
.advance(len('encoding'))
1778 self
.getWhiteSpace()
1779 startPos
= self
.pos()
1780 encoding
= self
.readToEOL()
1781 encoding
= self
._applyExpressionFilters
(encoding
, 'encoding', startPos
=startPos
)
1782 self
._compiler
.setModuleEncoding(encoding
.strip())
1784 def eatCompiler(self
):
1786 isLineClearToStartToken
= self
.isLineClearToStartToken()
1787 endOfFirstLine
= self
.findEOL()
1788 startPos
= self
.pos()
1789 self
.getDirectiveStartToken()
1790 self
.advance(len('compiler')) # to end of 'compiler'
1791 self
.getWhiteSpace()
1793 startPos
= self
.pos()
1794 settingName
= self
.getIdentifier()
1796 if settingName
.lower() == 'reset':
1797 self
.getExpression() # gobble whitespace & junk
1798 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
1799 self
._initializeSettings
()
1800 self
.configureParser()
1803 self
.getWhiteSpace()
1804 if self
.peek() == '=':
1807 raise ParseError(self
)
1808 valueExpr
= self
.getExpression()
1811 # @@TR: it's unlikely that anyone apply filters would have left this
1812 # directive enabled:
1813 # @@TR: fix up filtering, regardless
1814 self
._applyExpressionFilters
('%s=%r'%(settingName
, valueExpr
),
1815 'compiler', startPos
=startPos
)
1817 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
1819 self
._compiler
.setCompilerSetting(settingName
, valueExpr
)
1822 print >> out
, 'An error occurred while processing the following #compiler directive.'
1823 print >> out
, '-'*80
1824 print >> out
, self
[startPos
:endPos
]
1825 print >> out
, '-'*80
1826 print >> out
, 'Please check the syntax of these settings.'
1827 print >> out
, 'A full Python exception traceback follows.'
1831 def eatCompilerSettings(self
):
1833 isLineClearToStartToken
= self
.isLineClearToStartToken()
1834 endOfFirstLine
= self
.findEOL()
1835 self
.getDirectiveStartToken()
1836 self
.advance(len('compiler-settings')) # to end of 'settings'
1838 keywords
= self
.getTargetVarsList()
1839 self
.getExpression() # gobble any garbage
1841 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
1843 if 'reset' in keywords
:
1844 self
._compiler
._initializeSettings
()
1845 self
.configureParser()
1846 # @@TR: this implies a single-line #compiler-settings directive, and
1847 # thus we should parse forward for an end directive.
1848 # Subject to change in the future
1850 startPos
= self
.pos()
1851 settingsStr
= self
._eatToThisEndDirective
('compiler-settings')
1852 settingsStr
= self
._applyExpressionFilters
(settingsStr
, 'compilerSettings',
1855 self
._compiler
.setCompilerSettings(keywords
=keywords
, settingsStr
=settingsStr
)
1858 print >> out
, 'An error occurred while processing the following compiler settings.'
1859 print >> out
, '-'*80
1860 print >> out
, settingsStr
.strip()
1861 print >> out
, '-'*80
1862 print >> out
, 'Please check the syntax of these settings.'
1863 print >> out
, 'A full Python exception traceback follows.'
1868 isLineClearToStartToken
= self
.isLineClearToStartToken()
1869 endOfFirstLinePos
= self
.findEOL()
1870 startPos
= self
.pos()
1871 self
.getDirectiveStartToken()
1872 self
.advance(len('attr'))
1873 self
.getWhiteSpace()
1874 startPos
= self
.pos()
1875 if self
.matchCheetahVarStart():
1876 self
.getCheetahVarStartToken()
1877 attribName
= self
.getIdentifier()
1878 self
.getWhiteSpace()
1879 self
.getAssignmentOperator()
1880 expr
= self
.getExpression()
1881 expr
= self
._applyExpressionFilters
(expr
, 'attr', startPos
=startPos
)
1882 self
._compiler
.addAttribute(attribName
, expr
)
1883 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1885 def eatDecorator(self
):
1886 isLineClearToStartToken
= self
.isLineClearToStartToken()
1887 endOfFirstLinePos
= self
.findEOL()
1888 startPos
= self
.pos()
1889 self
.getDirectiveStartToken()
1890 #self.advance() # eat @
1891 startPos
= self
.pos()
1892 decoratorExpr
= self
.getExpression()
1893 decoratorExpr
= self
._applyExpressionFilters
(decoratorExpr
, 'decorator', startPos
=startPos
)
1894 self
._compiler
.addDecorator(decoratorExpr
)
1895 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1896 self
.getWhiteSpace()
1898 directiveName
= self
.matchDirective()
1899 if not directiveName
or directiveName
not in ('def', 'block', 'closure'):
1900 raise ParseError(self
, msg
='Expected #def, #block or #closure')
1905 self
._eatDefOrBlock
('def')
1909 startPos
= self
.pos()
1910 methodName
, rawSignature
= self
._eatDefOrBlock
('block')
1911 self
._compiler
._blockMetaData
[methodName
] = {
1913 'lineCol':self
.getRowCol(startPos
),
1916 def eatClosure(self
):
1918 self
._eatDefOrBlock
('closure')
1920 def _eatDefOrBlock(self
, directiveName
):
1922 assert directiveName
in ('def','block','closure')
1923 isLineClearToStartToken
= self
.isLineClearToStartToken()
1924 endOfFirstLinePos
= self
.findEOL()
1925 startPos
= self
.pos()
1926 self
.getDirectiveStartToken()
1927 self
.advance(len(directiveName
))
1928 self
.getWhiteSpace()
1929 if self
.matchCheetahVarStart():
1930 self
.getCheetahVarStartToken()
1931 methodName
= self
.getIdentifier()
1932 self
.getWhiteSpace()
1933 if self
.peek() == '(':
1934 argsList
= self
.getDefArgList()
1935 self
.advance() # past the closing ')'
1936 if argsList
and argsList
[0][0] == 'self':
1941 def includeBlockMarkers():
1942 if self
.setting('includeBlockMarkers'):
1943 startMarker
= self
.setting('blockMarkerStart')
1944 self
._compiler
.addStrConst(startMarker
[0] + methodName
+ startMarker
[1])
1946 # @@TR: fix up filtering
1947 self
._applyExpressionFilters
(self
[startPos
:self
.pos()], 'def', startPos
=startPos
)
1949 if self
.matchColonForSingleLineShortFormDirective():
1950 isNestedDef
= (self
.setting('allowNestedDefScopes')
1951 and [name
for name
in self
._openDirectivesStack
if name
=='def'])
1953 rawSignature
= self
[startPos
:endOfFirstLinePos
]
1954 self
._eatSingleLineDef
(directiveName
=directiveName
,
1955 methodName
=methodName
,
1958 endPos
=endOfFirstLinePos
)
1959 if directiveName
== 'def' and not isNestedDef
:
1960 #@@TR: must come before _eatRestOfDirectiveTag ... for some reason
1961 self
._compiler
.closeDef()
1962 elif directiveName
== 'block':
1963 includeBlockMarkers()
1964 self
._compiler
.closeBlock()
1965 elif directiveName
== 'closure' or isNestedDef
:
1966 self
._compiler
.dedent()
1968 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1970 if self
.peek()==':':
1972 self
.pushToOpenDirectivesStack(directiveName
)
1973 rawSignature
= self
[startPos
:self
.pos()]
1974 self
._eatMultiLineDef
(directiveName
=directiveName
,
1975 methodName
=methodName
,
1978 isLineClearToStartToken
=isLineClearToStartToken
)
1979 if directiveName
== 'block':
1980 includeBlockMarkers()
1982 return methodName
, rawSignature
1984 def _eatMultiLineDef(self
, directiveName
, methodName
, argsList
, startPos
,
1985 isLineClearToStartToken
=False):
1986 # filtered in calling method
1987 self
.getExpression() # slurp up any garbage left at the end
1988 signature
= self
[startPos
:self
.pos()]
1989 endOfFirstLinePos
= self
.findEOL()
1990 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
1991 parserComment
= ('## CHEETAH: generated from ' + signature
+
1992 ' at line %s, col %s' % self
.getRowCol(startPos
)
1995 isNestedDef
= (self
.setting('allowNestedDefScopes')
1996 and len([name
for name
in self
._openDirectivesStack
if name
=='def'])>1)
1997 if directiveName
=='block' or (directiveName
=='def' and not isNestedDef
):
1998 self
._compiler
.startMethodDef(methodName
, argsList
, parserComment
)
2000 self
._useSearchList
_orig
= self
.setting('useSearchList')
2001 self
.setSetting('useSearchList', False)
2002 self
._compiler
.addClosure(methodName
, argsList
, parserComment
)
2006 def _eatSingleLineDef(self
, directiveName
, methodName
, argsList
, startPos
, endPos
):
2007 # filtered in calling method
2008 fullSignature
= self
[startPos
:endPos
]
2009 parserComment
= ('## Generated from ' + fullSignature
+
2010 ' at line %s, col %s' % self
.getRowCol(startPos
)
2012 isNestedDef
= (self
.setting('allowNestedDefScopes')
2013 and [name
for name
in self
._openDirectivesStack
if name
=='def'])
2014 if directiveName
=='block' or (directiveName
=='def' and not isNestedDef
):
2015 self
._compiler
.startMethodDef(methodName
, argsList
, parserComment
)
2017 # @@TR: temporary hack of useSearchList
2018 useSearchList_orig
= self
.setting('useSearchList')
2019 self
.setSetting('useSearchList', False)
2020 self
._compiler
.addClosure(methodName
, argsList
, parserComment
)
2022 self
.getWhiteSpace(max=1)
2023 self
.parse(breakPoint
=endPos
)
2024 if directiveName
=='closure' or isNestedDef
: # @@TR: temporary hack of useSearchList
2025 self
.setSetting('useSearchList', useSearchList_orig
)
2027 def eatExtends(self
):
2029 isLineClearToStartToken
= self
.isLineClearToStartToken()
2030 endOfFirstLine
= self
.findEOL()
2031 self
.getDirectiveStartToken()
2032 self
.advance(len('extends'))
2033 self
.getWhiteSpace()
2034 startPos
= self
.pos()
2035 if self
.setting('allowExpressionsInExtendsDirective'):
2036 baseName
= self
.getExpression()
2038 baseName
= self
.getDottedName()
2040 baseName
= self
._applyExpressionFilters
(baseName
, 'extends', startPos
=startPos
)
2041 self
._compiler
.setBaseClass(baseName
) # in compiler
2042 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2044 def eatImplements(self
):
2046 isLineClearToStartToken
= self
.isLineClearToStartToken()
2047 endOfFirstLine
= self
.findEOL()
2048 self
.getDirectiveStartToken()
2049 self
.advance(len('implements'))
2050 self
.getWhiteSpace()
2051 startPos
= self
.pos()
2052 methodName
= self
.getIdentifier()
2053 if not self
.atEnd() and self
.peek() == '(':
2054 argsList
= self
.getDefArgList()
2055 self
.advance() # past the closing ')'
2056 if argsList
and argsList
[0][0] == 'self':
2061 # @@TR: need to split up filtering of the methodname and the args
2062 #methodName = self._applyExpressionFilters(methodName, 'implements', startPos=startPos)
2063 self
._applyExpressionFilters
(self
[startPos
:self
.pos()], 'implements', startPos
=startPos
)
2065 self
._compiler
.setMainMethodName(methodName
)
2066 self
._compiler
.setMainMethodArgs(argsList
)
2068 self
.getExpression() # throw away and unwanted crap that got added in
2069 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2073 isLineClearToStartToken
= self
.isLineClearToStartToken()
2074 endOfFirstLine
= self
.findEOL()
2075 self
.getDirectiveStartToken()
2076 self
.advance(len('super'))
2077 self
.getWhiteSpace()
2078 startPos
= self
.pos()
2079 if not self
.atEnd() and self
.peek() == '(':
2080 argsList
= self
.getDefArgList()
2081 self
.advance() # past the closing ')'
2082 if argsList
and argsList
[0][0] == 'self':
2087 self
._applyExpressionFilters
(self
[startPos
:self
.pos()], 'super', startPos
=startPos
)
2089 #parserComment = ('## CHEETAH: generated from ' + signature +
2090 # ' at line %s, col %s' % self.getRowCol(startPos)
2093 self
.getExpression() # throw away and unwanted crap that got added in
2094 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2095 self
._compiler
.addSuper(argsList
)
2099 isLineClearToStartToken
= self
.isLineClearToStartToken()
2100 endOfFirstLine
= self
.findEOL()
2101 self
.getDirectiveStartToken()
2103 self
.getWhiteSpace()
2105 if self
.startswith('local'):
2106 self
.getIdentifier()
2107 self
.getWhiteSpace()
2108 elif self
.startswith('global'):
2109 self
.getIdentifier()
2110 self
.getWhiteSpace()
2112 elif self
.startswith('module'):
2113 self
.getIdentifier()
2114 self
.getWhiteSpace()
2117 startsWithDollar
= self
.matchCheetahVarStart()
2118 startPos
= self
.pos()
2119 LVALUE
= self
.getExpression(pyTokensToBreakAt
=assignmentOps
, useNameMapper
=False).strip()
2120 OP
= self
.getAssignmentOperator()
2121 RVALUE
= self
.getExpression()
2122 expr
= LVALUE
+ ' ' + OP
+ ' ' + RVALUE
.strip()
2124 expr
= self
._applyExpressionFilters
(expr
, 'set', startPos
=startPos
)
2125 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2127 class Components
: pass # used for 'set global'
2128 exprComponents
= Components()
2129 exprComponents
.LVALUE
= LVALUE
2130 exprComponents
.OP
= OP
2131 exprComponents
.RVALUE
= RVALUE
2132 self
._compiler
.addSet(expr
, exprComponents
, style
)
2135 if self
.isLineClearToStartToken():
2136 self
._compiler
.handleWSBeforeDirective()
2137 self
._compiler
.commitStrConst()
2138 self
.readToEOL(gobble
=True)
2140 def eatEOLSlurpToken(self
):
2141 if self
.isLineClearToStartToken():
2142 self
._compiler
.handleWSBeforeDirective()
2143 self
._compiler
.commitStrConst()
2144 self
.readToEOL(gobble
=True)
2147 isLineClearToStartToken
= self
.isLineClearToStartToken()
2148 endOfFirstLinePos
= self
.findEOL()
2149 self
.getDirectiveStartToken()
2150 self
.advance(len('raw'))
2151 self
.getWhiteSpace()
2152 if self
.matchColonForSingleLineShortFormDirective():
2153 self
.advance() # skip over :
2154 self
.getWhiteSpace(max=1)
2155 rawBlock
= self
.readToEOL(gobble
=False)
2157 if self
.peek()==':':
2159 self
.getWhiteSpace()
2160 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2161 rawBlock
= self
._eatToThisEndDirective
('raw')
2162 self
._compiler
.addRawText(rawBlock
)
2164 def eatInclude(self
):
2166 isLineClearToStartToken
= self
.isLineClearToStartToken()
2167 endOfFirstLinePos
= self
.findEOL()
2168 self
.getDirectiveStartToken()
2169 self
.advance(len('include'))
2171 self
.getWhiteSpace()
2172 includeFrom
= 'file'
2174 if self
.startswith('raw'):
2178 self
.getWhiteSpace()
2179 if self
.startswith('source'):
2180 self
.advance(len('source'))
2182 self
.getWhiteSpace()
2183 if not self
.peek() == '=':
2184 raise ParseError(self
)
2186 startPos
= self
.pos()
2187 sourceExpr
= self
.getExpression()
2188 sourceExpr
= self
._applyExpressionFilters
(sourceExpr
, 'include', startPos
=startPos
)
2189 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2190 self
._compiler
.addInclude(sourceExpr
, includeFrom
, isRaw
)
2193 def eatDefMacro(self
):
2194 # @@TR: not filtered yet
2195 isLineClearToStartToken
= self
.isLineClearToStartToken()
2196 endOfFirstLinePos
= self
.findEOL()
2197 self
.getDirectiveStartToken()
2198 self
.advance(len('defmacro'))
2200 self
.getWhiteSpace()
2201 if self
.matchCheetahVarStart():
2202 self
.getCheetahVarStartToken()
2203 macroName
= self
.getIdentifier()
2204 self
.getWhiteSpace()
2205 if self
.peek() == '(':
2206 argsList
= self
.getDefArgList(useNameMapper
=False)
2207 self
.advance() # past the closing ')'
2208 if argsList
and argsList
[0][0] == 'self':
2213 assert not self
._directiveNamesAndParsers
.has_key(macroName
)
2214 argsList
.insert(0, ('src',None))
2215 argsList
.append(('parser','None'))
2216 argsList
.append(('macros','None'))
2217 argsList
.append(('compilerSettings','None'))
2218 argsList
.append(('isShortForm','None'))
2219 argsList
.append(('EOLCharsInShortForm','None'))
2220 argsList
.append(('startPos','None'))
2221 argsList
.append(('endPos','None'))
2223 if self
.matchColonForSingleLineShortFormDirective():
2224 self
.advance() # skip over :
2225 self
.getWhiteSpace(max=1)
2226 macroSrc
= self
.readToEOL(gobble
=False)
2227 self
.readToEOL(gobble
=True)
2229 if self
.peek()==':':
2231 self
.getWhiteSpace()
2232 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2233 macroSrc
= self
._eatToThisEndDirective
('defmacro')
2236 normalizedMacroSrc
= ''.join(
2237 ['%def callMacro('+','.join([defv
and '%s=%s'%(n
,defv
) or n
2238 for n
,defv
in argsList
])
2244 from Cheetah
.Template
import Template
2245 templateAPIClass
= self
.setting('templateAPIClassForDefMacro', default
=Template
)
2246 compilerSettings
= self
.setting('compilerSettingsForDefMacro', default
={})
2247 searchListForMacros
= self
.setting('searchListForDefMacro', default
=[])
2248 searchListForMacros
= list(searchListForMacros
) # copy to avoid mutation bugs
2249 searchListForMacros
.append({'macros':self
._macros
,
2251 'compilerSettings':self
.settings(),
2254 templateAPIClass
._updateSettingsWithPreprocessTokens
(
2255 compilerSettings
, placeholderToken
='@', directiveToken
='%')
2256 macroTemplateClass
= templateAPIClass
.compile(source
=normalizedMacroSrc
,
2257 compilerSettings
=compilerSettings
)
2258 #print normalizedMacroSrc
2259 #t = macroTemplateClass()
2260 #print t.callMacro('src')
2261 #print t.generatedClassCode()
2263 class MacroDetails
: pass
2264 macroDetails
= MacroDetails()
2265 macroDetails
.macroSrc
= macroSrc
2266 macroDetails
.argsList
= argsList
2267 macroDetails
.template
= macroTemplateClass(searchList
=searchListForMacros
)
2269 self
._macroDetails
[macroName
] = macroDetails
2270 self
._macros
[macroName
] = macroDetails
.template
.callMacro
2271 self
._directiveNamesAndParsers
[macroName
] = self
.eatMacroCall
2273 def eatMacroCall(self
):
2274 isLineClearToStartToken
= self
.isLineClearToStartToken()
2275 endOfFirstLinePos
= self
.findEOL()
2276 startPos
= self
.pos()
2277 self
.getDirectiveStartToken()
2278 macroName
= self
.getIdentifier()
2279 macro
= self
._macros
[macroName
]
2280 if hasattr(macro
, 'parse'):
2281 return macro
.parse(parser
=self
, startPos
=startPos
)
2283 if hasattr(macro
, 'parseArgs'):
2284 args
= macro
.parseArgs(parser
=self
, startPos
=startPos
)
2286 self
.getWhiteSpace()
2287 args
= self
.getExpression(useNameMapper
=False,
2288 pyTokensToBreakAt
=[':']).strip()
2290 if self
.matchColonForSingleLineShortFormDirective():
2292 self
.advance() # skip over :
2293 self
.getWhiteSpace(max=1)
2294 srcBlock
= self
.readToEOL(gobble
=False)
2295 EOLCharsInShortForm
= self
.readToEOL(gobble
=True)
2296 #self.readToEOL(gobble=False)
2299 if self
.peek()==':':
2301 self
.getWhiteSpace()
2302 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2303 srcBlock
= self
._eatToThisEndDirective
(macroName
)
2306 if hasattr(macro
, 'convertArgStrToDict'):
2307 kwArgs
= macro
.convertArgStrToDict(args
, parser
=self
, startPos
=startPos
)
2309 def getArgs(*pargs
, **kws
):
2311 exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
2313 assert not kwArgs
.has_key('src')
2314 kwArgs
['src'] = srcBlock
2316 if type(macro
)==new
.instancemethod
:
2317 co
= macro
.im_func
.func_code
2318 elif (hasattr(macro
, '__call__')
2319 and hasattr(macro
.__call
__, 'im_func')):
2320 co
= macro
.__call
__.im_func
.func_code
2322 co
= macro
.func_code
2323 availableKwArgs
= inspect
.getargs(co
)[0]
2325 if 'parser' in availableKwArgs
:
2326 kwArgs
['parser'] = self
2327 if 'macros' in availableKwArgs
:
2328 kwArgs
['macros'] = self
._macros
2329 if 'compilerSettings' in availableKwArgs
:
2330 kwArgs
['compilerSettings'] = self
.settings()
2331 if 'isShortForm' in availableKwArgs
:
2332 kwArgs
['isShortForm'] = isShortForm
2333 if isShortForm
and 'EOLCharsInShortForm' in availableKwArgs
:
2334 kwArgs
['EOLCharsInShortForm'] = EOLCharsInShortForm
2336 if 'startPos' in availableKwArgs
:
2337 kwArgs
['startPos'] = startPos
2338 if 'endPos' in availableKwArgs
:
2339 kwArgs
['endPos'] = self
.pos()
2341 srcFromMacroOutput
= macro(**kwArgs
)
2343 origParseSrc
= self
._src
2344 origBreakPoint
= self
.breakPoint()
2345 origPos
= self
.pos()
2346 # add a comment to the output about the macro src that is being parsed
2347 # or add a comment prefix to all the comments added by the compiler
2348 self
._src
= srcFromMacroOutput
2350 self
.setBreakPoint(len(srcFromMacroOutput
))
2352 self
.parse(assertEmptyStack
=False)
2354 self
._src
= origParseSrc
2355 self
.setBreakPoint(origBreakPoint
)
2356 self
.setPos(origPos
)
2359 #self._compiler.addRawText('end')
2362 isLineClearToStartToken
= self
.isLineClearToStartToken()
2363 endOfFirstLinePos
= self
.findEOL()
2364 lineCol
= self
.getRowCol()
2365 self
.getDirectiveStartToken()
2366 self
.advance(len('cache'))
2368 startPos
= self
.pos()
2369 argList
= self
.getDefArgList(useNameMapper
=True)
2370 argList
= self
._applyExpressionFilters
(argList
, 'cache', startPos
=startPos
)
2373 cacheInfo
= self
._compiler
.genCacheInfoFromArgList(argList
)
2374 self
._compiler
.startCacheRegion(cacheInfo
, lineCol
)
2376 if self
.matchColonForSingleLineShortFormDirective():
2377 self
.advance() # skip over :
2378 self
.getWhiteSpace(max=1)
2380 self
.parse(breakPoint
=self
.findEOL(gobble
=True))
2381 self
._compiler
.endCacheRegion()
2383 if self
.peek()==':':
2385 self
.getWhiteSpace()
2386 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2387 self
.pushToOpenDirectivesStack('cache')
2391 # @@TR: need to enable single line version of this
2392 isLineClearToStartToken
= self
.isLineClearToStartToken()
2393 endOfFirstLinePos
= self
.findEOL()
2394 lineCol
= self
.getRowCol()
2395 self
.getDirectiveStartToken()
2396 self
.advance(len('call'))
2397 startPos
= self
.pos()
2399 useAutocallingOrig
= self
.setting('useAutocalling')
2400 self
.setSetting('useAutocalling', False)
2401 self
.getWhiteSpace()
2402 if self
.matchCheetahVarStart():
2403 functionName
= self
.getCheetahVar()
2405 functionName
= self
.getCheetahVar(plain
=True, skipStartToken
=True)
2406 self
.setSetting('useAutocalling', useAutocallingOrig
)
2407 # @@TR: fix up filtering
2408 self
._applyExpressionFilters
(self
[startPos
:self
.pos()], 'call', startPos
=startPos
)
2410 self
.getWhiteSpace()
2411 args
= self
.getExpression(pyTokensToBreakAt
=[':']).strip()
2412 if self
.matchColonForSingleLineShortFormDirective():
2413 self
.advance() # skip over :
2414 self
._compiler
.startCallRegion(functionName
, args
, lineCol
)
2415 self
.getWhiteSpace(max=1)
2416 self
.parse(breakPoint
=self
.findEOL(gobble
=False))
2417 self
._compiler
.endCallRegion()
2419 if self
.peek()==':':
2421 self
.getWhiteSpace()
2422 self
.pushToOpenDirectivesStack("call")
2423 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2424 self
._compiler
.startCallRegion(functionName
, args
, lineCol
)
2426 def eatCallArg(self
):
2427 isLineClearToStartToken
= self
.isLineClearToStartToken()
2428 endOfFirstLinePos
= self
.findEOL()
2429 lineCol
= self
.getRowCol()
2430 self
.getDirectiveStartToken()
2432 self
.advance(len('arg'))
2433 startPos
= self
.pos()
2434 self
.getWhiteSpace()
2435 argName
= self
.getIdentifier()
2436 self
.getWhiteSpace()
2437 argName
= self
._applyExpressionFilters
(argName
, 'arg', startPos
=startPos
)
2438 self
._compiler
.setCallArg(argName
, lineCol
)
2439 if self
.peek() == ':':
2442 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2444 def eatFilter(self
):
2445 isLineClearToStartToken
= self
.isLineClearToStartToken()
2446 endOfFirstLinePos
= self
.findEOL()
2448 self
.getDirectiveStartToken()
2449 self
.advance(len('filter'))
2450 self
.getWhiteSpace()
2451 startPos
= self
.pos()
2452 if self
.matchCheetahVarStart():
2454 theFilter
= self
.getExpression(pyTokensToBreakAt
=[':'])
2457 theFilter
= self
.getIdentifier()
2458 self
.getWhiteSpace()
2459 theFilter
= self
._applyExpressionFilters
(theFilter
, 'filter', startPos
=startPos
)
2461 if self
.matchColonForSingleLineShortFormDirective():
2462 self
.advance() # skip over :
2463 self
.getWhiteSpace(max=1)
2464 self
._compiler
.setFilter(theFilter
, isKlass
)
2465 self
.parse(breakPoint
=self
.findEOL(gobble
=False))
2466 self
._compiler
.closeFilterBlock()
2468 if self
.peek()==':':
2470 self
.getWhiteSpace()
2471 self
.pushToOpenDirectivesStack("filter")
2472 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2473 self
._compiler
.setFilter(theFilter
, isKlass
)
2475 def eatErrorCatcher(self
):
2476 isLineClearToStartToken
= self
.isLineClearToStartToken()
2477 endOfFirstLinePos
= self
.findEOL()
2478 self
.getDirectiveStartToken()
2479 self
.advance(len('errorCatcher'))
2480 self
.getWhiteSpace()
2481 startPos
= self
.pos()
2482 errorCatcherName
= self
.getIdentifier()
2483 errorCatcherName
= self
._applyExpressionFilters
(
2484 errorCatcherName
, 'errorcatcher', startPos
=startPos
)
2485 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2486 self
._compiler
.setErrorCatcher(errorCatcherName
)
2488 def eatCapture(self
):
2489 # @@TR: this could be refactored to use the code in eatSimpleIndentingDirective
2491 isLineClearToStartToken
= self
.isLineClearToStartToken()
2492 endOfFirstLinePos
= self
.findEOL()
2493 lineCol
= self
.getRowCol()
2495 self
.getDirectiveStartToken()
2496 self
.advance(len('capture'))
2497 startPos
= self
.pos()
2498 self
.getWhiteSpace()
2500 expr
= self
.getExpression(pyTokensToBreakAt
=[':'])
2501 expr
= self
._applyExpressionFilters
(expr
, 'capture', startPos
=startPos
)
2502 if self
.matchColonForSingleLineShortFormDirective():
2503 self
.advance() # skip over :
2504 self
._compiler
.startCaptureRegion(assignTo
=expr
, lineCol
=lineCol
)
2505 self
.getWhiteSpace(max=1)
2506 self
.parse(breakPoint
=self
.findEOL(gobble
=False))
2507 self
._compiler
.endCaptureRegion()
2509 if self
.peek()==':':
2511 self
.getWhiteSpace()
2512 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLinePos
)
2513 self
.pushToOpenDirectivesStack("capture")
2514 self
._compiler
.startCaptureRegion(assignTo
=expr
, lineCol
=lineCol
)
2519 isLineClearToStartToken
= self
.isLineClearToStartToken()
2520 endOfFirstLine
= self
.findEOL()
2521 lineCol
= self
.getRowCol()
2522 self
.getDirectiveStartToken()
2523 startPos
= self
.pos()
2525 expressionParts
= self
.getExpressionParts(pyTokensToBreakAt
=[':'])
2526 expr
= ''.join(expressionParts
).strip()
2527 expr
= self
._applyExpressionFilters
(expr
, 'if', startPos
=startPos
)
2529 isTernaryExpr
= ('then' in expressionParts
and 'else' in expressionParts
)
2534 currentExpr
= conditionExpr
2535 for part
in expressionParts
:
2536 if part
.strip()=='then':
2537 currentExpr
= trueExpr
2538 elif part
.strip()=='else':
2539 currentExpr
= falseExpr
2541 currentExpr
.append(part
)
2543 conditionExpr
= ''.join(conditionExpr
)
2544 trueExpr
= ''.join(trueExpr
)
2545 falseExpr
= ''.join(falseExpr
)
2546 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2547 self
._compiler
.addTernaryExpr(conditionExpr
, trueExpr
, falseExpr
, lineCol
=lineCol
)
2548 elif self
.matchColonForSingleLineShortFormDirective():
2549 self
.advance() # skip over :
2550 self
._compiler
.addIf(expr
, lineCol
=lineCol
)
2551 self
.getWhiteSpace(max=1)
2552 self
.parse(breakPoint
=self
.findEOL(gobble
=True))
2553 self
._compiler
.commitStrConst()
2554 self
._compiler
.dedent()
2556 if self
.peek()==':':
2558 self
.getWhiteSpace()
2559 self
._eatRestOfDirectiveTag
(isLineClearToStartToken
, endOfFirstLine
)
2560 self
.pushToOpenDirectivesStack('if')
2561 self
._compiler
.addIf(expr
, lineCol
=lineCol
)
2563 ## end directive handlers
2564 def handleEndDef(self
):
2565 isNestedDef
= (self
.setting('allowNestedDefScopes')
2566 and [name
for name
in self
._openDirectivesStack
if name
=='def'])
2568 self
._compiler
.closeDef()
2570 # @@TR: temporary hack of useSearchList
2571 self
.setSetting('useSearchList', self
._useSearchList
_orig
)
2572 self
._compiler
.commitStrConst()
2573 self
._compiler
.dedent()
2576 def pushToOpenDirectivesStack(self
, directiveName
):
2577 assert directiveName
in self
._closeableDirectives
2578 self
._openDirectivesStack
.append(directiveName
)
2580 def popFromOpenDirectivesStack(self
, directiveName
):
2581 if not self
._openDirectivesStack
:
2582 raise ParseError(self
, msg
="#end found, but nothing to end")
2584 if self
._openDirectivesStack
[-1] == directiveName
:
2585 del self
._openDirectivesStack
[-1]
2587 raise ParseError(self
, msg
="#end %s found, expected #end %s" %(
2588 directiveName
, self
._openDirectivesStack
[-1]))
2590 def assertEmptyOpenDirectivesStack(self
):
2591 if self
._openDirectivesStack
:
2593 "Some #directives are missing their corresponding #end ___ tag: %s" %(
2594 ', '.join(self
._openDirectivesStack
)))
2595 raise ParseError(self
, msg
=errorMsg
)
2597 ##################################################
2598 ## Make an alias to export
2599 Parser
= _HighLevelParser