mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
88 lines
2.6 KiB
Python
88 lines
2.6 KiB
Python
|
"""
|
||
|
This module implements an interpreter for a REPL. It subclasses Python's
|
||
|
code.InteractiveInterpreter class, implementing most of its methods, but with
|
||
|
slight changes in order to be convenient for Grist's purposes
|
||
|
"""
|
||
|
|
||
|
import code
|
||
|
import sys
|
||
|
from StringIO import StringIO
|
||
|
from collections import namedtuple
|
||
|
|
||
|
SUCCESS = 0
|
||
|
INCOMPLETE = 1
|
||
|
ERROR = 2
|
||
|
|
||
|
EvalTuple = namedtuple("EvalTuple", ("output", "error", "status"))
|
||
|
|
||
|
#pylint: disable=exec-used, bare-except
|
||
|
class REPLInterpreter(code.InteractiveInterpreter):
|
||
|
|
||
|
def __init__(self):
|
||
|
code.InteractiveInterpreter.__init__(self)
|
||
|
self.error_text = ""
|
||
|
|
||
|
def runsource(self, source, filename="<input>", symbol="single"):
|
||
|
"""
|
||
|
Compiles and executes source. Returns an EvalTuple with a status
|
||
|
INCOMPLETE if the code is incomplete,
|
||
|
ERROR if it encountered a compilation or run-time error,
|
||
|
SUCCESS otherwise.
|
||
|
|
||
|
an output, which gives all of the output of the user's program
|
||
|
(with stderr piped to stdout, essentially, though mock-file objects are used)
|
||
|
|
||
|
an error, which reports a syntax error at compilation or a runtime error with a
|
||
|
Traceback.
|
||
|
"""
|
||
|
old_stdout = sys.stdout
|
||
|
old_stderr = sys.stderr
|
||
|
|
||
|
user_output = StringIO()
|
||
|
|
||
|
self.error_text = ""
|
||
|
try:
|
||
|
code = self.compile(source, filename, symbol)
|
||
|
except (OverflowError, SyntaxError, ValueError):
|
||
|
self.showsyntaxerror(filename)
|
||
|
status = ERROR
|
||
|
else:
|
||
|
status = INCOMPLETE if code is None else SUCCESS
|
||
|
|
||
|
if status == SUCCESS:
|
||
|
|
||
|
try:
|
||
|
# We use temproray variables to access stdio/stdout
|
||
|
# to make sure the client can't do funky things
|
||
|
# like get/set attr and have that hurt us
|
||
|
sys.stdout = user_output
|
||
|
sys.stderr = user_output
|
||
|
exec code in self.locals
|
||
|
except:
|
||
|
# bare except to catch absolutely all things the user can throw
|
||
|
self.showtraceback()
|
||
|
status = ERROR
|
||
|
finally:
|
||
|
sys.stdout = old_stdout
|
||
|
sys.stderr = old_stderr
|
||
|
|
||
|
program_output = user_output.getvalue()
|
||
|
user_output.close()
|
||
|
|
||
|
return EvalTuple(program_output, self.error_text, status)
|
||
|
|
||
|
def write(self, txt):
|
||
|
"""
|
||
|
Used by showsyntaxerror and showtraceback
|
||
|
"""
|
||
|
self.error_text += txt
|
||
|
|
||
|
def runcode(self, code):
|
||
|
"""
|
||
|
This would normally do the part of runsource after compiling the code, but doesn't quite
|
||
|
make sense as its own function for our purposes because it couldn't support an INCOMPLETE
|
||
|
return value, etc. We explicitly hide it here to make sure the base class's version isn't
|
||
|
called by accident.
|
||
|
"""
|
||
|
raise NotImplementedError("REPLInterpreter.runcode not implemented, use runsource instead")
|