diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py
index 8bfb1b985ea04a7e4cfba53163b622c796a2f3f9..537926a1aa88ed3372f36171cdf37d64e2439c7f 100644
--- a/lib/mako/codegen.py
+++ b/lib/mako/codegen.py
@@ -1,9 +1,12 @@
 """provides the Compiler object for generating module source code."""
 
 from StringIO import StringIO
+import time
 from mako.pygen import PythonPrinter
 from mako import util, ast
 
+MAGIC_NUMBER = 1
+
 class Compiler(object):
     def __init__(self, node, filename=None):
         self.node = node
@@ -17,32 +20,36 @@ class Compiler(object):
         f = FindPyDecls()
         self.node.accept_visitor(f)
         
-        components = []
-        class FindTopLevelComponents(object):
-            def visitComponentTag(self, node):
-                components.append(node)
-        self.node.accept_visitor(FindTopLevelComponents())
+        components = _find_top_level_components(self.node)
         
         (module_declared, module_undeclared) = (util.Set(), util.Set())
         
         buf = StringIO()
         printer = PythonPrinter(buf)
         
+        # module-level names, python code
+        printer.writeline("from mako import runtime")
+        printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER))
+        printer.writeline("_modified_time = %s" % repr(time.time()))
         printer.writeline("_template_filename=%s" % repr(self.filename))
+        buf.write("\n\n")
         for n in module_code:
-            (module_declared, module_undeclared) = self._get_declared([n], module_declared, module_undeclared)
+            (module_declared, module_undeclared) = _find_declared_identifiers([n], module_declared, module_undeclared)
             printer.writeline("# SOURCE LINE %d" % n.lineno, is_comment=True)
             printer.write_indented_block(n.text)
         
-        (declared, undeclared) = self._get_declared(self.node.nodes, module_declared)
-        self.node.accept_visitor(_GenerateRenderMethod(printer, undeclared))
+        # print main render() method
+        (declared, undeclared) = _find_declared_identifiers(self.node.nodes, module_declared)
+        self.node.accept_visitor(_GenerateRenderMethod(printer, declared, undeclared, components))
         printer.writeline(None)
         buf.write("\n\n")
 
+        # print render() for each top-level component
         for node in components:
             declared = util.Set(node.declared_identifiers()).union(module_declared)
-            (declared, undeclared) = self._get_declared(node.nodes, declared)
-            render = _GenerateRenderMethod(printer, undeclared, name="render_%s" % node.name, args=node.function_decl.get_argument_expressions())
+            (declared, undeclared) = _find_declared_identifiers(node.nodes, declared)
+            local_components = _find_top_level_components(node.nodes)
+            render = _GenerateRenderMethod(printer, declared, undeclared, components + local_components, name="render_%s" % node.name, args=node.function_decl.get_argument_expressions())
             for n in node.nodes:
                 n.accept_visitor(render)
             printer.writeline("return ''")
@@ -51,44 +58,9 @@ class Compiler(object):
             
         return buf.getvalue()
     
-    def _get_declared(self, nodes, declared=None, undeclared=None):
-        if declared is None:
-            declared = util.Set()
-        else:
-            declared = util.Set(declared)
-        if undeclared is None:
-            undeclared = util.Set()
-        else:
-            undeclared = util.Set(undeclared)
-        def check_declared(node):
-            for ident in node.declared_identifiers():
-                declared.add(ident)
-            for ident in node.undeclared_identifiers():
-                if ident != 'context' and ident not in declared:
-                    undeclared.add(ident)
-        class FindUndeclared(object):
-            def visitExpression(self, node):
-                check_declared(node)
-            def visitControlLine(self, node):
-                check_declared(node)
-            def visitCode(self, node):
-                if not node.ismodule:
-                    check_declared(node)
-            def visitComponentTag(self, node):
-                check_declared(node)
-            def visitIncludeTag(self, node):
-                # TODO: expressions for attributes
-                pass        
-            def visitNamespaceTag(self, node):
-                # TODO: expressions for attributes
-                pass
-        fd = FindUndeclared()
-        for n in nodes:
-            n.accept_visitor(FindUndeclared())        
-        return (declared, undeclared)
-        
+
 class _GenerateRenderMethod(object):
-    def __init__(self, printer, undeclared, name='render', in_component=False, args=None):
+    def __init__(self, printer, declared, undeclared, components, name='render', in_component=False, args=None):
         self.printer = printer
         self.in_component = in_component
         self.last_source_line = -1
@@ -97,43 +69,137 @@ class _GenerateRenderMethod(object):
         else:
             args = [a for a in ['context'] + args]
         printer.writeline("def %s(%s):" % (name, ','.join(args)))
+        
+        self.write_variable_declares(declared, undeclared, components)
+
+    def write_variable_declares(self, declared, undeclared, components):
+        comp_idents = dict([(c.name, c) for c in components])
         for ident in undeclared:
-            printer.writeline("%s = context.get(%s, None)" % (ident, repr(ident)))
-    def writeSourceComment(self, node):
+            if ident in comp_idents:
+                comp = comp_idents[ident]
+                if comp.is_root():
+                    self.write_component_decl(comp)
+                else:
+                    self.write_inline_component(comp, declared.union(undeclared), None)
+            else:
+                self.printer.writeline("%s = context.get(%s, None)" % (ident, repr(ident)))
+        
+    def write_source_comment(self, node):
         if self.last_source_line != node.lineno:
             self.printer.writeline("# SOURCE LINE %d" % node.lineno, is_comment=True)
             self.last_source_line = node.lineno
+
+    def write_component_decl(self, node):
+        funcname = node.function_decl.funcname
+        namedecls = node.function_decl.get_argument_expressions()
+        nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
+        nameargs.insert(0, 'context')
+        self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
+        self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs)))
+        self.printer.writeline(None)
+        
+    def write_inline_component(self, node, declared, undeclared):
+        namedecls = node.function_decl.get_argument_expressions()
+        self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls)))
+        components = _find_top_level_components(node.nodes)
+        (declared, undeclared) = _find_declared_identifiers(node.nodes, declared, undeclared)
+        self.write_variable_declares(declared, undeclared, components)
+        for n in node.nodes:
+            n.accept_visitor(self)
+        self.printer.writeline("return ''")
+        self.printer.writeline(None)
+
     def visitExpression(self, node):
-        self.writeSourceComment(node)
+        self.write_source_comment(node)
         self.printer.writeline("context.write(unicode(%s))" % node.text)
     def visitControlLine(self, node):
         if node.isend:
             self.printer.writeline(None)
         else:
-            self.writeSourceComment(node)
+            self.write_source_comment(node)
             self.printer.writeline(node.text)
     def visitText(self, node):
-        self.writeSourceComment(node)
+        self.write_source_comment(node)
         self.printer.writeline("context.write(%s)" % repr(node.content))
     def visitCode(self, node):
         if not node.ismodule:
-            self.writeSourceComment(node)
+            self.write_source_comment(node)
             self.printer.write_indented_block(node.text)
     def visitIncludeTag(self, node):
-        self.writeSourceComment(node)
+        self.write_source_comment(node)
         self.printer.writeline("context.include_file(%s, import=%s)" % (repr(node.attributes['file']), repr(node.attributes.get('import', False))))
     def visitNamespaceTag(self, node):
-        pass
-    def visitComponentTag(self, node):
-        self.writeSourceComment(node)
-        funcname = node.function_decl.funcname
-        namedecls = node.function_decl.get_argument_expressions()
-        nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
-        nameargs.insert(0, 'context')
-        self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
-        self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs)))
+        self.write_source_comment(node)
+        self.printer.writeline("def make_namespace():")
+        export = []
+        class NSComponentVisitor(object):
+            def visitComponentTag(s, node):
+                self.write_inline_component(node, None, None)
+                export.append(node.name)
+        vis = NSComponentVisitor()
+        for n in node.nodes:
+            n.accept_visitor(vis)
+        self.printer.writeline("return %s" % (repr(export)))
+        self.printer.writeline(None)
+        self.printer.writeline("class %sNamespace(runtime.Namespace):" % node.name)
+        self.printer.writeline("def __getattr__(self, key):")
+        self.printer.writeline("return self.contextual_callable(context, key)")
+        self.printer.writeline(None)
         self.printer.writeline(None)
+        self.printer.writeline("%s = %sNamespace(%s, callables=make_namespace())" % (node.name, node.name))
+        
+    def visitComponentTag(self, node):
+        pass
     def visitCallTag(self, node):
         pass
     def visitInheritTag(self, node):
         pass
+
+def _find_top_level_components(nodes):
+    components = []
+    class FindTopLevelComponents(object):
+        def visitComponentTag(self, node):
+            components.append(node)
+    ftl = FindTopLevelComponents()
+    if isinstance(nodes, list):
+        for n in nodes:
+            n.accept_visitor(ftl)
+    else:
+        nodes.accept_visitor(ftl)
+    return components
+
+def _find_declared_identifiers(nodes, declared=None, undeclared=None):
+    if declared is None:
+        declared = util.Set()
+    else:
+        declared = util.Set(declared)
+    if undeclared is None:
+        undeclared = util.Set()
+    else:
+        undeclared = util.Set(undeclared)
+    def check_declared(node):
+        for ident in node.declared_identifiers():
+            declared.add(ident)
+        for ident in node.undeclared_identifiers():
+            if ident != 'context' and ident not in declared:
+                undeclared.add(ident)
+    class FindUndeclared(object):
+        def visitExpression(self, node):
+            check_declared(node)
+        def visitControlLine(self, node):
+            check_declared(node)
+        def visitCode(self, node):
+            if not node.ismodule:
+                check_declared(node)
+        def visitComponentTag(self, node):
+            pass
+            #check_declared(node)
+        def visitIncludeTag(self, node):
+            # TODO: expressions for attributes
+            pass        
+        def visitNamespaceTag(self, node):
+            check_declared(node)
+    fd = FindUndeclared()
+    for n in nodes:
+        n.accept_visitor(FindUndeclared())        
+    return (declared, undeclared)
diff --git a/lib/mako/context.py b/lib/mako/context.py
deleted file mode 100644
index a9d8ece559cf8bc66a5f5dade5125247be9a20f5..0000000000000000000000000000000000000000
--- a/lib/mako/context.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""provides the Context class, the runtime namespace for templates."""
-
-
-class Context(object):
-    """provides runtime namespace and output buffer for templates."""
-    def __init__(self, buffer, **data):
-        self.buffer = buffer
-        self.data = data
-        # the Template instance currently rendering with this context.
-        self.with_template = None
-    def __getitem__(self, key):
-        return self.data[key]
-    def get(self, key, default=None):
-        return self.data.get(key, default)
-    def write(self, string):
-        self.buffer.write(string)
\ No newline at end of file
diff --git a/lib/mako/lexer.py b/lib/mako/lexer.py
index 81abdf98b1945f6975feb434d9dbecdd0532c268..2dfa5d1f3d4902e2e23f54e6d18ec295fedff664 100644
--- a/lib/mako/lexer.py
+++ b/lib/mako/lexer.py
@@ -93,7 +93,23 @@ class Lexer(object):
         return self.template
 
     def match_tag_start(self):
-        match = self.match(r'''\<%(\w+)\s+(.+?["'])?\s*(/)?>''', re.I | re.S )
+        match = self.match(r'''
+            \<%     # opening tag
+            
+            (\w+)   # keyword
+            
+            \s+     # some space
+            
+            ((?:\w+|=|".*?"|'.*?')*)  # attrname, = sign, string expression
+            
+            \s*     # more whitespace
+            
+            (/)?>   # closing
+            
+            ''', 
+            
+            re.I | re.S | re.X)
+            
         if match:
             (keyword, attr, isend) = (match.group(1).lower(), match.group(2), match.group(3))
             self.keyword = keyword
@@ -102,7 +118,6 @@ class Lexer(object):
                 for att in re.findall(r"\s*(\w+)\s*=\s*(?:'([^']*)'|\"([^\"]*)\")", attr):
                     (key, val1, val2) = att
                     attributes[key] = val1 or val2
-
             self.append_node(parsetree.Tag, keyword, attributes)
             if isend:
                 self.tag.pop()
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py
index 40e78077ae09fc9906d6f0b89646b4408caba8ab..34dbf7778864fb7e86d27e4f96efb17ebb065951 100644
--- a/lib/mako/parsetree.py
+++ b/lib/mako/parsetree.py
@@ -140,7 +140,7 @@ class _TagMeta(type):
         return type.__call__(cls, keyword, attributes, **kwargs)
         
 class Tag(Node):
-    """base class for tags.
+    """abstract base class for tags.
     
     <%sometag/>
     
@@ -150,27 +150,76 @@ class Tag(Node):
     """
     __metaclass__ = _TagMeta
     __keyword__ = None
-    def __init__(self, keyword, attributes, **kwargs):
+    def __init__(self, keyword, attributes, expressions, nonexpressions, required, **kwargs):
+        """construct a new Tag instance.
+        
+        this constructor not called directly, and is only called by subclasses.
+        
+        keyword - the tag keyword
+        
+        attributes - raw dictionary of attribute key/value pairs
+        
+        expressions - a util.Set of identifiers that are legal attributes, which can also contain embedded expressions
+        
+        nonexpressions - a util.Set of identifiers that are legal attributes, which cannot contain embedded expressions
+        
+        **kwargs - other arguments passed to the Node superclass (lineno, pos)"""
         super(Tag, self).__init__(**kwargs)
         self.keyword = keyword
         self.attributes = attributes
+        self._parse_attributes(expressions, nonexpressions)
+        missing = [r for r in required if r not in self.parsed_attributes]
+        if len(missing):
+            raise exceptions.CompileException("Missing attribute(s): %s" % ",".join([repr(m) for m in missing]), self.lineno, self.pos)
         self.parent = None
         self.nodes = []
     def is_root(self):
         return self.parent is None
     def get_children(self):
         return self.nodes
+    def _parse_attributes(self, expressions, nonexpressions):
+        undeclared_identifiers = util.Set()
+        self.parsed_attributes = {}
+        for key in self.attributes:
+            if key in expressions:
+                expr = []
+                for x in re.split(r'(\${.+?})', self.attributes[key]):
+                    m = re.match(r'^\${(.+?)}$', x)
+                    if m:
+                        code = ast.PythonCode(m.group(1), self.lineno, self.pos)
+                        undeclared_identifiers = undeclared_identifiers.union(code.undeclared_identifiers)
+                        expr.append(m.group(1))
+                    else:
+                        expr.append(repr(x))
+                self.parsed_attributes[key] = " + ".join(expr)
+            elif key in nonexpressions:
+                if re.search(r'${.+?}', self.attributes[key]):
+                    raise exceptions.CompileException("Attibute '%s' in tag '%s' does not allow embedded expressions"  %(key, self.keyword), self.lineno, self.pos)
+                self.parsed_attributes[key] = repr(self.attributes[key])
+            else:
+                raise exceptions.CompileException("Invalid attribute for tag '%s': '%s'" %(self.keyword, key), self.lineno, self.pos)        
     def __repr__(self):
         return "%s(%s, %s, %s, %s)" % (self.__class__.__name__, repr(self.keyword), repr(self.attributes), repr((self.lineno, self.pos)), repr([repr(x) for x in self.nodes]))
         
 class IncludeTag(Tag):
     __keyword__ = 'include'
+    def __init__(self, keyword, attributes, **kwargs):
+        super(IncludeTag, self).__init__(keyword, attributes, util.Set(['file', 'import']), util.Set([]), util.Set(['file']), **kwargs)
+    
 class NamespaceTag(Tag):
     __keyword__ = 'namespace'
+    def __init__(self, keyword, attributes, **kwargs):
+        super(NamespaceTag, self).__init__(keyword, attributes, util.Set(['file']), util.Set(['name']), util.Set(['name']), **kwargs)
+        self.name = attributes['name']
+    def declared_identifiers(self):
+        return [self.name]
+    def undeclared_identifiers(self):
+        return []
+        
 class ComponentTag(Tag):
     __keyword__ = 'component'
     def __init__(self, keyword, attributes, **kwargs):
-        super(ComponentTag, self).__init__(keyword, attributes, **kwargs)
+        super(ComponentTag, self).__init__(keyword, attributes, util.Set([]), util.Set(['name']), util.Set(['name']), **kwargs)
         name = attributes['name']
         if re.match(r'^[\w_]+$',name):
             name = name + "()"
@@ -183,9 +232,19 @@ class ComponentTag(Tag):
         for c in self.function_decl.defaults:
             res += list(ast.PythonCode(c, self.lineno, self.pos).undeclared_identifiers)
         return res
+        
 class CallTag(Tag):
     __keyword__ = 'call'
+    def __init__(self, keyword, attributes, **kwargs):
+        super(CallTag, self).__init__(keyword, attributes, util.Set([]), util.Set(['expr']), util.Set(['expr']), **kwargs)
+
 class InheritTag(Tag):
     __keyword__ = 'inherit'
+    def __init__(self, keyword, attributes, **kwargs):
+        super(InheritTag, self).__init__(keyword, attributes, util.Set(['file']), util.Set([]), util.Set(['file']), **kwargs)
+
 class PageTag(Tag):
-    __keyword__ = 'page'
\ No newline at end of file
+    __keyword__ = 'page'
+    def __init__(self, keyword, attributes, **kwargs):
+        super(PageTag, self).__init__(keyword, attributes, util.Set([]), util.Set([]), util.Set([]), **kwargs)
+    
\ No newline at end of file
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py
new file mode 100644
index 0000000000000000000000000000000000000000..76936c8abb55114eb61a202a6175a479ad7a8655
--- /dev/null
+++ b/lib/mako/runtime.py
@@ -0,0 +1,46 @@
+"""provides the Context class, the runtime namespace for templates."""
+from mako import exceptions
+
+class Context(object):
+    """provides runtime namespace and output buffer for templates."""
+    def __init__(self, buffer, **data):
+        self.buffer = buffer
+        self.data = data
+        # the Template instance currently rendering with this context.
+        self.with_template = None
+    def __getitem__(self, key):
+        return self.data[key]
+    def get(self, key, default=None):
+        return self.data.get(key, default)
+    def write(self, string):
+        self.buffer.write(string)
+        
+        
+class Namespace(object):
+    """provides access to collections of rendering methods, which can be local, from other templates, or from imported modules"""
+    def __init__(self, name, module=None, template=None, callables=None):
+        self.module = module
+        self.template = template
+        self.callables = callables
+        
+    def load(self, key):
+        if self.callables is not None:
+            try:
+                return self.callables[key]
+            except KeyError:
+                pass
+        if self.template is not None:
+            try:
+                return self.template.get_component(key)
+            except AttributeError:
+                pass
+        if self.module is not None:
+            try:
+                return getattr(self.module, key)
+            except AttributeError:
+                pass
+        raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key))
+        
+    def contextual_callable(self, context, key):
+        return lambda context, *args, **kwargs:self.load(key)(context, *args, **kwargs)
+        
\ No newline at end of file
diff --git a/lib/mako/template.py b/lib/mako/template.py
index 9df070f4cb52e42306b246cb8eda46d38cf44a49..08d925f575d0fc3853174a452dd7709a71ba9a34 100644
--- a/lib/mako/template.py
+++ b/lib/mako/template.py
@@ -3,7 +3,7 @@ as well as template runtime operations."""
 
 from mako.lexer import Lexer
 from mako.codegen import Compiler
-from mako.context import Context
+from mako.runtime import Context
 import imp, time, inspect, weakref, sys
 from StringIO import StringIO
 
@@ -80,11 +80,11 @@ class ComponentTemplate(Template):
 def _compile_text(text, identifier, filename):
     node = Lexer(text).parse()
     source = Compiler(node).render()
+    #print source
     cid = identifier
     module = imp.new_module(cid)
     code = compile(source, filename or cid, 'exec')
     exec code in module.__dict__, module.__dict__
-    module._modified_time = time.time()
     return (source, module)
     
 def _render(template, callable_, *args, **data):
diff --git a/test/lexer.py b/test/lexer.py
index 6bec2482f8e8ed69922854a9d88916d9041416ea..385111078d19dd4f05e2e7f1154592a6d8443e61 100644
--- a/test/lexer.py
+++ b/test/lexer.py
@@ -14,8 +14,7 @@ class LexerTest(unittest.TestCase):
         and some more text.
 """
         node = Lexer(template).parse()
-        #print repr(nodes)
-        assert repr(node) == r"""TemplateNode({}, [Text('\n<b>Hello world</b>\n        ', (1, 1)), ComponentTag('component', {'name': '"foo"'}, (3, 9), ["Text('\\n                this is a component.\\n        ', (3, 32))"]), Text('\n        \n        and some more text.\n', (5, 22))])"""
+        assert repr(node) == r"""TemplateNode({}, [Text('\n<b>Hello world</b>\n        ', (1, 1)), ComponentTag('component', {'name': 'foo'}, (3, 9), ["Text('\\n                this is a component.\\n        ', (3, 32))"]), Text('\n        \n        and some more text.\n', (5, 22))])"""
 
     def test_unclosed_tag(self):
         template = """
@@ -29,6 +28,28 @@ class LexerTest(unittest.TestCase):
         except exceptions.SyntaxException, e:
             assert str(e) == "Unclosed tag: <%component> at line: 5 char: 9"
 
+    def test_nonexistent_tag(self):
+        template = """
+            <%lala x="5"/>
+        """
+        try:
+            node = Lexer(template).parse()
+            assert False
+        except exceptions.CompileException, e:
+            assert str(e) == "No such tag: 'lala' at line: 2 char: 13"
+    
+    def test_component_syntax(self):
+        template = """
+        <%component lala>
+            hi
+        </%component>
+"""
+        try:
+            node = Lexer(template).parse()
+            assert False
+        except exceptions.CompileException, e:
+            assert str(e) == "Missing attribute(s): 'name' at line: 2 char: 9"
+            
     def test_expr_in_attribute(self):
         """test some slightly trickier expressions.
         
@@ -39,7 +60,8 @@ class LexerTest(unittest.TestCase):
         """
         nodes = Lexer(template).parse()
         #print nodes
-        assert repr(nodes) == r"""TemplateNode({}, [Text('\n            ', (1, 1)), CallTag('call', {'expr': '"foo>bar and \'lala\' or \'hoho\'"'}, (2, 13), []), Text('\n            ', (2, 57)), CallTag('call', {'expr': '\'foo<bar and hoho>lala and "x" + "y"\''}, (3, 13), []), Text('\n        ', (3, 64))])"""
+        assert repr(nodes) == r"""TemplateNode({}, [Text('\n            ', (1, 1)), CallTag('call', {'expr': "foo>bar and 'lala' or 'hoho'"}, (2, 13), []), Text('\n            ', (2, 57)), CallTag('call', {'expr': 'foo<bar and hoho>lala and "x" + "y"'}, (3, 13), []), Text('\n        ', (3, 64))])"""
+        
         
     def test_nesting(self):
         template = """
@@ -52,7 +74,7 @@ class LexerTest(unittest.TestCase):
         
         """
         nodes = Lexer(template).parse()
-        assert repr(nodes) == r"""TemplateNode({}, [Text('\n        \n        ', (1, 1)), NamespaceTag('namespace', {'name': '"ns"'}, (3, 9), ["Text('\\n            ', (3, 31))", 'ComponentTag(\'component\', {\'name\': \'"lala(hi, there)"\'}, (4, 13), ["Text(\'\\\\n                \', (4, 48))", \'CallTag(\\\'call\\\', {\\\'expr\\\': \\\'"something()"\\\'}, (5, 17), [])\', "Text(\'\\\\n            \', (5, 44))"])', "Text('\\n        ', (6, 26))"]), Text('\n        \n        ', (7, 22))])"""
+        assert repr(nodes) == r"""TemplateNode({}, [Text('\n        \n        ', (1, 1)), NamespaceTag('namespace', {'name': 'ns'}, (3, 9), ["Text('\\n            ', (3, 31))", 'ComponentTag(\'component\', {\'name\': \'lala(hi, there)\'}, (4, 13), ["Text(\'\\\\n                \', (4, 48))", "CallTag(\'call\', {\'expr\': \'something()\'}, (5, 17), [])", "Text(\'\\\\n            \', (5, 44))"])', "Text('\\n        ', (6, 26))"]), Text('\n        \n        ', (7, 22))])"""
     
     def test_code(self):
         template = """
@@ -107,8 +129,7 @@ class LexerTest(unittest.TestCase):
         ${hi()}
 """
         nodes = Lexer(template).parse()
-        #print nodes
-        assert repr(nodes) == r"""TemplateNode({}, [Text('\n        this is some ', (1, 1)), Expression('text', [], (2, 22)), Text(' and this is ', (2, 29)), Expression('textwith ', ['escapes', 'moreescapes'], (2, 42)), Text('\n        ', (2, 76)), ComponentTag('component', {'name': '"hi"'}, (3, 9), ["Text('\\n            give me ', (3, 31))", "Expression('foo()', [], (4, 21))", "Text(' and ', (4, 29))", "Expression('bar()', [], (4, 34))", "Text('\\n        ', (4, 42))"]), Text('\n        ', (5, 22)), Expression('hi()', [], (6, 9)), Text('\n', (6, 16))])"""
+        assert repr(nodes) == r"""TemplateNode({}, [Text('\n        this is some ', (1, 1)), Expression('text', [], (2, 22)), Text(' and this is ', (2, 29)), Expression('textwith ', ['escapes', 'moreescapes'], (2, 42)), Text('\n        ', (2, 76)), ComponentTag('component', {'name': 'hi'}, (3, 9), ["Text('\\n            give me ', (3, 31))", "Expression('foo()', [], (4, 21))", "Text(' and ', (4, 29))", "Expression('bar()', [], (4, 34))", "Text('\\n        ', (4, 42))"]), Text('\n        ', (5, 22)), Expression('hi()', [], (6, 9)), Text('\n', (6, 16))])"""
 
     def test_control_lines(self):
         template = """
diff --git a/test/template.py b/test/template.py
new file mode 100644
index 0000000000000000000000000000000000000000..167557ede532b5af04bac9ec64655408efc131dc
--- /dev/null
+++ b/test/template.py
@@ -0,0 +1,147 @@
+from mako.template import Template
+import unittest
+
+
+class ComponentTest(unittest.TestCase):
+    def test_component_noargs(self):
+        template = Template("""
+        
+        ${mycomp()}
+        
+        <%component name="mycomp">
+            hello mycomp ${variable}
+        </%component>
+        
+        """)
+        assert template.render(variable='hi').strip() == """hello mycomp hi"""
+
+    def test_component_blankargs(self):
+        template = Template("""
+        <%component name="mycomp()">
+            hello mycomp ${variable}
+        </%component>
+
+        ${mycomp()}""")
+        assert template.render(variable='hi').strip() == """hello mycomp hi"""
+
+    def test_component_args(self):
+        template = Template("""
+        <%component name="mycomp(a, b)">
+            hello mycomp ${variable}, ${a}, ${b}
+        </%component>
+
+        ${mycomp(5, 6)}""")
+        assert template.render(variable='hi', a=5, b=6).strip() == """hello mycomp hi, 5, 6"""
+
+    def test_inter_component(self):
+        """test components calling each other"""
+        template = Template("""
+${b()}
+
+<%component name="a">\
+im a
+</%component>
+
+<%component name="b">
+im b
+and heres a:  ${a()}
+</%component>
+
+<%component name="c">
+im c
+</%component>
+        """)
+        # check that "a" is declared in "b", but not in "c"
+        assert "a" not in template.module.render_c.func_code.co_varnames
+        assert "a" in template.module.render_b.func_code.co_varnames
+        
+        # then test output
+        assert template.render().strip() == "im b\nand heres a:  im a"
+
+    def test_nested_component(self):
+        template = """
+
+        ${hi()}
+        
+        <%component name="hi">
+            hey, im hi.
+            and heres ${foo()}, ${bar()}
+            
+            <%component name="foo">
+                this is foo
+            </%component>
+            
+            <%component name="bar">
+                this is bar
+            </%component>
+        </%component>
+"""
+        t = Template(template)
+        #print t.code
+        print t.render()
+
+    def test_nested_nested_component(self):
+        template = """
+        
+        ${a()}
+        <%component name="a">
+            <%component name="b1">
+            </%component>
+            <%component name="b2">
+                a_b2 ${c1()}
+                <%component name="c1">
+                    a_b2_c1
+                </%component>
+            </%component>
+            <%component name="b3">
+                a_b3 ${c1()}
+                <%component name="c1">
+                    a_b3_c1 heres x: ${x}
+                    <%
+                        y = 7
+                    %>
+                    y is ${y}
+                </%component>
+                <%component name="c2">
+                    a_b3_c2
+                    y is ${y}
+                    c1 is ${c1()}
+                </%component>
+                ${c2()}
+            </%component>
+            
+            ${b1()} ${b2()}  ${b3()}
+        </%component>
+"""
+
+        t = Template(template)
+        #print t.code
+        print t.render(x=5)
+        
+class ExceptionTest(unittest.TestCase):
+    def test_raise(self):
+        template = Template("""
+            <%
+                raise Exception("this is a test")
+            %>
+    """, format_exceptions=False)
+        try:
+            template.render()
+            assert False
+        except Exception, e:
+            assert str(e) == "this is a test"
+    def test_handler(self):
+        def handle(context, error):
+            context.write("error message is " + str(error))
+            return True
+            
+        template = Template("""
+            <%
+                raise Exception("this is a test")
+            %>
+    """, error_handler=handle)
+        assert template.render().strip() == """error message is this is a test"""
+        
+
+if __name__ == '__main__':
+    unittest.main()