Commit 9e161e97 authored by Fred Drake's avatar Fred Drake

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