[ZPT] CVS: Packages/TAL - TALInterpreter.py:1.46

fred@digicool.com fred@digicool.com
Thu, 7 Jun 2001 14:02:13 -0400 (EDT)


Update of /cvs-repository/Packages/TAL
In directory korak.digicool.com:/tmp/cvs-serv16059

Modified Files:
	TALInterpreter.py 
Log Message:
Lots of micro-performance adjustments using conventional Python
techniques of reducing the number of lookups and avoiding method
calls.

More interestingly, the dispatch machinery has changed, especially
TALInterpreter.iterpret().  Each instruction is now composed of a
2-tuple of the form (opcode, (arg1, arg2, ...)).  This is important as
it allows more efficient unpacking and construction of the argument
list for the call to the handlers for individual bytecodes.

The bytecode handlers are also located differently.  Instead of using
the opcode string to construct a method name, retrieve a bound method,
and then calling it via apply(), a dictionary of handler functions is
created as part of the class definition.  Handlers are retrieved from
this dictionary by opcode string (avoiding string concatenation and
bound method creation in the inner dispatch loop).  The handlers (now
functions rather than methods) are then called using apply(), creating
the arguments to the handler by tuple concatenation, which is faster
than creating the bound method and then using the slower
method-calling machinery.  Temporary variables are avoided whenever
possible.

The test for "debug mode" in TALInterpreter.interpret() was moved out
of the dispatch loop; it is used to select one of the two versions of
the loop.

Support has been added for two new bytecodes:
  rawtextColumn -- used when the TALGenerator can determine the
                   resulting column number ahead of time (the text
                   includes a newline)

  rawtextOffset -- used when the TALGenerator cannot determine the
                   final column of the text (there is no newline).

These new bytecodes allow the interpreter to avoid having to search
for a newline for all rawtext instructions -- the compiler is able to
determine which of these two cases is appropriate for each rawtext
chunk -- the old 'rawtext' instruction is no longer generated.

Re-phrased some conditions in if statements to allow more
short-circuiting.



--- Updated File TALInterpreter.py in package Packages/TAL --
--- TALInterpreter.py	2001/05/18 22:18:03	1.45
+++ TALInterpreter.py	2001/06/07 18:02:13	1.46
@@ -87,11 +87,16 @@
 """
 
 import sys
-import string
 import getopt
 import cgi
 
+from string import join, lower, rfind
 try:
+    from strop import lower, rfind
+except ImportError:
+    pass
+
+try:
     from cStringIO import StringIO
 except ImportError:
     from StringIO import StringIO
@@ -148,6 +153,7 @@
             self.repldict = None
         return TALGenerator.replaceAttrs(self, attrlist, repldict)
 
+
 class TALInterpreter:
 
     def __init__(self, program, macros, engine, stream=None,
@@ -159,6 +165,7 @@
         self.TALESError = engine.getTALESError()
         self.Default = engine.getDefault()
         self.stream = stream or sys.stdout
+        self._stream_write = self.stream.write
         self.debug = debug
         self.wrap = wrap
         self.metal = metal
@@ -183,12 +190,14 @@
 
     def restoreState(self, state):
         (self.position, self.col, self.stream, scopeLevel, level) = state
+        self._stream_write = self.stream.write
         assert self.level == level
         while self.scopeLevel > scopeLevel:
             self.do_endScope()
 
     def restoreOutputState(self, state):
         (dummy, self.col, self.stream, scopeLevel, level) = state
+        self._stream_write = self.stream.write
         assert self.level == level
         assert self.scopeLevel == scopeLevel
 
@@ -216,35 +225,44 @@
         assert self.level == 0
         assert self.scopeLevel == 0
         if self.col > 0:
-            self.stream_write("\n")
+            self._stream_write("\n")
+            self.col = 0
 
     def stream_write(self, s):
-        self.stream.write(s)
-        i = string.rfind(s, '\n')
+        self._stream_write(s)
+        i = rfind(s, '\n')
         if i < 0:
             self.col = self.col + len(s)
         else:
             self.col = len(s) - (i + 1)
 
+    bytecode_handlers = {}
+
     def interpret(self, program):
         self.level = self.level + 1
+        handlers = self.bytecode_handlers
+        _apply = apply
+        _tuple = tuple
+        tup = (self,)
         try:
-            for item in program:
-                methodName = "do_" + item[0]
-                args = item[1:]
-                if self.debug:
-                    s = "%s%s%s\n" % ("    "*self.level, methodName,
+            if self.debug:
+                for (opcode, args) in program:
+                    s = "%sdo_%s%s\n" % ("    "*self.level, opcode,
                                       repr(args))
                     if len(s) > 80:
                         s = s[:76] + "...\n"
                     sys.stderr.write(s)
-                method = getattr(self, methodName)
-                apply(method, args)
+                    _apply(handlers[opcode], tup + args)
+            else:
+                for (opcode, args) in program:
+                    _apply(handlers[opcode], tup + args)
         finally:
             self.level = self.level - 1
+            del tup
 
     def do_version(self, version):
         assert version == TAL_VERSION
+    bytecode_handlers["version"] = do_version
 
     def do_mode(self, mode):
         assert mode in ("html", "xml")
@@ -253,27 +271,30 @@
             self.endsep = " />"
         else:
             self.endsep = "/>"
+    bytecode_handlers["mode"] = do_mode
 
     def do_setPosition(self, position):
         self.position = position
+    bytecode_handlers["setPosition"] = do_setPosition
 
     def do_startEndTag(self, name, attrList):
-        self.startTagCommon(name, attrList, self.endsep)
-
-    def do_startTag(self, name, attrList):
-        self.startTagCommon(name, attrList, ">")
+        self.do_startTag(name, attrList, self.endsep)
+    bytecode_handlers["startEndTag"] = do_startEndTag
 
-    def startTagCommon(self, name, attrList, end):
+    def do_startTag(self, name, attrList, end=">"):
         if not attrList:
-            self.stream_write("<%s%s" % (name, end))
+            s = "<%s%s" % (name, end)
+            self.do_rawtextOffset(s, len(s))
             return
-        self.stream_write("<" + name)
-        align = self.col+1
+        _len = len
+        self._stream_write("<" + name)
+        self.col = self.col + _len(name) + 1
+        align = self.col + 1 + _len(name)
         if align >= self.wrap/2:
             align = 4 # Avoid a narrow column far to the right
         for item in attrList:
-            if len(item) == 2:
-                name, value = item[:2]
+            if _len(item) == 2:
+                name, value = item
             else:
                 ok, name, value = self.attrAction(item)
                 if not ok:
@@ -284,11 +305,19 @@
                 s = "%s=%s" % (name, quote(value))
             if (self.wrap and
                 self.col >= align and
-                self.col + 1 + len(s) > self.wrap):
-                self.stream_write("\n" + " "*align + s)
+                self.col + 1 + _len(s) > self.wrap):
+                self._stream_write("\n" + " "*align)
+                self.col = self.col + align
+            else:
+                s = " " + s
+            self._stream_write(s)
+            if "\n" in s:
+                self.col = _len(s) - (rfind(s, "\n") + 1)
             else:
-                self.stream_write(" " + s)
-        self.stream_write(end)
+                self.col = self.col + _len(s)
+        self._stream_write(end)
+        self.col = self.col + _len(end)
+    bytecode_handlers["startTag"] = do_startTag
 
     actionIndex = {"replace":0, "insert":1, "metal":2, "tal":3, "xmlns":4}
     def attrAction(self, item):
@@ -301,15 +330,15 @@
             return 0, name, value
         ok = 1
         if action <= 1 and self.tal:
-            if self.html and string.lower(name) in BOOLEAN_HTML_ATTRS:
+            if self.html and lower(name) in BOOLEAN_HTML_ATTRS:
                 evalue = self.engine.evaluateBoolean(item[3])
                 if evalue is self.Default:
                     if action == 1: # Cancelled insert
                         ok = 0
-                elif not evalue:
-                    ok = 0
-                else:
+                elif evalue:
                     value = None
+                else:
+                    ok = 0
             else:
                 evalue = self.engine.evaluateText(item[3])
                 if evalue is self.Default:
@@ -320,7 +349,7 @@
                     if value is None:
                         ok = 0
         elif action == 2 and self.metal:
-            i = string.rfind(name, ":") + 1
+            i = rfind(name, ":") + 1
             prefix, suffix = name[:i], name[i:]
             ##self.dumpMacroStack(prefix, suffix, value)
             what, macroName, slots = self.macroStack[-1]
@@ -355,31 +384,37 @@
         sys.stderr.write("+--------------------------------------\n")
 
     def do_endTag(self, name):
-        self.stream_write("</%s>" % name)
+        s = "</%s>" % name
+        self._stream_write(s)
+        self.col = self.col + len(s)
+    bytecode_handlers["endTag"] = do_endTag
 
     def do_beginScope(self):
         self.engine.beginScope()
         self.scopeLevel = self.scopeLevel + 1
+    bytecode_handlers["beginScope"] = do_beginScope
 
     def do_endScope(self):
         self.engine.endScope()
         self.scopeLevel = self.scopeLevel - 1
+    bytecode_handlers["endScope"] = do_endScope
 
     def do_setLocal(self, name, expr):
-        if not self.tal:
-            return
-        value = self.engine.evaluateValue(expr)
-        self.engine.setLocal(name, value)
+        if self.tal:
+            value = self.engine.evaluateValue(expr)
+            self.engine.setLocal(name, value)
+    bytecode_handlers["setLocal"] = do_setLocal
 
     def do_setGlobal(self, name, expr):
-        if not self.tal:
-            return
-        value = self.engine.evaluateValue(expr)
-        self.engine.setGlobal(name, value)
+        if self.tal:
+            value = self.engine.evaluateValue(expr)
+            self.engine.setGlobal(name, value)
+    bytecode_handlers["setGlobal"] = do_setGlobal
 
     def do_rawAttrs(self, dict):
         if self.tal:
             self.engine.setLocal("attrs", dict)
+    bytecode_handlers["rawAttrs"] = do_rawAttrs
 
     def do_insertText(self, expr, block):
         if not self.tal:
@@ -391,8 +426,8 @@
         if text is self.Default:
             self.interpret(block)
             return
-        text = cgi.escape(text)
-        self.stream_write(text)
+        self.stream_write(cgi.escape(text))
+    bytecode_handlers["insertText"] = do_insertText
 
     def do_insertStructure(self, expr, repldict, block):
         if not self.tal:
@@ -405,7 +440,7 @@
             self.interpret(block)
             return
         text = str(structure)
-        if not repldict and not self.strictinsert:
+        if not (repldict or self.strictinsert):
             # Take a shortcut, no error checking
             self.stream_write(text)
             return
@@ -413,6 +448,7 @@
             self.insertHTMLStructure(text, repldict)
         else:
             self.insertXMLStructure(text, repldict)
+    bytecode_handlers["insertStructure"] = do_insertStructure
 
     def insertHTMLStructure(self, text, repldict):
         from HTMLTALParser import HTMLTALParser
@@ -442,13 +478,22 @@
         iterator = self.engine.setRepeat(name, expr)
         while iterator.next():
             self.interpret(block)
+    bytecode_handlers["loop"] = do_loop
 
-    def do_rawtext(self, text):
-        self.stream_write(text)
+    def do_rawtextColumn(self, s, col):
+        self._stream_write(s)
+        self.col = col
+    bytecode_handlers["rawtextColumn"] = do_rawtextColumn
+
+    def do_rawtextOffset(self, s, offset):
+        self._stream_write(s)
+        self.col = self.col + offset
+    bytecode_handlers["rawtextOffset"] = do_rawtextOffset
 
     def do_condition(self, condition, block):
         if not self.tal or self.engine.evaluateBoolean(condition):
             self.interpret(block)
+    bytecode_handlers["condition"] = do_condition
 
     def do_defineMacro(self, macroName, macro):
         if not self.metal:
@@ -457,6 +502,7 @@
         self.pushMacro("define-macro", macroName, None)
         self.interpret(macro)
         self.popMacro()
+    bytecode_handlers["defineMacro"] = do_defineMacro
 
     def do_useMacro(self, macroName, macroExpr, compiledSlots, block):
         if not self.metal:
@@ -477,6 +523,7 @@
         self.pushMacro("use-macro", macroName, compiledSlots)
         self.interpret(macro)
         self.popMacro()
+    bytecode_handlers["useMacro"] = do_useMacro
 
     def do_fillSlot(self, slotName, block):
         if not self.metal:
@@ -485,6 +532,7 @@
         self.pushMacro("fill-slot", slotName, None)
         self.interpret(block)
         self.popMacro()
+    bytecode_handlers["fillSlot"] = do_fillSlot
 
     def do_defineSlot(self, slotName, block):
         if not self.metal:
@@ -500,6 +548,7 @@
         else:
             self.interpret(block)
         self.popMacro()
+    bytecode_handlers["defineSlot"] = do_defineSlot
 
     def do_onError(self, block, handler):
         if not self.tal:
@@ -507,6 +556,7 @@
             return
         state = self.saveState()
         self.stream = stream = StringIO()
+        self._stream_write = stream.write
         try:
             self.interpret(block)
         except self.TALESError, err:
@@ -521,6 +571,8 @@
         else:
             self.restoreOutputState(state)
             self.stream_write(stream.getvalue())
+    bytecode_handlers["onError"] = do_onError
+
 
 def test():
     from driver import FILE, parsefile