[Checkins] SVN: zope.tal/branches/pypyzpt/src/zope/tal/
test_talinterpreter runs above rpython
Godefroid Chapelle
gotcha at bubblenet.be
Wed Jun 6 10:33:28 EDT 2007
Log message for revision 76424:
test_talinterpreter runs above rpython
Changed:
D zope.tal/branches/pypyzpt/src/zope/tal/pypy/
A zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/
A zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/__init__.py
A zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/applevel.py
D zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter.py
A zope.tal/branches/pypyzpt/src/zope/tal/talpypy/
A zope.tal/branches/pypyzpt/src/zope/tal/talpypy/HOWTO.txt
A zope.tal/branches/pypyzpt/src/zope/tal/talpypy/__init__.py
D zope.tal/branches/pypyzpt/src/zope/tal/talpypy/talinterpreter/
A zope.tal/branches/pypyzpt/src/zope/tal/talpypy/talinterpreter/
D zope.tal/branches/pypyzpt/src/zope/tal/talpypy/test/
A zope.tal/branches/pypyzpt/src/zope/tal/talpypy/test/
U zope.tal/branches/pypyzpt/src/zope/tal/tests/test_talinterpreter.py
-=-
Added: zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/__init__.py
===================================================================
--- zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/__init__.py (rev 0)
+++ zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/__init__.py 2007-06-06 14:33:28 UTC (rev 76424)
@@ -0,0 +1,12 @@
+from pypy.interpreter.mixedmodule import MixedModule
+
+class Module(MixedModule):
+ """interpreter module."""
+
+ interpleveldefs = {
+ }
+
+ appleveldefs = {
+ 'TALInterpreter' : 'applevel.TALInterpreter',
+ 'normalize' : 'applevel.normalize',
+ }
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/applevel.py (from rev 76390, zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter.py)
===================================================================
--- zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/applevel.py (rev 0)
+++ zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter/applevel.py 2007-06-06 14:33:28 UTC (rev 76424)
@@ -0,0 +1,967 @@
+##############################################################################
+#
+# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Interpreter for a pre-compiled TAL program.
+
+$Id$
+"""
+import cgi
+import sys
+
+
+from zope.i18nmessageid import Message
+from zope.tal.taldefs import quote, TAL_VERSION, METALError
+from zope.tal.taldefs import isCurrentVersion
+from zope.tal.taldefs import getProgramVersion, getProgramMode
+from zope.tal.translationcontext import TranslationContext
+from zope.tal.alttalgenerator import AltTALGenerator
+
+
+# Avoid constructing this tuple over and over
+I18nMessageTypes = (Message,)
+
+TypesToTranslate = I18nMessageTypes + (str, unicode)
+
+BOOLEAN_HTML_ATTRS = frozenset([
+ # List of Boolean attributes in HTML that should be rendered in
+ # minimized form (e.g. <img ismap> rather than <img ismap="">)
+ # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
+ # TODO: The problem with this is that this is not valid XML and
+ # can't be parsed back!
+ "compact", "nowrap", "ismap", "declare", "noshade", "checked",
+ "disabled", "readonly", "multiple", "selected", "noresize",
+ "defer"
+])
+
+_nulljoin = ''.join
+_spacejoin = ' '.join
+
+def normalize(text):
+ # Now we need to normalize the whitespace in implicit message ids and
+ # implicit $name substitution values by stripping leading and trailing
+ # whitespace, and folding all internal whitespace to a single space.
+ return _spacejoin(text.split())
+
+
+
+class MacroStackItem(object):
+ def __init__(self, macroName, slots, definingName, extending, entering, i18nContext):
+ self.macroName = macroName
+ self.slots = slots
+ self.definingName = definingName
+ self.extending = extending
+ self.entering = entering
+ self.i18nContext = i18nContext
+
+class TALInterpreter(object):
+ """TAL interpreter.
+
+ Some notes on source annotations. They are HTML/XML comments added to the
+ output whenever sourceFile is changed by a setSourceFile bytecode. Source
+ annotations are disabled by default, but you can turn them on by passing a
+ sourceAnnotations argument to the constructor. You can change the format
+ of the annotations by overriding formatSourceAnnotation in a subclass.
+
+ The output of the annotation is delayed until some actual text is output
+ for two reasons:
+
+ 1. setPosition bytecode follows setSourceFile, and we need position
+ information to output the line number.
+ 2. Comments are not allowed in XML documents before the <?xml?>
+ declaration.
+
+ For performance reasons (TODO: premature optimization?) instead of checking
+ the value of _pending_source_annotation on every write to the output
+ stream, the _stream_write attribute is changed to point to
+ _annotated_stream_write method whenever _pending_source_annotation is
+ set to True, and to _stream.write when it is False. The following
+ invariant always holds:
+
+ if self._pending_source_annotation:
+ assert self._stream_write is self._annotated_stream_write
+ else:
+ assert self._stream_write is self.stream.write
+
+ """
+
+ def __init__(self, program, macros, engine, stream=None,
+ debug=0, wrap=60, metal=1, tal=1, showtal=-1,
+ strictinsert=1, stackLimit=100, i18nInterpolate=1,
+ sourceAnnotations=0, altgenclass=AltTALGenerator):
+ """Create a TAL interpreter.
+
+ Optional arguments:
+
+ stream -- output stream (defaults to sys.stdout).
+
+ debug -- enable debugging output to sys.stderr (off by default).
+
+ wrap -- try to wrap attributes on opening tags to this number of
+ column (default: 60).
+
+ metal -- enable METAL macro processing (on by default).
+
+ tal -- enable TAL processing (on by default).
+
+ showtal -- do not strip away TAL directives. A special value of
+ -1 (which is the default setting) enables showtal when TAL
+ processing is disabled, and disables showtal when TAL processing is
+ enabled. Note that you must use 0, 1, or -1; true boolean values
+ are not supported (TODO: why?).
+
+ strictinsert -- enable TAL processing and stricter HTML/XML
+ checking on text produced by structure inserts (on by default).
+ Note that Zope turns this value off by default.
+
+ stackLimit -- set macro nesting limit (default: 100).
+
+ i18nInterpolate -- enable i18n translations (default: on).
+
+ sourceAnnotations -- enable source annotations with HTML comments
+ (default: off).
+
+ """
+ self.program = program
+ self.engine = engine # Execution engine (aka context)
+ self.Default = engine.getDefault()
+ self._pending_source_annotation = False
+ self._currentTag = ""
+ self._stream_stack = [stream or sys.stdout]
+ self.popStream()
+ self.debug = debug
+ self.wrap = wrap
+ self.metal = metal
+ self.tal = tal
+ if tal:
+ self.dispatch = self.bytecode_handlers_tal
+ else:
+ self.dispatch = self.bytecode_handlers
+ assert showtal in (-1, 0, 1)
+ if showtal == -1:
+ showtal = (not tal)
+ self.showtal = showtal
+ self.strictinsert = strictinsert
+ self.stackLimit = stackLimit
+ self.html = 0
+ self.endsep = "/>"
+ self.endlen = len(self.endsep)
+ # macroStack entries are MacroStackItem instances;
+ # the entries are mutated while on the stack
+ self.macroStack = []
+ # `inUseDirective` is set iff we're handling either a
+ # metal:use-macro or a metal:extend-macro
+ self.inUseDirective = False
+ self.position = None, None # (lineno, offset)
+ self.col = 0
+ self.level = 0
+ self.scopeLevel = 0
+ self.sourceFile = None
+ self.i18nStack = []
+ self.i18nInterpolate = i18nInterpolate
+ self.i18nContext = TranslationContext()
+ self.sourceAnnotations = sourceAnnotations
+ self.altgenclass = altgenclass
+
+ def StringIO(self):
+ # Third-party products wishing to provide a full Unicode-aware
+ # StringIO can do so by monkey-patching this method.
+ return FasterStringIO()
+
+ def saveState(self):
+ return (self.position, self.col, self.stream, self._stream_stack,
+ self.scopeLevel, self.level, self.i18nContext)
+
+ def restoreState(self, state):
+ (self.position, self.col, self.stream,
+ self._stream_stack, scopeLevel, level, i18n) = state
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+ assert self.level == level
+ while self.scopeLevel > scopeLevel:
+ self.engine.endScope()
+ self.scopeLevel = self.scopeLevel - 1
+ self.engine.setPosition(self.position)
+ self.i18nContext = i18n
+
+ def restoreOutputState(self, state):
+ (dummy, self.col, self.stream,
+ self._stream_stack, scopeLevel, level, i18n) = state
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+ assert self.level == level
+ assert self.scopeLevel == scopeLevel
+
+ def pushMacro(self, macroName, slots, definingName, extending):
+ if len(self.macroStack) >= self.stackLimit:
+ raise METALError("macro nesting limit (%d) exceeded "
+ "by %s" % (self.stackLimit, `macroName`))
+ self.macroStack.append(
+ MacroStackItem(macroName, slots, definingName, extending,
+ True, self.i18nContext))
+
+ def popMacro(self):
+ return self.macroStack.pop()
+
+ def __call__(self):
+ assert self.level == 0
+ assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
+ self.interpret(self.program)
+ assert self.level == 0
+ assert self.scopeLevel == 0
+ assert self.i18nContext.parent is None
+ if self.col > 0:
+ self._stream_write("\n")
+ self.col = 0
+
+ def pushStream(self, newstream):
+ self._stream_stack.append(self.stream)
+ self.stream = newstream
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+
+ def popStream(self):
+ self.stream = self._stream_stack.pop()
+ if self._pending_source_annotation:
+ self._stream_write = self._annotated_stream_write
+ else:
+ self._stream_write = self.stream.write
+
+ def _annotated_stream_write(self, s):
+ idx = s.find('<?xml')
+ if idx >= 0 or s.isspace():
+ # Do not preprend comments in front of the <?xml?> declaration.
+ end_of_doctype = s.find('?>', idx)
+ if end_of_doctype > idx:
+ self.stream.write(s[:end_of_doctype+2])
+ s = s[end_of_doctype+2:]
+ # continue
+ else:
+ self.stream.write(s)
+ return
+ self._pending_source_annotation = False
+ self._stream_write = self.stream.write
+ self._stream_write(self.formatSourceAnnotation())
+ self._stream_write(s)
+
+ def formatSourceAnnotation(self):
+ lineno = self.position[0]
+ if lineno is None:
+ location = self.sourceFile
+ else:
+ location = '%s (line %s)' % (self.sourceFile, lineno)
+ sep = '=' * 78
+ return '<!--\n%s\n%s\n%s\n-->' % (sep, location, sep)
+
+ def stream_write(self, s,
+ len=len):
+ self._stream_write(s)
+ i = s.rfind('\n')
+ if i < 0:
+ self.col = self.col + len(s)
+ else:
+ self.col = len(s) - (i + 1)
+
+ bytecode_handlers = {}
+
+ def interpret(self, program):
+ oldlevel = self.level
+ self.level = oldlevel + 1
+ handlers = self.dispatch
+ try:
+ 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)
+ handlers[opcode](self, args)
+ else:
+ for (opcode, args) in program:
+ handlers[opcode](self, args)
+ finally:
+ self.level = oldlevel
+
+ def do_version(self, version):
+ assert version == TAL_VERSION
+ bytecode_handlers["version"] = do_version
+
+ def do_mode(self, mode):
+ assert mode in ("html", "xml")
+ self.html = (mode == "html")
+ if self.html:
+ self.endsep = " />"
+ else:
+ self.endsep = "/>"
+ self.endlen = len(self.endsep)
+ bytecode_handlers["mode"] = do_mode
+
+ def do_setSourceFile(self, source_file):
+ self.sourceFile = source_file
+ self.engine.setSourceFile(source_file)
+ if self.sourceAnnotations:
+ self._pending_source_annotation = True
+ self._stream_write = self._annotated_stream_write
+
+ bytecode_handlers["setSourceFile"] = do_setSourceFile
+
+ def do_setPosition(self, position):
+ self.position = position
+ self.engine.setPosition(position)
+ bytecode_handlers["setPosition"] = do_setPosition
+
+ def do_startEndTag(self, stuff):
+ self.do_startTag(stuff, self.endsep, self.endlen)
+ bytecode_handlers["startEndTag"] = do_startEndTag
+
+ def do_startTag(self, (name, attrList),
+ end=">", endlen=1, _len=len):
+ # The bytecode generator does not cause calls to this method
+ # for start tags with no attributes; those are optimized down
+ # to rawtext events. Hence, there is no special "fast path"
+ # for that case.
+ self._currentTag = name
+ L = ["<", name]
+ append = L.append
+ col = self.col + _len(name) + 1
+ wrap = self.wrap
+ align = col + 1
+ if align >= wrap/2:
+ align = 4 # Avoid a narrow column far to the right
+ attrAction = self.dispatch["<attrAction>"]
+ try:
+ for item in attrList:
+ if _len(item) == 2:
+ rendered = item[1:]
+ else:
+ # item[2] is the 'action' field:
+ if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
+ if not self.showtal:
+ continue
+ rendered = self.attrAction(item)
+ else:
+ rendered = attrAction(self, item)
+ if not rendered:
+ continue
+ for s in rendered:
+ slen = _len(s)
+ if (wrap and
+ col >= align and
+ col + 1 + slen > wrap):
+ append("\n")
+ append(" "*align)
+ col = align + slen
+ else:
+ append(" ")
+ col = col + 1 + slen
+ append(s)
+ append(end)
+ col = col + endlen
+ finally:
+ self._stream_write(_nulljoin(L))
+ self.col = col
+ bytecode_handlers["startTag"] = do_startTag
+
+ def attrAction(self, item):
+ name, value, action = item[:3]
+ if action == 'insert':
+ return ()
+ macs = self.macroStack
+ if action == 'metal' and self.metal and macs:
+ # Drop all METAL attributes at a use-depth beyond the first
+ # use-macro and its extensions
+ if len(macs) > 1:
+ for macro in macs[1:]:
+ if not macro.extending:
+ return ()
+ if not macs[-1].entering:
+ return ()
+ macs[-1].entering = False
+ # Convert or drop depth-one METAL attributes.
+ i = name.rfind(":") + 1
+ prefix, suffix = name[:i], name[i:]
+ if suffix == "define-macro":
+ # Convert define-macro as we enter depth one.
+ useName = macs[0].macroName
+ defName = macs[0].definingName
+ res = []
+ if defName:
+ res.append('%sdefine-macro=%s' % (prefix, quote(defName)))
+ if useName:
+ res.append('%suse-macro=%s' % (prefix, quote(useName)))
+ return res
+ elif suffix == "define-slot":
+ name = prefix + "fill-slot"
+ elif suffix == "fill-slot":
+ pass
+ else:
+ return ()
+
+ if value is None:
+ value = name
+ else:
+ value = "%s=%s" % (name, quote(value))
+ return [value]
+
+ def attrAction_tal(self, item):
+ name, value, action = item[:3]
+ ok = 1
+ expr, xlat, msgid = item[3:]
+ if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
+ evalue = self.engine.evaluateBoolean(item[3])
+ if evalue is self.Default:
+ if action == 'insert': # Cancelled insert
+ ok = 0
+ elif evalue:
+ value = None
+ else:
+ ok = 0
+ elif expr is not None:
+ evalue = self.engine.evaluateText(item[3])
+ if evalue is self.Default:
+ if action == 'insert': # Cancelled insert
+ ok = 0
+ else:
+ if evalue is None:
+ ok = 0
+ value = evalue
+
+ if ok:
+ if xlat:
+ translated = self.translate(msgid or value, value)
+ if translated is not None:
+ value = translated
+ elif isinstance(value, I18nMessageTypes):
+ translated = self.translate(value)
+ if translated is not None:
+ value = translated
+ if value is None:
+ value = name
+ return ["%s=%s" % (name, quote(value))]
+ else:
+ return ()
+ bytecode_handlers["<attrAction>"] = attrAction
+
+ def no_tag(self, start, program):
+ state = self.saveState()
+ self.stream = stream = self.StringIO()
+ self._stream_write = stream.write
+ self.interpret(start)
+ self.restoreOutputState(state)
+ self.interpret(program)
+
+ def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
+ omit=0):
+ if tag_ns and not self.showtal:
+ return self.no_tag(start, program)
+
+ self.interpret(start)
+ if not isend:
+ self.interpret(program)
+ s = '</%s>' % name
+ self._stream_write(s)
+ self.col = self.col + len(s)
+
+ def do_optTag_tal(self, stuff):
+ cexpr = stuff[1]
+ if cexpr is not None and (cexpr == '' or
+ self.engine.evaluateBoolean(cexpr)):
+ self.no_tag(stuff[-2], stuff[-1])
+ else:
+ self.do_optTag(stuff)
+ bytecode_handlers["optTag"] = do_optTag
+
+ def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
+ self._stream_write(s)
+ self.col = col
+ self.do_setPosition(position)
+ if closeprev:
+ engine = self.engine
+ engine.endScope()
+ engine.beginScope()
+ else:
+ self.engine.beginScope()
+ self.scopeLevel = self.scopeLevel + 1
+
+ def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
+ self._stream_write(s)
+ self.col = col
+ engine = self.engine
+ self.position = position
+ engine.setPosition(position)
+ if closeprev:
+ engine.endScope()
+ engine.beginScope()
+ else:
+ engine.beginScope()
+ self.scopeLevel = self.scopeLevel + 1
+ engine.setLocal("attrs", dict)
+ bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
+
+ def do_beginScope(self, dict):
+ self.engine.beginScope()
+ self.scopeLevel = self.scopeLevel + 1
+
+ def do_beginScope_tal(self, dict):
+ engine = self.engine
+ engine.beginScope()
+ engine.setLocal("attrs", dict)
+ self.scopeLevel = self.scopeLevel + 1
+ bytecode_handlers["beginScope"] = do_beginScope
+
+ def do_endScope(self, notused=None):
+ self.engine.endScope()
+ self.scopeLevel = self.scopeLevel - 1
+ bytecode_handlers["endScope"] = do_endScope
+
+ def do_setLocal(self, notused):
+ pass
+
+ def do_setLocal_tal(self, (name, expr)):
+ self.engine.setLocal(name, self.engine.evaluateValue(expr))
+ bytecode_handlers["setLocal"] = do_setLocal
+
+ def do_setGlobal_tal(self, (name, expr)):
+ self.engine.setGlobal(name, self.engine.evaluateValue(expr))
+ bytecode_handlers["setGlobal"] = do_setLocal
+
+ def do_beginI18nContext(self, settings):
+ get = settings.get
+ self.i18nContext = TranslationContext(self.i18nContext,
+ domain=get("domain"),
+ source=get("source"),
+ target=get("target"))
+ bytecode_handlers["beginI18nContext"] = do_beginI18nContext
+
+ def do_endI18nContext(self, notused=None):
+ self.i18nContext = self.i18nContext.parent
+ assert self.i18nContext is not None
+ bytecode_handlers["endI18nContext"] = do_endI18nContext
+
+ def do_insertText(self, stuff):
+ self.interpret(stuff[1])
+ bytecode_handlers["insertText"] = do_insertText
+ bytecode_handlers["insertI18nText"] = do_insertText
+
+ def _writeText(self, text):
+ # '&' must be done first!
+ s = text.replace(
+ "&", "&").replace("<", "<").replace(">", ">")
+ self._stream_write(s)
+ i = s.rfind('\n')
+ if i < 0:
+ self.col += len(s)
+ else:
+ self.col = len(s) - (i + 1)
+
+ def do_insertText_tal(self, stuff):
+ text = self.engine.evaluateText(stuff[0])
+ if text is None:
+ return
+ if text is self.Default:
+ self.interpret(stuff[1])
+ return
+ if isinstance(text, I18nMessageTypes):
+ # Translate this now.
+ text = self.translate(text)
+ self._writeText(text)
+
+ def do_insertI18nText_tal(self, stuff):
+ # TODO: Code duplication is BAD, we need to fix it later
+ text = self.engine.evaluateText(stuff[0])
+ if text is not None:
+ if text is self.Default:
+ self.interpret(stuff[1])
+ else:
+ if isinstance(text, TypesToTranslate):
+ text = self.translate(text)
+ self._writeText(text)
+
+ def do_i18nVariable(self, stuff):
+ varname, program, expression, structure = stuff
+ if expression is None:
+ # The value is implicitly the contents of this tag, so we have to
+ # evaluate the mini-program to get the value of the variable.
+ state = self.saveState()
+ try:
+ tmpstream = self.StringIO()
+ self.pushStream(tmpstream)
+ try:
+ self.interpret(program)
+ finally:
+ self.popStream()
+ if self.html and self._currentTag == "pre":
+ value = tmpstream.getvalue()
+ else:
+ value = normalize(tmpstream.getvalue())
+ finally:
+ self.restoreState(state)
+ else:
+ # TODO: Seems like this branch not used anymore, we
+ # need to remove it
+
+ # Evaluate the value to be associated with the variable in the
+ # i18n interpolation dictionary.
+ if structure:
+ value = self.engine.evaluateStructure(expression)
+ else:
+ value = self.engine.evaluate(expression)
+
+ # evaluate() does not do any I18n, so we do it here.
+ if isinstance(value, I18nMessageTypes):
+ # Translate this now.
+ value = self.translate(value)
+
+ if not structure:
+ value = cgi.escape(unicode(value))
+
+ # Either the i18n:name tag is nested inside an i18n:translate in which
+ # case the last item on the stack has the i18n dictionary and string
+ # representation, or the i18n:name and i18n:translate attributes are
+ # in the same tag, in which case the i18nStack will be empty. In that
+ # case we can just output the ${name} to the stream
+ i18ndict, srepr = self.i18nStack[-1]
+ i18ndict[varname] = value
+ placeholder = '${%s}' % varname
+ srepr.append(placeholder)
+ self._stream_write(placeholder)
+ bytecode_handlers['i18nVariable'] = do_i18nVariable
+
+ def do_insertTranslation(self, stuff):
+ i18ndict = {}
+ srepr = []
+ obj = None
+ self.i18nStack.append((i18ndict, srepr))
+ msgid = stuff[0]
+ # We need to evaluate the content of the tag because that will give us
+ # several useful pieces of information. First, the contents will
+ # include an implicit message id, if no explicit one was given.
+ # Second, it will evaluate any i18nVariable definitions in the body of
+ # the translation (necessary for $varname substitutions).
+ #
+ # Use a temporary stream to capture the interpretation of the
+ # subnodes, which should /not/ go to the output stream.
+ currentTag = self._currentTag
+ tmpstream = self.StringIO()
+ self.pushStream(tmpstream)
+ try:
+ self.interpret(stuff[1])
+ finally:
+ self.popStream()
+ # We only care about the evaluated contents if we need an implicit
+ # message id. All other useful information will be in the i18ndict on
+ # the top of the i18nStack.
+ default = tmpstream.getvalue()
+ if not msgid:
+ if self.html and currentTag == "pre":
+ msgid = default
+ else:
+ msgid = normalize(default)
+ self.i18nStack.pop()
+ # See if there is was an i18n:data for msgid
+ if len(stuff) > 2:
+ obj = self.engine.evaluate(stuff[2])
+ xlated_msgid = self.translate(msgid, default, i18ndict, obj)
+ # TODO: I can't decide whether we want to cgi escape the translated
+ # string or not. OTOH not doing this could introduce a cross-site
+ # scripting vector by allowing translators to sneak JavaScript into
+ # translations. OTOH, for implicit interpolation values, we don't
+ # want to escape stuff like ${name} <= "<b>Timmy</b>".
+ assert xlated_msgid is not None
+ self._stream_write(xlated_msgid)
+ bytecode_handlers['insertTranslation'] = do_insertTranslation
+
+ def do_insertStructure(self, stuff):
+ self.interpret(stuff[2])
+ bytecode_handlers["insertStructure"] = do_insertStructure
+ bytecode_handlers["insertI18nStructure"] = do_insertStructure
+
+ def do_insertStructure_tal(self, (expr, repldict, block)):
+ structure = self.engine.evaluateStructure(expr)
+ if structure is None:
+ return
+ if structure is self.Default:
+ self.interpret(block)
+ return
+ if isinstance(structure, I18nMessageTypes):
+ text = self.translate(structure)
+ else:
+ text = unicode(structure)
+ if not (repldict or self.strictinsert):
+ # Take a shortcut, no error checking
+ self.stream_write(text)
+ return
+ if self.html:
+ self.insertHTMLStructure(text, repldict)
+ else:
+ self.insertXMLStructure(text, repldict)
+
+ def do_insertI18nStructure_tal(self, (expr, repldict, block)):
+ # TODO: Code duplication is BAD, we need to fix it later
+ structure = self.engine.evaluateStructure(expr)
+ if structure is not None:
+ if structure is self.Default:
+ self.interpret(block)
+ else:
+ if not isinstance(structure, TypesToTranslate):
+ structure = unicode(structure)
+ text = self.translate(structure)
+ if not (repldict or self.strictinsert):
+ # Take a shortcut, no error checking
+ self.stream_write(text)
+ elif self.html:
+ self.insertHTMLStructure(text, repldict)
+ else:
+ self.insertXMLStructure(text, repldict)
+
+ def insertHTMLStructure(self, text, repldict):
+ from zope.tal.htmltalparser import HTMLTALParser
+ gen = self.altgenclass(repldict, self.engine, 0)
+ p = HTMLTALParser(gen) # Raises an exception if text is invalid
+ p.parseString(text)
+ program, macros = p.getCode()
+ self.interpret(program)
+
+ def insertXMLStructure(self, text, repldict):
+ from zope.tal.talparser import TALParser
+ gen = self.altgenclass(repldict, self.engine, 0)
+ p = TALParser(gen)
+ gen.enable(0)
+ p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
+ gen.enable(1)
+ p.parseFragment(text) # Raises an exception if text is invalid
+ gen.enable(0)
+ p.parseFragment('</foo>', 1)
+ program, macros = gen.getCode()
+ self.interpret(program)
+
+ def do_evaluateCode(self, stuff):
+ lang, program = stuff
+ # Use a temporary stream to capture the interpretation of the
+ # subnodes, which should /not/ go to the output stream.
+ tmpstream = self.StringIO()
+ self.pushStream(tmpstream)
+ try:
+ self.interpret(program)
+ finally:
+ self.popStream()
+ code = tmpstream.getvalue()
+ output = self.engine.evaluateCode(lang, code)
+ self._stream_write(output)
+ bytecode_handlers["evaluateCode"] = do_evaluateCode
+
+ def do_loop(self, (name, expr, block)):
+ self.interpret(block)
+
+ def do_loop_tal(self, (name, expr, block)):
+ iterator = self.engine.setRepeat(name, expr)
+ while iterator.next():
+ self.interpret(block)
+ bytecode_handlers["loop"] = do_loop
+
+ def translate(self, msgid, default=None, i18ndict=None,
+ obj=None, domain=None):
+ if default is None:
+ default = getattr(msgid, 'default', unicode(msgid))
+ if i18ndict is None:
+ i18ndict = {}
+ if domain is None:
+ domain = getattr(msgid, 'domain', self.i18nContext.domain)
+ if obj:
+ i18ndict.update(obj)
+ if not self.i18nInterpolate:
+ return msgid
+ # TODO: We need to pass in one of context or target_language
+ return self.engine.translate(msgid, self.i18nContext.domain,
+ i18ndict, default=default)
+
+ 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)):
+ wasInUse = self.inUseDirective
+ self.inUseDirective = False
+ self.interpret(macro)
+ self.inUseDirective = wasInUse
+ bytecode_handlers["defineMacro"] = do_defineMacro
+
+ def do_useMacro(self, (macroName, macroExpr, compiledSlots, block),
+ definingName=None, extending=False):
+ if not self.metal:
+ self.interpret(block)
+ return
+ macro = self.engine.evaluateMacro(macroExpr)
+ if macro is self.Default:
+ macro = block
+ else:
+ if not isCurrentVersion(macro):
+ raise METALError("macro %s has incompatible version %s" %
+ (`macroName`, `getProgramVersion(macro)`),
+ self.position)
+ mode = getProgramMode(macro)
+ if mode != (self.html and "html" or "xml"):
+ raise METALError("macro %s has incompatible mode %s" %
+ (`macroName`, `mode`), self.position)
+ self.pushMacro(macroName, compiledSlots, definingName, extending)
+
+ # We want 'macroname' name to be always available as a variable
+ outer = self.engine.getValue('macroname')
+ self.engine.setLocal('macroname', macroName.rsplit('/', 1)[-1])
+
+ prev_source = self.sourceFile
+ wasInUse = self.inUseDirective
+ self.inUseDirective = True
+ self.interpret(macro)
+ self.inUseDirective = wasInUse
+
+ if self.sourceFile != prev_source:
+ self.engine.setSourceFile(prev_source)
+ self.sourceFile = prev_source
+ self.popMacro()
+ # Push the outer macroname again.
+ self.engine.setLocal('macroname', outer)
+ bytecode_handlers["useMacro"] = do_useMacro
+
+ def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block,
+ definingName)):
+ # extendMacro results from a combination of define-macro and
+ # use-macro. definingName has the value of the
+ # metal:define-macro attribute.
+ extending = self.metal and self.inUseDirective
+ self.do_useMacro((macroName, macroExpr, compiledSlots, block),
+ definingName, extending)
+ bytecode_handlers["extendMacro"] = do_extendMacro
+
+ def do_fillSlot(self, (slotName, block)):
+ # This is only executed if the enclosing 'use-macro' evaluates
+ # to 'default'.
+ self.interpret(block)
+ bytecode_handlers["fillSlot"] = do_fillSlot
+
+ def do_defineSlot(self, (slotName, block)):
+ if not self.metal:
+ self.interpret(block)
+ return
+ macs = self.macroStack
+ if macs:
+ len_macs = len(macs)
+ # Measure the extension depth of this use-macro
+ depth = 1
+ while depth < len_macs:
+ if macs[-depth].extending:
+ depth += 1
+ else:
+ break
+ # Search for a slot filler from the most specific to the
+ # most general macro. The most general is at the top of
+ # the stack.
+ slot = None
+ i = len_macs - 1
+ while i >= (len_macs - depth):
+ slot = macs[i].slots.get(slotName)
+ if slot is not None:
+ break
+ i -= 1
+ if slot is not None:
+ # Found a slot filler. Temporarily chop the macro
+ # stack starting at the macro that filled the slot and
+ # render the slot filler.
+ chopped = macs[i:]
+ del macs[i:]
+ try:
+ self.interpret(slot)
+ finally:
+ # Restore the stack entries.
+ for mac in chopped:
+ mac.entering = False # Not entering
+ macs.extend(chopped)
+ return
+ # Falling out of the 'if' allows the macro to be interpreted.
+ self.interpret(block)
+ bytecode_handlers["defineSlot"] = do_defineSlot
+
+ def do_onError(self, (block, handler)):
+ self.interpret(block)
+
+ def do_onError_tal(self, (block, handler)):
+ state = self.saveState()
+ self.stream = stream = self.StringIO()
+ self._stream_write = stream.write
+ try:
+ self.interpret(block)
+ # TODO: this should not catch ZODB.POSException.ConflictError.
+ # The ITALExpressionEngine interface should provide a way of
+ # getting the set of exception types that should not be
+ # handled.
+ except:
+ exc = sys.exc_info()[1]
+ self.restoreState(state)
+ engine = self.engine
+ engine.beginScope()
+ error = engine.createErrorInfo(exc, self.position)
+ engine.setLocal('error', error)
+ try:
+ self.interpret(handler)
+ finally:
+ engine.endScope()
+ else:
+ self.restoreOutputState(state)
+ self.stream_write(stream.getvalue())
+ bytecode_handlers["onError"] = do_onError
+
+ bytecode_handlers_tal = bytecode_handlers.copy()
+ bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
+ bytecode_handlers_tal["beginScope"] = do_beginScope_tal
+ bytecode_handlers_tal["setLocal"] = do_setLocal_tal
+ bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
+ bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
+ bytecode_handlers_tal["insertI18nStructure"] = do_insertI18nStructure_tal
+ bytecode_handlers_tal["insertText"] = do_insertText_tal
+ bytecode_handlers_tal["insertI18nText"] = do_insertI18nText_tal
+ bytecode_handlers_tal["loop"] = do_loop_tal
+ bytecode_handlers_tal["onError"] = do_onError_tal
+ bytecode_handlers_tal["<attrAction>"] = attrAction_tal
+ bytecode_handlers_tal["optTag"] = do_optTag_tal
+
+class FasterStringIO:
+ """
+ implement only what we need
+ """
+ def __init__(self):
+ self.buflist = []
+
+ def getvalue(self):
+ return u''.join(self.buflist)
+
+ def write(self, s):
+ self.buflist.append(s)
+
Deleted: zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter.py
===================================================================
--- zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter.py 2007-06-06 14:22:06 UTC (rev 76423)
+++ zope.tal/branches/pypyzpt/src/zope/tal/talinterpreter.py 2007-06-06 14:33:28 UTC (rev 76424)
@@ -1,967 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Interpreter for a pre-compiled TAL program.
-
-$Id$
-"""
-import cgi
-import sys
-
-
-from zope.i18nmessageid import Message
-from zope.tal.taldefs import quote, TAL_VERSION, METALError
-from zope.tal.taldefs import isCurrentVersion
-from zope.tal.taldefs import getProgramVersion, getProgramMode
-from zope.tal.translationcontext import TranslationContext
-from zope.tal.alttalgenerator import AltTALGenerator
-
-
-# Avoid constructing this tuple over and over
-I18nMessageTypes = (Message,)
-
-TypesToTranslate = I18nMessageTypes + (str, unicode)
-
-BOOLEAN_HTML_ATTRS = frozenset([
- # List of Boolean attributes in HTML that should be rendered in
- # minimized form (e.g. <img ismap> rather than <img ismap="">)
- # From http://www.w3.org/TR/xhtml1/#guidelines (C.10)
- # TODO: The problem with this is that this is not valid XML and
- # can't be parsed back!
- "compact", "nowrap", "ismap", "declare", "noshade", "checked",
- "disabled", "readonly", "multiple", "selected", "noresize",
- "defer"
-])
-
-_nulljoin = ''.join
-_spacejoin = ' '.join
-
-def normalize(text):
- # Now we need to normalize the whitespace in implicit message ids and
- # implicit $name substitution values by stripping leading and trailing
- # whitespace, and folding all internal whitespace to a single space.
- return _spacejoin(text.split())
-
-
-
-class MacroStackItem(object):
- def __init__(self, macroName, slots, definingName, extending, entering, i18nContext):
- self.macroName = macroName
- self.slots = slots
- self.definingName = definingName
- self.extending = extending
- self.entering = entering
- self.i18nContext = i18nContext
-
-class TALInterpreter(object):
- """TAL interpreter.
-
- Some notes on source annotations. They are HTML/XML comments added to the
- output whenever sourceFile is changed by a setSourceFile bytecode. Source
- annotations are disabled by default, but you can turn them on by passing a
- sourceAnnotations argument to the constructor. You can change the format
- of the annotations by overriding formatSourceAnnotation in a subclass.
-
- The output of the annotation is delayed until some actual text is output
- for two reasons:
-
- 1. setPosition bytecode follows setSourceFile, and we need position
- information to output the line number.
- 2. Comments are not allowed in XML documents before the <?xml?>
- declaration.
-
- For performance reasons (TODO: premature optimization?) instead of checking
- the value of _pending_source_annotation on every write to the output
- stream, the _stream_write attribute is changed to point to
- _annotated_stream_write method whenever _pending_source_annotation is
- set to True, and to _stream.write when it is False. The following
- invariant always holds:
-
- if self._pending_source_annotation:
- assert self._stream_write is self._annotated_stream_write
- else:
- assert self._stream_write is self.stream.write
-
- """
-
- def __init__(self, program, macros, engine, stream=None,
- debug=0, wrap=60, metal=1, tal=1, showtal=-1,
- strictinsert=1, stackLimit=100, i18nInterpolate=1,
- sourceAnnotations=0, altgenclass=AltTALGenerator):
- """Create a TAL interpreter.
-
- Optional arguments:
-
- stream -- output stream (defaults to sys.stdout).
-
- debug -- enable debugging output to sys.stderr (off by default).
-
- wrap -- try to wrap attributes on opening tags to this number of
- column (default: 60).
-
- metal -- enable METAL macro processing (on by default).
-
- tal -- enable TAL processing (on by default).
-
- showtal -- do not strip away TAL directives. A special value of
- -1 (which is the default setting) enables showtal when TAL
- processing is disabled, and disables showtal when TAL processing is
- enabled. Note that you must use 0, 1, or -1; true boolean values
- are not supported (TODO: why?).
-
- strictinsert -- enable TAL processing and stricter HTML/XML
- checking on text produced by structure inserts (on by default).
- Note that Zope turns this value off by default.
-
- stackLimit -- set macro nesting limit (default: 100).
-
- i18nInterpolate -- enable i18n translations (default: on).
-
- sourceAnnotations -- enable source annotations with HTML comments
- (default: off).
-
- """
- self.program = program
- self.engine = engine # Execution engine (aka context)
- self.Default = engine.getDefault()
- self._pending_source_annotation = False
- self._currentTag = ""
- self._stream_stack = [stream or sys.stdout]
- self.popStream()
- self.debug = debug
- self.wrap = wrap
- self.metal = metal
- self.tal = tal
- if tal:
- self.dispatch = self.bytecode_handlers_tal
- else:
- self.dispatch = self.bytecode_handlers
- assert showtal in (-1, 0, 1)
- if showtal == -1:
- showtal = (not tal)
- self.showtal = showtal
- self.strictinsert = strictinsert
- self.stackLimit = stackLimit
- self.html = 0
- self.endsep = "/>"
- self.endlen = len(self.endsep)
- # macroStack entries are MacroStackItem instances;
- # the entries are mutated while on the stack
- self.macroStack = []
- # `inUseDirective` is set iff we're handling either a
- # metal:use-macro or a metal:extend-macro
- self.inUseDirective = False
- self.position = None, None # (lineno, offset)
- self.col = 0
- self.level = 0
- self.scopeLevel = 0
- self.sourceFile = None
- self.i18nStack = []
- self.i18nInterpolate = i18nInterpolate
- self.i18nContext = TranslationContext()
- self.sourceAnnotations = sourceAnnotations
- self.altgenclass = altgenclass
-
- def StringIO(self):
- # Third-party products wishing to provide a full Unicode-aware
- # StringIO can do so by monkey-patching this method.
- return FasterStringIO()
-
- def saveState(self):
- return (self.position, self.col, self.stream, self._stream_stack,
- self.scopeLevel, self.level, self.i18nContext)
-
- def restoreState(self, state):
- (self.position, self.col, self.stream,
- self._stream_stack, scopeLevel, level, i18n) = state
- if self._pending_source_annotation:
- self._stream_write = self._annotated_stream_write
- else:
- self._stream_write = self.stream.write
- assert self.level == level
- while self.scopeLevel > scopeLevel:
- self.engine.endScope()
- self.scopeLevel = self.scopeLevel - 1
- self.engine.setPosition(self.position)
- self.i18nContext = i18n
-
- def restoreOutputState(self, state):
- (dummy, self.col, self.stream,
- self._stream_stack, scopeLevel, level, i18n) = state
- if self._pending_source_annotation:
- self._stream_write = self._annotated_stream_write
- else:
- self._stream_write = self.stream.write
- assert self.level == level
- assert self.scopeLevel == scopeLevel
-
- def pushMacro(self, macroName, slots, definingName, extending):
- if len(self.macroStack) >= self.stackLimit:
- raise METALError("macro nesting limit (%d) exceeded "
- "by %s" % (self.stackLimit, `macroName`))
- self.macroStack.append(
- MacroStackItem(macroName, slots, definingName, extending,
- True, self.i18nContext))
-
- def popMacro(self):
- return self.macroStack.pop()
-
- def __call__(self):
- assert self.level == 0
- assert self.scopeLevel == 0
- assert self.i18nContext.parent is None
- self.interpret(self.program)
- assert self.level == 0
- assert self.scopeLevel == 0
- assert self.i18nContext.parent is None
- if self.col > 0:
- self._stream_write("\n")
- self.col = 0
-
- def pushStream(self, newstream):
- self._stream_stack.append(self.stream)
- self.stream = newstream
- if self._pending_source_annotation:
- self._stream_write = self._annotated_stream_write
- else:
- self._stream_write = self.stream.write
-
- def popStream(self):
- self.stream = self._stream_stack.pop()
- if self._pending_source_annotation:
- self._stream_write = self._annotated_stream_write
- else:
- self._stream_write = self.stream.write
-
- def _annotated_stream_write(self, s):
- idx = s.find('<?xml')
- if idx >= 0 or s.isspace():
- # Do not preprend comments in front of the <?xml?> declaration.
- end_of_doctype = s.find('?>', idx)
- if end_of_doctype > idx:
- self.stream.write(s[:end_of_doctype+2])
- s = s[end_of_doctype+2:]
- # continue
- else:
- self.stream.write(s)
- return
- self._pending_source_annotation = False
- self._stream_write = self.stream.write
- self._stream_write(self.formatSourceAnnotation())
- self._stream_write(s)
-
- def formatSourceAnnotation(self):
- lineno = self.position[0]
- if lineno is None:
- location = self.sourceFile
- else:
- location = '%s (line %s)' % (self.sourceFile, lineno)
- sep = '=' * 78
- return '<!--\n%s\n%s\n%s\n-->' % (sep, location, sep)
-
- def stream_write(self, s,
- len=len):
- self._stream_write(s)
- i = s.rfind('\n')
- if i < 0:
- self.col = self.col + len(s)
- else:
- self.col = len(s) - (i + 1)
-
- bytecode_handlers = {}
-
- def interpret(self, program):
- oldlevel = self.level
- self.level = oldlevel + 1
- handlers = self.dispatch
- try:
- 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)
- handlers[opcode](self, args)
- else:
- for (opcode, args) in program:
- handlers[opcode](self, args)
- finally:
- self.level = oldlevel
-
- def do_version(self, version):
- assert version == TAL_VERSION
- bytecode_handlers["version"] = do_version
-
- def do_mode(self, mode):
- assert mode in ("html", "xml")
- self.html = (mode == "html")
- if self.html:
- self.endsep = " />"
- else:
- self.endsep = "/>"
- self.endlen = len(self.endsep)
- bytecode_handlers["mode"] = do_mode
-
- def do_setSourceFile(self, source_file):
- self.sourceFile = source_file
- self.engine.setSourceFile(source_file)
- if self.sourceAnnotations:
- self._pending_source_annotation = True
- self._stream_write = self._annotated_stream_write
-
- bytecode_handlers["setSourceFile"] = do_setSourceFile
-
- def do_setPosition(self, position):
- self.position = position
- self.engine.setPosition(position)
- bytecode_handlers["setPosition"] = do_setPosition
-
- def do_startEndTag(self, stuff):
- self.do_startTag(stuff, self.endsep, self.endlen)
- bytecode_handlers["startEndTag"] = do_startEndTag
-
- def do_startTag(self, (name, attrList),
- end=">", endlen=1, _len=len):
- # The bytecode generator does not cause calls to this method
- # for start tags with no attributes; those are optimized down
- # to rawtext events. Hence, there is no special "fast path"
- # for that case.
- self._currentTag = name
- L = ["<", name]
- append = L.append
- col = self.col + _len(name) + 1
- wrap = self.wrap
- align = col + 1
- if align >= wrap/2:
- align = 4 # Avoid a narrow column far to the right
- attrAction = self.dispatch["<attrAction>"]
- try:
- for item in attrList:
- if _len(item) == 2:
- rendered = item[1:]
- else:
- # item[2] is the 'action' field:
- if item[2] in ('metal', 'tal', 'xmlns', 'i18n'):
- if not self.showtal:
- continue
- rendered = self.attrAction(item)
- else:
- rendered = attrAction(self, item)
- if not rendered:
- continue
- for s in rendered:
- slen = _len(s)
- if (wrap and
- col >= align and
- col + 1 + slen > wrap):
- append("\n")
- append(" "*align)
- col = align + slen
- else:
- append(" ")
- col = col + 1 + slen
- append(s)
- append(end)
- col = col + endlen
- finally:
- self._stream_write(_nulljoin(L))
- self.col = col
- bytecode_handlers["startTag"] = do_startTag
-
- def attrAction(self, item):
- name, value, action = item[:3]
- if action == 'insert':
- return ()
- macs = self.macroStack
- if action == 'metal' and self.metal and macs:
- # Drop all METAL attributes at a use-depth beyond the first
- # use-macro and its extensions
- if len(macs) > 1:
- for macro in macs[1:]:
- if not macro.extending:
- return ()
- if not macs[-1].entering:
- return ()
- macs[-1].entering = False
- # Convert or drop depth-one METAL attributes.
- i = name.rfind(":") + 1
- prefix, suffix = name[:i], name[i:]
- if suffix == "define-macro":
- # Convert define-macro as we enter depth one.
- useName = macs[0].macroName
- defName = macs[0].definingName
- res = []
- if defName:
- res.append('%sdefine-macro=%s' % (prefix, quote(defName)))
- if useName:
- res.append('%suse-macro=%s' % (prefix, quote(useName)))
- return res
- elif suffix == "define-slot":
- name = prefix + "fill-slot"
- elif suffix == "fill-slot":
- pass
- else:
- return ()
-
- if value is None:
- value = name
- else:
- value = "%s=%s" % (name, quote(value))
- return [value]
-
- def attrAction_tal(self, item):
- name, value, action = item[:3]
- ok = 1
- expr, xlat, msgid = item[3:]
- if self.html and name.lower() in BOOLEAN_HTML_ATTRS:
- evalue = self.engine.evaluateBoolean(item[3])
- if evalue is self.Default:
- if action == 'insert': # Cancelled insert
- ok = 0
- elif evalue:
- value = None
- else:
- ok = 0
- elif expr is not None:
- evalue = self.engine.evaluateText(item[3])
- if evalue is self.Default:
- if action == 'insert': # Cancelled insert
- ok = 0
- else:
- if evalue is None:
- ok = 0
- value = evalue
-
- if ok:
- if xlat:
- translated = self.translate(msgid or value, value)
- if translated is not None:
- value = translated
- elif isinstance(value, I18nMessageTypes):
- translated = self.translate(value)
- if translated is not None:
- value = translated
- if value is None:
- value = name
- return ["%s=%s" % (name, quote(value))]
- else:
- return ()
- bytecode_handlers["<attrAction>"] = attrAction
-
- def no_tag(self, start, program):
- state = self.saveState()
- self.stream = stream = self.StringIO()
- self._stream_write = stream.write
- self.interpret(start)
- self.restoreOutputState(state)
- self.interpret(program)
-
- def do_optTag(self, (name, cexpr, tag_ns, isend, start, program),
- omit=0):
- if tag_ns and not self.showtal:
- return self.no_tag(start, program)
-
- self.interpret(start)
- if not isend:
- self.interpret(program)
- s = '</%s>' % name
- self._stream_write(s)
- self.col = self.col + len(s)
-
- def do_optTag_tal(self, stuff):
- cexpr = stuff[1]
- if cexpr is not None and (cexpr == '' or
- self.engine.evaluateBoolean(cexpr)):
- self.no_tag(stuff[-2], stuff[-1])
- else:
- self.do_optTag(stuff)
- bytecode_handlers["optTag"] = do_optTag
-
- def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)):
- self._stream_write(s)
- self.col = col
- self.do_setPosition(position)
- if closeprev:
- engine = self.engine
- engine.endScope()
- engine.beginScope()
- else:
- self.engine.beginScope()
- self.scopeLevel = self.scopeLevel + 1
-
- def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)):
- self._stream_write(s)
- self.col = col
- engine = self.engine
- self.position = position
- engine.setPosition(position)
- if closeprev:
- engine.endScope()
- engine.beginScope()
- else:
- engine.beginScope()
- self.scopeLevel = self.scopeLevel + 1
- engine.setLocal("attrs", dict)
- bytecode_handlers["rawtextBeginScope"] = do_rawtextBeginScope
-
- def do_beginScope(self, dict):
- self.engine.beginScope()
- self.scopeLevel = self.scopeLevel + 1
-
- def do_beginScope_tal(self, dict):
- engine = self.engine
- engine.beginScope()
- engine.setLocal("attrs", dict)
- self.scopeLevel = self.scopeLevel + 1
- bytecode_handlers["beginScope"] = do_beginScope
-
- def do_endScope(self, notused=None):
- self.engine.endScope()
- self.scopeLevel = self.scopeLevel - 1
- bytecode_handlers["endScope"] = do_endScope
-
- def do_setLocal(self, notused):
- pass
-
- def do_setLocal_tal(self, (name, expr)):
- self.engine.setLocal(name, self.engine.evaluateValue(expr))
- bytecode_handlers["setLocal"] = do_setLocal
-
- def do_setGlobal_tal(self, (name, expr)):
- self.engine.setGlobal(name, self.engine.evaluateValue(expr))
- bytecode_handlers["setGlobal"] = do_setLocal
-
- def do_beginI18nContext(self, settings):
- get = settings.get
- self.i18nContext = TranslationContext(self.i18nContext,
- domain=get("domain"),
- source=get("source"),
- target=get("target"))
- bytecode_handlers["beginI18nContext"] = do_beginI18nContext
-
- def do_endI18nContext(self, notused=None):
- self.i18nContext = self.i18nContext.parent
- assert self.i18nContext is not None
- bytecode_handlers["endI18nContext"] = do_endI18nContext
-
- def do_insertText(self, stuff):
- self.interpret(stuff[1])
- bytecode_handlers["insertText"] = do_insertText
- bytecode_handlers["insertI18nText"] = do_insertText
-
- def _writeText(self, text):
- # '&' must be done first!
- s = text.replace(
- "&", "&").replace("<", "<").replace(">", ">")
- self._stream_write(s)
- i = s.rfind('\n')
- if i < 0:
- self.col += len(s)
- else:
- self.col = len(s) - (i + 1)
-
- def do_insertText_tal(self, stuff):
- text = self.engine.evaluateText(stuff[0])
- if text is None:
- return
- if text is self.Default:
- self.interpret(stuff[1])
- return
- if isinstance(text, I18nMessageTypes):
- # Translate this now.
- text = self.translate(text)
- self._writeText(text)
-
- def do_insertI18nText_tal(self, stuff):
- # TODO: Code duplication is BAD, we need to fix it later
- text = self.engine.evaluateText(stuff[0])
- if text is not None:
- if text is self.Default:
- self.interpret(stuff[1])
- else:
- if isinstance(text, TypesToTranslate):
- text = self.translate(text)
- self._writeText(text)
-
- def do_i18nVariable(self, stuff):
- varname, program, expression, structure = stuff
- if expression is None:
- # The value is implicitly the contents of this tag, so we have to
- # evaluate the mini-program to get the value of the variable.
- state = self.saveState()
- try:
- tmpstream = self.StringIO()
- self.pushStream(tmpstream)
- try:
- self.interpret(program)
- finally:
- self.popStream()
- if self.html and self._currentTag == "pre":
- value = tmpstream.getvalue()
- else:
- value = normalize(tmpstream.getvalue())
- finally:
- self.restoreState(state)
- else:
- # TODO: Seems like this branch not used anymore, we
- # need to remove it
-
- # Evaluate the value to be associated with the variable in the
- # i18n interpolation dictionary.
- if structure:
- value = self.engine.evaluateStructure(expression)
- else:
- value = self.engine.evaluate(expression)
-
- # evaluate() does not do any I18n, so we do it here.
- if isinstance(value, I18nMessageTypes):
- # Translate this now.
- value = self.translate(value)
-
- if not structure:
- value = cgi.escape(unicode(value))
-
- # Either the i18n:name tag is nested inside an i18n:translate in which
- # case the last item on the stack has the i18n dictionary and string
- # representation, or the i18n:name and i18n:translate attributes are
- # in the same tag, in which case the i18nStack will be empty. In that
- # case we can just output the ${name} to the stream
- i18ndict, srepr = self.i18nStack[-1]
- i18ndict[varname] = value
- placeholder = '${%s}' % varname
- srepr.append(placeholder)
- self._stream_write(placeholder)
- bytecode_handlers['i18nVariable'] = do_i18nVariable
-
- def do_insertTranslation(self, stuff):
- i18ndict = {}
- srepr = []
- obj = None
- self.i18nStack.append((i18ndict, srepr))
- msgid = stuff[0]
- # We need to evaluate the content of the tag because that will give us
- # several useful pieces of information. First, the contents will
- # include an implicit message id, if no explicit one was given.
- # Second, it will evaluate any i18nVariable definitions in the body of
- # the translation (necessary for $varname substitutions).
- #
- # Use a temporary stream to capture the interpretation of the
- # subnodes, which should /not/ go to the output stream.
- currentTag = self._currentTag
- tmpstream = self.StringIO()
- self.pushStream(tmpstream)
- try:
- self.interpret(stuff[1])
- finally:
- self.popStream()
- # We only care about the evaluated contents if we need an implicit
- # message id. All other useful information will be in the i18ndict on
- # the top of the i18nStack.
- default = tmpstream.getvalue()
- if not msgid:
- if self.html and currentTag == "pre":
- msgid = default
- else:
- msgid = normalize(default)
- self.i18nStack.pop()
- # See if there is was an i18n:data for msgid
- if len(stuff) > 2:
- obj = self.engine.evaluate(stuff[2])
- xlated_msgid = self.translate(msgid, default, i18ndict, obj)
- # TODO: I can't decide whether we want to cgi escape the translated
- # string or not. OTOH not doing this could introduce a cross-site
- # scripting vector by allowing translators to sneak JavaScript into
- # translations. OTOH, for implicit interpolation values, we don't
- # want to escape stuff like ${name} <= "<b>Timmy</b>".
- assert xlated_msgid is not None
- self._stream_write(xlated_msgid)
- bytecode_handlers['insertTranslation'] = do_insertTranslation
-
- def do_insertStructure(self, stuff):
- self.interpret(stuff[2])
- bytecode_handlers["insertStructure"] = do_insertStructure
- bytecode_handlers["insertI18nStructure"] = do_insertStructure
-
- def do_insertStructure_tal(self, (expr, repldict, block)):
- structure = self.engine.evaluateStructure(expr)
- if structure is None:
- return
- if structure is self.Default:
- self.interpret(block)
- return
- if isinstance(structure, I18nMessageTypes):
- text = self.translate(structure)
- else:
- text = unicode(structure)
- if not (repldict or self.strictinsert):
- # Take a shortcut, no error checking
- self.stream_write(text)
- return
- if self.html:
- self.insertHTMLStructure(text, repldict)
- else:
- self.insertXMLStructure(text, repldict)
-
- def do_insertI18nStructure_tal(self, (expr, repldict, block)):
- # TODO: Code duplication is BAD, we need to fix it later
- structure = self.engine.evaluateStructure(expr)
- if structure is not None:
- if structure is self.Default:
- self.interpret(block)
- else:
- if not isinstance(structure, TypesToTranslate):
- structure = unicode(structure)
- text = self.translate(structure)
- if not (repldict or self.strictinsert):
- # Take a shortcut, no error checking
- self.stream_write(text)
- elif self.html:
- self.insertHTMLStructure(text, repldict)
- else:
- self.insertXMLStructure(text, repldict)
-
- def insertHTMLStructure(self, text, repldict):
- from zope.tal.htmltalparser import HTMLTALParser
- gen = self.altgenclass(repldict, self.engine, 0)
- p = HTMLTALParser(gen) # Raises an exception if text is invalid
- p.parseString(text)
- program, macros = p.getCode()
- self.interpret(program)
-
- def insertXMLStructure(self, text, repldict):
- from zope.tal.talparser import TALParser
- gen = self.altgenclass(repldict, self.engine, 0)
- p = TALParser(gen)
- gen.enable(0)
- p.parseFragment('<!DOCTYPE foo PUBLIC "foo" "bar"><foo>')
- gen.enable(1)
- p.parseFragment(text) # Raises an exception if text is invalid
- gen.enable(0)
- p.parseFragment('</foo>', 1)
- program, macros = gen.getCode()
- self.interpret(program)
-
- def do_evaluateCode(self, stuff):
- lang, program = stuff
- # Use a temporary stream to capture the interpretation of the
- # subnodes, which should /not/ go to the output stream.
- tmpstream = self.StringIO()
- self.pushStream(tmpstream)
- try:
- self.interpret(program)
- finally:
- self.popStream()
- code = tmpstream.getvalue()
- output = self.engine.evaluateCode(lang, code)
- self._stream_write(output)
- bytecode_handlers["evaluateCode"] = do_evaluateCode
-
- def do_loop(self, (name, expr, block)):
- self.interpret(block)
-
- def do_loop_tal(self, (name, expr, block)):
- iterator = self.engine.setRepeat(name, expr)
- while iterator.next():
- self.interpret(block)
- bytecode_handlers["loop"] = do_loop
-
- def translate(self, msgid, default=None, i18ndict=None,
- obj=None, domain=None):
- if default is None:
- default = getattr(msgid, 'default', unicode(msgid))
- if i18ndict is None:
- i18ndict = {}
- if domain is None:
- domain = getattr(msgid, 'domain', self.i18nContext.domain)
- if obj:
- i18ndict.update(obj)
- if not self.i18nInterpolate:
- return msgid
- # TODO: We need to pass in one of context or target_language
- return self.engine.translate(msgid, self.i18nContext.domain,
- i18ndict, default=default)
-
- 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)):
- wasInUse = self.inUseDirective
- self.inUseDirective = False
- self.interpret(macro)
- self.inUseDirective = wasInUse
- bytecode_handlers["defineMacro"] = do_defineMacro
-
- def do_useMacro(self, (macroName, macroExpr, compiledSlots, block),
- definingName=None, extending=False):
- if not self.metal:
- self.interpret(block)
- return
- macro = self.engine.evaluateMacro(macroExpr)
- if macro is self.Default:
- macro = block
- else:
- if not isCurrentVersion(macro):
- raise METALError("macro %s has incompatible version %s" %
- (`macroName`, `getProgramVersion(macro)`),
- self.position)
- mode = getProgramMode(macro)
- if mode != (self.html and "html" or "xml"):
- raise METALError("macro %s has incompatible mode %s" %
- (`macroName`, `mode`), self.position)
- self.pushMacro(macroName, compiledSlots, definingName, extending)
-
- # We want 'macroname' name to be always available as a variable
- outer = self.engine.getValue('macroname')
- self.engine.setLocal('macroname', macroName.rsplit('/', 1)[-1])
-
- prev_source = self.sourceFile
- wasInUse = self.inUseDirective
- self.inUseDirective = True
- self.interpret(macro)
- self.inUseDirective = wasInUse
-
- if self.sourceFile != prev_source:
- self.engine.setSourceFile(prev_source)
- self.sourceFile = prev_source
- self.popMacro()
- # Push the outer macroname again.
- self.engine.setLocal('macroname', outer)
- bytecode_handlers["useMacro"] = do_useMacro
-
- def do_extendMacro(self, (macroName, macroExpr, compiledSlots, block,
- definingName)):
- # extendMacro results from a combination of define-macro and
- # use-macro. definingName has the value of the
- # metal:define-macro attribute.
- extending = self.metal and self.inUseDirective
- self.do_useMacro((macroName, macroExpr, compiledSlots, block),
- definingName, extending)
- bytecode_handlers["extendMacro"] = do_extendMacro
-
- def do_fillSlot(self, (slotName, block)):
- # This is only executed if the enclosing 'use-macro' evaluates
- # to 'default'.
- self.interpret(block)
- bytecode_handlers["fillSlot"] = do_fillSlot
-
- def do_defineSlot(self, (slotName, block)):
- if not self.metal:
- self.interpret(block)
- return
- macs = self.macroStack
- if macs:
- len_macs = len(macs)
- # Measure the extension depth of this use-macro
- depth = 1
- while depth < len_macs:
- if macs[-depth].extending:
- depth += 1
- else:
- break
- # Search for a slot filler from the most specific to the
- # most general macro. The most general is at the top of
- # the stack.
- slot = None
- i = len_macs - 1
- while i >= (len_macs - depth):
- slot = macs[i].slots.get(slotName)
- if slot is not None:
- break
- i -= 1
- if slot is not None:
- # Found a slot filler. Temporarily chop the macro
- # stack starting at the macro that filled the slot and
- # render the slot filler.
- chopped = macs[i:]
- del macs[i:]
- try:
- self.interpret(slot)
- finally:
- # Restore the stack entries.
- for mac in chopped:
- mac.entering = False # Not entering
- macs.extend(chopped)
- return
- # Falling out of the 'if' allows the macro to be interpreted.
- self.interpret(block)
- bytecode_handlers["defineSlot"] = do_defineSlot
-
- def do_onError(self, (block, handler)):
- self.interpret(block)
-
- def do_onError_tal(self, (block, handler)):
- state = self.saveState()
- self.stream = stream = self.StringIO()
- self._stream_write = stream.write
- try:
- self.interpret(block)
- # TODO: this should not catch ZODB.POSException.ConflictError.
- # The ITALExpressionEngine interface should provide a way of
- # getting the set of exception types that should not be
- # handled.
- except:
- exc = sys.exc_info()[1]
- self.restoreState(state)
- engine = self.engine
- engine.beginScope()
- error = engine.createErrorInfo(exc, self.position)
- engine.setLocal('error', error)
- try:
- self.interpret(handler)
- finally:
- engine.endScope()
- else:
- self.restoreOutputState(state)
- self.stream_write(stream.getvalue())
- bytecode_handlers["onError"] = do_onError
-
- bytecode_handlers_tal = bytecode_handlers.copy()
- bytecode_handlers_tal["rawtextBeginScope"] = do_rawtextBeginScope_tal
- bytecode_handlers_tal["beginScope"] = do_beginScope_tal
- bytecode_handlers_tal["setLocal"] = do_setLocal_tal
- bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal
- bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal
- bytecode_handlers_tal["insertI18nStructure"] = do_insertI18nStructure_tal
- bytecode_handlers_tal["insertText"] = do_insertText_tal
- bytecode_handlers_tal["insertI18nText"] = do_insertI18nText_tal
- bytecode_handlers_tal["loop"] = do_loop_tal
- bytecode_handlers_tal["onError"] = do_onError_tal
- bytecode_handlers_tal["<attrAction>"] = attrAction_tal
- bytecode_handlers_tal["optTag"] = do_optTag_tal
-
-class FasterStringIO:
- """
- implement only what we need
- """
- def __init__(self):
- self.buflist = []
-
- def getvalue(self):
- return u''.join(self.buflist)
-
- def write(self, s):
- self.buflist.append(s)
-
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talpypy (from rev 76390, zope.tal/branches/pypyzpt/src/zope/tal/pypy)
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talpypy/HOWTO.txt (from rev 76423, zope.tal/branches/pypyzpt/src/zope/tal/pypy/HOWTO.txt)
===================================================================
--- zope.tal/branches/pypyzpt/src/zope/tal/talpypy/HOWTO.txt (rev 0)
+++ zope.tal/branches/pypyzpt/src/zope/tal/talpypy/HOWTO.txt 2007-06-06 14:33:28 UTC (rev 76424)
@@ -0,0 +1,6 @@
+run the following command to compile (you might need to setup PYTHONPATH) :
+
+python path_to_pypy/bin/compilemodule.py test zope.tal.pypy
+
+first argument is name of module you compile
+second argument is path to the package where you find the RPython
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talpypy/__init__.py (from rev 76423, zope.tal/branches/pypyzpt/src/zope/tal/pypy/__init__.py)
===================================================================
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talpypy/talinterpreter (from rev 76423, zope.tal/branches/pypyzpt/src/zope/tal/pypy/talinterpreter)
Copied: zope.tal/branches/pypyzpt/src/zope/tal/talpypy/test (from rev 76423, zope.tal/branches/pypyzpt/src/zope/tal/pypy/test)
Modified: zope.tal/branches/pypyzpt/src/zope/tal/tests/test_talinterpreter.py
===================================================================
--- zope.tal/branches/pypyzpt/src/zope/tal/tests/test_talinterpreter.py 2007-06-06 14:22:06 UTC (rev 76423)
+++ zope.tal/branches/pypyzpt/src/zope/tal/tests/test_talinterpreter.py 2007-06-06 14:33:28 UTC (rev 76424)
@@ -26,7 +26,12 @@
from zope.tal.taldefs import TALExpressionError
from zope.tal.htmltalparser import HTMLTALParser
from zope.tal.talparser import TALParser
-from zope.tal.talinterpreter import TALInterpreter
+
+#from zope.tal.talinterpreter import TALInterpreter
+from pypy.interpreter.mixedmodule import testmodule
+talinterpreter = testmodule('talinterpreter', 'zope.tal')
+TALInterpreter = talinterpreter.TALInterpreter
+
from zope.tal.talgenerator import TALGenerator
from zope.tal.dummyengine import DummyEngine
from zope.tal.dummyengine import MultipleDomainsDummyEngine
More information about the Checkins
mailing list