home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / XBMC_for_Windows-8.10.exe / system / python / spyce / spyceCompile.py < prev    next >
Encoding:
Python Source  |  2008-11-03  |  38.3 KB  |  1,083 lines

  1. ##################################################
  2. # SPYCE - Python-based HTML Scripting
  3. # Copyright (c) 2002 Rimon Barr.
  4. #
  5. # Refer to spyce.py
  6. # CVS: $Id: spyceCompile.py 5659 2006-04-27 16:15:15Z jwnmulder $
  7. ##################################################
  8.  
  9. #rimtodo:
  10. # - fix compaction (it assumed newlines parsed independently)
  11. # - active tags
  12.  
  13. #try:
  14. #  exec('import sre as re')  # due to stack limitations of sre
  15. #  # exec to be backwards compatible with Python 1.5
  16. #except:
  17. #  import re
  18. import re  # otherwise apache 2.0 pcre library conflicts
  19.            # we just can't win! either stack limits (sre), or 
  20.            # library conflicts (pre)! :)
  21.  
  22. from cStringIO import StringIO
  23. import sys, string, token, tokenize, os
  24. import spyceTag, spyceException, spyceUtil
  25.  
  26.  
  27. __doc__ = '''Compile Spyce files into Python code.'''
  28.  
  29. ##################################################
  30. # Special method names
  31. #
  32.  
  33. SPYCE_CLASS = 'spyceImpl'
  34. SPYCE_INIT_FUNC = 'spyceStart'
  35. SPYCE_DESTROY_FUNC = 'spyceFinish'
  36. SPYCE_PROCESS_FUNC = 'spyceProcess'
  37. SPYCE_GLOBAL_CODE = '__SPYCE_GLOBAL_CODE_CONSTANT'
  38. SPYCE_WRAPPER = '_spyceWrapper'
  39. DEFAULT_CODEPOINT = [SPYCE_PROCESS_FUNC]
  40.  
  41. ##################################################
  42. # Tokens
  43. #
  44.  
  45. T_ESC      = -2
  46. T_EOF      = -1
  47. T_TEXT     = 0
  48. T_EVAL     = 1
  49. T_STMT     = 2
  50. T_CHUNK    = 3
  51. T_CHUNKG   = 4
  52. T_DIRECT   = 5
  53. T_LAMBDA   = 6
  54. T_END      = 7
  55. T_CMNT     = 8
  56. T_END_CMNT = 9
  57.  
  58. TOKENS = (
  59.   # in the order that they should be tested
  60.   # (i.e. usually longest first)
  61.   (T_ESC,      r'\\\[\[', r'\\<%', r'\\\]\]', r'\\%>'),  # escapes
  62.   (T_CHUNK,    r'\[\[\\', r'<%\\'),                      # open chunk
  63.   (T_CHUNKG,   r'\[\[\\\\', r'<%\\\\'),                  # open global chunk
  64.   (T_EVAL,     r'\[\[=', r'<%='),                        # open eval
  65.   (T_DIRECT,   r'\[\[\.', r'<%\.'),                      # open directive
  66.   (T_LAMBDA,   r'\[\[spy', r'<%spy'),                    # open lambda
  67.   (T_CMNT,     r'\[\[--', r'<%--'),                      # open comment
  68.   (T_END_CMNT, r'--\]\]', r'--%>'),                      # close comment
  69.   (T_STMT,     r'\[\[', r'<%'),                          # open statement
  70.   (T_END,      r'\]\]', r'%>'),                          # close
  71. )
  72.  
  73. def genTokensRE(tokens):
  74.   regexp = []
  75.   typelookup = [None,]
  76.   for group in tokens:
  77.     type, matchstrings = group[0], group[1:]
  78.     for s in matchstrings:
  79.       regexp.append('(%s)' % s)
  80.       typelookup.append(type)
  81.   regexp = string.join(regexp, '|')
  82.   return re.compile(regexp, re.M), typelookup
  83.  
  84. RE_TOKENS = None
  85. TOKEN_TYPES = None
  86. if not RE_TOKENS:
  87.   RE_TOKENS, TOKEN_TYPES = genTokensRE(TOKENS)
  88.  
  89. def spyceTokenize(buf):
  90.   # scan using regexp
  91.   tokens = []
  92.   buflen = len(buf)
  93.   pos = 0
  94.   brow = bcol = erow = ecol = 0
  95.   while pos<buflen:
  96.     m = RE_TOKENS.search(buf, pos)
  97.     try:
  98.       mstart, mend = m.start(), m.end()
  99.       other, token = buf[pos:mstart], buf[mstart:mend]
  100.       if other:
  101.         tokens.append((T_TEXT, other, pos, mstart))
  102.       try:
  103.         type = TOKEN_TYPES[m.lastindex]
  104.       except AttributeError, e: 
  105.         # Python 1.5 does not support lastindex
  106.         lastindex = 1
  107.         for x in m.groups():
  108.           if x: break
  109.           lastindex = lastindex + 1
  110.         type = TOKEN_TYPES[lastindex]
  111.       if type==T_ESC:
  112.         token = token[1:]
  113.         type = T_TEXT
  114.       tokens.append((type, token, mstart, mend))
  115.       pos = mend
  116.     except AttributeError, e:
  117.       # handle text before EOF...
  118.       other = buf[pos:]
  119.       if other:
  120.         tokens.append((T_TEXT, other, pos, buflen))
  121.       pos = buflen
  122.   # compute row, col
  123.   brow, bcol = 1, 0
  124.   tokens2 = []
  125.   for type, text, begin, end in tokens:
  126.     lines = string.split(text[:-1], '\n')
  127.     numlines = len(lines)
  128.     erow = brow + numlines - 1
  129.     ecol = bcol
  130.     if numlines>1: ecol = 0
  131.     ecol = ecol + len(lines[-1])
  132.     tokens2.append((type, text, (brow, bcol), (erow, ecol)))
  133.     if text[-1]=='\n':
  134.       brow = erow + 1
  135.       bcol = 0
  136.     else:
  137.       brow = erow
  138.       bcol = ecol + 1
  139.   return tokens2
  140.  
  141.  
  142. def spyceTokenize4Parse(buf):
  143.   # add eof and reverse (so that you can pop() tokens)
  144.   tokens = spyceTokenize(buf)
  145.   if tokens:
  146.     _, _, _, end = tokens[-1]
  147.   tokens.append((T_EOF, '<EOF>', end, end))
  148.   tokens.reverse()
  149.   return tokens
  150.  
  151. def processMagic(buf):
  152.   if buf[:2]=='#!':
  153.     buf = string.join(string.split(buf, '\n')[1:], '\n')
  154.   return buf
  155.  
  156. ##################################################
  157. # Directives / Active Tags / Multi-line quotes
  158. #
  159.  
  160. DIRECTIVE_NAME = re.compile('[a-zA-Z][-a-zA-Z0-9_:]*')
  161. DIRECTIVE_ATTR = re.compile(
  162.     r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
  163.     r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./:;+*%?!&$\(\)_#=~]*))?')
  164. def parseDirective(text):
  165.   "Parse a Spyce directive into name and an attribute list."
  166.   attrs = {}
  167.   match = DIRECTIVE_NAME.match(text)
  168.   if not match: return None, {}
  169.   name = string.lower(text[:match.end()])
  170.   text = string.strip(text[match.end()+1:])
  171.   while text:
  172.     match = DIRECTIVE_ATTR.match(text)
  173.     if not match: break
  174.     attrname, rest, attrvalue = match.group(1, 2, 3)
  175.     if not rest: attrvalue = None
  176.     elif attrvalue[:1] == "'" == attrvalue[-1:] or \
  177.         attrvalue[:1] == '"' == attrvalue[-1:]:
  178.       attrvalue = attrvalue[1:-1]
  179.     attrs[string.lower(attrname)] = attrvalue
  180.     text = text[match.end()+1:]
  181.   return name, attrs
  182.  
  183. RE_LIB_TAG = re.compile(r'''
  184.   <                                    # beginning of tag
  185.   (?P<end>/?)                          # ending tag
  186.   (?P<lib>[a-zA-Z][-.a-zA-Z0-9_]*):    # lib name
  187.   (?P<name>[a-zA-Z][-.a-zA-Z0-9_]*)    # tag name
  188.   (?P<attrs>(?:\s+                     # attributes
  189.     (?:[a-zA-Z_][-.:a-zA-Z0-9_]*       # attribute name
  190.       (?:\s*=\s*                       # value indicator
  191.         (?:'[^']*'                     # LITA-enclosed value
  192.           |"[^"]*"                     # LIT-enclosed value
  193.           |[^'">\s]+                   # bare value
  194.         )
  195.       )?
  196.     )
  197.   )*)
  198.   \s*                                  # trailing whitespace
  199.   (?P<single>/?)                       # single / unpaired tag
  200.   >                                    # end of tag
  201. ''', re.VERBOSE)
  202.  
  203. def calcEndPos(begin, str):
  204.   if not str: raise 'empty string'
  205.   beginrow, begincol = begin
  206.   eol = 0
  207.   if str[-1]=='\n': 
  208.     str = str[:-1]+' '
  209.     eol = 1
  210.   lines = string.split(str, '\n')
  211.   endrow = beginrow + len(lines)-1
  212.   if endrow!=beginrow:
  213.     begincol = 0
  214.   endcol = begincol + len(lines[-1]) - 1
  215.   beginrow, begincol = endrow, endcol + 1
  216.   if eol:
  217.     begincol = 0
  218.     beginrow = beginrow + 1
  219.   return (endrow, endcol), (beginrow, begincol)
  220.  
  221. RE_MULTI_LINE_QUOTE_BEGIN = re.compile(r'r?'+"(''')|"+'(""")')
  222. def removeMultiLineQuotes(s):
  223.   def findMultiLineQuote(s):
  224.     quotelist = []
  225.     def eatToken(type, string, begin, end, _, quotelist=quotelist):
  226.       if type == token.STRING and RE_MULTI_LINE_QUOTE_BEGIN.match(string):
  227.         quotelist.append((string, begin,end))
  228.     tokenize.tokenize(StringIO(s).readline, eatToken)
  229.     return quotelist
  230.   def replaceRegionWithLine(s, begin, end, s2):
  231.     (beginrow, begincol), (endrow, endcol) = begin, end
  232.     beginrow, endrow = beginrow-1, endrow-1
  233.     s = string.split(s, '\n')
  234.     s1, s3 = s[:beginrow], s[endrow+1:]
  235.     s2 = s[beginrow][:begincol] + s2 + s[endrow][endcol:]
  236.     return string.join(s1 + [s2] + s3, '\n')
  237.   match = findMultiLineQuote(s)
  238.   offsets = {}
  239.   for _, (obr, _), (oer, _) in match:
  240.     offsets[obr] = oer - obr
  241.   while match:
  242.     s2, begin, end = match[0]
  243.     s = replaceRegionWithLine(s, begin, end, `eval(s2)`)
  244.     match = findMultiLineQuote(s)
  245.   return s, offsets
  246.  
  247. ##################################################
  248. # Pre-Python AST
  249. #
  250.  
  251. # ast node types
  252. AST_PY      = 0
  253. AST_PYEVAL  = 1
  254. AST_TEXT    = 2
  255. AST_COMPACT = 3
  256.  
  257. # compacting modes
  258. COMPACT_OFF   = 0
  259. COMPACT_LINE  = 1
  260. COMPACT_SPACE = 2
  261. COMPACT_FULL  = 3
  262.  
  263. class ppyAST:
  264.   "Generate a pre-Python AST"
  265.   def __init__(self):
  266.     "Initialise parser data structures, AST, token handlers, ..."
  267.     # set up ast
  268.     self._code = { 
  269.       'elements': {}, 
  270.       'leafs': [], 
  271.     }
  272.     self._codepoint = self._code
  273.     self._codepointname = []
  274.     self._mods = []
  275.     self._taglibs = {}
  276.   def getCode(self):
  277.     return self._code
  278.   def getModules(self):
  279.     return self._mods
  280.   def getTaglibs(self):
  281.     return self._taglibs
  282.   # routines to navigate AST
  283.   def selectCodepoint(self, codepointname):
  284.     code = self._code
  285.     for point in codepointname:
  286.       code = code['elements'][point]
  287.     self._codepoint = code
  288.     self._codepointname = codepointname
  289.   def getCodepoint(self):
  290.     return self._codepointname
  291.   def descendCodepoint(self, codepointSuffix):
  292.     self._codepointname.append(codepointSuffix)
  293.     self.selectCodepoint(self._codepointname)
  294.   def ascendCodepoint(self):
  295.     suffix = self._codepointname.pop()
  296.     self.selectCodepoint(self._codepointname)
  297.     return suffix
  298.   # routines that modify the ast
  299.   def appendCodepoint(self, codepointSuffix, firstline, ref=None):
  300.     self._codepoint['elements'][codepointSuffix] = {
  301.       'elements': {},
  302.       'leafs': [],
  303.     }
  304.     self.descendCodepoint(codepointSuffix)
  305.     self.addCode(string.strip(firstline), ref) # note: firstline is not indented
  306.   def addCode(self, code, ref=None):
  307.     self._codepoint['leafs'].append((AST_PY, code, ref))
  308.   def addGlobalCode(self, code, ref=None):
  309.     codepoint = self.getCodepoint()
  310.     self.selectCodepoint([SPYCE_GLOBAL_CODE])
  311.     self.addCode(code+'\n', ref)
  312.     self.selectCodepoint(codepoint)
  313.     pass
  314.   def addEval(self, eval, ref=None):
  315.     self._codepoint['leafs'].append((AST_PYEVAL, eval, ref))
  316.   def addCodeIndented(self, code, ref=None, globalcode=0):
  317.     code, replacelist = removeMultiLineQuotes(code)
  318.     # funky hack: put in NULLs to preserve indentation
  319.     #   NULLs don't appear in code, and the BraceConverter will
  320.     #   turn them back into spaces. If we leave them as spaces,
  321.     #   BraceConverter is just going to ignore them and pay attention
  322.     #   only to the braces. (not the best compile-time performance!)
  323.     code = string.split(code, '\n')
  324.     code = map(lambda l: (len(l)-len(string.lstrip(l)), l), code)
  325.     code = map(lambda (indent, l): chr(0)*indent + l, code)
  326.     code.append('')
  327.     # split code lines
  328.     (brow, bcol), (erow, ecol), text, file = ref
  329.     row = brow
  330.     for l in code:
  331.       cbcol = 0
  332.       cecol = len(l)
  333.       if row==brow: cbcol = bcol
  334.       if row==erow: cecol = ecol
  335.       try: row2 = row + replacelist[row-brow+1]
  336.       except: row2 = row
  337.       ref = (row, cbcol), (row2, cecol), l, file
  338.       if globalcode: self.addGlobalCode(l, ref)
  339.       else: self.addCode(l, ref)
  340.       row = row2 + 1
  341.   def addText(self, text, ref=None):
  342.     self._codepoint['leafs'].append((AST_TEXT, text, ref))
  343.   def addCompact(self, compact, ref):
  344.     self._codepoint['leafs'].append((AST_COMPACT, compact, ref))
  345.   def addModule(self, modname, modfrom, modas):
  346.     self._mods.append((modname, modfrom, modas))
  347.   def addTaglib(self, libname, libfrom=None, libas=None):
  348.     if not libas: libas=libname
  349.     self._taglibs[libas] = libname, libfrom
  350.  
  351.  
  352. ##################################################
  353. # Parse
  354. #
  355.  
  356. class spyceParse:
  357.   def __init__(self, server, buf, filename, sig):
  358.     try:
  359.       # initialization
  360.       self._tagChecker = spyceTag.spyceTagChecker(server)
  361.       self._load_spylambda = 0
  362.       self._load_taglib = 0
  363.       self._curdir, self._curfile = os.getcwd(), '<string>'
  364.       if filename:
  365.         self._curdir, self._curfile = os.path.split(filename)
  366.       if not self._curdir:
  367.         self._curdir = os.getcwd()
  368.       # prime ast
  369.       self._ast = ppyAST()
  370.       self._ast.selectCodepoint([])
  371.       self._ast.appendCodepoint(SPYCE_GLOBAL_CODE, '')
  372.       self._ast.addGlobalCode('''
  373. try:
  374.   exec('from __future__ import nested_scopes')
  375. except: pass
  376. from spyceException import spyceDone, spyceRedirect, spyceRuntimeException
  377.   ''')
  378.       # define spyceProcess
  379.       self._ast.selectCodepoint([])
  380.       self._ast.appendCodepoint(SPYCE_PROCESS_FUNC, 'def '+SPYCE_PROCESS_FUNC+'('+sig+')')
  381.       # spyceProcess pre
  382.       self._ast.selectCodepoint(DEFAULT_CODEPOINT)
  383.       self._ast.addCode('try:{', None)
  384.       self._ast.addCode('pass', None)
  385.       # spyceProcess body
  386.       self._tokens = spyceTokenize4Parse(processMagic(buf))
  387.       self._tokenType = None
  388.       self.popToken()
  389.       self.processSpyce()
  390.       # spyceProcess post
  391.       self._ast.addCode('} except spyceDone: pass', None)
  392.       self._ast.addCode('except spyceRedirect: raise', None)
  393.       self._ast.addCode('except KeyboardInterrupt: raise', None)
  394.       self._ast.addCode('except:{ raise spyceRuntimeException(%s) }'%SPYCE_WRAPPER, None)
  395.       # post processing
  396.       if self._load_taglib: self._ast.addModule('taglib', None, None)
  397.       if self._load_spylambda: self._ast.addModule('spylambda', None, None)
  398.       self._tagChecker.finish()
  399.     except spyceException.spyceSyntaxError, e:
  400.       raise
  401.       if e.info:
  402.         begin, end, text, _ = e.info
  403.         e.info = begin, end, text, self._curfile
  404.       raise e
  405.   def info(self):
  406.     return self._ast.getCode(), self._ast.getModules()
  407.   def popToken(self):
  408.     if self._tokenType!=T_EOF:
  409.       self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd = self._tokens.pop()
  410.  
  411.   def processSpyce(self):
  412.     while self._tokenType!=T_EOF:
  413.       [
  414.         self.processText,        # T_TEXT
  415.         self.processEval,        # T_EVAL
  416.         self.processStmt,        # T_STMT
  417.         self.processChunk,       # T_CHUNK
  418.         self.processGlobalChunk, # T_CHUNKG
  419.         self.processDirective,   # T_DIRECT
  420.         self.processUnexpected,  # T_LAMBDA
  421.         self.processUnexpected,  # T_END
  422.         self.processComment,     # T_CMNT
  423.         self.processUnexpected,  # T_END_CMNT
  424.       ][self._tokenType]()
  425.       self.popToken()
  426.   def processComment(self):
  427.     # collect comment
  428.     self.popToken()
  429.     while self._tokenType not in [T_END_CMNT, T_EOF]:
  430.       self.popToken()
  431.     if self._tokenType==T_EOF:
  432.       self.processUnexpected()
  433.   def processText(self):
  434.     "Process HTML (possibly with some active tags)"
  435.     html, begin, end = self._tokenText, self._tokenBegin, self._tokenEnd
  436.     m = RE_LIB_TAG.search(html)
  437.     while m:
  438.       plain = html[:m.start()]
  439.       if plain:
  440.         plain_end, tag_begin = calcEndPos(begin, plain)
  441.         self._ast.addText(plain, (begin, plain_end, '<html string>', self._curfile))
  442.       else: tag_begin = begin
  443.       tag = m.group(0)
  444.       tag_end, begin = calcEndPos(tag_begin, tag)
  445.       self.processActiveTag(tag, 
  446.         not not m.group('end'), m.group('lib'), m.group('name'), 
  447.         m.group('attrs'), not m.group('single'),
  448.         tag_begin, tag_end)
  449.       html = html[m.end():]
  450.       m = RE_LIB_TAG.search(html)
  451.     self._ast.addText(html, (begin, end, '<html string>', self._curfile))
  452.   def processActiveTag(self, tag, tagend, taglib, tagname, tagattrs, tagpair, begin, end):
  453.     "Process HTML tags"
  454.     # make sure prefix belongs to loaded taglibrary
  455.     if not self._ast._taglibs.has_key(taglib):
  456.       self._ast.addText(tag, (begin, end, '<html string>', self._curfile))
  457.       return
  458.     # parse process tag attributes
  459.     _, tagattrs = parseDirective('x '+tagattrs)
  460.     # syntax check
  461.     if not tagend: # start tag
  462.       self._tagChecker.startTag(self._ast._taglibs[taglib], tagname, tagattrs, tagpair, (begin, end, tag, self._curfile))
  463.     else: # end tag
  464.       self._tagChecker.endTag(self._ast._taglibs[taglib], tagname, (begin, end, tag, self._curfile))
  465.     # add tag python code
  466.     if tagpair:  # paired tag
  467.       if not tagend:  # open tag
  468.         self._ast.addCode('try: {', (begin, end, tag, self._curfile))
  469.         self._ast.addCode('taglib.tagPush(%s, %s, %s, %s)' % (
  470.             repr(taglib), repr(tagname), repr(tagattrs), repr(tagpair)), 
  471.           (begin, end, tag, self._curfile))
  472.         self._ast.addCode('try: {', (begin, end, tag, self._curfile))
  473.         self._ast.addCode('if taglib.tagBegin(): {',
  474.           (begin, end, tag, self._curfile))
  475.         self._ast.addCode('try: {', (begin, end, tag, self._curfile))
  476.         self._ast.addCode('while 1: {', (begin, end, tag, self._curfile))
  477.       else:  # close tag
  478.         self._ast.addCode('if not taglib.tagBody(): break', (begin, end, tag, self._curfile))
  479.         self._ast.addCode('}', (begin, end, tag, self._curfile))
  480.         self._ast.addCode('} finally: taglib.tagEnd()', (begin, end, tag, self._curfile))
  481.         self._ast.addCode('}', (begin, end, tag, self._curfile))
  482.         self._ast.addCode('} except: taglib.tagCatch()', (begin, end, tag, self._curfile))
  483.         self._ast.addCode('} finally: taglib.tagPop()', (begin, end, tag, self._curfile))
  484.     else: # singleton
  485.       self._ast.addCode('try: {', (begin, end, tag, self._curfile))
  486.       self._ast.addCode('taglib.tagPush(%s, %s, %s, %s)' % (
  487.           repr(taglib), repr(tagname), repr(tagattrs), repr(tagpair)), 
  488.         (begin, end, tag, self._curfile))
  489.       self._ast.addCode('try: {', (begin, end, tag, self._curfile))
  490.       self._ast.addCode('taglib.tagBegin()',
  491.         (begin, end, tag, self._curfile))
  492.       self._ast.addCode('} except: taglib.tagCatch()', (begin, end, tag, self._curfile))
  493.       self._ast.addCode('} finally: taglib.tagPop()', (begin, end, tag, self._curfile))
  494.   def processEval(self):
  495.     # collect expression
  496.     begin = self._tokenBegin
  497.     self.popToken()
  498.     expr = ''
  499.     while self._tokenType not in [T_END, T_EOF]:
  500.       if self._tokenType==T_TEXT:
  501.         expr = expr + self._tokenText
  502.       elif self._tokenType==T_LAMBDA:
  503.         expr = expr + self.processLambda()
  504.       else: self.processUnexpected()
  505.       self.popToken()
  506.     expr = string.strip(expr)
  507.     if not expr: self.processUnexpected()
  508.     # add expression to ast
  509.     self._ast.addEval(expr, (begin, self._tokenEnd, '='+expr, self._curfile))
  510.   def processStmt(self):
  511.     # collect statement
  512.     self.popToken()
  513.     beginrow, begincol = self._tokenBegin
  514.     stmt = ''
  515.     while self._tokenType not in [T_END, T_EOF]:
  516.       if self._tokenType==T_TEXT:
  517.         stmt = stmt + self._tokenText
  518.       elif self._tokenType==T_LAMBDA:
  519.         stmt = stmt + self.processLambda()
  520.       else: self.processUnexpected()
  521.       endrow, endcol = self._tokenEnd
  522.       self.popToken()
  523.     if not string.strip(stmt): self.processUnexpected()
  524.     # add statement to ast, row-by-row
  525.     currow = beginrow
  526.     lines = string.split(stmt, '\n')
  527.     for l in lines:
  528.       if currow==beginrow: curcolbegin = begincol
  529.       else: curcolbegin = 0
  530.       if currow==endrow: curcolend = endcol
  531.       else: curcolend = len(l)
  532.       l = string.strip(l)
  533.       if l:
  534.         self._ast.addCode(l, ((currow, curcolbegin), (currow, curcolend), l, self._curfile))
  535.       currow = currow + 1
  536.   def processChunk(self, globalChunk=0):
  537.     # collect chunk
  538.     self.popToken()
  539.     begin = self._tokenBegin
  540.     chunk = ''
  541.     while self._tokenType not in [T_END, T_EOF]:
  542.       if self._tokenType==T_TEXT:
  543.         chunk = chunk + self._tokenText
  544.       elif self._tokenType==T_LAMBDA:
  545.         chunk = chunk + self.processLambda()
  546.       else: self.processUnexpected()
  547.       end = self._tokenEnd
  548.       self.popToken()
  549.     chunk = string.split(chunk, '\n')
  550.     # eliminate initial blank lines
  551.     brow, bcol = begin
  552.     while chunk and not string.strip(chunk[0]):
  553.       chunk = chunk[1:]
  554.       brow = brow + 1
  555.       bcol = 0
  556.     begin = brow, bcol
  557.     if not chunk: self.processUnexpected()
  558.     # outdent chunk based on first line
  559.     # note: modifies multi-line strings having more spaces than first line outdent
  560.     #    by removing outdent number of spaces at the beginning of each line.
  561.     #    -- difficult to deal with efficiently (without parsing python) so just 
  562.     #    don't do this!
  563.     outdent = len(chunk[0]) - len(string.lstrip(chunk[0]))
  564.     for i in range(len(chunk)):
  565.       if string.strip(chunk[i][:outdent]):
  566.         chunk[i] = ' '*outdent + chunk[i]
  567.     chunk = map(lambda l, outdent=outdent: l[outdent:], chunk)
  568.     chunk = string.join(chunk, '\n')
  569.     # add chunk block at ast
  570.     if chunk:
  571.       try:
  572.         self._ast.addCodeIndented(chunk, (begin, end, chunk, self._curfile), globalChunk)
  573.       except tokenize.TokenError, e:
  574.         msg, _ = e
  575.         raise spyceException.spyceSyntaxError(msg, (begin, end, chunk, self._curfile) )
  576.   def processGlobalChunk(self):
  577.     self.processChunk(1)
  578.   def processDirective(self):
  579.     # collect directive
  580.     begin = self._tokenBegin
  581.     self.popToken()
  582.     directive = ''
  583.     while self._tokenType not in [T_END, T_EOF]:
  584.       if self._tokenType==T_TEXT:
  585.         directive = directive + self._tokenText
  586.       else: self.processUnexpected()
  587.       end = self._tokenEnd
  588.       self.popToken()
  589.     directive = string.strip(directive)
  590.     if not directive: self.processUnexpected()
  591.     # process directives
  592.     name, attrs = parseDirective(directive)
  593.     if name=='compact':
  594.       compact_mode = COMPACT_FULL
  595.       if attrs.has_key('mode'):
  596.         mode = string.lower(attrs['mode'])
  597.         if mode=='off':
  598.           compact_mode = COMPACT_OFF
  599.         elif mode=='line':
  600.           compact_mode = COMPACT_LINE
  601.         elif mode=='space':
  602.           compact_mode = COMPACT_SPACE
  603.         elif mode=='full':
  604.           compact_mode = COMPACT_FULL
  605.         else:
  606.           raise spyceException.spyceSyntaxError('invalid compacting mode "%s" specified'%mode, (begin, end, directive, self._curfile))
  607.       self._ast.addCompact(compact_mode, (begin, end, '<spyce compact directive>', self._curfile))
  608.     elif name in ('module', 'import'):
  609.       if not attrs.has_key('name') and not attrs.has_key('names'):
  610.         raise spyceException.spyceSyntaxError('name or names attribute required', (begin, end, directive, self._curfile) )
  611.       if attrs.has_key('names'):
  612.         mod_names = filter(None, map(string.strip, string.split(attrs['names'],',')))
  613.         for mod_name in mod_names:
  614.           self._ast.addModule(mod_name, None, None)
  615.           self._ast.addCode('%s.init()'%mod_name, (begin, end, directive, self._curfile))
  616.       else:
  617.         mod_name = attrs['name']
  618.         mod_from = spyceUtil.extractValue(attrs, 'from')
  619.         mod_as = spyceUtil.extractValue(attrs, 'as')
  620.         mod_args = spyceUtil.extractValue(attrs, 'args', '')
  621.         if mod_as: theName=mod_as
  622.         else: theName=mod_name
  623.         self._ast.addModule(mod_name, mod_from, mod_as)
  624.         self._ast.addCode('%s.init(%s)'%(theName,mod_args), (begin, end, directive, self._curfile))
  625.     elif name in ('taglib',):
  626.       if not attrs.has_key('name') and not attrs.has_key('names'):
  627.         raise spyceException.spyceSyntaxError('name or names attribute required', (begin, end, directive, self._curfile) )
  628.       fullfile = os.path.join(self._curdir, self._curfile)
  629.       if attrs.has_key('names'):
  630.         taglib_names = filter(None, map(string.strip, string.split(attrs['names'],',')))
  631.         for taglib_name in taglib_names:
  632.           self._tagChecker.loadLib(taglib_name, None, None, fullfile, (begin, end, directive, self._curfile))
  633.           self._ast.addTaglib(taglib_name)
  634.           self._load_taglib = 1
  635.           self._ast.addCode('taglib.load(%s)'%repr(taglib_name), (begin, end, directive, self._curfile))
  636.       else:
  637.         taglib_name = attrs['name']
  638.         taglib_from = spyceUtil.extractValue(attrs, 'from')
  639.         taglib_as = spyceUtil.extractValue(attrs, 'as')
  640.         self._tagChecker.loadLib(taglib_name, taglib_from, taglib_as, fullfile, (begin, end, directive, self._curfile))
  641.         self._ast.addTaglib(taglib_name, taglib_from, taglib_as)
  642.         self._load_taglib = 1
  643.         self._ast.addCode('taglib.load(%s, %s, %s)'%(repr(taglib_name), repr(taglib_from), repr(taglib_as)), (begin, end, directive, self._curfile))
  644.     elif name=='include':
  645.       if not attrs.has_key('file'):
  646.         raise spyceException.spyceSyntaxError('file attribute missing', (begin, end, directive, self._curfile) )
  647.       filename = os.path.join(self._curdir, attrs['file'])
  648.       f = None
  649.       try:
  650.         try:
  651.           f = open(filename)
  652.           buf = f.read()
  653.         finally:
  654.           if f: f.close()
  655.       except KeyboardInterrupt: raise
  656.       except:
  657.         raise spyceException.spyceSyntaxError('unable to open included file: %s'%filename, (begin, end, directive, self._curfile) )
  658.       prev = (self._curdir, self._curfile, self._tokens,
  659.         self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd)
  660.       self._curdir, self._curfile = os.path.dirname(filename), filename
  661.       self._tokens = spyceTokenize4Parse(processMagic(buf))
  662.       self.popToken()
  663.       self.processSpyce()
  664.       (self._curdir, self._curfile, self._tokens,
  665.         self._tokenType, self._tokenText, self._tokenBegin, self._tokenEnd) = prev
  666.     else:
  667.       raise spyceException.spyceSyntaxError('invalid spyce directive', (begin, end, directive, self._curfile) )
  668.   def processLambda(self):
  669.     # collect lambda
  670.     self.popToken()
  671.     begin = self._tokenBegin
  672.     lamb = ''
  673.     depth = 1
  674.     while self._tokenType!=T_EOF:
  675.       if self._tokenType in [T_END,]:
  676.         depth = depth - 1
  677.         if not depth: break
  678.         lamb = lamb + self._tokenText
  679.       elif self._tokenType in [T_EVAL, T_STMT, T_CHUNK, T_CHUNKG, T_DIRECT, T_LAMBDA]:
  680.         depth = depth + 1
  681.         lamb = lamb + self._tokenText
  682.       elif self._tokenType==T_CMNT:
  683.         self.processComment()
  684.       else:
  685.         lamb = lamb + self._tokenText
  686.       end = self._tokenEnd
  687.       self.popToken()
  688.     # process lambda
  689.     lamb = string.split(lamb, ':')
  690.     try:
  691.       params = lamb[0]
  692.       memoize = 0
  693.       if params and params[0]=='!':
  694.         params = params[1:]
  695.         memoize = 1
  696.       lamb = string.join(lamb[1:],':')
  697.     except:
  698.       raise spyceException.spyceSyntaxError('invalid spyce lambda', (begin, end, lamb, self._curfile))
  699.     self._load_spylambda = 1
  700.     lamb = 'spylambda.define(%s,%s,%d)' % (`string.strip(params)`, `lamb`, memoize)
  701.     return lamb
  702.   def processUnexpected(self):
  703.     raise spyceException.spyceSyntaxError('unexpected token: "%s"'%self._tokenText, 
  704.       (self._tokenBegin, self._tokenEnd, self._tokenText, self._curfile))
  705.  
  706. ##################################################
  707. # Peep-hole optimizer
  708. #
  709.  
  710. class spyceOptimize:
  711.   def __init__(self, ast):
  712.     self.compaction(ast)
  713.     self.sideBySideWrites(ast)
  714.     #self.splitCodeLines(ast)
  715.   def splitCodeLines(self, ast):
  716.     elements, leafs = ast['elements'], ast['leafs']
  717.     for el in elements.keys():
  718.       self.splitCodeLines(elements[el])
  719.     if leafs:
  720.       i = 0
  721.       while i<len(leafs):
  722.         row = 1
  723.         type, text, ref = leafs[i]
  724.         if type == AST_PY and ref:
  725.           (brow, bcol), (erow, ecol), code, file = ref
  726.           lines = string.split(code, '\n')
  727.           if code==text and len(lines)>1:
  728.             del leafs[i]
  729.             row = brow
  730.             for l in lines:
  731.               cbcol = 0
  732.               cecol = len(l)
  733.               if row==brow: cbcol = bcol
  734.               if row==erow: becol = ecol
  735.               leafs.insert(i+(brow-row), (AST_PY, l, ((row, cbcol), (row, cecol), l, file)))
  736.               row = row + 1
  737.         i = i + row
  738.  
  739.   def sideBySideWrites(self, ast):
  740.     elements, leafs = ast['elements'], ast['leafs']
  741.     for el in elements.keys():
  742.       self.sideBySideWrites(elements[el])
  743.     if leafs:
  744.       i = 0
  745.       while i+1<len(leafs):
  746.         type1, text1, ref1 = leafs[i]
  747.         type2, text2, ref2 = leafs[i+1]
  748.         file1 = None
  749.         file2 = None
  750.         if ref1:
  751.           _, _, _, file1 = ref1
  752.         if ref2:
  753.           _, _, _, file2 = ref2
  754.         if type1==AST_TEXT and type2==AST_TEXT and file1==file2:
  755.           text = text1 + text2
  756.           begin, _, orig, _ = ref1
  757.           _, end, _, _ = ref2
  758.           leafs[i] = AST_TEXT, text, (begin, end, orig, file1)
  759.           del leafs[i+1]
  760.           i = i - 1
  761.         i = i+1
  762.   def compaction(self, ast):
  763.     elements, leafs = ast['elements'], ast['leafs']
  764.     compact = COMPACT_LINE
  765.     for el in elements.keys():
  766.       self.compaction(elements[el])
  767.     if leafs:
  768.       i = 0
  769.       while i<len(leafs):
  770.         type, text, ref = leafs[i]
  771.         if type==AST_COMPACT:
  772.           compact = text
  773.         elif type==AST_TEXT:
  774.           # line compaction
  775.           if compact==COMPACT_LINE or compact==COMPACT_FULL:
  776.             # remove any trailing whitespace
  777.             text = string.split(text, '\n')
  778.             for j in range(len(text)-1):
  779.               text[j] = string.rstrip(text[j])
  780.             text = string.join(text, '\n')
  781.             # gobble the end of the line
  782.             ((row, _), _, _, file) = ref
  783.             rowtext = string.split(text, '\n')
  784.             if rowtext: rowtext = string.strip(rowtext[0])
  785.             crow = row ; cfile = file
  786.             j = i
  787.             while j and not rowtext:
  788.               j = j - 1
  789.               type2, text2, ref2 = leafs[j]
  790.               if ref2: (_, (crow, _), _, cfile) = ref2
  791.               if crow != row or file != cfile: break
  792.               if type2 == AST_TEXT:
  793.                 text2 = string.split(text2, '\n')
  794.                 if text2: text2 = text2[-1]
  795.                 rowtext = rowtext + string.strip(text2)
  796.               elif type2 == AST_PYEVAL:
  797.                 rowtext = 'x'
  798.             if not rowtext:
  799.               text = string.split(text, '\n')
  800.               if text and not string.strip(text[0]):
  801.                 text = text[1:]
  802.               text = string.join(text, '\n')
  803.             # gobble beginning of the line
  804.             (_, (row, _), _, file) = ref
  805.             rowtext = string.split(text, '\n')
  806.             if rowtext: rowtext = string.strip(rowtext[-1])
  807.             crow = row ; cfile = file
  808.             j = i + 1
  809.             while j<len(leafs) and not rowtext:
  810.               type2, text2, ref2 = leafs[j]
  811.               if ref2: ((crow, _), _, _, cfile) = ref2
  812.               if crow != row or file != cfile: break
  813.               if type2 == AST_TEXT:
  814.                 text2 = string.split(text2, '\n')
  815.                 if text2: text2 = text2[0]
  816.                 rowtext = rowtext + string.strip(text2)
  817.               elif type2 == AST_PYEVAL:
  818.                 rowtext = 'x'
  819.               j = j + 1
  820.             if not rowtext:
  821.               text = string.split(text, '\n')
  822.               if text: text[-1] = string.strip(text[-1])
  823.               text = string.join(text, '\n')
  824.           # space compaction
  825.           if compact==COMPACT_SPACE or compact==COMPACT_FULL:
  826.             text = spyceUtil.spaceCompact(text)
  827.           # update text, if any
  828.           if text: leafs[i] = type, text, ref
  829.           else: 
  830.             del leafs[i]
  831.             i = i -1
  832.         elif type in [AST_PY, AST_PYEVAL]:
  833.           pass
  834.         else:
  835.           raise 'error: unknown AST node type'
  836.         i = i + 1
  837.  
  838. ##################################################
  839. # Output classes
  840. #
  841.  
  842. class LineWriter:
  843.   "Output class that counts lines written."
  844.   def __init__(self, f, initialLine = 1):
  845.     self.f = f
  846.     self.lineno = initialLine
  847.   def write(self, s):
  848.     self.f.write(s)
  849.     self.lineno = self.lineno + len(string.split(s,'\n'))-1
  850.   def writeln(self, s):
  851.     self.f.write(s+'\n')
  852.   def close(self):
  853.     self.f.close()
  854.  
  855. class IndentingWriter:
  856.   "Output class that helps with indentation of code."
  857.   # Note: this writer is line-oriented.
  858.   def __init__(self, f, indentSize=2):
  859.     self._f = f
  860.     self._indentSize = indentSize
  861.     self._indent = 0
  862.     self._indentString = ' '*(self._indent*self._indentSize)
  863.     self._currentLine = ''
  864.   def close(self):
  865.     if self._indent > 0:
  866.       raise 'unmatched open brace'
  867.     self._f.close()
  868.   def indent(self):
  869.     self._indent = self._indent + 1
  870.     self._indentString = ' '*(self._indent*self._indentSize)
  871.   def outdent(self):
  872.     self._indent = self._indent - 1
  873.     if self._indent<0: 
  874.       raise 'unmatched close brace'
  875.     self._indentString = ' '*(self._indent*self._indentSize)
  876.   def dumpLine(self, s):
  877.     self._f.write(self._indentString+s+'\n')
  878.   def write(self, s):
  879.     self._currentLine = self._currentLine + s
  880.     lines = string.split(self._currentLine, '\n')
  881.     for l in lines[:-1]:
  882.       self.dumpLine(l)
  883.     self._currentLine=lines[-1]
  884.   def writeln(self, s=''):
  885.     self.write(s+'\n')
  886.   # remaining methods are defined in terms of writeln(), indent(), outdent()
  887.   def pln(self, s=''):
  888.     self.writeln(s)
  889.   def pIln(self, s=''):
  890.     self.indent(); self.pln(s)
  891.   def plnI(self, s=''):
  892.     self.pln(s); self.indent()
  893.   def pOln(self, s=''):
  894.     self.outdent(); self.pln(s)
  895.   def plnO(self, s=''):
  896.     self.pln(s); self.outdent()
  897.   def pOlnI(self, s=''):
  898.     self.outdent(); self.pln(s); self.indent()
  899.   def pIlnO(self, s=''):
  900.     self.indent(); self.pln(s); self.outdent()
  901.  
  902. ##################################################
  903. # Print out Braced Python
  904. #
  905.  
  906. class emitBracedPython:
  907.   def __init__(self, out, ast):
  908.     out = LineWriter(out)
  909.     self._spyceRefs = {}
  910.     # text compaction
  911.     self.compact = COMPACT_LINE
  912.     self._gobblelineNumber = 1
  913.     self._gobblelineText = ''
  914.     # do the deed!
  915.     self.emitSpyceRec(out, self._spyceRefs, None, ast['elements'], ast['leafs'][1:])
  916.   def getSpyceRefs(self):
  917.     return self._spyceRefs
  918.   def emitSpyceRec(self, out, spyceRefs, header, elements, leafs):
  919.     if header:
  920.       out.write(header+':{\n')
  921.     def processLevel(el, out=out, spyceRefs=spyceRefs, self=self):
  922.       self.emitSpyceRec(out, spyceRefs, el['leafs'][0][1], el['elements'], el['leafs'][1:])
  923.     try:
  924.       processLevel(elements[SPYCE_GLOBAL_CODE])
  925.       del elements[SPYCE_GLOBAL_CODE]
  926.     except KeyError: pass
  927.     for el in elements.keys():
  928.       processLevel(elements[el])
  929.     if leafs:
  930.       for type, text, ref in leafs:
  931.         line1 = out.lineno
  932.         if type==AST_TEXT:
  933.           out.write('response.writeStatic(%s)\n' % `text`)
  934.         elif type==AST_PY:
  935.           out.write(text+'\n')
  936.         elif type==AST_PYEVAL:
  937.           out.write('response.writeExpr(%s)\n' % text)
  938.         elif type==AST_COMPACT:
  939.           self.compact = text
  940.         else:
  941.           raise 'error: unknown AST node type'
  942.         line2 = out.lineno
  943.         if ref:
  944.           for l in range(line1, line2):
  945.             spyceRefs[l] = ref
  946.     if not leafs and not elements:
  947.       out.write('pass\n')
  948.     if header:
  949.       out.write('}\n')
  950.  
  951. ##################################################
  952. # Print out regular Python
  953. #
  954.  
  955. class BraceConverter:
  956.   "Convert Python with braces into indented (normal) Python code."
  957.   def __init__(self, out):
  958.     self.out = IndentingWriter(out)
  959.     self.prevname = 0
  960.     self.prevstring = 0
  961.     self.dictlevel = 0
  962.   def emitToken(self, type, string):
  963.     if type==token.NAME:
  964.       if self.prevname: self.out.write(' ')
  965.       if self.prevstring: self.out.write(' ')
  966.       self.out.write(string)
  967.     elif type==token.STRING:
  968.       if self.prevname: self.out.write(' ')
  969.       string  = `eval(string)`  # get rid of multi-line strings
  970.       self.out.write(string)
  971.     elif type==token.NUMBER:
  972.       if self.prevname: self.out.write(' ')
  973.       self.out.write(string)
  974.     elif type==token.OP:
  975.       if string=='{': 
  976.         if self.prevcolon and not self.dictlevel:
  977.           self.out.plnI()
  978.         else:
  979.           self.dictlevel = self.dictlevel + 1
  980.           self.out.write(string)
  981.       elif string=='}':
  982.         if not self.dictlevel:
  983.           self.out.plnO()
  984.         else:
  985.           self.dictlevel = self.dictlevel - 1
  986.           self.out.write(string)
  987.       else:
  988.         self.out.write(string)
  989.     elif type==token.ERRORTOKEN and string==chr(0):
  990.       self.out.write(' ')
  991.     else:
  992.       #print type, token.tok_name[type], `string`
  993.       self.out.write(string)
  994.     self.prevname = type==token.NAME
  995.     self.prevstring = type==token.STRING
  996.     self.prevcolon = type==token.OP and string==':'
  997.  
  998. def emitPython(out, bracedPythonCode, spyceRefs):
  999.   out = LineWriter(out)
  1000.   spyceRefs2 = {}
  1001.   braceConv = BraceConverter(out)
  1002.   def eatToken(type, string, begin, end, _, out=out, braceConv=braceConv, spyceRefs=spyceRefs, spyceRefs2=spyceRefs2):
  1003.     try:
  1004.       beginrow, _ = begin
  1005.       line1 = out.lineno
  1006.       braceConv.emitToken(type, string)
  1007.       line2 = out.lineno
  1008.       if spyceRefs.has_key(beginrow):
  1009.         for l in range(line1, line2):
  1010.           spyceRefs2[l] = spyceRefs[beginrow]
  1011.     except:
  1012.       raise spyceException.spyceSyntaxError(sys.exc_info()[0])
  1013.   try:
  1014.     tokenize.tokenize(StringIO(bracedPythonCode).readline, eatToken)
  1015.   except tokenize.TokenError, e:
  1016.     msg, (row, col) = e
  1017.     raise spyceException.spyceSyntaxError(msg)
  1018.   return spyceRefs2
  1019.  
  1020. def calcRowCol(str, pos):
  1021.   lines = string.split(str, '\n')
  1022.   row = 1
  1023.   while pos > len(lines[0]):
  1024.     pos = pos - len(lines[0]) - 1
  1025.     del lines[0]
  1026.     row = row + 1
  1027.   return row, pos
  1028.  
  1029. RE_BRACES = re.compile('{|}')
  1030. def checkBalancedParens(str, refs):
  1031.   m = RE_BRACES.search(str)
  1032.   stack = []
  1033.   try:
  1034.     while m:
  1035.       if m.group(0)=='{': stack.append(m)
  1036.       else: stack.pop()
  1037.       m = RE_BRACES.search(str, m.end())
  1038.   except IndexError: 
  1039.     row, _ = calcRowCol(str, m.start())
  1040.     try: info = refs[row]
  1041.     except KeyError: info =None
  1042.     raise spyceException.spyceSyntaxError("unbalanced open brace '{'", info)
  1043.   if stack:
  1044.     m = stack[-1]
  1045.     row, _ = calcRowCol(str, m.start())
  1046.     try: info = refs[row]
  1047.     except KeyError: info =None
  1048.     raise spyceException.spyceSyntaxError("unbalanced close brace '}'", info)
  1049.  
  1050. ##############################################
  1051. # Compile spyce files
  1052. #
  1053.  
  1054. def spyceCompile(buf, filename, sig, server):
  1055.   # parse 
  1056.   ast, libs = spyceParse(server, buf, filename, sig).info()
  1057.   # optimize the ast
  1058.   spyceOptimize(ast)
  1059.   # generate braced code
  1060.   out = StringIO()
  1061.   refs = emitBracedPython(out, ast).getSpyceRefs()
  1062.   # then, generate regular python code
  1063.   bracedPythonCode = out.getvalue()
  1064.   checkBalancedParens(bracedPythonCode, refs)
  1065.   out = StringIO()
  1066.   refs = emitPython(out, bracedPythonCode, refs)
  1067.   return out.getvalue(), refs, libs
  1068.  
  1069. def test():
  1070.   import spyce
  1071.   f = open(sys.argv[1])
  1072.   spycecode = f.read()
  1073.   f.close()
  1074.   tokens = spyceTokenize(processMagic(spycecode))
  1075.   for type, text, begin, end in tokens:
  1076.     print '%s (%s, %s): %s' % (type, begin, end, `text`)
  1077.   pythoncode, refs, libs = spyceCompile(spycecode, sys.argv[1], '', spyce.getServer())
  1078.   print pythoncode
  1079.  
  1080. if __name__ == '__main__':
  1081.   test()
  1082.  
  1083.