diff options
author | Guido van Rossum <guido@python.org> | 1998-09-22 20:38:53 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 1998-09-22 20:38:53 (GMT) |
commit | 5227f0fdcd99d90acdd712f96ea5cd2d7331c53f (patch) | |
tree | eae3b08430f922e946898478a14e2c5c4d88e577 | |
parent | 218eb75ba712d28e8999b270666fd18b25d128fd (diff) | |
download | cpython-5227f0fdcd99d90acdd712f96ea5cd2d7331c53f.zip cpython-5227f0fdcd99d90acdd712f96ea5cd2d7331c53f.tar.gz cpython-5227f0fdcd99d90acdd712f96ea5cd2d7331c53f.tar.bz2 |
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.
-rw-r--r-- | Lib/code.py | 308 |
1 files changed, 206 insertions, 102 deletions
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 @@ def compile_command(source, filename="<input>", symbol="single"): 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 @@ def compile_command(source, filename="<input>", symbol="single"): 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,136 +89,116 @@ def compile_command(source, filename="<input>", symbol="single"): raise SyntaxError, err1 -class InteractiveConsole: - """Closely emulate the behavior of the interactive Python interpreter. +class InteractiveInterpreter: + """Base class for InteractiveConsole. + + 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). - After code by Jeff Epler and Fredrik Lundh. """ - 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 - self.resetbuffer() - def resetbuffer(self): - """Reset the input buffer (but not the variables!).""" - self.buffer = [] + def runsource(self, source, filename="<input>", symbol="single"): + """Compile and run some source in the interpreter. - def interact(self, banner=None): - """Closely emulate the interactive Python console.""" - try: - sys.ps1 - except AttributeError: - sys.ps1 = ">>> " - try: - sys.ps2 - except AttributeError: - sys.ps2 = "... " - if banner is None: - self.write("Python %s on %s\n%s\n(%s)\n" % - (sys.version, sys.platform, sys.copyright, - self.__class__.__name__)) - else: - self.write("%s\n" % str(banner)) - more = 0 - while 1: - try: - if more: - prompt = sys.ps2 - else: - prompt = sys.ps1 - try: - line = self.raw_input(prompt) - except EOFError: - self.write("\n") - break - else: - more = self.push(line) - except KeyboardInterrupt: - self.write("\nKeyboardInterrupt\n") - self.resetbuffer() - more = 0 + Arguments are as for compile_command(). - def push(self, line): - """Push a line to the interpreter. - - The line should not have a trailing newline. + One several things can happen: - One of three things will happen: - - 1) The input is incorrect; compile_command() raised - SyntaxError. A syntax traceback will be printed. + 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. + compile_command() returned None. Nothing happens. 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. + 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. (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 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. """ - self.buffer.append(line) - try: - x = compile_command(string.join(self.buffer, "\n"), - filename=self.filename) - except SyntaxError: + code = compile_command(source, filename, symbol) + except (OverflowError, SyntaxError): # Case 1 - self.showsyntaxerror() - self.resetbuffer() + self.showsyntaxerror(filename) return 0 - if x is None: + 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 x in self.locals + exec code in self.locals except SystemExit: raise except: self.showtraceback() - self.resetbuffer() - return 0 - def showsyntaxerror(self): + 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.exc_info()[:2] - # Work hard to stuff the correct filename in the exception - try: - msg, (filename, lineno, offset, line) = value - except: - pass - else: + 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: - value = SyntaxError(msg, (self.filename, lineno, offset, line)) + msg, (dummy_filename, lineno, offset, line) = value except: - value = msg, (self.filename, lineno, offset, line) + # 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) @@ -207,9 +212,14 @@ class InteractiveConsole: """ 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[0] + 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 @@ -224,6 +234,98 @@ class InteractiveConsole: """ 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.""" + self.buffer = [] + + def interact(self, banner=None): + """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: + sys.ps1 = ">>> " + try: + sys.ps2 + except AttributeError: + sys.ps2 = "... " + if banner is None: + self.write("Python %s on %s\n%s\n(%s)\n" % + (sys.version, sys.platform, sys.copyright, + self.__class__.__name__)) + else: + self.write("%s\n" % str(banner)) + more = 0 + while 1: + try: + if more: + prompt = sys.ps2 + else: + prompt = sys.ps1 + try: + line = self.raw_input(prompt) + except EOFError: + self.write("\n") + break + else: + more = self.push(line) + except KeyboardInterrupt: + self.write("\nKeyboardInterrupt\n") + self.resetbuffer() + more = 0 + + def push(self, line): + """Push a line to the interpreter. + + 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) + source = string.join(self.buffer, "\n") + more = self.runsource(source, self.filename) + if not more: + self.resetbuffer() + return more + def raw_input(self, prompt=""): """Write a prompt and read a line. @@ -242,24 +344,26 @@ def interact(banner=None, readfunc=None, locals=None): """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() |