- Lexer/Compile exceptions propigate throughout lexer/parsetree/ast
using a more portable **exception_kwargs collection
- added "source" member to the dict propigated to Lexer/Compile exceptions
- RichTraceback can access original template source as a unicode object
using either 'source' memebr on Lexer/Compile exception, or 'source'
property on ModuleInfo, fixes #37
- unit tests for #37
diff --git a/CHANGES b/CHANGES
index b7406c6..f3476a5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -8,6 +8,8 @@
 - if plain Python defs are used with <%call>, a decorator
   @runtime.supports_callable exists to ensure that the "caller"
   stack is properly handled for the def.
+- fix to RichTraceback and exception reporting to get template
+  source code as a unicode object #37
     
 0.1.5
 - AST expression generation - added in just about everything 
diff --git a/lib/mako/ast.py b/lib/mako/ast.py
index db51a88..0d00f30 100644
--- a/lib/mako/ast.py
+++ b/lib/mako/ast.py
@@ -12,15 +12,15 @@
 from StringIO import StringIO
 import re
 
-def parse(code, mode, lineno, pos, filename):
+def parse(code, mode, **exception_kwargs):
     try:
         return compiler_parse(code, mode)
     except SyntaxError, e:
-        raise exceptions.SyntaxException("(%s) %s (%s)" % (e.__class__.__name__, str(e), repr(code[0:50])), lineno, pos, filename)
+        raise exceptions.SyntaxException("(%s) %s (%s)" % (e.__class__.__name__, str(e), repr(code[0:50])), **exception_kwargs)
     
 class PythonCode(object):
     """represents information about a string containing Python code"""
-    def __init__(self, code, lineno, pos, filename):
+    def __init__(self, code, **exception_kwargs):
         self.code = code
         
         # represents all identifiers which are assigned to at some point in the code
@@ -36,7 +36,7 @@
         # - AST is less likely to break with version changes (for example, the behavior of co_names changed a little bit
         # in python version 2.5)
         if isinstance(code, basestring):
-            expr = parse(code.lstrip(), "exec", lineno, pos, filename)
+            expr = parse(code.lstrip(), "exec", **exception_kwargs)
         else:
             expr = code
         
@@ -97,14 +97,14 @@
                         self._add_declared(alias)
                     else:
                         if mod == '*':
-                            raise exceptions.CompileException("'import *' is not supported, since all identifier names must be explicitly declared.  Please use the form 'from <modulename> import <name1>, <name2>, ...' instead.", lineno, pos, filename)
+                            raise exceptions.CompileException("'import *' is not supported, since all identifier names must be explicitly declared.  Please use the form 'from <modulename> import <name1>, <name2>, ...' instead.", **exception_kwargs)
                         self._add_declared(mod)
         f = FindIdentifiers()
         visitor.walk(expr, f) #, walker=walker())
 
 class ArgumentList(object):
     """parses a fragment of code as a comma-separated list of expressions"""
-    def __init__(self, code, lineno, pos, filename):
+    def __init__(self, code, **exception_kwargs):
         self.codeargs = []
         self.args = []
         self.declared_identifiers = util.Set()
@@ -112,7 +112,7 @@
         class FindTuple(object):
             def visitTuple(s, node, *args):
                 for n in node.nodes:
-                    p = PythonCode(n, lineno, pos, filename)
+                    p = PythonCode(n, **exception_kwargs)
                     self.codeargs.append(p)
                     self.args.append(ExpressionGenerator(n).value())
                     self.declared_identifiers = self.declared_identifiers.union(p.declared_identifiers)
@@ -122,7 +122,7 @@
                 # if theres text and no trailing comma, insure its parsed
                 # as a tuple by adding a trailing comma
                 code  += ","
-            expr = parse(code, "exec", lineno, pos, filename)
+            expr = parse(code, "exec", **exception_kwargs)
         else:
             expr = code
 
@@ -138,10 +138,10 @@
         except (MyException, e):
     etc.
     """
-    def __init__(self, code, lineno, pos, filename):
+    def __init__(self, code, **exception_kwargs):
         m = re.match(r'^(\w+)(?:\s+(.*?))?:$', code.strip())
         if not m:
-            raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, lineno, pos, filename)
+            raise exceptions.CompileException("Fragment '%s' is not a partial control statement" % code, **exception_kwargs)
         (keyword, expr) = m.group(1,2)
         if keyword in ['for','if', 'while']:
             code = code + "pass"
@@ -152,8 +152,8 @@
         elif keyword == 'except':
             code = "try:pass\n" + code + "pass"
         else:
-            raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, lineno, pos, filename)
-        super(PythonFragment, self).__init__(code, lineno, pos, filename)
+            raise exceptions.CompileException("Unsupported control keyword: '%s'" % keyword, **exception_kwargs)
+        super(PythonFragment, self).__init__(code, **exception_kwargs)
         
 class walker(visitor.ASTVisitor):
     def dispatch(self, node, *args):
@@ -163,9 +163,9 @@
         
 class FunctionDecl(object):
     """function declaration"""
-    def __init__(self, code, lineno, pos, filename, allow_kwargs=True):
+    def __init__(self, code, allow_kwargs=True, **exception_kwargs):
         self.code = code
-        expr = parse(code, "exec", lineno, pos, filename)
+        expr = parse(code, "exec", **exception_kwargs)
         class ParseFunc(object):
             def visitFunction(s, node, *args):
                 self.funcname = node.name
@@ -177,9 +177,9 @@
         f = ParseFunc()
         visitor.walk(expr, f)
         if not hasattr(self, 'funcname'):
-            raise exceptions.CompileException("Code '%s' is not a function declaration" % code, lineno, pos, filename)
+            raise exceptions.CompileException("Code '%s' is not a function declaration" % code, **exception_kwargs)
         if not allow_kwargs and self.kwargs:
-            raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], lineno, pos, filename)
+            raise exceptions.CompileException("'**%s' keyword argument not allowed here" % self.argnames[-1], **exception_kwargs)
             
     def get_argument_expressions(self, include_defaults=True):
         """return the argument declarations of this FunctionDecl as a printable list."""
@@ -207,8 +207,8 @@
 
 class FunctionArgs(FunctionDecl):
     """the argument portion of a function declaration"""
-    def __init__(self, code, lineno, pos, filename, **kwargs):
-        super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, lineno, pos, filename, **kwargs)
+    def __init__(self, code, **kwargs):
+        super(FunctionArgs, self).__init__("def ANON(%s):pass" % code, **kwargs)
         
             
 class ExpressionGenerator(object):
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py
index db520da..379cb4c 100644
--- a/lib/mako/codegen.py
+++ b/lib/mako/codegen.py
@@ -14,20 +14,21 @@
 MAGIC_NUMBER = 2
 
 
-def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None):
+def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None):
     """generate module source code given a parsetree node, uri, and optional source filename"""
     buf = util.FastEncodingBuffer()
     printer = PythonPrinter(buf)
-    _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports), node)
+    _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding), node)
     return buf.getvalue()
 
 class _CompileContext(object):
-    def __init__(self, uri, filename, default_filters, buffer_filters, imports):
+    def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding):
         self.uri = uri
         self.filename = filename
         self.default_filters = default_filters
         self.buffer_filters = buffer_filters
         self.imports = imports
+        self.source_encoding = source_encoding
         
 class _GenerateRenderMethod(object):
     """a template visitor object which generates the full module source for a template."""
@@ -116,12 +117,13 @@
         self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename))
         self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri))
         self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)")
+        self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding))
         if self.compiler.imports:
             buf = ''
             for imp in self.compiler.imports:
                 buf += imp + "\n"
                 self.printer.writeline(imp)
-            impcode = ast.PythonCode(buf, 0, 0, 'template defined imports')
+            impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports')
         else:
             impcode = None
         
@@ -445,8 +447,7 @@
             else:
                 x = e
                 e = locate_encode(e)
-                if e is None:
-                    raise "der its none " + x
+                assert e is not None
             target = "%s(%s)" % (e, target)
         return target
         
diff --git a/lib/mako/exceptions.py b/lib/mako/exceptions.py
index 264068e..b855adb 100644
--- a/lib/mako/exceptions.py
+++ b/lib/mako/exceptions.py
@@ -20,18 +20,20 @@
     else:
         return " in file '%s' at line: %d char: %d" % (filename, lineno, pos)     
 class CompileException(MakoException):
-    def __init__(self, message, lineno, pos, filename):
+    def __init__(self, message, source, lineno, pos, filename):
         MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
         self.lineno =lineno
         self.pos = pos
         self.filename = filename
+        self.source = source
                     
 class SyntaxException(MakoException):
-    def __init__(self, message, lineno, pos, filename):
+    def __init__(self, message, source, lineno, pos, filename):
         MakoException.__init__(self, message + _format_filepos(lineno, pos, filename))
         self.lineno =lineno
         self.pos = pos
         self.filename = filename
+        self.source = source
         
 class TemplateLookupException(MakoException):
     pass
@@ -68,7 +70,8 @@
         if self.error is None:
             self.error = t
         if isinstance(self.error, CompileException) or isinstance(self.error, SyntaxException):
-            self.source = file(self.error.filename).read()
+            import mako.template
+            self.source = self.error.source
             self.lineno = self.error.lineno
             self._has_source = True
         self.reverse_records = [r for r in self.records]
diff --git a/lib/mako/lexer.py b/lib/mako/lexer.py
index c871340..dafa10d 100644
--- a/lib/mako/lexer.py
+++ b/lib/mako/lexer.py
@@ -30,7 +30,9 @@
             self.preprocessor = [preprocessor]
         else:
             self.preprocessor = preprocessor
-        
+            
+    exception_kwargs = property(lambda self:{'source':self.text, 'lineno':self.matched_lineno, 'pos':self.matched_charpos, 'filename':self.filename})
+    
     def match(self, regexp, flags=None):
         """match the given regular expression string and flags to the current text position.
         
@@ -73,7 +75,7 @@
             if match:
                 m = self.match(r'.*?%s' % match.group(1), re.S)
                 if not m:
-                    raise exceptions.SyntaxException("Unmatched '%s'" % match.group(1), self.matched_lineno, self.matched_charpos, self.filename)
+                    raise exceptions.SyntaxException("Unmatched '%s'" % match.group(1), **self.exception_kwargs)
             else:
                 match = self.match(r'(%s)' % r'|'.join(text))
                 if match:
@@ -81,9 +83,10 @@
                 else:
                     match = self.match(r".*?(?=\"|\'|#|%s)" % r'|'.join(text), re.S)
                     if not match:
-                        raise exceptions.SyntaxException("Expected: %s" % ','.join(text), self.matched_lineno, self.matched_charpos, self.filename)
+                        raise exceptions.SyntaxException("Expected: %s" % ','.join(text), **self.exception_kwargs)
                 
     def append_node(self, nodecls, *args, **kwargs):
+        kwargs.setdefault('source', self.text)
         kwargs.setdefault('lineno', self.matched_lineno)
         kwargs.setdefault('pos', self.matched_charpos)
         kwargs['filename'] = self.filename
@@ -102,7 +105,7 @@
             elif node.is_primary:
                 self.control_line.append(node)
             elif len(self.control_line) and not self.control_line[-1].is_ternary(node.keyword):
-                raise exceptions.SyntaxException("Keyword '%s' not a legal ternary for keyword '%s'" % (node.keyword, self.control_line[-1].keyword), self.matched_lineno, self.matched_charpos, self.filename)
+                raise exceptions.SyntaxException("Keyword '%s' not a legal ternary for keyword '%s'" % (node.keyword, self.control_line[-1].keyword), **self.exception_kwargs)
 
     def escape_code(self, text):
         if self.encoding:
@@ -121,12 +124,12 @@
                 try:
                     self.text = self.text.decode(self.encoding)
                 except UnicodeDecodeError, e:
-                    raise exceptions.CompileException("Unicode decode operation of encoding '%s' failed" % self.encoding, 0, 0, self.filename)
+                    raise exceptions.CompileException("Unicode decode operation of encoding '%s' failed" % self.encoding, self.text.decode('utf-8', 'ignore'), 0, 0, self.filename)
             else:
                 try:
                     self.text = self.text.decode()
                 except UnicodeDecodeError, e:
-                    raise exceptions.CompileException("Could not read template using encoding of 'ascii'.  Did you forget a magic encoding comment?", 0, 0, self.filename)
+                    raise exceptions.CompileException("Could not read template using encoding of 'ascii'.  Did you forget a magic encoding comment?", self.text.decode('utf-8', 'ignore'), 0, 0, self.filename)
 
         self.textlength = len(self.text)
             
@@ -156,9 +159,9 @@
             raise exceptions.CompileException("assertion failed")
             
         if len(self.tag):
-            raise exceptions.SyntaxException("Unclosed tag: <%%%s>" % self.tag[-1].keyword, self.matched_lineno, self.matched_charpos, self.filename)
+            raise exceptions.SyntaxException("Unclosed tag: <%%%s>" % self.tag[-1].keyword, **self.exception_kwargs)
         if len(self.control_line):
-            raise exceptions.SyntaxException("Unterminated control keyword: '%s'" % self.control_line[-1].keyword, self.control_line[-1].lineno, self.control_line[-1].pos, self.filename)
+            raise exceptions.SyntaxException("Unterminated control keyword: '%s'" % self.control_line[-1].keyword, self.text, self.control_line[-1].lineno, self.control_line[-1].pos, self.filename)
         return self.template
 
     def match_encoding(self):
@@ -199,7 +202,7 @@
                 if keyword == 'text':
                     match = self.match(r'(.*?)(?=\</%text>)',  re.S)
                     if not match:
-                        raise exceptions.SyntaxException("Unclosed tag: <%%%s>" % self.tag[-1].keyword, self.matched_lineno, self.matched_charpos, self.filename)
+                        raise exceptions.SyntaxException("Unclosed tag: <%%%s>" % self.tag[-1].keyword, **self.exception_kwargs)
                     self.append_node(parsetree.Text, match.group(1))
                     return self.match_tag_end()
             return True
@@ -210,9 +213,9 @@
         match = self.match(r'\</%[\t ]*(.+?)[\t ]*>')
         if match:
             if not len(self.tag):
-                raise exceptions.SyntaxException("Closing tag without opening tag: </%%%s>" % match.group(1), self.matched_lineno, self.matched_charpos, self.filename)
+                raise exceptions.SyntaxException("Closing tag without opening tag: </%%%s>" % match.group(1), **self.exception_kwargs)
             elif self.tag[-1].keyword != match.group(1):
-                raise exceptions.SyntaxException("Closing tag </%%%s> does not match tag: <%%%s>" % (match.group(1), self.tag[-1].keyword), self.matched_lineno, self.matched_charpos, self.filename)
+                raise exceptions.SyntaxException("Closing tag </%%%s> does not match tag: <%%%s>" % (match.group(1), self.tag[-1].keyword), **self.exception_kwargs)
             self.tag.pop()
             return True
         else:
@@ -287,15 +290,15 @@
             if operator == '%':
                 m2 = re.match(r'(end)?(\w+)\s*(.*)', text)
                 if not m2:
-                    raise exceptions.SyntaxException("Invalid control line: '%s'" % text, self.matched_lineno, self.matched_charpos, self.filename)
+                    raise exceptions.SyntaxException("Invalid control line: '%s'" % text, **self.exception_kwargs)
                 (isend, keyword) = m2.group(1, 2)
                 isend = (isend is not None)
                 
                 if isend:
                     if not len(self.control_line):
-                        raise exceptions.SyntaxException("No starting keyword '%s' for '%s'" % (keyword, text), self.matched_lineno, self.matched_charpos, self.filename)
+                        raise exceptions.SyntaxException("No starting keyword '%s' for '%s'" % (keyword, text), **self.exception_kwargs)
                     elif self.control_line[-1].keyword != keyword:
-                        raise exceptions.SyntaxException("Keyword '%s' doesn't match keyword '%s'" % (text, self.control_line[-1].keyword), self.matched_lineno, self.matched_charpos, self.filename)
+                        raise exceptions.SyntaxException("Keyword '%s' doesn't match keyword '%s'" % (text, self.control_line[-1].keyword), **self.exception_kwargs)
                 self.append_node(parsetree.ControlLine, keyword, isend, self.escape_code(text))
             else:
                 self.append_node(parsetree.Comment, text)
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py
index ebed303..0c24e0b 100644
--- a/lib/mako/parsetree.py
+++ b/lib/mako/parsetree.py
@@ -11,10 +11,14 @@
 
 class Node(object):
     """base class for a Node in the parse tree."""
-    def __init__(self, lineno, pos, filename):
+    def __init__(self, source, lineno, pos, filename):
+        self.source = source
         self.lineno = lineno
         self.pos = pos
         self.filename = filename
+        
+    exception_kwargs = property(lambda self:{'source':self.source, 'lineno':self.lineno, 'pos':self.pos, 'filename':self.filename})
+    
     def get_children(self):
         return []
     def accept_visitor(self, visitor):
@@ -27,7 +31,7 @@
 class TemplateNode(Node):
     """a 'container' node that stores the overall collection of nodes."""
     def __init__(self, filename):
-        super(TemplateNode, self).__init__(0, 0, filename)
+        super(TemplateNode, self).__init__('', 0, 0, filename)
         self.nodes = []
         self.page_attributes = {}
     def get_children(self):
@@ -52,7 +56,7 @@
             self._declared_identifiers = []
             self._undeclared_identifiers = []
         else:
-            code = ast.PythonFragment(text, self.lineno, self.pos, self.filename)
+            code = ast.PythonFragment(text, **self.exception_kwargs)
             (self._declared_identifiers, self._undeclared_identifiers) = (code.declared_identifiers, code.undeclared_identifiers)
     def declared_identifiers(self):
         return self._declared_identifiers
@@ -94,7 +98,7 @@
         super(Code, self).__init__(**kwargs)
         self.text = text
         self.ismodule = ismodule
-        self.code = ast.PythonCode(text, self.lineno, self.pos, self.filename)
+        self.code = ast.PythonCode(text, **self.exception_kwargs)
     def declared_identifiers(self):
         return self.code.declared_identifiers
     def undeclared_identifiers(self):
@@ -124,8 +128,8 @@
         super(Expression, self).__init__(**kwargs)
         self.text = text
         self.escapes = escapes
-        self.escapes_code = ast.ArgumentList(escapes, self.lineno, self.pos, self.filename)
-        self.code = ast.PythonCode(text, self.lineno, self.pos, self.filename)
+        self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs)
+        self.code = ast.PythonCode(text, **self.exception_kwargs)
     def declared_identifiers(self):
         return []
     def undeclared_identifiers(self):
@@ -145,7 +149,7 @@
         try:
             cls = _TagMeta._classmap[keyword]
         except KeyError:
-            raise exceptions.CompileException("No such tag: '%s'" % keyword, kwargs['lineno'], kwargs['pos'], kwargs['filename'])
+            raise exceptions.CompileException("No such tag: '%s'" % keyword, source=kwargs['source'], lineno=kwargs['lineno'], pos=kwargs['pos'], filename=kwargs['filename'])
         return type.__call__(cls, keyword, attributes, **kwargs)
         
 class Tag(Node):
@@ -179,7 +183,7 @@
         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.filename)
+            raise exceptions.CompileException("Missing attribute(s): %s" % ",".join([repr(m) for m in missing]), **self.exception_kwargs)
         self.parent = None
         self.nodes = []
     def is_root(self):
@@ -195,7 +199,7 @@
                 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, self.filename)
+                        code = ast.PythonCode(m.group(1), **self.exception_kwargs)
                         undeclared_identifiers = undeclared_identifiers.union(code.undeclared_identifiers)
                         expr.append(m.group(1))
                     else:
@@ -204,10 +208,10 @@
                 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.filename)
+                    raise exceptions.CompileException("Attibute '%s' in tag '%s' does not allow embedded expressions"  %(key, self.keyword), **self.exception_kwargs)
                 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, self.filename)
+                raise exceptions.CompileException("Invalid attribute for tag '%s': '%s'" %(self.keyword, key), **self.exception_kwargs)
         self.expression_undeclared_identifiers = undeclared_identifiers
     def declared_identifiers(self):
         return []
@@ -220,7 +224,7 @@
     __keyword__ = 'include'
     def __init__(self, keyword, attributes, **kwargs):
         super(IncludeTag, self).__init__(keyword, attributes, ('file', 'import', 'args'), (), ('file',), **kwargs)
-        self.page_args = ast.PythonCode("__DUMMY(%s)" % attributes.get('args', ''), self.lineno, self.pos, self.filename)
+        self.page_args = ast.PythonCode("__DUMMY(%s)" % attributes.get('args', ''), **self.exception_kwargs)
     def declared_identifiers(self):
         return []
     def undeclared_identifiers(self):
@@ -233,7 +237,7 @@
         super(NamespaceTag, self).__init__(keyword, attributes, (), ('name','inheritable','file','import','module'), (), **kwargs)
         self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self))))
         if not 'name' in attributes and not 'import' in attributes:
-            raise exceptions.CompileException("'name' and/or 'import' attributes are required for <%namespace>", self.lineno, self.pos, self.filename)
+            raise exceptions.CompileException("'name' and/or 'import' attributes are required for <%namespace>", **self.exception_kwargs)
     def declared_identifiers(self):
         return []
 
@@ -241,7 +245,7 @@
     __keyword__ = 'text'
     def __init__(self, keyword, attributes, **kwargs):
         super(TextTag, self).__init__(keyword, attributes, (), ('filter'), (), **kwargs)
-        self.filter_args = ast.ArgumentList(attributes.get('filter', ''), self.lineno, self.pos, self.filename)
+        self.filter_args = ast.ArgumentList(attributes.get('filter', ''), **self.exception_kwargs)
         
 class DefTag(Tag):
     __keyword__ = 'def'
@@ -249,24 +253,24 @@
         super(DefTag, self).__init__(keyword, attributes, ('buffered', 'cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url'), ('name','filter'), ('name',), **kwargs)
         name = attributes['name']
         if re.match(r'^[\w_]+$',name):
-            raise exceptions.CompileException("Missing parenthesis in %def", self.lineno, self.pos, self.filename)
-        self.function_decl = ast.FunctionDecl("def " + name + ":pass", self.lineno, self.pos, self.filename)
+            raise exceptions.CompileException("Missing parenthesis in %def", **self.exception_kwargs)
+        self.function_decl = ast.FunctionDecl("def " + name + ":pass", **self.exception_kwargs)
         self.name = self.function_decl.funcname
-        self.filter_args = ast.ArgumentList(attributes.get('filter', ''), self.lineno, self.pos, self.filename)
+        self.filter_args = ast.ArgumentList(attributes.get('filter', ''), **self.exception_kwargs)
     def declared_identifiers(self):
         return self.function_decl.argnames
     def undeclared_identifiers(self):
         res = []
         for c in self.function_decl.defaults:
-            res += list(ast.PythonCode(c, self.lineno, self.pos, self.filename).undeclared_identifiers)
+            res += list(ast.PythonCode(c, **self.exception_kwargs).undeclared_identifiers)
         return res + list(self.filter_args.undeclared_identifiers.difference(util.Set(filters.DEFAULT_ESCAPES.keys())))
 
 class CallTag(Tag):
     __keyword__ = 'call'
     def __init__(self, keyword, attributes, **kwargs):
         super(CallTag, self).__init__(keyword, attributes, ('args'), ('expr',), ('expr',), **kwargs)
-        self.code = ast.PythonCode(attributes['expr'], self.lineno, self.pos, self.filename)
-        self.body_decl = ast.FunctionArgs(attributes.get('args', ''), self.lineno, self.pos, self.filename)
+        self.code = ast.PythonCode(attributes['expr'], **self.exception_kwargs)
+        self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs)
     def declared_identifiers(self):
         return self.code.declared_identifiers.union(self.body_decl.argnames)
     def undeclared_identifiers(self):
@@ -281,8 +285,8 @@
     __keyword__ = 'page'
     def __init__(self, keyword, attributes, **kwargs):
         super(PageTag, self).__init__(keyword, attributes, ('cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url', 'args', 'expression_filter'), (), (), **kwargs)
-        self.body_decl = ast.FunctionArgs(attributes.get('args', ''), self.lineno, self.pos, self.filename)
-        self.filter_args = ast.ArgumentList(attributes.get('expression_filter', ''), self.lineno, self.pos, self.filename)
+        self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs)
+        self.filter_args = ast.ArgumentList(attributes.get('expression_filter', ''), **self.exception_kwargs)
     def declared_identifiers(self):
         return self.body_decl.argnames
         
diff --git a/lib/mako/template.py b/lib/mako/template.py
index 028d11a..8254105 100644
--- a/lib/mako/template.py
+++ b/lib/mako/template.py
@@ -169,15 +169,22 @@
     code = property(_get_code)
     def _get_source(self):
         if self.template_source is not None:
-            return self.template_source
+            if self.module._source_encoding and not isinstance(self.template_source, unicode):
+                return self.template_source.decode(self.module._source_encoding)
+            else:
+                return self.template_source
         else:
-            return file(self.template_filename).read()
+            if self.module._source_encoding:
+                return file(self.template_filename).read().decode(self.module._source_encoding)
+            else:
+                return file(self.template_filename).read()
     source = property(_get_source)
         
 def _compile_text(template, text, filename):
     identifier = template.module_id
-    node = Lexer(text, filename, input_encoding=template.input_encoding, preprocessor=template.preprocessor).parse()
-    source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports)
+    lexer = Lexer(text, filename, input_encoding=template.input_encoding, preprocessor=template.preprocessor)
+    node = lexer.parse()
+    source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding)
     #print source
     cid = identifier
     module = imp.new_module(cid)
@@ -188,8 +195,9 @@
 def _compile_module_file(template, text, filename, outputpath):
     identifier = template.module_id
     (dest, name) = tempfile.mkstemp()
-    node = Lexer(text, filename, input_encoding=template.input_encoding, preprocessor=template.preprocessor).parse()
-    source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports)
+    lexer = Lexer(text, filename, input_encoding=template.input_encoding, preprocessor=template.preprocessor)
+    node = lexer.parse()
+    source = codegen.compile(node, template.uri, filename, default_filters=template.default_filters, buffer_filters=template.buffer_filters, imports=template.imports, source_encoding=lexer.encoding)
     os.write(dest, source)
     os.close(dest)
     shutil.move(name, outputpath)
diff --git a/test/ast.py b/test/ast.py
index 4df03fa..f8d604d 100644
--- a/test/ast.py
+++ b/test/ast.py
@@ -3,6 +3,8 @@
 from mako import ast, util, exceptions
 from compiler import parse
 
+exception_kwargs = {'source':'', 'lineno':0, 'pos':0, 'filename':''}
+
 class AstParseTest(unittest.TestCase):
     def setUp(self):
         pass
@@ -23,11 +25,11 @@
 print "hello world, ", a, b
 print "Another expr", c
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.declared_identifiers == util.Set(['a','b','c', 'g', 'h', 'i', 'u', 'k', 'j', 'gh', 'lar', 'x'])
         assert parsed.undeclared_identifiers == util.Set(['x', 'q', 'foo', 'gah', 'blah'])
     
-        parsed = ast.PythonCode("x + 5 * (y-z)", 0, 0, None)
+        parsed = ast.PythonCode("x + 5 * (y-z)", **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['x', 'y', 'z'])
         assert parsed.declared_identifiers == util.Set()
 
@@ -41,7 +43,7 @@
 for x in data:
     result.append(x+7)
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['get_data'])
         assert parsed.declared_identifiers == util.Set(['result', 'data', 'x', 'hoho', 'foobar', 'foo', 'yaya'])
 
@@ -54,7 +56,7 @@
 [z for z in range(1, z)]
 (q for q in range (1, q))
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['x', 'y', 'z', 'q'])
     
     def test_locate_identifiers_4(self):
@@ -64,7 +66,7 @@
 def mydef(mydefarg):
     print "mda is", mydefarg
 """    
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['y'])
         assert parsed.declared_identifiers == util.Set(['mydef', 'x'])
     
@@ -75,7 +77,7 @@
 except:
     print y
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['x', 'y'])
     
     def test_locate_identifiers_6(self):
@@ -83,7 +85,7 @@
 def foo():
     return bar()
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['bar'])
     
         code = """
@@ -91,7 +93,7 @@
     return x, y, z
 print x
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['z', 'x'])
         assert parsed.declared_identifiers == util.Set(['lala'])
     
@@ -102,7 +104,7 @@
             z = 7
 print z
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['z'])
         assert parsed.declared_identifiers == util.Set(['lala'])
     
@@ -110,7 +112,7 @@
         code = """
 import foo.bar
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.declared_identifiers == util.Set(['foo'])
         assert parsed.undeclared_identifiers == util.Set()
 
@@ -121,7 +123,7 @@
     def hoho(self):
         x = 5
 """
-        parsed = ast.PythonCode(code, 0, 0, None)
+        parsed = ast.PythonCode(code, **exception_kwargs)
         assert parsed.declared_identifiers == util.Set(['Hi'])
         assert parsed.undeclared_identifiers == util.Set()
         
@@ -131,41 +133,41 @@
 import x as bar
 """
         try:
-            parsed = ast.PythonCode(code, 0, 0, None)
+            parsed = ast.PythonCode(code, **exception_kwargs)
             assert False
         except exceptions.CompileException, e:
             assert str(e).startswith("'import *' is not supported")
             
     def test_python_fragment(self):
-        parsed = ast.PythonFragment("for x in foo:", 0, 0, None)
+        parsed = ast.PythonFragment("for x in foo:", **exception_kwargs)
         assert parsed.declared_identifiers == util.Set(['x'])
         assert parsed.undeclared_identifiers == util.Set(['foo'])
         
-        parsed = ast.PythonFragment("try:", 0, 0, None)
+        parsed = ast.PythonFragment("try:", **exception_kwargs)
 
-        parsed = ast.PythonFragment("except MyException, e:", 0, 0, None)
+        parsed = ast.PythonFragment("except MyException, e:", **exception_kwargs)
         assert parsed.declared_identifiers == util.Set(['e'])
         assert parsed.undeclared_identifiers == util.Set(['MyException'])
     
     def test_argument_list(self):
-        parsed = ast.ArgumentList("3, 5, 'hi', x+5, context.get('lala')", 0, 0, None)
+        parsed = ast.ArgumentList("3, 5, 'hi', x+5, context.get('lala')", **exception_kwargs)
         assert parsed.undeclared_identifiers == util.Set(['x', 'context'])
         assert [x for x in parsed.args] == ["3", "5", "'hi'", "(x + 5)", "context.get('lala')"]
 
-        parsed = ast.ArgumentList("h", 0, 0, None)
+        parsed = ast.ArgumentList("h", **exception_kwargs)
         assert parsed.args == ["h"]
 
     def test_function_decl(self):
         """test getting the arguments from a function"""
         code = "def foo(a, b, c=None, d='hi', e=x, f=y+7):pass"
-        parsed = ast.FunctionDecl(code, 0, 0, None)
+        parsed = ast.FunctionDecl(code, **exception_kwargs)
         assert parsed.funcname=='foo'
         assert parsed.argnames==['a', 'b', 'c', 'd', 'e', 'f']
 
     def test_function_decl_2(self):
         """test getting the arguments from a function"""
         code = "def foo(a, b, c=None, *args, **kwargs):pass"
-        parsed = ast.FunctionDecl(code, 0, 0, None)
+        parsed = ast.FunctionDecl(code, **exception_kwargs)
         assert parsed.funcname=='foo'
         assert parsed.argnames==['a', 'b', 'c', 'args', 'kwargs']
     
diff --git a/test/template.py b/test/template.py
index 730f654..a2fc1cc 100644
--- a/test/template.py
+++ b/test/template.py
@@ -3,6 +3,7 @@
 from mako.template import Template
 from mako.lookup import TemplateLookup
 from mako.ext.preprocessors import convert_comments
+from mako import exceptions
 import unittest, re, os
 from util import flatten_result, result_lines
 
@@ -12,6 +13,10 @@
     os.mkdir('./test_htdocs/subdir')
 file('./test_htdocs/unicode.html', 'w').write("""## -*- coding: utf-8 -*-
 Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »""")
+file('./test_htdocs/unicode_syntax_error.html', 'w').write("""## -*- coding: utf-8 -*-
+<% print 'Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! » %>""")
+file('./test_htdocs/unicode_runtime_error.html', 'w').write("""## -*- coding: utf-8 -*-
+<% print 'Alors vous imaginez ma surprise, au lever du jour, quand une drôle de petit voix m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »' + int(5/0) %>""")
     
 class EncodingTest(unittest.TestCase):
     def test_unicode(self):
@@ -243,7 +248,49 @@
 """)
         assert t.render().strip() == "y is hi"
 
+class RichTracebackTest(unittest.TestCase):
+    def do_test_traceback(self, utf8, memory, syntax):
+        if memory:
+            if syntax:
+                source = u'## coding: utf-8\n<% print "m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! » %>'
+            else:
+                source = u'## coding: utf-8\n<% print u"m’a réveillé. Elle disait: « S’il vous plaît… dessine-moi un mouton! »" + str(5/0) %>'
+            if utf8:
+                source = source.encode('utf-8')
+            else:
+                source = source
+            templateargs = {'text':source}
+        else:
+            if syntax:
+                filename = './test_htdocs/unicode_syntax_error.html'
+            else:
+                filename = './test_htdocs/unicode_runtime_error.html'
+            source = file(filename).read()
+            if not utf8:
+                source = source.decode('utf-8')
+            templateargs = {'filename':filename}
+        try:
+            template = Template(**templateargs)
+            if not syntax:
+                template.render_unicode()
+            assert False
+        except Exception, e:
+            tback = exceptions.RichTraceback()
+            if utf8:
+                assert tback.source == source.decode('utf-8')
+            else:
+                assert tback.source == source
 
+for utf8 in (True, False):
+    for memory in (True, False):
+        for syntax in (True, False):
+            def do_test(self):
+                self.do_test_traceback(utf8, memory, syntax)
+            name = 'test_%s_%s_%s' % (utf8 and 'utf8' or 'unicode', memory and 'memory' or 'file', syntax and 'syntax' or 'runtime')
+            do_test.__name__ = name
+            setattr(RichTracebackTest, name, do_test)
+
+        
 class ModuleDirTest(unittest.TestCase):
     def test_basic(self):
         file('./test_htdocs/modtest.html', 'w').write("""this is a test""")