root/galaxy-central/eggs/Cheetah-2.2.2-py2.6-macosx-10.6-universal-ucs2.egg/Cheetah/Parser.py @ 3

リビジョン 3, 100.9 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1# $Id: Parser.py,v 1.137 2008/03/10 05:25:13 tavis_rudd Exp $
2"""Parser classes for Cheetah's Compiler
3
4Classes:
5  ParseError( Exception )
6  _LowLevelParser( Cheetah.SourceReader.SourceReader ), basically a lexer
7  _HighLevelParser( _LowLevelParser )
8  Parser === _HighLevelParser (an alias)
9
10Meta-Data
11================================================================================
12Author: Tavis Rudd <tavis@damnsimple.com>
13Version: $Revision: 1.137 $
14Start Date: 2001/08/01
15Last Revision Date: $Date: 2008/03/10 05:25:13 $
16"""
17__author__ = "Tavis Rudd <tavis@damnsimple.com>"
18__revision__ = "$Revision: 1.137 $"[11:-2]
19
20import os
21import sys
22import re
23from re import DOTALL, MULTILINE
24from types import StringType, ListType, TupleType, ClassType, TypeType
25import time
26from tokenize import pseudoprog
27import inspect
28import new
29import traceback
30
31from Cheetah.SourceReader import SourceReader
32from Cheetah import Filters
33from Cheetah import ErrorCatchers
34from Cheetah.Unspecified import Unspecified
35from Cheetah.Macros.I18n import I18n
36
37# re tools
38_regexCache = {}
39def cachedRegex(pattern):
40    if pattern not in _regexCache:
41        _regexCache[pattern] = re.compile(pattern)
42    return _regexCache[pattern]
43
44def escapeRegexChars(txt,
45                     escapeRE=re.compile(r'([\$\^\*\+\.\?\{\}\[\]\(\)\|\\])')):
46   
47    """Return a txt with all special regular expressions chars escaped."""
48   
49    return escapeRE.sub(r'\\\1' , txt)
50
51def group(*choices): return '(' + '|'.join(choices) + ')'
52def nongroup(*choices): return '(?:' + '|'.join(choices) + ')'
53def namedGroup(name, *choices): return '(P:<' + name +'>' + '|'.join(choices) + ')'
54def any(*choices): return apply(group, choices) + '*'
55def maybe(*choices): return apply(group, choices) + '?'
56
57##################################################
58## CONSTANTS & GLOBALS ##
59
60NO_CACHE = 0
61STATIC_CACHE = 1
62REFRESH_CACHE = 2
63
64SET_LOCAL = 0
65SET_GLOBAL = 1
66SET_MODULE = 2
67
68##################################################
69## Tokens for the parser ##
70
71#generic
72identchars = "abcdefghijklmnopqrstuvwxyz" \
73            "ABCDEFGHIJKLMNOPQRSTUVWXYZ_"
74namechars = identchars + "0123456789"
75
76#operators
77powerOp = '**'
78unaryArithOps = ('+', '-', '~')
79binaryArithOps = ('+', '-', '/', '//','%')
80shiftOps = ('>>','<<')
81bitwiseOps = ('&','|','^')
82assignOp = '='
83augAssignOps = ('+=','-=','/=','*=', '**=','^=','%=',
84          '>>=','<<=','&=','|=', )
85assignmentOps = (assignOp,) + augAssignOps
86
87compOps = ('<','>','==','!=','<=','>=', '<>', 'is', 'in',)
88booleanOps = ('and','or','not')
89operators = (powerOp,) + unaryArithOps + binaryArithOps \
90            + shiftOps + bitwiseOps + assignmentOps \
91            + compOps + booleanOps
92
93delimeters = ('(',')','{','}','[',']',
94              ',','.',':',';','=','`') + augAssignOps
95
96
97keywords = ('and',       'del',       'for',       'is',        'raise',
98            'assert',    'elif',      'from',      'lambda',    'return',
99            'break',     'else',      'global',    'not',       'try',   
100            'class',     'except',    'if',        'or',        'while',
101            'continue',  'exec',      'import',    'pass',
102            'def',       'finally',   'in',        'print',
103            )
104
105single3 = "'''"
106double3 = '"""'
107
108tripleQuotedStringStarts =  ("'''", '"""',
109                             "r'''", 'r"""', "R'''", 'R"""',
110                             "u'''", 'u"""', "U'''", 'U"""',
111                             "ur'''", 'ur"""', "Ur'''", 'Ur"""',
112                             "uR'''", 'uR"""', "UR'''", 'UR"""')
113
114tripleQuotedStringPairs = {"'''": single3, '"""': double3,
115                           "r'''": single3, 'r"""': double3,
116                           "u'''": single3, 'u"""': double3,
117                           "ur'''": single3, 'ur"""': double3,
118                           "R'''": single3, 'R"""': double3,
119                           "U'''": single3, 'U"""': double3,
120                           "uR'''": single3, 'uR"""': double3,
121                           "Ur'''": single3, 'Ur"""': double3,
122                           "UR'''": single3, 'UR"""': double3,
123                           }
124
125closurePairs= {')':'(',']':'[','}':'{'}
126closurePairsRev= {'(':')','[':']','{':'}'}
127
128##################################################
129## Regex chunks for the parser ##
130
131tripleQuotedStringREs = {}
132def makeTripleQuoteRe(start, end):
133    start = escapeRegexChars(start)
134    end = escapeRegexChars(end)
135    return re.compile(r'(?:' + start + r').*?' + r'(?:' + end + r')', re.DOTALL)
136
137for start, end in tripleQuotedStringPairs.items():
138    tripleQuotedStringREs[start] = makeTripleQuoteRe(start, end)
139
140WS = r'[ \f\t]*' 
141EOL = r'\r\n|\n|\r'
142EOLZ = EOL + r'|\Z'
143escCharLookBehind = nongroup(r'(?<=\A)',r'(?<!\\)')
144nameCharLookAhead = r'(?=[A-Za-z_])'
145identRE=re.compile(r'[a-zA-Z_][a-zA-Z_0-9]*')
146EOLre=re.compile(r'(?:\r\n|\r|\n)')
147
148specialVarRE=re.compile(r'([a-zA-z_]+)@') # for matching specialVar comments
149# e.g. ##author@ Tavis Rudd
150
151unicodeDirectiveRE = re.compile(
152    r'(?:^|\r\n|\r|\n)\s*#\s{0,5}unicode[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re.MULTILINE)
153encodingDirectiveRE = re.compile(
154    r'(?:^|\r\n|\r|\n)\s*#\s{0,5}encoding[:\s]*([-\w.]*)\s*(?:\r\n|\r|\n)', re.MULTILINE)
155
156escapedNewlineRE = re.compile(r'(?<!\\)\\n')
157
158directiveNamesAndParsers = {
159    # importing and inheritance
160    'import':None,
161    'from':None,
162    'extends': 'eatExtends',
163    'implements': 'eatImplements',
164    'super': 'eatSuper',
165
166    # output, filtering, and caching
167    'slurp': 'eatSlurp',
168    'raw': 'eatRaw',
169    'include': 'eatInclude',
170    'cache': 'eatCache',
171    'filter': 'eatFilter',
172    'echo': None,
173    'silent': None,
174    'transform' : 'eatTransform',
175   
176    'call': 'eatCall',
177    'arg': 'eatCallArg',
178   
179    'capture': 'eatCapture',
180   
181    # declaration, assignment, and deletion
182    'attr': 'eatAttr',
183    'def': 'eatDef',
184    'block': 'eatBlock',
185    '@': 'eatDecorator',
186    'defmacro': 'eatDefMacro',
187   
188    'closure': 'eatClosure',
189   
190    'set': 'eatSet',
191    'del': None,
192   
193    # flow control
194    'if': 'eatIf',
195    'while': None,
196    'for': None,
197    'else': None,
198    'elif': None,
199    'pass': None,
200    'break': None,
201    'continue': None,
202    'stop': None,
203    'return': None,
204    'yield': None,
205   
206    # little wrappers
207    'repeat': None,
208    'unless': None,
209   
210    # error handling
211    'assert': None,
212    'raise': None,
213    'try': None,
214    'except': None,
215    'finally': None,
216    'errorCatcher': 'eatErrorCatcher',
217   
218    # intructions to the parser and compiler
219    'breakpoint': 'eatBreakPoint',
220    'compiler': 'eatCompiler',
221    'compiler-settings': 'eatCompilerSettings',
222   
223    # misc
224    'shBang': 'eatShbang',
225    'encoding': 'eatEncoding',
226   
227    'end': 'eatEndDirective',
228    }
229
230endDirectiveNamesAndHandlers = {
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
237    'filter': None,
238    'errorCatcher':None,           
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
245    }
246
247##################################################
248## CLASSES ##
249
250# @@TR: SyntaxError doesn't call exception.__str__ for some reason!
251#class ParseError(SyntaxError):
252class ParseError(ValueError):
253    def __init__(self, stream, msg='Invalid Syntax', extMsg='', lineno=None, col=None):
254        self.stream = stream
255        if stream.pos() >= len(stream):
256            stream.setPos(len(stream) -1)
257        self.msg = msg
258        self.extMsg = extMsg
259        self.lineno = lineno
260        self.col = col
261       
262    def __str__(self):
263        return self.report()
264
265    def report(self):
266        stream = self.stream
267        if stream.filename():
268            f = " in file %s" % stream.filename()
269        else:
270            f = ''
271        report = ''
272        if self.lineno:
273            lineno = self.lineno
274            row, col, line = (lineno, (self.col or 0),
275                              self.stream.splitlines()[lineno-1])
276        else:
277            row, col, line = self.stream.getRowColLine()
278
279        ## get the surrounding lines
280        lines = stream.splitlines()
281        prevLines = []                  # (rowNum, content)
282        for i in range(1,4):
283            if row-1-i <=0:
284                break
285            prevLines.append( (row-i,lines[row-1-i]) )
286
287        nextLines = []                  # (rowNum, content)
288        for i in range(1,4):
289            if not row-1+i < len(lines):
290                break
291            nextLines.append( (row+i,lines[row-1+i]) )
292        nextLines.reverse()
293       
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'
299        while prevLines:
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"
304       
305        while nextLines:
306            lineInfo = nextLines.pop()
307            report += "%(row)-4d|%(line)s\n"% {'row':lineInfo[0], 'line':lineInfo[1]}
308        ## add the extra msg
309        if self.extMsg:
310            report += self.extMsg + '\n'
311           
312        return report
313
314class ForbiddenSyntax(ParseError): pass
315class ForbiddenExpression(ForbiddenSyntax): pass
316class ForbiddenDirective(ForbiddenSyntax): pass
317
318class CheetahVariable:
319    def __init__(self, nameChunks, useNameMapper=True, cacheToken=None,
320                 rawSource=None):
321        self.nameChunks = nameChunks
322        self.useNameMapper = useNameMapper
323        self.cacheToken = cacheToken
324        self.rawSource = rawSource
325       
326class Placeholder(CheetahVariable): pass
327
328class ArgList:
329    """Used by _LowLevelParser.getArgList()"""
330
331    def __init__(self):
332        self.argNames = []
333        self.defVals = []
334        self.i = 0
335
336    def addArgName(self, name):
337        self.argNames.append( name )
338        self.defVals.append( None )
339
340    def next(self):
341        self.i += 1
342
343    def addToDefVal(self, token):
344        i = self.i
345        if self.defVals[i] == None:
346            self.defVals[i] = ''
347        self.defVals[i] += token
348   
349    def merge(self):
350        defVals = self.defVals
351        for i in range(len(defVals)):
352            if type(defVals[i]) == StringType:
353                defVals[i] = defVals[i].strip()
354               
355        return map(None, [i.strip() for i in self.argNames], defVals)
356   
357    def __str__(self):
358        return str(self.merge())
359   
360class _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
363    state management.
364    """
365
366    _settingsManager = None
367
368    def setSettingsManager(self, settingsManager):
369        self._settingsManager = settingsManager
370       
371    def setting(self, key, default=Unspecified):
372        if default is Unspecified:
373            return self._settingsManager.setting(key)
374        else:
375            return self._settingsManager.setting(key, default=default)
376       
377    def setSetting(self, key, val):
378        self._settingsManager.setSetting(key, val)
379
380    def settings(self):
381        return self._settingsManager.settings()
382       
383    def updateSettings(self, settings):
384        self._settingsManager.updateSettings(settings)
385
386    def _initializeSettings(self):
387        self._settingsManager._initializeSettings()
388   
389    def configureParser(self):
390        """Is called by the Compiler instance after the parser has had a
391        settingsManager assigned with self.setSettingsManager()
392        """
393        self._makeCheetahVarREs()
394        self._makeCommentREs()
395        self._makeDirectiveREs()
396        self._makePspREs()
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,
408            self.matchDirective,
409            self.matchPSPStartToken,
410            self.matchEOLSlurpToken,
411            ]
412
413    ## regex setup ##
414
415    def _makeCheetahVarREs(self):
416       
417        """Setup the regexs for Cheetah $var parsing."""
418
419        num = r'[0-9\.]+'
420        interval =   (r'(?P<interval>' +
421                      num + r's|' +
422                      num + r'm|' +
423                      num + r'h|' +
424                      num + r'd|' +
425                      num + r'w|' +
426                      num + ')'
427                      )
428   
429        cacheToken = (r'(?:' +
430                      r'(?P<REFRESH_CACHE>\*' + interval + '\*)'+
431                      '|' +
432                      r'(?P<STATIC_CACHE>\*)' +
433                      '|' +                     
434                      r'(?P<NO_CACHE>)' +
435                      ')')
436        self.cacheTokenRE = cachedRegex(cacheToken)
437
438        silentPlaceholderToken = (r'(?:' +
439                                  r'(?P<SILENT>' +escapeRegexChars('!')+')'+
440                                  '|' +
441                                  r'(?P<NOT_SILENT>)' +
442                                  ')')
443        self.silentPlaceholderTokenRE = cachedRegex(silentPlaceholderToken)
444       
445        self.cheetahVarStartRE = cachedRegex(
446            escCharLookBehind +
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
451            r'(?=[A-Za-z_])')
452        validCharsLookAhead = r'(?=[A-Za-z_\*!\{\(\[])'
453        self.cheetahVarStartToken = self.setting('cheetahVarStartToken')
454        self.cheetahVarStartTokenRE = cachedRegex(
455            escCharLookBehind +
456            escapeRegexChars(self.setting('cheetahVarStartToken'))
457            +validCharsLookAhead
458            )
459
460        self.cheetahVarInExpressionStartTokenRE = cachedRegex(
461            escapeRegexChars(self.setting('cheetahVarStartToken'))
462            +r'(?=[A-Za-z_])'
463            )
464
465        self.expressionPlaceholderStartRE = cachedRegex(
466            escCharLookBehind +
467            r'(?P<startToken>' + escapeRegexChars(self.setting('cheetahVarStartToken')) + ')' +
468            r'(?P<cacheToken>' + cacheToken + ')' +
469            #r'\[[ \t\f]*'
470            r'(?:\{|\(|\[)[ \t\f]*'
471            + r'(?=[^\)\}\]])'
472            )
473
474        if self.setting('EOLSlurpToken'):
475            self.EOLSlurpRE = cachedRegex(
476                escapeRegexChars(self.setting('EOLSlurpToken'))
477                + r'[ \t\f]*'
478                + r'(?:'+EOL+')'
479                )
480        else:
481            self.EOLSlurpRE = None
482
483
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)
488        del startTokenEsc
489       
490        startTokenEsc = escapeRegexChars(
491            self.setting('multiLineCommentStartToken'))
492        endTokenEsc = escapeRegexChars(
493            self.setting('multiLineCommentEndToken'))
494        self.multiLineCommentTokenStartRE = cachedRegex(escCharLookBehind +
495                                                       startTokenEsc)
496        self.multiLineCommentEndTokenRE = cachedRegex(escCharLookBehind +
497                                                     endTokenEsc)
498       
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)
512
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)
521
522
523    def isLineClearToStartToken(self, pos=None):
524        return self.isLineClearToPos(pos)
525
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
532            self.matchDirective
533            self.matchPSPStartToken
534            self.matchEOLSlurpToken
535
536        Returns None if no match.
537        """
538        match = None
539        if self.peek() in self._possibleNonStrConstantChars:
540            for matcher in self._nonStrConstMatchers:
541                match = matcher()
542                if match:
543                    break
544        return match
545
546    def matchPyToken(self):
547        match = pseudoprog.match(self.src(), self.pos())
548       
549        if match and match.group() in tripleQuotedStringStarts:
550            TQSmatch = tripleQuotedStringREs[match.group()].match(self.src(), self.pos())
551            if TQSmatch:
552                return TQSmatch
553        return match
554       
555    def getPyToken(self):
556        match = self.matchPyToken()
557        if match is None:
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())
562
563    def matchEOLSlurpToken(self):
564        if self.EOLSlurpRE:
565            return self.EOLSlurpRE.match(self.src(), self.pos())
566
567    def getEOLSlurpToken(self):
568        match = self.matchEOLSlurpToken()
569        if not match:
570            raise ParseError(self, msg='Invalid EOL slurp token')
571        return self.readTo(match.end())
572
573    def matchCommentStartToken(self):
574        return self.commentStartTokenRE.match(self.src(), self.pos())
575   
576    def getCommentStartToken(self):
577        match = self.matchCommentStartToken()
578        if not match:
579            raise ParseError(self, msg='Invalid single-line comment start token')
580        return self.readTo(match.end())
581
582    def matchMultiLineCommentStartToken(self):
583        return self.multiLineCommentTokenStartRE.match(self.src(), self.pos())
584   
585    def getMultiLineCommentStartToken(self):
586        match = self.matchMultiLineCommentStartToken()
587        if not match:
588            raise ParseError(self, msg='Invalid multi-line comment start token')
589        return self.readTo(match.end())
590
591    def matchMultiLineCommentEndToken(self):
592        return self.multiLineCommentEndTokenRE.match(self.src(), self.pos())
593   
594    def getMultiLineCommentEndToken(self):
595        match = self.matchMultiLineCommentEndToken()
596        if not match:
597            raise ParseError(self, msg='Invalid multi-line comment end token')
598        return self.readTo(match.end())
599
600    def getCommaSeparatedSymbols(self):
601        """
602            Loosely based on getDottedName to pull out comma separated
603            named chunks
604        """
605        srcLen = len(self)
606        pieces = []
607        nameChunks = []
608
609        if not self.peek() in identchars:
610            raise ParseError(self)
611   
612        while self.pos() < srcLen:
613            c = self.peek()
614            if c in namechars:
615                nameChunk = self.getIdentifier()
616                nameChunks.append(nameChunk)
617            elif c == '.':
618                if self.pos()+1 <srcLen and self.peek(1) in identchars:
619                    nameChunks.append(self.getc())
620                else:
621                    break
622            elif c == ',':
623                self.getc()
624                pieces.append(''.join(nameChunks))
625                nameChunks = []
626            elif c in (' ', '\t'):
627                self.getc()
628            else:
629                break
630
631        if nameChunks:
632            pieces.append(''.join(nameChunks))
633
634        return pieces
635   
636    def getDottedName(self):
637        srcLen = len(self)
638        nameChunks = []
639       
640        if not self.peek() in identchars:
641            raise ParseError(self)
642   
643        while self.pos() < srcLen:
644            c = self.peek()
645            if c in namechars:
646                nameChunk = self.getIdentifier()
647                nameChunks.append(nameChunk)
648            elif c == '.':
649                if self.pos()+1 <srcLen and self.peek(1) in identchars:
650                    nameChunks.append(self.getc())
651                else:
652                    break
653            else:
654                break
655
656        return ''.join(nameChunks)
657
658    def matchIdentifier(self):
659        return identRE.match(self.src(), self.pos())
660   
661    def getIdentifier(self):
662        match = self.matchIdentifier()
663        if not match:
664            raise ParseError(self, msg='Invalid identifier')
665        return self.readTo(match.end())
666
667    def matchOperator(self):
668        match = self.matchPyToken()
669        if match and match.group() not in operators:
670            match = None
671        return match
672
673    def getOperator(self):
674        match = self.matchOperator()
675        if not match:
676            raise ParseError(self, msg='Expected operator')
677        return self.readTo( match.end() )
678
679    def matchAssignmentOperator(self):
680        match = self.matchPyToken()
681        if match and match.group() not in assignmentOps:
682            match = None
683        return match
684       
685    def getAssignmentOperator(self):
686        match = self.matchAssignmentOperator()
687        if not match:
688            raise ParseError(self, msg='Expected assignment operator')
689        return self.readTo( match.end() )
690
691    def matchDirective(self):
692        """Returns False or the name of the directive matched.
693        """
694        startPos = self.pos()
695        if not self.matchDirectiveStartToken():
696            return False
697        self.getDirectiveStartToken()
698        directiveName = self.matchDirectiveName()
699        self.setPos(startPos)
700        return directiveName
701
702    def matchDirectiveName(self, directiveNameChars=identchars+'0123456789-@'):
703        startPos = self.pos()
704        possibleMatches = self._directiveNamesAndParsers.keys()
705        name = ''
706        match = None
707
708        while not self.atEnd():
709            c = self.getc()
710            if not c in directiveNameChars:
711                break
712            name += c
713            if name == '@':
714                if not self.atEnd() and self.peek() in identchars:
715                    match = '@'
716                break
717            possibleMatches = [dn for dn in possibleMatches if dn.startswith(name)]
718            if not possibleMatches:
719                break
720            elif (name in possibleMatches and (self.atEnd() or self.peek() not in directiveNameChars)):
721                match = name
722                break
723
724        self.setPos(startPos)
725        return match
726       
727    def matchDirectiveStartToken(self):
728        return self.directiveStartTokenRE.match(self.src(), self.pos())
729   
730    def getDirectiveStartToken(self):
731        match = self.matchDirectiveStartToken()
732        if not match:
733            raise ParseError(self, msg='Invalid directive start token')
734        return self.readTo(match.end())
735
736    def matchDirectiveEndToken(self):
737        return self.directiveEndTokenRE.match(self.src(), self.pos())
738   
739    def getDirectiveEndToken(self):
740        match = self.matchDirectiveEndToken()
741        if not match:
742            raise ParseError(self, msg='Invalid directive end token')
743        return self.readTo(match.end())
744
745       
746    def matchColonForSingleLineShortFormDirective(self):
747        if not self.atEnd() and self.peek()==':':
748            restOfLine = self[self.pos()+1:self.findEOL()]
749            restOfLine = restOfLine.strip()
750            if not restOfLine:
751                return False
752            elif self.commentStartTokenRE.match(restOfLine):
753                return False
754            else: # non-whitespace, non-commment chars found
755                return True
756        return False       
757
758    def matchPSPStartToken(self):
759        return self.PSPStartTokenRE.match(self.src(), self.pos())
760
761    def matchPSPEndToken(self):
762        return self.PSPEndTokenRE.match(self.src(), self.pos())
763
764    def getPSPStartToken(self):
765        match = self.matchPSPStartToken()
766        if not match:
767            raise ParseError(self, msg='Invalid psp start token')
768        return self.readTo(match.end())
769
770    def getPSPEndToken(self):
771        match = self.matchPSPEndToken()
772        if not match:
773            raise ParseError(self, msg='Invalid psp end token')
774        return self.readTo(match.end())
775
776    def matchCheetahVarStart(self):
777        """includes the enclosure and cache token"""
778        return self.cheetahVarStartRE.match(self.src(), self.pos())
779
780    def matchCheetahVarStartToken(self):
781        """includes the enclosure and cache token"""
782        return self.cheetahVarStartTokenRE.match(self.src(), self.pos())
783
784    def matchCheetahVarInExpressionStartToken(self):
785        """no enclosures or cache tokens allowed"""
786        return self.cheetahVarInExpressionStartTokenRE.match(self.src(), self.pos())
787
788    def matchVariablePlaceholderStart(self):
789        """includes the enclosure and cache token"""
790        return self.cheetahVarStartRE.match(self.src(), self.pos())
791
792    def matchExpressionPlaceholderStart(self):
793        """includes the enclosure and cache token"""
794        return self.expressionPlaceholderStartRE.match(self.src(), self.pos())       
795
796    def getCheetahVarStartToken(self):
797        """just the start token, not the enclosure or cache token"""
798        match = self.matchCheetahVarStartToken()
799        if not match:
800            raise ParseError(self, msg='Expected Cheetah $var start token')           
801        return self.readTo( match.end() )
802
803
804    def getCacheToken(self):
805        try:
806            token = self.cacheTokenRE.match(self.src(), self.pos())
807            self.setPos( token.end() )
808            return token.group()
809        except:
810            raise ParseError(self, msg='Expected cache token')
811
812    def getSilentPlaceholderToken(self):
813        try:
814            token = self.silentPlaceholderTokenRE.match(self.src(), self.pos())
815            self.setPos( token.end() )
816            return token.group()
817        except:
818            raise ParseError(self, msg='Expected silent placeholder token')
819
820
821
822    def getTargetVarsList(self):
823        varnames = []
824        while not self.atEnd():
825            if self.peek() in ' \t\f':
826                self.getWhiteSpace()
827            elif self.peek() in '\r\n':
828                break
829            elif self.startswith(','):
830                self.advance()
831            elif self.startswith('in ') or self.startswith('in\t'):
832                break
833            #elif self.matchCheetahVarStart():
834            elif self.matchCheetahVarInExpressionStartToken():
835                self.getCheetahVarStartToken()
836                self.getSilentPlaceholderToken()
837                self.getCacheToken()
838                varnames.append( self.getDottedName() )
839            elif self.matchIdentifier():
840                varnames.append( self.getDottedName() )
841            else:
842                break
843        return varnames
844       
845    def getCheetahVar(self, plain=False, skipStartToken=False):
846        """This is called when parsing inside expressions. Cache tokens are only
847        valid in placeholders so this method discards any cache tokens found.
848        """
849        if not skipStartToken:
850            self.getCheetahVarStartToken()
851        self.getSilentPlaceholderToken()
852        self.getCacheToken()
853        return self.getCheetahVarBody(plain=plain)
854           
855    def getCheetahVarBody(self, plain=False):
856        # @@TR: this should be in the compiler
857        return self._compiler.genCheetahVar(self.getCheetahVarNameChunks(), plain=plain)
858       
859    def getCheetahVarNameChunks(self):
860       
861        """
862        nameChunks = list of Cheetah $var subcomponents represented as tuples
863          [ (namemapperPart,autoCall,restOfName),
864          ]
865        where:
866          namemapperPart = the dottedName base
867          autocall = where NameMapper should use autocalling on namemapperPart
868          restOfName = any arglist, index, or slice
869
870        If restOfName contains a call arglist (e.g. '(1234)') then autocall is
871        False, otherwise it defaults to True.
872
873        EXAMPLE
874        ------------------------------------------------------------------------
875
876        if the raw CheetahVar is
877          $a.b.c[1].d().x.y.z
878         
879        nameChunks is the list
880          [ ('a.b.c',True,'[1]'),
881            ('d',False,'()'),     
882            ('x.y.z',True,''),   
883          ]
884
885        """
886
887        chunks = []
888        while self.pos() < len(self):
889            rest = ''
890            autoCall = True
891            if not self.peek() in identchars + '.':
892                break
893            elif self.peek() == '.':
894               
895                if self.pos()+1 < len(self) and self.peek(1) in identchars:
896                    self.advance()  # discard the period as it isn't needed with NameMapper
897                else:
898                    break
899               
900            dottedName = self.getDottedName()
901            if not self.atEnd() and self.peek() in '([':
902                if self.peek() == '(':
903                    rest = self.getCallArgString()
904                else:
905                    rest = self.getExpression(enclosed=True)
906               
907                period = max(dottedName.rfind('.'), 0)
908                if period:
909                    chunks.append( (dottedName[:period], autoCall, '') )
910                    dottedName = dottedName[period+1:]
911                if rest and rest[0]=='(':
912                    autoCall = False
913            chunks.append( (dottedName, autoCall, rest) )
914
915        return chunks
916   
917
918    def getCallArgString(self,
919                         enclosures=[],  # list of tuples (char, pos), where char is ({ or [
920                         useNameMapper=Unspecified):
921
922        """ Get a method/function call argument string.
923
924        This method understands *arg, and **kw
925        """
926
927        # @@TR: this settings mangling should be removed
928        if useNameMapper is not Unspecified:
929            useNameMapper_orig = self.setting('useNameMapper')
930            self.setSetting('useNameMapper', useNameMapper)
931       
932        if enclosures:
933            pass
934        else:
935            if not self.peek() == '(':
936                raise ParseError(self, msg="Expected '('")
937            startPos = self.pos()
938            self.getc()
939            enclosures = [('(', startPos),
940                          ]
941       
942        argStringBits = ['(']
943        addBit = argStringBits.append
944
945        while 1:
946            if self.atEnd():
947                open = enclosures[-1][0]
948                close = closurePairsRev[open]
949                self.setPos(enclosures[-1][1])
950                raise ParseError(
951                    self, msg="EOF was reached before a matching '" + close +
952                    "' was found for the '" + open + "'")
953
954            c = self.peek()
955            if c in ")}]": # get the ending enclosure and break               
956                if not enclosures:
957                    raise ParseError(self)
958                c = self.getc()
959                open = closurePairs[c]
960                if enclosures[-1][0] == open:
961                    enclosures.pop()
962                    addBit(')') 
963                    break
964                else:
965                    raise ParseError(self)
966            elif c in " \t\f\r\n":
967                addBit(self.getc())
968            elif self.matchCheetahVarInExpressionStartToken():
969                startPos = self.pos()
970                codeFor1stToken = self.getCheetahVar()
971                WS = self.getWhiteSpace()
972                if not self.atEnd() and self.peek() == '=':
973                    nextToken = self.getPyToken()
974                    if nextToken == '=':
975                        endPos = self.pos()
976                        self.setPos(startPos)
977                        codeFor1stToken = self.getCheetahVar(plain=True)
978                        self.setPos(endPos)
979                       
980                    ## finally
981                    addBit( codeFor1stToken + WS + nextToken )
982                else:
983                    addBit( codeFor1stToken + WS)
984            elif self.matchCheetahVarStart():
985                # it has syntax that is only valid at the top level
986                self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
987            else:
988                beforeTokenPos = self.pos()
989                token = self.getPyToken()
990                if token in ('{','(','['):
991                    self.rev()
992                    token = self.getExpression(enclosed=True)
993                token = self.transformToken(token, beforeTokenPos)
994                addBit(token)
995
996        if useNameMapper is not Unspecified:
997            self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
998
999        return ''.join(argStringBits)
1000   
1001    def getDefArgList(self, exitPos=None, useNameMapper=False):
1002
1003        """ Get an argument list. Can be used for method/function definition
1004        argument lists or for #directive argument lists. Returns a list of
1005        tuples in the form (argName, defVal=None) with one tuple for each arg
1006        name.
1007
1008        These defVals are always strings, so (argName, defVal=None) is safe even
1009        with a case like (arg1, arg2=None, arg3=1234*2), which would be returned as
1010        [('arg1', None),
1011         ('arg2', 'None'),
1012         ('arg3', '1234*2'),         
1013        ]
1014
1015        This method understands *arg, and **kw
1016
1017        """
1018
1019        if self.peek() == '(':
1020            self.advance()
1021        else:
1022            exitPos = self.findEOL()  # it's a directive so break at the EOL
1023        argList = ArgList()
1024        onDefVal = False
1025
1026        # @@TR: this settings mangling should be removed
1027        useNameMapper_orig = self.setting('useNameMapper')
1028        self.setSetting('useNameMapper', useNameMapper)
1029
1030        while 1:
1031            if self.atEnd():
1032                raise ParseError(
1033                    self, msg="EOF was reached before a matching ')'"+
1034                    " was found for the '('")
1035
1036            if self.pos() == exitPos:
1037                break
1038
1039            c = self.peek()
1040            if c == ")" or self.matchDirectiveEndToken():
1041                break
1042            elif c == ":":
1043                break           
1044            elif c in " \t\f\r\n":
1045                if onDefVal:
1046                    argList.addToDefVal(c)
1047                self.advance()
1048            elif c == '=':
1049                onDefVal = True
1050                self.advance()
1051            elif c == ",":
1052                argList.next()
1053                onDefVal = False
1054                self.advance()
1055            elif self.startswith(self.cheetahVarStartToken) and not onDefVal:
1056                self.advance(len(self.cheetahVarStartToken))
1057            elif self.matchIdentifier() and not onDefVal:
1058                argList.addArgName( self.getIdentifier() )
1059            elif onDefVal:
1060                if self.matchCheetahVarInExpressionStartToken():
1061                    token = self.getCheetahVar()
1062                elif self.matchCheetahVarStart():
1063                    # it has syntax that is only valid at the top level                   
1064                    self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()
1065                else:
1066                    beforeTokenPos = self.pos()
1067                    token = self.getPyToken()
1068                    if token in ('{','(','['):
1069                        self.rev()
1070                        token = self.getExpression(enclosed=True)
1071                    token = self.transformToken(token, beforeTokenPos)
1072                argList.addToDefVal(token)
1073            elif c == '*' and not onDefVal:
1074                varName = self.getc()
1075                if self.peek() == '*':
1076                    varName += self.getc()
1077                if not self.matchIdentifier():
1078                    raise ParseError(self)
1079                varName += self.getIdentifier()
1080                argList.addArgName(varName)
1081            else:
1082                raise ParseError(self)
1083
1084               
1085        self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
1086        return argList.merge()
1087   
1088    def getExpressionParts(self,
1089                           enclosed=False,
1090                           enclosures=None, # list of tuples (char, pos), where char is ({ or [
1091                           pyTokensToBreakAt=None, # only works if not enclosed
1092                           useNameMapper=Unspecified,
1093                           ):
1094
1095        """ Get a Cheetah expression that includes $CheetahVars and break at
1096        directive end tokens, the end of an enclosure, or at a specified
1097        pyToken.
1098        """
1099
1100        if useNameMapper is not Unspecified:
1101            useNameMapper_orig = self.setting('useNameMapper')
1102            self.setSetting('useNameMapper', useNameMapper)
1103
1104        if enclosures is None:
1105            enclosures = []
1106       
1107        srcLen = len(self)
1108        exprBits = []
1109        while 1:
1110            if self.atEnd():
1111                if enclosures:
1112                    open = enclosures[-1][0]
1113                    close = closurePairsRev[open]
1114                    self.setPos(enclosures[-1][1])
1115                    raise ParseError(
1116                        self, msg="EOF was reached before a matching '" + close +
1117                        "' was found for the '" + open + "'")
1118                else:
1119                    break
1120
1121            c = self.peek()
1122            if c in "{([":
1123                exprBits.append(c)
1124                enclosures.append( (c, self.pos()) )
1125                self.advance()               
1126            elif enclosed and not enclosures:
1127                break               
1128            elif c in "])}":
1129                if not enclosures:
1130                    raise ParseError(self)
1131                open = closurePairs[c]
1132                if enclosures[-1][0] == open:
1133                    enclosures.pop()
1134                    exprBits.append(c)
1135                else:
1136                    open = enclosures[-1][0]
1137                    close = closurePairsRev[open]
1138                    row, col = self.getRowCol()
1139                    self.setPos(enclosures[-1][1])
1140                    raise ParseError(
1141                        self, msg= "A '" + c + "' was found at line " + str(row) +
1142                        ", col " + str(col) +
1143                        " before a matching '" + close +
1144                        "' was found\nfor the '" + open + "'")
1145                self.advance()
1146                               
1147            elif c in " \f\t":
1148                exprBits.append(self.getWhiteSpace())           
1149            elif self.matchDirectiveEndToken() and not enclosures:
1150                break           
1151            elif c == "\\" and self.pos()+1 < srcLen:
1152                eolMatch = EOLre.match(self.src(), self.pos()+1)
1153                if not eolMatch:
1154                    self.advance()
1155                    raise ParseError(self, msg='Line ending expected')
1156                self.setPos( eolMatch.end() )
1157            elif c in '\r\n':
1158                if enclosures:
1159                    self.advance()                   
1160                else:
1161                    break                   
1162            elif self.matchCheetahVarInExpressionStartToken():
1163                expr = self.getCheetahVar()
1164                exprBits.append(expr)
1165            elif self.matchCheetahVarStart():
1166                # it has syntax that is only valid at the top level               
1167                self._raiseErrorAboutInvalidCheetahVarSyntaxInExpr()                   
1168            else:               
1169                beforeTokenPos = self.pos()
1170                token = self.getPyToken()
1171                if (not enclosures
1172                    and pyTokensToBreakAt
1173                    and token in pyTokensToBreakAt):
1174                   
1175                    self.setPos(beforeTokenPos)
1176                    break
1177
1178                token = self.transformToken(token, beforeTokenPos)
1179                       
1180                exprBits.append(token)                   
1181                if identRE.match(token):
1182                    if token == 'for':
1183                        expr = self.getExpression(useNameMapper=False, pyTokensToBreakAt=['in'])
1184                        exprBits.append(expr)
1185                    else:
1186                        exprBits.append(self.getWhiteSpace())
1187                        if not self.atEnd() and self.peek() == '(':
1188                            exprBits.append(self.getCallArgString())                   
1189        ##
1190        if useNameMapper is not Unspecified:                           
1191            self.setSetting('useNameMapper', useNameMapper_orig) # @@TR: see comment above
1192        return exprBits
1193
1194    def getExpression(self,
1195                      enclosed=False,
1196                      enclosures=None, # list of tuples (char, pos), where # char is ({ or [
1197                      pyTokensToBreakAt=None,
1198                      useNameMapper=Unspecified,
1199                      ):
1200        """Returns the output of self.getExpressionParts() as a concatenated
1201        string rather than as a list.
1202        """
1203        return ''.join(self.getExpressionParts(
1204            enclosed=enclosed, enclosures=enclosures,
1205            pyTokensToBreakAt=pyTokensToBreakAt,
1206            useNameMapper=useNameMapper))
1207
1208
1209    def transformToken(self, token, beforeTokenPos):
1210        """Takes a token from the expression being parsed and performs and
1211        special transformations required by Cheetah.
1212
1213        At the moment only Cheetah's c'$placeholder strings' are transformed.
1214        """
1215        if token=='c' and not self.atEnd() and self.peek() in '\'"':
1216            nextToken = self.getPyToken()
1217            token = nextToken.upper()
1218            theStr = eval(token)
1219            endPos = self.pos()
1220            if not theStr:
1221                return
1222           
1223            if token.startswith(single3) or token.startswith(double3):
1224                startPosIdx = 3
1225            else:
1226                startPosIdx = 1
1227            #print 'CHEETAH STRING', nextToken, theStr, startPosIdx
1228            self.setPos(beforeTokenPos+startPosIdx+1)
1229            outputExprs = []
1230            strConst = ''
1231            while self.pos() < (endPos-startPosIdx):
1232                if self.matchCheetahVarStart() or self.matchExpressionPlaceholderStart():
1233                    if strConst:
1234                        outputExprs.append(repr(strConst))
1235                        strConst = ''
1236                    placeholderExpr = self.getPlaceholder()
1237                    outputExprs.append('str('+placeholderExpr+')')
1238                else:
1239                    strConst += self.getc()
1240            self.setPos(endPos)
1241            if strConst:
1242                outputExprs.append(repr(strConst))
1243            #if not self.atEnd() and self.matches('.join('):
1244            #    print 'DEBUG***'
1245            token = "''.join(["+','.join(outputExprs)+"])"
1246        return token
1247
1248    def _raiseErrorAboutInvalidCheetahVarSyntaxInExpr(self):
1249        match = self.matchCheetahVarStart()
1250        groupdict = match.groupdict()
1251        if groupdict.get('cacheToken'):
1252            raise ParseError(
1253                self,
1254                msg='Cache tokens are not valid inside expressions. '
1255                'Use them in top-level $placeholders only.')                   
1256        elif groupdict.get('enclosure'):                   
1257            raise ParseError(
1258                self,
1259                msg='Long-form placeholders - ${}, $(), $[], etc. are not valid inside expressions. '
1260                'Use them in top-level $placeholders only.')
1261        else:
1262            raise ParseError(
1263                self,
1264                msg='This form of $placeholder syntax is not valid here.')
1265       
1266
1267    def getPlaceholder(self, allowCacheTokens=False, plain=False, returnEverything=False):
1268        # filtered
1269        for callback in self.setting('preparsePlaceholderHooks'):
1270            callback(parser=self)
1271
1272        startPos = self.pos()
1273        lineCol = self.getRowCol(startPos)
1274        startToken = self.getCheetahVarStartToken()
1275        silentPlaceholderToken = self.getSilentPlaceholderToken()
1276        if silentPlaceholderToken:
1277            isSilentPlaceholder = True
1278        else:
1279            isSilentPlaceholder = False
1280           
1281       
1282        if allowCacheTokens:
1283            cacheToken = self.getCacheToken()
1284            cacheTokenParts = self.cacheTokenRE.match(cacheToken).groupdict()       
1285        else:
1286            cacheTokenParts = {}
1287
1288        if self.peek() in '({[':         
1289            pos = self.pos()
1290            enclosureOpenChar = self.getc()
1291            enclosures = [ (enclosureOpenChar, pos) ]
1292            self.getWhiteSpace()
1293        else:
1294            enclosures = []
1295
1296        filterArgs = None
1297        if self.matchIdentifier():
1298            nameChunks = self.getCheetahVarNameChunks()
1299            expr = self._compiler.genCheetahVar(nameChunks[:], plain=plain)
1300            restOfExpr = None
1301            if enclosures:
1302                WS = self.getWhiteSpace()
1303                expr += WS
1304                if self.setting('allowPlaceholderFilterArgs') and self.peek()==',':
1305                    filterArgs = self.getCallArgString(enclosures=enclosures)[1:-1]
1306                else:
1307                    if self.peek()==closurePairsRev[enclosureOpenChar]:
1308                        self.getc()
1309                    else:
1310                        restOfExpr = self.getExpression(enclosed=True, enclosures=enclosures)
1311                        if restOfExpr[-1] == closurePairsRev[enclosureOpenChar]:
1312                            restOfExpr = restOfExpr[:-1]
1313                        expr += restOfExpr
1314            rawPlaceholder = self[startPos: self.pos()]
1315        else:
1316            expr = self.getExpression(enclosed=True, enclosures=enclosures)
1317            if expr[-1] == closurePairsRev[enclosureOpenChar]:
1318                expr = expr[:-1]
1319            rawPlaceholder=self[startPos: self.pos()]
1320           
1321        expr = self._applyExpressionFilters(expr,'placeholder',
1322                                            rawExpr=rawPlaceholder,startPos=startPos)
1323        for callback in self.setting('postparsePlaceholderHooks'):
1324            callback(parser=self)
1325
1326        if returnEverything:
1327            return (expr, rawPlaceholder, lineCol, cacheTokenParts,
1328                    filterArgs, isSilentPlaceholder)
1329        else:
1330            return expr
1331       
1332
1333class _HighLevelParser(_LowLevelParser):
1334    """This class is a StateMachine for parsing Cheetah source and
1335    sending state dependent code generation commands to
1336    Cheetah.Compiler.Compiler.
1337    """
1338    def __init__(self, src, filename=None, breakPoint=None, compiler=None):
1339        _LowLevelParser.__init__(self, src, filename=filename, breakPoint=breakPoint)
1340        self.setSettingsManager(compiler)
1341        self._compiler = compiler
1342        self.setupState()
1343        self.configureParser()
1344
1345    def setupState(self):
1346        self._macros = {}       
1347        self._macroDetails = {}
1348        self._openDirectivesStack = []
1349
1350    def cleanup(self):
1351        """Cleanup to remove any possible reference cycles
1352        """
1353        self._macros.clear()
1354        for macroname, macroDetails in self._macroDetails.items():
1355            macroDetails.template.shutdown()
1356            del macroDetails.template
1357        self._macroDetails.clear()
1358
1359    def configureParser(self):
1360        _LowLevelParser.configureParser(self)
1361        self._initDirectives()
1362   
1363    def _initDirectives(self):
1364        def normalizeParserVal(val):
1365            if isinstance(val, (str,unicode)):
1366                handler = getattr(self, val)
1367            elif type(val) in (ClassType, TypeType):
1368                handler = val(self)
1369            elif callable(val):
1370                handler = val
1371            elif val is None:
1372                handler = val
1373            else:
1374                raise Exception('Invalid parser/handler value %r for %s'%(val, name))
1375            return handler
1376       
1377        normalizeHandlerVal = normalizeParserVal
1378
1379        _directiveNamesAndParsers = directiveNamesAndParsers.copy()
1380        customNamesAndParsers = self.setting('directiveNamesAndParsers',{})
1381        _directiveNamesAndParsers.update(customNamesAndParsers)
1382
1383        _endDirectiveNamesAndHandlers = endDirectiveNamesAndHandlers.copy()
1384        customNamesAndHandlers = self.setting('endDirectiveNamesAndHandlers',{})
1385        _endDirectiveNamesAndHandlers.update(customNamesAndHandlers)       
1386       
1387        self._directiveNamesAndParsers = {}
1388        for name, val in _directiveNamesAndParsers.items():
1389            if val in (False, 0):
1390                continue
1391            self._directiveNamesAndParsers[name] = normalizeParserVal(val)
1392
1393        self._endDirectiveNamesAndHandlers = {}       
1394        for name, val in _endDirectiveNamesAndHandlers.items():
1395            if val in (False, 0):
1396                continue
1397            self._endDirectiveNamesAndHandlers[name] = normalizeHandlerVal(val)
1398       
1399        self._closeableDirectives = ['def','block','closure','defmacro',
1400                                     'call',
1401                                     'capture',
1402                                     'cache',
1403                                     'filter',
1404                                     'if','unless',
1405                                     'for','while','repeat',
1406                                     'try',
1407                                     ]
1408        for directiveName in self.setting('closeableDirectives',[]):
1409            self._closeableDirectives.append(directiveName)
1410
1411
1412
1413        macroDirectives = self.setting('macroDirectives',{})
1414        macroDirectives['i18n'] = I18n
1415
1416
1417        for macroName, callback in macroDirectives.items():
1418            if type(callback) in (ClassType, TypeType):
1419                callback = callback(parser=self)
1420            assert callback               
1421            self._macros[macroName] = callback
1422            self._directiveNamesAndParsers[macroName] = self.eatMacroCall
1423           
1424    def _applyExpressionFilters(self, expr, exprType, rawExpr=None, startPos=None):
1425        """Pipes cheetah expressions through a set of optional filter hooks.
1426
1427        The filters are functions which may modify the expressions or raise
1428        a ForbiddenExpression exception if the expression is not allowed.  They
1429        are defined in the compiler setting 'expressionFilterHooks'.
1430
1431        Some intended use cases:
1432
1433         - to implement 'restricted execution' safeguards in cases where you
1434           can't trust the author of the template.
1435
1436         - to enforce style guidelines 
1437           
1438        filter call signature:  (parser, expr, exprType, rawExpr=None, startPos=None)
1439         - parser is the Cheetah parser 
1440         - expr is the expression to filter.  In some cases the parser will have
1441           already modified it from the original source code form.  For example,
1442           placeholders will have been translated into namemapper calls.  If you
1443           need to work with the original source, see rawExpr.       
1444         - exprType is the name of the directive, 'psp', or 'placeholder'. All
1445           lowercase.  @@TR: These will eventually be replaced with a set of
1446           constants.
1447         - rawExpr is the original source string that Cheetah parsed.  This
1448           might be None in some cases.
1449         - startPos is the character position in the source string/file
1450           where the parser started parsing the current expression.
1451
1452        @@TR: I realize this use of the term 'expression' is a bit wonky as many
1453         of the 'expressions' are actually statements, but I haven't thought of
1454         a better name yet.  Suggestions?
1455        """
1456        for callback in self.setting('expressionFilterHooks'):
1457            expr = callback(parser=self, expr=expr,  exprType=exprType,
1458                            rawExpr=rawExpr, startPos=startPos)
1459        return expr
1460
1461    def _filterDisabledDirectives(self, directiveName):
1462        directiveName = directiveName.lower()
1463        if (directiveName in self.setting('disabledDirectives')
1464            or (self.setting('enabledDirectives')
1465                and directiveName not in self.setting('enabledDirectives'))):
1466            for callback in self.setting('disabledDirectiveHooks'):
1467                callback(parser=self, directiveName=directiveName)
1468            raise ForbiddenDirective(self, msg='This %r directive is disabled'%directiveName)
1469       
1470    ## main parse loop
1471
1472    def parse(self, breakPoint=None, assertEmptyStack=True):
1473        if breakPoint:
1474            origBP = self.breakPoint()
1475            self.setBreakPoint(breakPoint)
1476            assertEmptyStack = False
1477
1478        while not self.atEnd():
1479            if self.matchCommentStartToken():
1480                self.eatComment()
1481            elif self.matchMultiLineCommentStartToken():
1482                self.eatMultiLineComment()
1483            elif self.matchVariablePlaceholderStart():
1484                self.eatPlaceholder()
1485            elif self.matchExpressionPlaceholderStart():
1486                self.eatPlaceholder()
1487            elif self.matchDirective():
1488                self.eatDirective()
1489            elif self.matchPSPStartToken():
1490                self.eatPSP()
1491            elif self.matchEOLSlurpToken():
1492                self.eatEOLSlurpToken()
1493            else:
1494                self.eatPlainText()
1495        if assertEmptyStack:
1496            self.assertEmptyOpenDirectivesStack()
1497        if breakPoint:
1498            self.setBreakPoint(origBP)
1499           
1500    ## non-directive eat methods   
1501               
1502    def eatPlainText(self):
1503        startPos = self.pos()
1504        match = None
1505        while not self.atEnd():
1506            match = self.matchTopLevelToken()
1507            if match:
1508                break
1509            else:
1510                self.advance()
1511        strConst = self.readTo(self.pos(), start=startPos)
1512        self._compiler.addStrConst(strConst)
1513        return match
1514
1515    def eatComment(self):
1516        isLineClearToStartToken = self.isLineClearToStartToken()
1517        if isLineClearToStartToken:
1518            self._compiler.handleWSBeforeDirective()
1519        self.getCommentStartToken()           
1520        comm = self.readToEOL(gobble=isLineClearToStartToken)
1521        self._compiler.addComment(comm)
1522
1523    def eatMultiLineComment(self):
1524        isLineClearToStartToken = self.isLineClearToStartToken()
1525        endOfFirstLine = self.findEOL()
1526
1527        self.getMultiLineCommentStartToken()
1528        endPos = startPos = self.pos()
1529        level = 1
1530        while 1:
1531            endPos = self.pos()
1532            if self.atEnd():
1533                break
1534            if self.matchMultiLineCommentStartToken():
1535                self.getMultiLineCommentStartToken()
1536                level += 1
1537            elif self.matchMultiLineCommentEndToken():
1538                self.getMultiLineCommentEndToken()
1539                level -= 1
1540            if not level:
1541                break
1542            self.advance()
1543        comm = self.readTo(endPos, start=startPos)
1544
1545        if not self.atEnd():
1546            self.getMultiLineCommentEndToken()
1547
1548        if (not self.atEnd()) and self.setting('gobbleWhitespaceAroundMultiLineComments'):
1549            restOfLine = self[self.pos():self.findEOL()]
1550            if not restOfLine.strip(): # WS only to EOL
1551                self.readToEOL(gobble=isLineClearToStartToken)
1552
1553            if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLine):
1554                self._compiler.handleWSBeforeDirective()
1555       
1556        self._compiler.addComment(comm)
1557
1558    def eatPlaceholder(self):
1559        (expr, rawPlaceholder,
1560         lineCol, cacheTokenParts,
1561         filterArgs, isSilentPlaceholder) = self.getPlaceholder(
1562            allowCacheTokens=True, returnEverything=True)
1563       
1564        self._compiler.addPlaceholder(
1565            expr,
1566            filterArgs=filterArgs,
1567            rawPlaceholder=rawPlaceholder,
1568            cacheTokenParts=cacheTokenParts,
1569            lineCol=lineCol,
1570            silentMode=isSilentPlaceholder)
1571        return
1572       
1573    def eatPSP(self):
1574        # filtered
1575        self._filterDisabledDirectives(directiveName='psp')
1576        self.getPSPStartToken()
1577        endToken = self.setting('PSPEndToken')
1578        startPos = self.pos()           
1579        while not self.atEnd():
1580            if self.peek() == endToken[0]:
1581                if self.matchPSPEndToken():
1582                    break
1583            self.advance()
1584        pspString = self.readTo(self.pos(), start=startPos).strip()
1585        pspString = self._applyExpressionFilters(pspString, 'psp', startPos=startPos)
1586        self._compiler.addPSP(pspString)
1587        self.getPSPEndToken()
1588
1589    ## generic directive eat methods
1590    _simpleIndentingDirectives = '''
1591    else elif for while repeat unless try except finally'''.split()
1592    _simpleExprDirectives = '''
1593    pass continue stop return yield break
1594    del assert raise
1595    silent echo   
1596    import from'''.split()
1597    _directiveHandlerNames = {'import':'addImportStatement',
1598                              'from':'addImportStatement', }
1599    def eatDirective(self):
1600        directiveName = self.matchDirective()
1601        self._filterDisabledDirectives(directiveName)
1602
1603        for callback in self.setting('preparseDirectiveHooks'):
1604            callback(parser=self, directiveName=directiveName)
1605
1606        # subclasses can override the default behaviours here by providing an
1607        # eater method in self._directiveNamesAndParsers[directiveName]
1608        directiveParser = self._directiveNamesAndParsers.get(directiveName)
1609        if directiveParser:
1610            directiveParser()
1611        elif directiveName in self._simpleIndentingDirectives:
1612            handlerName = self._directiveHandlerNames.get(directiveName)
1613            if not handlerName:
1614                handlerName = 'add'+directiveName.capitalize()
1615            handler = getattr(self._compiler, handlerName)
1616            self.eatSimpleIndentingDirective(directiveName, callback=handler)
1617        elif directiveName in self._simpleExprDirectives:
1618            handlerName = self._directiveHandlerNames.get(directiveName)
1619            if not handlerName:
1620                handlerName = 'add'+directiveName.capitalize()
1621            handler = getattr(self._compiler, handlerName)
1622            if directiveName in ('silent', 'echo'):
1623                includeDirectiveNameInExpr = False
1624            else:
1625                includeDirectiveNameInExpr = True
1626            expr = self.eatSimpleExprDirective(
1627                directiveName,
1628                includeDirectiveNameInExpr=includeDirectiveNameInExpr)
1629            handler(expr)
1630        ##   
1631        for callback in self.setting('postparseDirectiveHooks'):
1632            callback(parser=self, directiveName=directiveName)
1633
1634    def _eatRestOfDirectiveTag(self, isLineClearToStartToken, endOfFirstLinePos):
1635        foundComment = False
1636        if self.matchCommentStartToken():
1637            pos = self.pos()
1638            self.advance()
1639            if not self.matchDirective():
1640                self.setPos(pos)
1641                foundComment = True
1642                self.eatComment() # this won't gobble the EOL
1643            else:
1644                self.setPos(pos)
1645           
1646        if not foundComment and self.matchDirectiveEndToken():
1647                self.getDirectiveEndToken()
1648        elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
1649            # still gobble the EOL if a comment was found.
1650            self.readToEOL(gobble=True)
1651           
1652        if isLineClearToStartToken and (self.atEnd() or self.pos() > endOfFirstLinePos):
1653            self._compiler.handleWSBeforeDirective()
1654
1655    def _eatToThisEndDirective(self, directiveName):
1656        finalPos = endRawPos = startPos = self.pos()
1657        directiveChar = self.setting('directiveStartToken')[0]
1658        isLineClearToStartToken = False
1659        while not self.atEnd():
1660            if self.peek() == directiveChar:
1661                if self.matchDirective() == 'end':
1662                    endRawPos = self.pos()
1663                    self.getDirectiveStartToken()
1664                    self.advance(len('end'))
1665                    self.getWhiteSpace()
1666                    if self.startswith(directiveName):
1667                        if self.isLineClearToStartToken(endRawPos):
1668                            isLineClearToStartToken = True
1669                            endRawPos = self.findBOL(endRawPos)
1670                        self.advance(len(directiveName)) # to end of directiveName
1671                        self.getWhiteSpace()
1672                        finalPos = self.pos()
1673                        break
1674            self.advance()
1675            finalPos = endRawPos = self.pos()
1676
1677        textEaten = self.readTo(endRawPos, start=startPos)
1678        self.setPos(finalPos)
1679       
1680        endOfFirstLinePos = self.findEOL()
1681       
1682        if self.matchDirectiveEndToken():
1683            self.getDirectiveEndToken()
1684        elif isLineClearToStartToken and (not self.atEnd()) and self.peek() in '\r\n':
1685            self.readToEOL(gobble=True)
1686           
1687        if isLineClearToStartToken and self.pos() > endOfFirstLinePos:
1688            self._compiler.handleWSBeforeDirective()
1689        return textEaten
1690
1691
1692    def eatSimpleExprDirective(self, directiveName, includeDirectiveNameInExpr=True):
1693        # filtered
1694        isLineClearToStartToken = self.isLineClearToStartToken()
1695        endOfFirstLine = self.findEOL()
1696        self.getDirectiveStartToken()
1697        if not includeDirectiveNameInExpr:
1698            self.advance(len(directiveName))
1699        startPos = self.pos()
1700        expr = self.getExpression().strip()
1701        directiveName = expr.split()[0]
1702        expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
1703        if directiveName in self._closeableDirectives:
1704            self.pushToOpenDirectivesStack(directiveName)
1705        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
1706        return expr
1707
1708    def eatSimpleIndentingDirective(self, directiveName, callback,
1709                                    includeDirectiveNameInExpr=False):
1710        # filtered
1711        isLineClearToStartToken = self.isLineClearToStartToken()
1712        endOfFirstLinePos = self.findEOL()
1713        lineCol = self.getRowCol()
1714        self.getDirectiveStartToken()
1715        if directiveName not in 'else elif for while try except finally'.split():
1716            self.advance(len(directiveName))
1717        startPos = self.pos()
1718
1719        self.getWhiteSpace()
1720
1721        expr = self.getExpression(pyTokensToBreakAt=[':'])
1722        expr = self._applyExpressionFilters(expr, directiveName, startPos=startPos)
1723        if self.matchColonForSingleLineShortFormDirective():
1724            self.advance() # skip over :
1725            if directiveName in 'else elif except finally'.split():
1726                callback(expr, dedent=False, lineCol=lineCol)
1727            else:
1728                callback(expr, lineCol=lineCol)
1729               
1730            self.getWhiteSpace(max=1)
1731            self.parse(breakPoint=self.findEOL(gobble=True))
1732            self._compiler.commitStrConst()
1733            self._compiler.dedent()
1734        else:
1735            if self.peek()==':':
1736                self.advance()
1737            self.getWhiteSpace()
1738            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
1739            if directiveName in self._closeableDirectives:
1740                self.pushToOpenDirectivesStack(directiveName)
1741            callback(expr, lineCol=lineCol)
1742
1743    def eatEndDirective(self):
1744        isLineClearToStartToken = self.isLineClearToStartToken()
1745        self.getDirectiveStartToken()
1746        self.advance(3)                 # to end of 'end'
1747        self.getWhiteSpace()
1748        pos = self.pos()
1749        directiveName = False
1750        for key in self._endDirectiveNamesAndHandlers.keys():
1751            if self.find(key, pos) == pos:
1752                directiveName = key
1753                break
1754        if not directiveName:
1755            raise ParseError(self, msg='Invalid end directive')
1756       
1757        endOfFirstLinePos = self.findEOL()
1758        self.getExpression() # eat in any extra comment-like crap
1759        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)           
1760        if directiveName in self._closeableDirectives:
1761            self.popFromOpenDirectivesStack(directiveName)
1762
1763        # subclasses can override the default behaviours here by providing an
1764        # end-directive handler in self._endDirectiveNamesAndHandlers[directiveName]
1765        if self._endDirectiveNamesAndHandlers.get(directiveName):
1766            handler = self._endDirectiveNamesAndHandlers[directiveName]
1767            handler()
1768        elif directiveName in 'block capture cache call filter errorCatcher'.split():
1769            if key == 'block':
1770                self._compiler.closeBlock()
1771            elif key == 'capture':
1772                self._compiler.endCaptureRegion()
1773            elif key == 'cache':
1774                self._compiler.endCacheRegion()
1775            elif key == 'call':
1776                self._compiler.endCallRegion()
1777            elif key == 'filter':
1778                self._compiler.closeFilterBlock()
1779            elif key == 'errorCatcher':
1780                self._compiler.turnErrorCatcherOff()
1781        elif directiveName in 'while for if try repeat unless'.split():
1782            self._compiler.commitStrConst()
1783            self._compiler.dedent()
1784        elif directiveName=='closure':
1785            self._compiler.commitStrConst()
1786            self._compiler.dedent()
1787            # @@TR: temporary hack of useSearchList
1788            self.setSetting('useSearchList', self._useSearchList_orig)           
1789
1790    ## specific directive eat methods
1791   
1792    def eatBreakPoint(self):
1793        """Tells the parser to stop parsing at this point and completely ignore
1794        everything else.
1795
1796        This is a debugging tool.
1797        """
1798        self.setBreakPoint(self.pos())
1799
1800    def eatShbang(self):
1801        # filtered
1802        self.getDirectiveStartToken()
1803        self.advance(len('shBang'))
1804        self.getWhiteSpace()
1805        startPos = self.pos()
1806        shBang = self.readToEOL()
1807        shBang = self._applyExpressionFilters(shBang, 'shbang', startPos=startPos)
1808        self._compiler.setShBang(shBang.strip())
1809
1810    def eatEncoding(self):
1811        # filtered
1812        self.getDirectiveStartToken()
1813        self.advance(len('encoding'))
1814        self.getWhiteSpace()
1815        startPos = self.pos()
1816        encoding = self.readToEOL()
1817        encoding = self._applyExpressionFilters(encoding, 'encoding', startPos=startPos)               
1818        self._compiler.setModuleEncoding(encoding.strip())
1819       
1820    def eatCompiler(self):
1821        # filtered
1822        isLineClearToStartToken = self.isLineClearToStartToken()
1823        endOfFirstLine = self.findEOL()
1824        startPos = self.pos()
1825        self.getDirectiveStartToken()
1826        self.advance(len('compiler'))   # to end of 'compiler'
1827        self.getWhiteSpace()
1828
1829        startPos = self.pos()
1830        settingName = self.getIdentifier()
1831
1832        if settingName.lower() == 'reset':
1833            self.getExpression() # gobble whitespace & junk
1834            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
1835            self._initializeSettings()
1836            self.configureParser()
1837            return
1838       
1839        self.getWhiteSpace()
1840        if self.peek() == '=':
1841            self.advance()
1842        else:
1843            raise ParseError(self)
1844        valueExpr = self.getExpression()
1845        endPos = self.pos()
1846
1847        # @@TR: it's unlikely that anyone apply filters would have left this
1848        # directive enabled:
1849        # @@TR: fix up filtering, regardless
1850        self._applyExpressionFilters('%s=%r'%(settingName, valueExpr),
1851                                     'compiler', startPos=startPos)
1852       
1853        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
1854        try:
1855            self._compiler.setCompilerSetting(settingName, valueExpr)
1856        except:
1857            out = sys.stderr
1858            print >> out, 'An error occurred while processing the following #compiler directive.'
1859            print >> out, '-'*80
1860            print >> out, self[startPos:endPos]
1861            print >> out, '-'*80
1862            print >> out, 'Please check the syntax of these settings.'
1863            print >> out, 'A full Python exception traceback follows.'
1864            raise
1865
1866
1867    def eatCompilerSettings(self):
1868        # filtered         
1869        isLineClearToStartToken = self.isLineClearToStartToken()
1870        endOfFirstLine = self.findEOL()
1871        self.getDirectiveStartToken()
1872        self.advance(len('compiler-settings'))   # to end of 'settings'
1873       
1874        keywords = self.getTargetVarsList()
1875        self.getExpression()            # gobble any garbage
1876           
1877        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
1878
1879        if 'reset' in keywords:
1880            self._compiler._initializeSettings()
1881            self.configureParser()
1882            # @@TR: this implies a single-line #compiler-settings directive, and
1883            # thus we should parse forward for an end directive.
1884            # Subject to change in the future
1885            return
1886        startPos = self.pos()
1887        settingsStr = self._eatToThisEndDirective('compiler-settings')           
1888        settingsStr = self._applyExpressionFilters(settingsStr, 'compilerSettings',
1889                                                   startPos=startPos)
1890        try:
1891            self._compiler.setCompilerSettings(keywords=keywords, settingsStr=settingsStr)
1892        except:
1893            out = sys.stderr
1894            print >> out, 'An error occurred while processing the following compiler settings.'
1895            print >> out, '-'*80
1896            print >> out, settingsStr.strip()
1897            print >> out, '-'*80
1898            print >> out, 'Please check the syntax of these settings.'
1899            print >> out, 'A full Python exception traceback follows.'
1900            raise
1901
1902    def eatAttr(self):
1903        # filtered         
1904        isLineClearToStartToken = self.isLineClearToStartToken()
1905        endOfFirstLinePos = self.findEOL()
1906        startPos = self.pos()
1907        self.getDirectiveStartToken()
1908        self.advance(len('attr'))
1909        self.getWhiteSpace()
1910        startPos = self.pos()
1911        if self.matchCheetahVarStart():
1912            self.getCheetahVarStartToken()
1913        attribName = self.getIdentifier()
1914        self.getWhiteSpace()
1915        self.getAssignmentOperator()
1916        expr = self.getExpression()
1917        expr = self._applyExpressionFilters(expr, 'attr', startPos=startPos)
1918        self._compiler.addAttribute(attribName, expr)
1919        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
1920
1921    def eatDecorator(self):
1922        isLineClearToStartToken = self.isLineClearToStartToken()
1923        endOfFirstLinePos = self.findEOL()
1924        startPos = self.pos()
1925        self.getDirectiveStartToken()
1926        #self.advance() # eat @
1927        startPos = self.pos()
1928        decoratorExpr = self.getExpression()
1929        decoratorExpr = self._applyExpressionFilters(decoratorExpr, 'decorator', startPos=startPos)
1930        self._compiler.addDecorator(decoratorExpr)
1931        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
1932        self.getWhiteSpace()
1933
1934        directiveName = self.matchDirective()
1935        if not directiveName or directiveName not in ('def', 'block', 'closure', '@'):
1936            raise ParseError(
1937                self, msg='Expected #def, #block, #closure or another @decorator')
1938        self.eatDirective()
1939       
1940    def eatDef(self):
1941        # filtered         
1942        self._eatDefOrBlock('def')
1943
1944    def eatBlock(self):
1945        # filtered
1946        startPos = self.pos()
1947        methodName, rawSignature = self._eatDefOrBlock('block')
1948        self._compiler._blockMetaData[methodName] = {
1949            'raw':rawSignature,
1950            'lineCol':self.getRowCol(startPos),
1951            }
1952
1953    def eatClosure(self):
1954        # filtered         
1955        self._eatDefOrBlock('closure')
1956       
1957    def _eatDefOrBlock(self, directiveName):
1958        # filtered
1959        assert directiveName in ('def','block','closure')
1960        isLineClearToStartToken = self.isLineClearToStartToken()
1961        endOfFirstLinePos = self.findEOL()
1962        startPos = self.pos()
1963        self.getDirectiveStartToken()
1964        self.advance(len(directiveName))
1965        self.getWhiteSpace()
1966        if self.matchCheetahVarStart():
1967            self.getCheetahVarStartToken()
1968        methodName = self.getIdentifier()
1969        self.getWhiteSpace()
1970        if self.peek() == '(':
1971            argsList = self.getDefArgList()
1972            self.advance()              # past the closing ')'
1973            if argsList and argsList[0][0] == 'self':
1974                del argsList[0]
1975        else:
1976            argsList=[]
1977
1978        def includeBlockMarkers():
1979            if self.setting('includeBlockMarkers'):
1980                startMarker = self.setting('blockMarkerStart')
1981                self._compiler.addStrConst(startMarker[0] + methodName + startMarker[1])
1982
1983        # @@TR: fix up filtering
1984        self._applyExpressionFilters(self[startPos:self.pos()], 'def', startPos=startPos)
1985
1986        if self.matchColonForSingleLineShortFormDirective():
1987            isNestedDef = (self.setting('allowNestedDefScopes')
1988                           and [name for name in self._openDirectivesStack if name=='def'])
1989            self.getc()
1990            rawSignature = self[startPos:endOfFirstLinePos]
1991            self._eatSingleLineDef(directiveName=directiveName,
1992                                   methodName=methodName,
1993                                   argsList=argsList,
1994                                   startPos=startPos,
1995                                   endPos=endOfFirstLinePos)
1996            if directiveName == 'def' and not isNestedDef:
1997                #@@TR: must come before _eatRestOfDirectiveTag ... for some reason
1998                self._compiler.closeDef()
1999            elif directiveName == 'block':
2000                includeBlockMarkers()
2001                self._compiler.closeBlock()
2002            elif directiveName == 'closure' or isNestedDef:
2003                self._compiler.dedent()
2004               
2005            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2006        else:
2007            if self.peek()==':':
2008                self.getc()           
2009            self.pushToOpenDirectivesStack(directiveName)
2010            rawSignature = self[startPos:self.pos()]
2011            self._eatMultiLineDef(directiveName=directiveName,
2012                                  methodName=methodName,
2013                                  argsList=argsList,
2014                                  startPos=startPos,
2015                                  isLineClearToStartToken=isLineClearToStartToken)
2016            if directiveName == 'block':
2017                includeBlockMarkers()
2018
2019        return methodName, rawSignature
2020
2021    def _eatMultiLineDef(self, directiveName, methodName, argsList, startPos,
2022                         isLineClearToStartToken=False):
2023        # filtered in calling method
2024        self.getExpression()            # slurp up any garbage left at the end
2025        signature = self[startPos:self.pos()]
2026        endOfFirstLinePos = self.findEOL()
2027        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2028        signature = ' '.join([line.strip() for line in signature.splitlines()])
2029        parserComment = ('## CHEETAH: generated from ' + signature +
2030                         ' at line %s, col %s' % self.getRowCol(startPos)
2031                         + '.')
2032
2033        isNestedDef = (self.setting('allowNestedDefScopes')
2034                       and len([name for name in self._openDirectivesStack if name=='def'])>1)
2035        if directiveName=='block' or (directiveName=='def' and not isNestedDef):
2036            self._compiler.startMethodDef(methodName, argsList, parserComment)
2037        else: #closure
2038            self._useSearchList_orig = self.setting('useSearchList')
2039            self.setSetting('useSearchList', False)
2040            self._compiler.addClosure(methodName, argsList, parserComment)
2041
2042        return methodName
2043
2044    def _eatSingleLineDef(self, directiveName, methodName, argsList, startPos, endPos):
2045        # filtered in calling method       
2046        fullSignature = self[startPos:endPos]
2047        parserComment = ('## Generated from ' + fullSignature +
2048                         ' at line %s, col %s' % self.getRowCol(startPos)
2049                         + '.')
2050        isNestedDef = (self.setting('allowNestedDefScopes')
2051                       and [name for name in self._openDirectivesStack if name=='def'])
2052        if directiveName=='block' or (directiveName=='def' and not isNestedDef):
2053            self._compiler.startMethodDef(methodName, argsList, parserComment)
2054        else: #closure
2055            # @@TR: temporary hack of useSearchList
2056            useSearchList_orig = self.setting('useSearchList')
2057            self.setSetting('useSearchList', False)           
2058            self._compiler.addClosure(methodName, argsList, parserComment)           
2059
2060        self.getWhiteSpace(max=1)
2061        self.parse(breakPoint=endPos)       
2062        if directiveName=='closure' or isNestedDef: # @@TR: temporary hack of useSearchList
2063            self.setSetting('useSearchList', useSearchList_orig)
2064   
2065    def eatExtends(self):
2066        # filtered
2067        isLineClearToStartToken = self.isLineClearToStartToken()
2068        endOfFirstLine = self.findEOL()
2069        self.getDirectiveStartToken()
2070        self.advance(len('extends'))
2071        self.getWhiteSpace()
2072        startPos = self.pos()
2073        if self.setting('allowExpressionsInExtendsDirective'):
2074            baseName = self.getExpression()
2075        else:
2076            baseName = self.getCommaSeparatedSymbols()
2077            baseName = ', '.join(baseName)
2078                       
2079        baseName = self._applyExpressionFilters(baseName, 'extends', startPos=startPos)
2080        self._compiler.setBaseClass(baseName) # in compiler
2081        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
2082           
2083    def eatImplements(self):
2084        # filtered
2085        isLineClearToStartToken = self.isLineClearToStartToken()
2086        endOfFirstLine = self.findEOL()
2087        self.getDirectiveStartToken()
2088        self.advance(len('implements'))
2089        self.getWhiteSpace()       
2090        startPos = self.pos()
2091        methodName = self.getIdentifier()
2092        if not self.atEnd() and self.peek() == '(':
2093            argsList = self.getDefArgList()
2094            self.advance()              # past the closing ')'
2095            if argsList and argsList[0][0] == 'self':
2096                del argsList[0]
2097        else:
2098            argsList=[]
2099
2100        # @@TR: need to split up filtering of the methodname and the args
2101        #methodName = self._applyExpressionFilters(methodName, 'implements', startPos=startPos)
2102        self._applyExpressionFilters(self[startPos:self.pos()], 'implements', startPos=startPos)
2103
2104        self._compiler.setMainMethodName(methodName)
2105        self._compiler.setMainMethodArgs(argsList)
2106           
2107        self.getExpression()  # throw away and unwanted crap that got added in
2108        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
2109
2110    def eatSuper(self):
2111        # filtered
2112        isLineClearToStartToken = self.isLineClearToStartToken()
2113        endOfFirstLine = self.findEOL()
2114        self.getDirectiveStartToken()
2115        self.advance(len('super'))
2116        self.getWhiteSpace()
2117        startPos = self.pos()
2118        if not self.atEnd() and self.peek() == '(':
2119            argsList = self.getDefArgList()
2120            self.advance()              # past the closing ')'
2121            if argsList and argsList[0][0] == 'self':
2122                del argsList[0]
2123        else:
2124            argsList=[]
2125
2126        self._applyExpressionFilters(self[startPos:self.pos()], 'super', startPos=startPos)
2127
2128        #parserComment = ('## CHEETAH: generated from ' + signature +
2129        #                 ' at line %s, col %s' % self.getRowCol(startPos)
2130        #                 + '.')
2131
2132        self.getExpression()  # throw away and unwanted crap that got added in
2133        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
2134        self._compiler.addSuper(argsList)
2135
2136    def eatSet(self):
2137        # filtered
2138        isLineClearToStartToken = self.isLineClearToStartToken()
2139        endOfFirstLine = self.findEOL()
2140        self.getDirectiveStartToken()
2141        self.advance(3)
2142        self.getWhiteSpace()
2143        style = SET_LOCAL
2144        if self.startswith('local'):
2145            self.getIdentifier()
2146            self.getWhiteSpace()
2147        elif self.startswith('global'):
2148            self.getIdentifier()
2149            self.getWhiteSpace()
2150            style = SET_GLOBAL
2151        elif self.startswith('module'):
2152            self.getIdentifier()
2153            self.getWhiteSpace()
2154            style = SET_MODULE
2155
2156        startsWithDollar = self.matchCheetahVarStart()
2157        startPos = self.pos()
2158        LVALUE = self.getExpression(pyTokensToBreakAt=assignmentOps, useNameMapper=False).strip()
2159        OP = self.getAssignmentOperator()
2160        RVALUE = self.getExpression()       
2161        expr = LVALUE + ' ' + OP + ' ' + RVALUE.strip()
2162       
2163        expr = self._applyExpressionFilters(expr, 'set', startPos=startPos)
2164        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)
2165
2166        class Components: pass # used for 'set global'
2167        exprComponents = Components()
2168        exprComponents.LVALUE = LVALUE
2169        exprComponents.OP = OP
2170        exprComponents.RVALUE = RVALUE
2171        self._compiler.addSet(expr, exprComponents, style)
2172   
2173    def eatSlurp(self):
2174        if self.isLineClearToStartToken():
2175            self._compiler.handleWSBeforeDirective()
2176        self._compiler.commitStrConst()
2177        self.readToEOL(gobble=True)
2178
2179    def eatEOLSlurpToken(self):
2180        if self.isLineClearToStartToken():
2181            self._compiler.handleWSBeforeDirective()
2182        self._compiler.commitStrConst()
2183        self.readToEOL(gobble=True)       
2184
2185    def eatRaw(self):
2186        isLineClearToStartToken = self.isLineClearToStartToken()
2187        endOfFirstLinePos = self.findEOL()
2188        self.getDirectiveStartToken()
2189        self.advance(len('raw'))
2190        self.getWhiteSpace()
2191        if self.matchColonForSingleLineShortFormDirective():
2192            self.advance() # skip over :
2193            self.getWhiteSpace(max=1)
2194            rawBlock = self.readToEOL(gobble=False)
2195        else:
2196            if self.peek()==':':
2197                self.advance()
2198            self.getWhiteSpace()
2199            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2200            rawBlock = self._eatToThisEndDirective('raw')
2201        self._compiler.addRawText(rawBlock)
2202   
2203    def eatInclude(self):
2204        # filtered
2205        isLineClearToStartToken = self.isLineClearToStartToken()
2206        endOfFirstLinePos = self.findEOL()
2207        self.getDirectiveStartToken()
2208        self.advance(len('include'))
2209
2210        self.getWhiteSpace()
2211        includeFrom = 'file'
2212        isRaw = False
2213        if self.startswith('raw'):
2214            self.advance(3)
2215            isRaw=True
2216           
2217        self.getWhiteSpace()           
2218        if self.startswith('source'):
2219            self.advance(len('source'))
2220            includeFrom = 'str'
2221            self.getWhiteSpace()
2222            if not self.peek() == '=':
2223                raise ParseError(self)
2224            self.advance()
2225        startPos = self.pos()
2226        sourceExpr = self.getExpression()
2227        sourceExpr = self._applyExpressionFilters(sourceExpr, 'include', startPos=startPos)       
2228        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2229        self._compiler.addInclude(sourceExpr, includeFrom, isRaw)
2230
2231   
2232    def eatDefMacro(self):
2233        # @@TR: not filtered yet
2234        isLineClearToStartToken = self.isLineClearToStartToken()
2235        endOfFirstLinePos = self.findEOL()
2236        self.getDirectiveStartToken()
2237        self.advance(len('defmacro'))
2238
2239        self.getWhiteSpace()
2240        if self.matchCheetahVarStart():
2241            self.getCheetahVarStartToken()
2242        macroName = self.getIdentifier()
2243        self.getWhiteSpace()
2244        if self.peek() == '(':
2245            argsList = self.getDefArgList(useNameMapper=False)
2246            self.advance()              # past the closing ')'
2247            if argsList and argsList[0][0] == 'self':
2248                del argsList[0]
2249        else:
2250            argsList=[]
2251
2252        assert not self._directiveNamesAndParsers.has_key(macroName)
2253        argsList.insert(0, ('src',None))
2254        argsList.append(('parser','None'))
2255        argsList.append(('macros','None'))
2256        argsList.append(('compilerSettings','None'))
2257        argsList.append(('isShortForm','None'))
2258        argsList.append(('EOLCharsInShortForm','None'))       
2259        argsList.append(('startPos','None'))
2260        argsList.append(('endPos','None'))
2261       
2262        if self.matchColonForSingleLineShortFormDirective():
2263            self.advance() # skip over :
2264            self.getWhiteSpace(max=1)
2265            macroSrc = self.readToEOL(gobble=False)
2266            self.readToEOL(gobble=True)
2267        else:
2268            if self.peek()==':':
2269                self.advance()
2270            self.getWhiteSpace()
2271            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2272            macroSrc = self._eatToThisEndDirective('defmacro')
2273
2274        #print argsList
2275        normalizedMacroSrc = ''.join(
2276            ['%def callMacro('+','.join([defv and '%s=%s'%(n,defv) or n
2277                                         for n,defv in argsList])
2278             +')\n',
2279             macroSrc,
2280             '%end def'])
2281
2282       
2283        from Cheetah.Template import Template
2284        templateAPIClass = self.setting('templateAPIClassForDefMacro', default=Template)
2285        compilerSettings = self.setting('compilerSettingsForDefMacro', default={})
2286        searchListForMacros = self.setting('searchListForDefMacro', default=[])
2287        searchListForMacros = list(searchListForMacros) # copy to avoid mutation bugs
2288        searchListForMacros.append({'macros':self._macros,
2289                                    'parser':self,
2290                                    'compilerSettings':self.settings(),                                   
2291                                    })
2292       
2293        templateAPIClass._updateSettingsWithPreprocessTokens(
2294            compilerSettings, placeholderToken='@', directiveToken='%')
2295        macroTemplateClass = templateAPIClass.compile(source=normalizedMacroSrc,
2296                                                      compilerSettings=compilerSettings)
2297        #print normalizedMacroSrc
2298        #t = macroTemplateClass()
2299        #print t.callMacro('src')
2300        #print t.generatedClassCode()
2301       
2302        class MacroDetails: pass
2303        macroDetails = MacroDetails()
2304        macroDetails.macroSrc = macroSrc
2305        macroDetails.argsList = argsList
2306        macroDetails.template = macroTemplateClass(searchList=searchListForMacros)
2307
2308        self._macroDetails[macroName] = macroDetails
2309        self._macros[macroName] = macroDetails.template.callMacro
2310        self._directiveNamesAndParsers[macroName] = self.eatMacroCall
2311
2312    def eatMacroCall(self):
2313        isLineClearToStartToken = self.isLineClearToStartToken()
2314        endOfFirstLinePos = self.findEOL()
2315        startPos = self.pos()
2316        self.getDirectiveStartToken()
2317        macroName = self.getIdentifier()
2318        macro = self._macros[macroName]
2319        if hasattr(macro, 'parse'):
2320            return macro.parse(parser=self, startPos=startPos)
2321       
2322        if hasattr(macro, 'parseArgs'):
2323            args = macro.parseArgs(parser=self, startPos=startPos)
2324        else:
2325            self.getWhiteSpace()
2326            args = self.getExpression(useNameMapper=False,
2327                                      pyTokensToBreakAt=[':']).strip()
2328
2329        if self.matchColonForSingleLineShortFormDirective():
2330            isShortForm = True
2331            self.advance() # skip over :
2332            self.getWhiteSpace(max=1)
2333            srcBlock = self.readToEOL(gobble=False)
2334            EOLCharsInShortForm = self.readToEOL(gobble=True)
2335            #self.readToEOL(gobble=False)
2336        else:
2337            isShortForm = False
2338            if self.peek()==':':
2339                self.advance()
2340            self.getWhiteSpace()
2341            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2342            srcBlock = self._eatToThisEndDirective(macroName)
2343
2344
2345        if hasattr(macro, 'convertArgStrToDict'):
2346            kwArgs = macro.convertArgStrToDict(args, parser=self, startPos=startPos)
2347        else:
2348            def getArgs(*pargs, **kws):
2349                return pargs, kws
2350            exec 'positionalArgs, kwArgs = getArgs(%(args)s)'%locals()
2351
2352        assert not kwArgs.has_key('src')
2353        kwArgs['src'] = srcBlock
2354
2355        if type(macro)==new.instancemethod:
2356            co = macro.im_func.func_code
2357        elif (hasattr(macro, '__call__')
2358              and hasattr(macro.__call__, 'im_func')):
2359            co = macro.__call__.im_func.func_code
2360        else:
2361            co = macro.func_code
2362        availableKwArgs = inspect.getargs(co)[0]
2363       
2364        if 'parser' in availableKwArgs:
2365            kwArgs['parser'] = self
2366        if 'macros' in availableKwArgs:
2367            kwArgs['macros'] = self._macros
2368        if 'compilerSettings' in availableKwArgs:
2369            kwArgs['compilerSettings'] = self.settings()
2370        if 'isShortForm' in availableKwArgs:
2371            kwArgs['isShortForm'] = isShortForm
2372        if isShortForm and 'EOLCharsInShortForm' in availableKwArgs:
2373            kwArgs['EOLCharsInShortForm'] = EOLCharsInShortForm
2374
2375        if 'startPos' in availableKwArgs:
2376            kwArgs['startPos'] = startPos
2377        if 'endPos' in availableKwArgs:
2378            kwArgs['endPos'] = self.pos()
2379
2380        srcFromMacroOutput = macro(**kwArgs)
2381
2382        origParseSrc = self._src
2383        origBreakPoint = self.breakPoint()
2384        origPos = self.pos()
2385        # add a comment to the output about the macro src that is being parsed
2386        # or add a comment prefix to all the comments added by the compiler
2387        self._src = srcFromMacroOutput
2388        self.setPos(0)
2389        self.setBreakPoint(len(srcFromMacroOutput))
2390       
2391        self.parse(assertEmptyStack=False)
2392
2393        self._src = origParseSrc
2394        self.setBreakPoint(origBreakPoint)
2395        self.setPos(origPos)               
2396
2397
2398        #self._compiler.addRawText('end')
2399       
2400    def eatCache(self):
2401        isLineClearToStartToken = self.isLineClearToStartToken()
2402        endOfFirstLinePos = self.findEOL()
2403        lineCol = self.getRowCol()
2404        self.getDirectiveStartToken()
2405        self.advance(len('cache'))
2406
2407        startPos = self.pos()
2408        argList = self.getDefArgList(useNameMapper=True)
2409        argList = self._applyExpressionFilters(argList, 'cache', startPos=startPos)
2410
2411        def startCache():
2412            cacheInfo = self._compiler.genCacheInfoFromArgList(argList)
2413            self._compiler.startCacheRegion(cacheInfo, lineCol)
2414
2415        if self.matchColonForSingleLineShortFormDirective():           
2416            self.advance() # skip over :
2417            self.getWhiteSpace(max=1)
2418            startCache()
2419            self.parse(breakPoint=self.findEOL(gobble=True))
2420            self._compiler.endCacheRegion()
2421        else:
2422            if self.peek()==':':
2423                self.advance()
2424            self.getWhiteSpace()           
2425            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2426            self.pushToOpenDirectivesStack('cache')
2427            startCache()       
2428
2429    def eatCall(self):
2430        # @@TR: need to enable single line version of this
2431        isLineClearToStartToken = self.isLineClearToStartToken()
2432        endOfFirstLinePos = self.findEOL()
2433        lineCol = self.getRowCol()
2434        self.getDirectiveStartToken()
2435        self.advance(len('call'))
2436        startPos = self.pos()
2437       
2438        useAutocallingOrig = self.setting('useAutocalling')
2439        self.setSetting('useAutocalling', False)
2440        self.getWhiteSpace()
2441        if self.matchCheetahVarStart():
2442            functionName = self.getCheetahVar()
2443        else:
2444            functionName = self.getCheetahVar(plain=True, skipStartToken=True)
2445        self.setSetting('useAutocalling', useAutocallingOrig)
2446        # @@TR: fix up filtering
2447        self._applyExpressionFilters(self[startPos:self.pos()], 'call', startPos=startPos)
2448
2449        self.getWhiteSpace()
2450        args = self.getExpression(pyTokensToBreakAt=[':']).strip()
2451        if self.matchColonForSingleLineShortFormDirective():
2452            self.advance() # skip over :
2453            self._compiler.startCallRegion(functionName, args, lineCol)
2454            self.getWhiteSpace(max=1)
2455            self.parse(breakPoint=self.findEOL(gobble=False))
2456            self._compiler.endCallRegion()
2457        else:
2458            if self.peek()==':':
2459                self.advance()
2460            self.getWhiteSpace()
2461            self.pushToOpenDirectivesStack("call")           
2462            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2463            self._compiler.startCallRegion(functionName, args, lineCol)
2464
2465    def eatCallArg(self):
2466        isLineClearToStartToken = self.isLineClearToStartToken()
2467        endOfFirstLinePos = self.findEOL()
2468        lineCol = self.getRowCol()
2469        self.getDirectiveStartToken()
2470
2471        self.advance(len('arg'))
2472        startPos = self.pos()
2473        self.getWhiteSpace()
2474        argName = self.getIdentifier()
2475        self.getWhiteSpace()
2476        argName = self._applyExpressionFilters(argName, 'arg', startPos=startPos)
2477        self._compiler.setCallArg(argName, lineCol)
2478        if self.peek() == ':':
2479            self.getc()
2480        else:       
2481            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2482
2483    def eatFilter(self):
2484        isLineClearToStartToken = self.isLineClearToStartToken()
2485        endOfFirstLinePos = self.findEOL()
2486
2487        self.getDirectiveStartToken()
2488        self.advance(len('filter'))
2489        self.getWhiteSpace()
2490        startPos = self.pos()
2491        if self.matchCheetahVarStart():
2492            isKlass = True
2493            theFilter = self.getExpression(pyTokensToBreakAt=[':'])
2494        else:
2495            isKlass = False
2496            theFilter = self.getIdentifier()
2497            self.getWhiteSpace()
2498        theFilter = self._applyExpressionFilters(theFilter, 'filter', startPos=startPos)           
2499
2500        if self.matchColonForSingleLineShortFormDirective():
2501            self.advance() # skip over :
2502            self.getWhiteSpace(max=1)           
2503            self._compiler.setFilter(theFilter, isKlass)
2504            self.parse(breakPoint=self.findEOL(gobble=False))
2505            self._compiler.closeFilterBlock()
2506        else:
2507            if self.peek()==':':
2508                self.advance()
2509            self.getWhiteSpace()
2510            self.pushToOpenDirectivesStack("filter")
2511            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2512            self._compiler.setFilter(theFilter, isKlass)       
2513
2514    def eatTransform(self):
2515        isLineClearToStartToken = self.isLineClearToStartToken()
2516        endOfFirstLinePos = self.findEOL()
2517
2518        self.getDirectiveStartToken()
2519        self.advance(len('transform'))
2520        self.getWhiteSpace()
2521        startPos = self.pos()
2522        if self.matchCheetahVarStart():
2523            isKlass = True
2524            transformer = self.getExpression(pyTokensToBreakAt=[':'])
2525        else:
2526            isKlass = False
2527            transformer = self.getIdentifier()
2528            self.getWhiteSpace()
2529        transformer = self._applyExpressionFilters(transformer, 'transform', startPos=startPos)
2530
2531        if self.peek()==':':
2532            self.advance()
2533        self.getWhiteSpace()
2534        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2535        self._compiler.setTransform(transformer, isKlass)
2536
2537       
2538    def eatErrorCatcher(self):
2539        isLineClearToStartToken = self.isLineClearToStartToken()
2540        endOfFirstLinePos = self.findEOL()
2541        self.getDirectiveStartToken()
2542        self.advance(len('errorCatcher'))
2543        self.getWhiteSpace()
2544        startPos = self.pos()
2545        errorCatcherName = self.getIdentifier()
2546        errorCatcherName = self._applyExpressionFilters(
2547            errorCatcherName, 'errorcatcher', startPos=startPos)
2548        self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)       
2549        self._compiler.setErrorCatcher(errorCatcherName)
2550
2551    def eatCapture(self):
2552        # @@TR:  this could be refactored to use the code in eatSimpleIndentingDirective
2553        # filtered
2554        isLineClearToStartToken = self.isLineClearToStartToken()
2555        endOfFirstLinePos = self.findEOL()
2556        lineCol = self.getRowCol()
2557
2558        self.getDirectiveStartToken()
2559        self.advance(len('capture'))       
2560        startPos = self.pos()
2561        self.getWhiteSpace()
2562
2563        expr = self.getExpression(pyTokensToBreakAt=[':'])
2564        expr = self._applyExpressionFilters(expr, 'capture', startPos=startPos)
2565        if self.matchColonForSingleLineShortFormDirective():
2566            self.advance() # skip over :
2567            self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
2568            self.getWhiteSpace(max=1)
2569            self.parse(breakPoint=self.findEOL(gobble=False))
2570            self._compiler.endCaptureRegion()
2571        else:
2572            if self.peek()==':':
2573                self.advance()
2574            self.getWhiteSpace()
2575            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLinePos)
2576            self.pushToOpenDirectivesStack("capture")
2577            self._compiler.startCaptureRegion(assignTo=expr, lineCol=lineCol)
2578       
2579
2580    def eatIf(self):
2581        # filtered
2582        isLineClearToStartToken = self.isLineClearToStartToken()
2583        endOfFirstLine = self.findEOL()
2584        lineCol = self.getRowCol()
2585        self.getDirectiveStartToken()
2586        startPos = self.pos()
2587       
2588        expressionParts = self.getExpressionParts(pyTokensToBreakAt=[':'])
2589        expr = ''.join(expressionParts).strip()
2590        expr = self._applyExpressionFilters(expr, 'if', startPos=startPos)
2591
2592        isTernaryExpr = ('then' in expressionParts and 'else' in expressionParts)
2593        if isTernaryExpr:
2594            conditionExpr = []
2595            trueExpr = []
2596            falseExpr = []
2597            currentExpr = conditionExpr
2598            for part in expressionParts:
2599                if part.strip()=='then':
2600                    currentExpr = trueExpr
2601                elif part.strip()=='else':
2602                    currentExpr = falseExpr
2603                else:
2604                    currentExpr.append(part)
2605                   
2606            conditionExpr = ''.join(conditionExpr)
2607            trueExpr = ''.join(trueExpr)
2608            falseExpr = ''.join(falseExpr)
2609            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)           
2610            self._compiler.addTernaryExpr(conditionExpr, trueExpr, falseExpr, lineCol=lineCol)
2611        elif self.matchColonForSingleLineShortFormDirective():
2612            self.advance() # skip over :
2613            self._compiler.addIf(expr, lineCol=lineCol)
2614            self.getWhiteSpace(max=1)
2615            self.parse(breakPoint=self.findEOL(gobble=True))           
2616            self._compiler.commitStrConst()           
2617            self._compiler.dedent()
2618        else:
2619            if self.peek()==':':
2620                self.advance()
2621            self.getWhiteSpace()               
2622            self._eatRestOfDirectiveTag(isLineClearToStartToken, endOfFirstLine)           
2623            self.pushToOpenDirectivesStack('if')
2624            self._compiler.addIf(expr, lineCol=lineCol)
2625
2626    ## end directive handlers
2627    def handleEndDef(self):
2628        isNestedDef = (self.setting('allowNestedDefScopes')
2629                       and [name for name in self._openDirectivesStack if name=='def'])
2630        if not isNestedDef:
2631            self._compiler.closeDef()
2632        else:
2633            # @@TR: temporary hack of useSearchList
2634            self.setSetting('useSearchList', self._useSearchList_orig)                   
2635            self._compiler.commitStrConst()
2636            self._compiler.dedent()
2637    ###
2638
2639    def pushToOpenDirectivesStack(self, directiveName):
2640        assert directiveName in self._closeableDirectives
2641        self._openDirectivesStack.append(directiveName)
2642
2643    def popFromOpenDirectivesStack(self, directiveName):
2644        if not self._openDirectivesStack:
2645            raise ParseError(self, msg="#end found, but nothing to end")
2646       
2647        if self._openDirectivesStack[-1] == directiveName:
2648            del self._openDirectivesStack[-1]
2649        else:
2650            raise ParseError(self, msg="#end %s found, expected #end %s" %(
2651                directiveName, self._openDirectivesStack[-1]))
2652
2653    def assertEmptyOpenDirectivesStack(self):
2654        if self._openDirectivesStack:
2655            errorMsg = (
2656                "Some #directives are missing their corresponding #end ___ tag: %s" %(
2657                ', '.join(self._openDirectivesStack)))
2658            raise ParseError(self, msg=errorMsg)
2659
2660##################################################
2661## Make an alias to export   
2662Parser = _HighLevelParser
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。