changeset 331:d8fc236ca3d8 experimental-compiler

match/select functionality added. continued development of generators/streams/adaptation.
author zzzeek
date Mon, 06 Nov 2006 18:31:38 +0000
parents d81532240de7
children 2fdf34945dfd
files genshi/codegen/adapters.py genshi/codegen/generator.py genshi/codegen/interp.py genshi/codegen/output.py genshi/codegen/tests/template.py genshi/codegen/tests/test_generator.py genshi/path.py
diffstat 7 files changed, 144 insertions(+), 45 deletions(-) [+]
line wrap: on
line diff
--- a/genshi/codegen/adapters.py
+++ b/genshi/codegen/adapters.py
@@ -1,6 +1,8 @@
 """a set of Inline adapters, which convert from inlined structures to Genshi core structures"""
 
 from genshi.path import Path
+from genshi.core import Attrs
+from genshi import template
 
 def get_attrib(attrib, name, default=None):
     """return an 'attribute' name from a list of tuples, similar to genshi.core.Attrib"""
@@ -10,14 +12,10 @@
     return default
 
 class InlineStream(object):
-    """works similarly to genshi.core.Stream"""
-    def __init__(self, generator, context):
-        self.code = generator.code
-        self.filters = generator.filters
-        self.context = context
-        self.stream = self.code.go(self.context)
+    def __init__(self, stream):
+        self.stream = stream
     def __iter__(self):
-        return list(self.stream)
+        return iter(self.stream)
 
     def __or__(self, function):
         return InlineStream(function(self))
@@ -36,13 +34,10 @@
         return output
 
     def select(self, path, namespaces=None, variables=None):
-        return InlinedPath(path).select(self, namespaces, variables)
+        return InlinePath(path).select(self, namespaces, variables)
 
     def serialize(self, method='xml', **kwargs):
-        stream = self.stream
-        for filter_ in self.filters:
-            stream = filter_(stream)
-        for evt in stream:
+        for evt in self.stream:
             yield evt[3]
 
     def __str__(self):
@@ -51,19 +46,67 @@
     def __unicode__(self):
         return self.render(encoding=None)
 
-class InlineQName(object):
+class InlineEvent(object):
+    __eventtypes__ = {}
+    def __new__(cls, event):
+        return object.__new__(InlineEvent.__eventtypes__.get(event[0], InlineEvent), event)
+    def __init__(self, event):
+        self.event = event
+    def to_genshi(self):
+        return self.event[0:3]
+
+class InlineStartEvent(InlineEvent):
+    def to_genshi(self):
+        return (self.event[0], (InlineQName(self.event), Attrs(self.event[1][2])), self.event[2])
+InlineEvent.__eventtypes__[template.START] = InlineStartEvent
+        
+class InlineQName(unicode):
     """creates a QName-like object from a START event"""
-    def __init__(self, event):
+    def __new__(cls, event):
+        self = unicode.__new__(cls, u'{%s}%s' % (event[1][0], event[1][1]))
         self.namespace = event[1][0]
         self.localname = event[1][1]
+        return self
         
-class InlinedPath(Path):
+class InlinePath(Path):
     """overrides Path.test to adapt incoming events from inlined to Genshi."""
     def test(self, ignore_context=False):
-        t = super(InlinedPath, self).test(ignore_context=ignore_context)
+        t = super(InlinePath, self).test(ignore_context=ignore_context)
         def _test(event, namespaces, variables, updateonly=False):
-            if event[0] is START:
-                return t((event[0], (InlineQName(event), event[1][1]), event[2]), namespaces, variables, updateonly=updateonly)
-            else:
-                return t(event[0:3], namespaces, variables, updateonly=updateonly)
+            return t(InlineEvent(event).to_genshi(), namespaces, variables, updateonly=updateonly)
         return _test
+    def select(self, stream, namespaces=None, variables=None):
+        if namespaces is None:
+            namespaces = {}
+        if variables is None:
+            variables = {}
+        stream = iter(stream)
+        def _generate():
+            test = self.test()
+            for event in stream:
+                result = test(event, namespaces, variables)
+                if result is True:
+                    yield event
+                    depth = 1
+                    while depth > 0:
+                        subevent = stream.next()
+                        if subevent[0] is template.START:
+                            depth += 1
+                        elif subevent[0] is template.END:
+                            depth -= 1
+                        yield subevent
+                        test(subevent, namespaces, variables, updateonly=True)
+                # assume 3-tupled return events are just the event that was tested.
+                # TODO: Path tests will no longer return these ?
+                elif isinstance(result, tuple):
+                    yield event
+                elif result:
+                    # in genshi.path.Path, this could be an Attrs or a 3-tupled event.
+                    # here, we only want Attrs to come out.
+                    yield result
+        return InlineStream(_generate())
+
+    def __repr__(self):
+        return "InlinePath(%s, %s, %s)" % (repr(self.source), repr(self.filename), repr(self.lineno))
+        
+        
\ No newline at end of file
--- a/genshi/codegen/generator.py
+++ b/genshi/codegen/generator.py
@@ -17,7 +17,7 @@
 from genshi.path import Path
 from genshi.core import QName
 from genshi.codegen.printer import PythonPrinter, PYTHON_LINE, PYTHON_COMMENT, PYTHON_BLOCK
-from genshi.codegen import serialize, adapters, output
+from genshi.codegen import serialize, adapters, output, interp
 from compiler import ast, parse, visitor
 import sets, re
 
@@ -55,6 +55,22 @@
         return list(s.identifiers)
 ForDirectivePrinter()
 
+class MatchDirectivePrinter(DirectivePrinter):
+    __directive__ = template.MatchDirective
+    def produce_directive(self, gencontext, directive, event, substream):
+        yield (PYTHON_LINE, "def do_match(select):")
+        for evt in gencontext.gen_stream(substream):
+            yield evt
+        yield (PYTHON_LINE, "")
+        index = gencontext.path_index_by_directive[directive]
+        yield (PYTHON_LINE, """context._match_templates.append(
+                (INLINE_PATHS[%d].test(ignore_context=True),INLINE_PATHS[%d], do_match, _namespaces)
+            )""" % (index, index    )
+        )
+    def declared_identifiers(self, gencontext, directive, event):
+        return ['select']
+MatchDirectivePrinter()
+
 class IfDirectivePrinter(DirectivePrinter):
     __directive__ = template.IfDirective
     def produce_directive(self, gencontext, directive, event, substream):
@@ -103,6 +119,7 @@
                'xhtml': serialize.XHTMLSerializeFilter,
                'html':  serialize.HTMLSerializeFilter,
                'text':  serialize.TextSerializeFilter}[method]())
+               
         self.code = self._generate_module()
         self.filters = filters or []
         if strip_whitespace:
@@ -116,8 +133,11 @@
             assert isinstance(ctxt, Context)
         else:
             ctxt = Context(**kwargs)
-        
-        return adapters.InlineStream(self, ctxt)
+
+        stream = interp._match(self.code.go(ctxt), ctxt)
+        for _filter in self.filters:
+            stream = _filter(stream)
+        return adapters.InlineStream(stream)
         
     def _generate_code_events(self):
         return PythonPrinter(
@@ -152,10 +172,14 @@
         self.serializer = serializer
         self.defs_without_params = sets.Set()
         self.defs = sets.Set()
+        self.path_index_by_directive = {}
     def generate(self):
-        for evt in self.start():
+        for evt in self.produce_preamble():
             yield evt
         stream = list(self.stream)
+        yield (PYTHON_LINE, "INLINE_PATHS = [%s]" % ','.join(["adapters." + repr(p) for p in self.find_paths(stream)]))
+        yield (PYTHON_LINE, "def go(context):")
+        yield (PYTHON_LINE, "_namespaces = {u'py': u'http://genshi.edgewall.org/'}")
         for expr in self.find_identifiers(stream, sets.ImmutableSet()):
             yield expr
         for evt in self.gen_stream(stream):
@@ -163,6 +187,17 @@
         for  evt in self.end():
             yield evt
 
+    def find_paths(self, stream):
+        preexec_paths = []
+        for evt in stream:
+            if evt[0] is template.SUB:
+                for directive in evt[1][0]:
+                    if isinstance(directive, template.MatchDirective):
+                        ip = adapters.InlinePath(directive.path.source, directive.path.filename, directive.path.lineno)
+                        preexec_paths.append(ip)
+                        self.path_index_by_directive[directive] = len(preexec_paths) -1
+        return preexec_paths
+        
     def find_identifiers(self, stream, stack):
         """locate undeclared python identifiers in the given stream.  stack is an empty set."""
         for evt in stream:
@@ -201,12 +236,18 @@
             elif kind is template.TEXT:
                 for evt in self.produce_text_event(event):
                     yield evt
+            elif kind is template.START_NS:
+                for evt in self.produce_start_ns_event(event):
+                    yield evt
+            elif kind is template.END_NS:
+                for evt in self.produce_end_ns_event(event):
+                    yield evt
     def produce_preamble(self):
         for line in [
             "from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT, DOCTYPE, Stream",
             "from genshi.template import Context, Template",
             "from genshi.path import Path",
-            "from genshi.codegen import interp",
+            "from genshi.codegen import interp, adapters",
             "EXPR = Template.EXPR"
         ]:
             yield (PYTHON_LINE, line)
@@ -217,10 +258,6 @@
     def produce_directive(self, directive, event, substream):
         for evt in _directive_printers[directive.__class__].produce_directive(self, directive, event, substream):
             yield evt
-    def start(self):
-        for evt in self.produce_preamble():
-            yield evt
-        yield (PYTHON_LINE, "def go(context):")
     def end(self):
         yield (PYTHON_LINE, "")
     def produce_start_event(self, event):
@@ -254,4 +291,20 @@
             repr(event[2]),
             repr(unicode(event[3]))
         ))
+        
+    def produce_start_ns_event(self, event):
+        yield (PYTHON_LINE, "yield (START_NS, (%s), %s, %s)" % (
+            repr(unicode(event[1])),
+            repr(event[2]),
+            repr(unicode(''))
+        ))
+        yield (PYTHON_LINE, "_namespaces[%s] = %s" % (repr(event[1][0]), repr(unicode(event[1][1]))))
+    def produce_end_ns_event(self, event):
+        yield (PYTHON_LINE, "del _namespaces[%s]" % (repr(event[1])))
+        yield (PYTHON_LINE, "yield (START_NS, (%s), %s, %s)" % (
+            repr(unicode(event[1])),
+            repr(event[2]),
+            repr(unicode(''))
+        ))
 
+
--- a/genshi/codegen/interp.py
+++ b/genshi/codegen/interp.py
@@ -1,6 +1,9 @@
+"""defines resources available to generated modules"""
+
 from genshi.template import MarkupTemplate, Template, Context
 from genshi.path import Path
 from genshi.output import HTMLSerializer
+from genshi.codegen import adapters
 from compiler import ast, parse, visitor
 from genshi.core import START, END, START_NS, END_NS, TEXT, COMMENT, DOCTYPE, QName, Stream
 EXPR = Template.EXPR
@@ -33,6 +36,7 @@
                 break
 
     for event in stream:
+#        print "TESTING EVENT", event
         if not match_templates or (event[0] is not START and
                                    event[0] is not END):
             yield event
@@ -52,22 +56,16 @@
                 #for filter_ in self.filters[3:]:
                 #    content = filter_(content, ctxt)
                 content = list(content)
-
+                
                 for test in [mt[0] for mt in match_templates]:
                     test(tail[0][0:3], namespaces, ctxt, updateonly=True)
 
                 def select(path):
-                    return render_inline(InlinedPath(path).select(Stream(content), namespaces, ctxt))
+                    return adapters.InlinePath(path).select(Stream(content), namespaces, ctxt)
                 
-                # ctxt.push()/pop() is usually not going to be necessary for inlined execution
-                # since python scopes will handle most pushing/popping
-                ctxt.push(dict(select=select))
-
                 # similarly, no need for _eval (eval is inlined) as well as _flatten (inlined code already "flattened")
-                for event in _match(template, ctxt, match_templates[:idx] + match_templates[idx + 1:]):
+                for event in _match(template(select), ctxt, match_templates[:idx] + match_templates[idx + 1:]):
                     yield event
-
-                ctxt.pop()
                 break
         else:
             yield event
@@ -82,7 +80,3 @@
         else:
             yield TEXT, unicode(result), pos, result
 
-def run_inlined(module, data):
-    context = Context(**data)
-    for item in _match(module.go(context), context):
-        yield item[3]
--- a/genshi/codegen/output.py
+++ b/genshi/codegen/output.py
@@ -1,3 +1,4 @@
+"""output filters that apply to inline-generated streams"""
 from itertools import chain
 try:
     frozenset
--- a/genshi/codegen/tests/template.py
+++ b/genshi/codegen/tests/template.py
@@ -13,6 +13,7 @@
 class MarkupTemplateAdapter(object):
     def __init__(self, text):
         self.generator = Generator(RealMarkupTemplate(text), strip_whitespace=True)
+        print u''.join(self.generator._generate_code_events())
     def generate(self, *args, **kwargs):
         return self.generator.generate(*args, **kwargs)
 
--- a/genshi/codegen/tests/test_generator.py
+++ b/genshi/codegen/tests/test_generator.py
@@ -25,6 +25,11 @@
       xmlns:xi="http://www.w3.org/2001/XInclude"
       lang="en">
  <body>
+    <!-- remove this match to get much faster performance -->
+    <py:match path='*[@class="message"]'>
+        matched the message, which was ${select('*|text()')}
+        </py:match>
+        
     <div py:for="item in items()">
         ${lala + 'hi'}
         <div py:for="x in foo">
@@ -55,7 +60,8 @@
 data = {'lala':'hi', 'items':lambda:["one", "two", "three"], 'foo':['f1', 'f2', 'f3']}
     
 t = MarkupTemplate(text)
-print t.generate(**data).render()
+#print t.generate(**data).render()
+#sys.exit()
 
 g = generator.Generator(t)
 pycode =  u''.join(g._generate_code_events())
@@ -63,6 +69,7 @@
 
 print str(g.generate(**data))
 
+
 print "Running MarkupTemplate.generate()/HTMLSerializer..."
 now = time.time()
 for x in range(1,1000):
--- a/genshi/path.py
+++ b/genshi/path.py
@@ -78,6 +78,8 @@
         @param text: the path expression
         """
         self.source = text
+        self.filename = filename
+        self.lineno = lineno
         self.paths = PathParser(text, filename, lineno).parse()
 
     def __repr__(self):
@@ -200,7 +202,6 @@
 
                     # The node test matched
                     if matched:
-
                         # Check all the predicates for this step
                         if predicates:
                             for predicate in predicates:
@@ -266,7 +267,6 @@
                         cutoff[:] = []
                         break
                     cursors[-1] = cursor
-
             return retval
 
         return _test
Copyright (C) 2012-2017 Edgewall Software