[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