Reworked it quite a bit.  There are now two classes: a base class,
InteractiveInterpreter, which handles parsing and interpreter state
but doesn't know deal with buffering or prompting or input file
naming.  And a derived class, InteractiveConsole, which adds buffering
and prompting and supports setting the filename once.  Also tweak the
algorithm in compile_command() a bit so that input consisting of all
blank lines or comments always succeeds immediately, and note the fact
that apart from SyntaxError it can also raise OverflowError.
diff --git a/Lib/code.py b/Lib/code.py
index f28371f..3554b35 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -1,4 +1,7 @@
-"""Utilities dealing with code objects."""
+"""Utilities dealing with code objects.
+
+Inspired by similar code by Jeff Epler and Fredrik Lundh.
+"""
 
 import sys
 import string
@@ -13,14 +16,19 @@
     filename -- optional filename from which source was read; default "<input>"
     symbol -- optional grammar start symbol; "single" (default) or "eval"
 
-    Return value / exception raised:
+    Return value / exceptions raised:
 
     - Return a code object if the command is complete and valid
     - Return None if the command is incomplete
-    - Raise SyntaxError if the command is a syntax error
+    - Raise SyntaxError or OverflowError if the command is a syntax error
+      (OverflowError if the error is in a numeric constant)
 
     Approach:
-    
+
+    First, check if the source consists entirely of blank lines and
+    comments; if so, replace it with 'pass', because the built-in
+    parser doesn't always do the right thing for these.
+
     Compile three times: as is, with \n, and with \n\n appended.  If
     it compiles as is, it's complete.  If it compiles with one \n
     appended, we expect more.  If it doesn't compile either way, we
@@ -28,10 +36,27 @@
     If the errors are the same, the code is broken.  But if the errors
     are different, we expect more.  Not intuitive; not even guaranteed
     to hold in future releases; but this matches the compiler's
-    behavior in Python 1.4 and 1.5.
+    behavior from Python 1.4 through 1.5.2, at least.
+
+    Caveat:
+
+    It is possible (but not likely) that the parser stops parsing
+    with a successful outcome before reaching the end of the source;
+    in this case, trailing symbols may be ignored instead of causing an
+    error.  For example, a backslash followed by two newlines may be
+    followed by arbitrary garbage.  This will be fixed once the API
+    for the parser is better.
 
     """
 
+    # Check for source consisting of only blank lines and comments
+    for line in string.split(source, "\n"):
+        line = string.strip(line)
+        if line and line[0] != '#':
+            break               # Leave it alone
+    else:
+        source = "pass"         # Replace it with a 'pass' statement
+
     err = err1 = err2 = None
     code = code1 = code2 = None
 
@@ -64,32 +89,189 @@
         raise SyntaxError, err1
 
 
-class InteractiveConsole:
-    """Closely emulate the behavior of the interactive Python interpreter.
+class InteractiveInterpreter:
+    """Base class for InteractiveConsole.
 
-    After code by Jeff Epler and Fredrik Lundh.
+    This class deals with parsing and interpreter state (the user's
+    namespace); it doesn't deal with input buffering or prompting or
+    input file naming (the filename is always passed in explicitly).
+
     """
 
-    def __init__(self, filename="<console>", locals=None):
+    def __init__(self, locals=None):
         """Constructor.
 
-        The optional filename argument specifies the (file)name of the
-        input stream; it will show up in tracebacks.  It defaults to
-        '<console>'.
+        The optional 'locals' argument specifies the dictionary in
+        which code will be executed; it defaults to a newly created
+        dictionary with key "__name__" set to "__console__" and key
+        "__doc__" set to None.
 
         """
-        self.filename = filename
         if locals is None:
-            locals = {}
+            locals = {"__name__": "__console__", "__doc__": None}
         self.locals = locals
+
+    def runsource(self, source, filename="<input>", symbol="single"):
+        """Compile and run some source in the interpreter.
+
+        Arguments are as for compile_command().
+
+        One several things can happen:
+
+        1) The input is incorrect; compile_command() raised an
+        exception (SyntaxError or OverflowError).  A syntax traceback
+        will be printed by calling the showsyntaxerror() method.
+
+        2) The input is incomplete, and more input is required;
+        compile_command() returned None.  Nothing happens.
+
+        3) The input is complete; compile_command() returned a code
+        object.  The code is executed by calling self.runcode() (which
+        also handles run-time exceptions, except for SystemExit).
+
+        The return value is 1 in case 2, 0 in the other cases (unless
+        an exception is raised).  The return value can be used to
+        decide whether to use sys.ps1 or sys.ps2 to prompt the next
+        line.
+
+        """
+        try:
+            code = compile_command(source, filename, symbol)
+        except (OverflowError, SyntaxError):
+            # Case 1
+            self.showsyntaxerror(filename)
+            return 0
+
+        if code is None:
+            # Case 2
+            return 1
+
+        # Case 3
+        self.runcode(code)
+        return 0
+
+    def runcode(self, code):
+        """Execute a code object.
+
+        When an exception occurs, self.showtraceback() is called to
+        display a traceback.  All exceptions are caught except
+        SystemExit, which is reraised.
+
+        A note about KeyboardInterrupt: this exception may occur
+        elsewhere in this code, and may not always be caught.  The
+        caller should be prepared to deal with it.
+
+        """
+        try:
+            exec code in self.locals
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+
+    def showsyntaxerror(self, filename=None):
+        """Display the syntax error that just occurred.
+
+        This doesn't display a stack trace because there isn't one.
+
+        If a filename is given, it is stuffed in the exception instead
+        of what was there before (because Python's parser always uses
+        "<string>" when reading from a string).
+
+        The output is written by self.write(), below.
+
+        """
+        type, value, sys.last_traceback = sys.exc_info()
+        sys.last_type = type
+        sys.last_value = value
+        if filename and type is SyntaxError:
+            # Work hard to stuff the correct filename in the exception
+            try:
+                msg, (dummy_filename, lineno, offset, line) = value
+            except:
+                # Not the format we expect; leave it alone
+                pass
+            else:
+                # Stuff in the right filename
+                try:
+                    # Assume SyntaxError is a class exception
+                    value = SyntaxError(msg, (filename, lineno, offset, line))
+                except:
+                    # If that failed, assume SyntaxError is a string
+                    value = msg, (filename, lineno, offset, line)
+        list = traceback.format_exception_only(type, value)
+        map(self.write, list)
+
+    def showtraceback(self):
+        """Display the exception that just occurred.
+
+        We remove the first stack item because it is our own code.
+
+        The output is written by self.write(), below.
+
+        """
+        try:
+            type, value, tb = sys.exc_info()
+            sys.last_type = type
+            sys.last_value = value
+            sys.last_traceback = tb
+            tblist = traceback.extract_tb(tb)
+            del tblist[:1]
+            list = traceback.format_list(tblist)
+            if list:
+                list.insert(0, "Traceback (innermost last):\n")
+            list[len(list):] = traceback.format_exception_only(type, value)
+        finally:
+            tblist = tb = None
+        map(self.write, list)
+
+    def write(self, data):
+        """Write a string.
+
+        The base implementation writes to sys.stderr; a subclass may
+        replace this with a different implementation.
+
+        """
+        sys.stderr.write(data)
+
+
+class InteractiveConsole(InteractiveInterpreter):
+    """Closely emulate the behavior of the interactive Python interpreter.
+
+    This class builds on InteractiveInterpreter and adds prompting
+    using the familiar sys.ps1 and sys.ps2, and input buffering.
+
+    """
+
+    def __init__(self, locals=None, filename="<console>"):
+        """Constructor.
+
+        The optional locals argument will be passed to the
+        InteractiveInterpreter base class.
+
+        The optional filename argument should specify the (file)name
+        of the input stream; it will show up in tracebacks.
+
+        """
+        InteractiveInterpreter.__init__(self, locals)
+        self.filename = filename
         self.resetbuffer()
 
     def resetbuffer(self):
-        """Reset the input buffer (but not the variables!)."""
+        """Reset the input buffer."""
         self.buffer = []
 
     def interact(self, banner=None):
-        """Closely emulate the interactive Python console."""
+        """Closely emulate the interactive Python console.
+
+        The optional banner argument specify the banner to print
+        before the first interaction; by default it prints a banner
+        similar to the one printed by the real Python interpreter,
+        followed by the current class name in parentheses (so as not
+        to confuse this with the real interpreter -- since it's so
+        close!).
+
+        """
         try:
             sys.ps1
         except AttributeError:
@@ -126,103 +308,23 @@
     def push(self, line):
         """Push a line to the interpreter.
 
-        The line should not have a trailing newline.
-
-        One of three things will happen:
-
-        1) The input is incorrect; compile_command() raised
-        SyntaxError.  A syntax traceback will be printed.
-
-        2) The input is incomplete, and more input is required;
-        compile_command() returned None.
-
-        3) The input is complete; compile_command() returned a code
-        object.  The code is executed.  When an exception occurs, a
-        traceback is printed.  All exceptions are caught except
-        SystemExit, which is reraised.
-
-        The return value is 1 in case 2, 0 in the other cases.  (The
-        return value can be used to decide whether to use sys.ps1 or
-        sys.ps2 to prompt the next line.)
-
-        A note about KeyboardInterrupt: this exception may occur
-        elsewhere in this code, and will not always be caught.  The
-        caller should be prepared to deal with it.
+        The line should not have a trailing newline; it may have
+        internal newlines.  The line is appended to a buffer and the
+        interpreter's runsource() method is called with the
+        concatenated contents of the buffer as source.  If this
+        indicates that the command was executed or invalid, the buffer
+        is reset; otherwise, the command is incomplete, and the buffer
+        is left as it was after the line was appended.  The return
+        value is 1 if more input is required, 0 if the line was dealt
+        with in some way (this is the same as runsource()).
 
         """
         self.buffer.append(line)
-
-        try:
-            x = compile_command(string.join(self.buffer, "\n"),
-                                filename=self.filename)
-        except SyntaxError:
-            # Case 1
-            self.showsyntaxerror()
+        source = string.join(self.buffer, "\n")
+        more = self.runsource(source, self.filename)
+        if not more:
             self.resetbuffer()
-            return 0
-
-        if x is None:
-            # Case 2
-            return 1
-
-        # Case 3
-        try:
-            exec x in self.locals
-        except SystemExit:
-            raise
-        except:
-            self.showtraceback()
-        self.resetbuffer()
-        return 0
-
-    def showsyntaxerror(self):
-        """Display the syntax error that just occurred.
-
-        This doesn't display a stack trace because there isn't one.
-
-        The output is written by self.write(), below.
-
-        """
-        type, value = sys.exc_info()[:2]
-        # Work hard to stuff the correct filename in the exception
-        try:
-            msg, (filename, lineno, offset, line) = value
-        except:
-            pass
-        else:
-            try:
-                value = SyntaxError(msg, (self.filename, lineno, offset, line))
-            except:
-                value = msg, (self.filename, lineno, offset, line)
-        list = traceback.format_exception_only(type, value)
-        map(self.write, list)
-
-    def showtraceback(self):
-        """Display the exception that just occurred.
-
-        We remove the first stack item because it is our own code.
-
-        The output is written by self.write(), below.
-
-        """
-        try:
-            type, value, tb = sys.exc_info()
-            tblist = traceback.extract_tb(tb)
-            del tblist[0]
-            list = traceback.format_list(tblist)
-            list[len(list):] = traceback.format_exception_only(type, value)
-        finally:
-            tblist = tb = None
-        map(self.write, list)
-
-    def write(self, data):
-        """Write a string.
-
-        The base implementation writes to sys.stderr; a subclass may
-        replace this with a different implementation.
-
-        """
-        sys.stderr.write(data)
+        return more
 
     def raw_input(self, prompt=""):
         """Write a prompt and read a line.
@@ -242,24 +344,26 @@
     """Closely emulate the interactive Python interpreter.
 
     This is a backwards compatible interface to the InteractiveConsole
-    class.  It attempts to import the readline module to enable GNU
-    readline if it is available.
+    class.  When readfunc is not specified, it attempts to import the
+    readline module to enable GNU readline if it is available.
 
     Arguments (all optional, all default to None):
 
     banner -- passed to InteractiveConsole.interact()
     readfunc -- if not None, replaces InteractiveConsole.raw_input()
-    locals -- passed to InteractiveConsole.__init__()
+    locals -- passed to InteractiveInterpreter.__init__()
 
     """
-    try:
-        import readline
-    except:
-        pass
-    console = InteractiveConsole(locals=locals)
+    console = InteractiveConsole(locals)
     if readfunc is not None:
         console.raw_input = readfunc
+    else:
+        try:
+            import readline
+        except:
+            pass
     console.interact(banner)
-                
+
+
 if __name__ == '__main__':
     interact()