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