(core) Remove REPL code

Summary: Remove repl.py, REPLTab.js, some wiring code, CSS, and a test in testscript.json.

Test Plan: NA

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2923
This commit is contained in:
Alex Hall
2021-07-20 14:52:21 +02:00
parent 67aca9ccf6
commit 1f6e693b6e
12 changed files with 1 additions and 757 deletions

View File

@@ -36,7 +36,6 @@ import table as table_module
from user import User # pylint:disable=wrong-import-order
import useractions
import column
import repl
import urllib_patch # noqa imported for side effect # pylint:disable=unused-import
log = logger.Logger(__name__, logger.INFO)
@@ -232,9 +231,6 @@ class Engine(object):
# A flag for when a useraction causes a schema change, to verify consistency afterwards.
self._schema_updated = False
# Locals dict for recently executed code in the REPL
self._repl = repl.REPLInterpreter()
# Stores an exception representing the first unevaluated cell met while recomputing the
# current cell.
self._cell_required_error = None
@@ -976,11 +972,6 @@ class Engine(object):
for (col_obj, values) in cols}
)
def eval_user_code(self, src):
ret = self._repl.runsource(src)
self.gencode.usercode.__dict__.update(self._repl.locals)
return ret
def invalidate_records(self, table_id, row_ids=depend.ALL_ROWS, col_ids=None,
data_cols_to_recompute=frozenset()):
"""
@@ -1017,8 +1008,6 @@ class Engine(object):
def rebuild_usercode(self):
"""
Compiles the usercode from the schema, and updates all tables and columns to match.
Also, keeps the locals in the repl in sync with the user code, so that the repl has access to
usercode and vice-versa.
"""
self.gencode.make_module(self.schema)
@@ -1040,7 +1029,6 @@ class Engine(object):
for table_id, table in six.iteritems(old_tables):
if table_id not in self.tables:
self._update_table_model(table, None)
self._repl.locals.pop(table_id, None)
# Update docmodel with references to the updated metadata tables.
self.docmodel.update_tables()
@@ -1048,11 +1036,6 @@ class Engine(object):
# Set flag to rebuild dependencies of trigger columns after any potential renames, etc.
self.trigger_columns_changed()
# The order here is important to make sure that when we update the usercode,
# we don't overwrite with outdated usercode entries
self._repl.locals.update(self.gencode.usercode.__dict__)
self.gencode.usercode.__dict__.update(self._repl.locals)
# Update the context used for autocompletions.
self._autocomplete_context = AutocompleteContext(self.gencode.usercode.__dict__)

View File

@@ -1,91 +0,0 @@
"""
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 collections import namedtuple
import six
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 = six.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, 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()
try:
user_output.close()
except:
pass
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")

View File

@@ -2709,167 +2709,6 @@
}]
]
}, {
//------------------------------------------------------------------------
"TEST_CASE" : "eval_code",
//------------------------------------------------------------------------
"BODY": [
["LOAD_SAMPLE", "basic"],
["APPLY", {
"USER_ACTIONS": [
// Basic tests
["EvalCode", "print('cats')", null],
["EvalCode", "2 * 3 - 1 // 7 + 4", null],
// Exception
["EvalCode", "raise Exception('everything broke')", null],
// Incomplete structure
["EvalCode", "for x in range(1, 100):", null],
// Re-evaluation
["EvalCode", "print('cats')", 1],
["EvalCode", "print('dogs')", 1],
// Function definition
["EvalCode", "def foo(x):\n\treturn x * 10\n", null],
["EvalCode", "foo(10)", null]
],
"ACTIONS" : {
"stored" : [
["AddRecord", "_grist_REPL_Hist", 1,
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
["AddRecord", "_grist_REPL_Hist", 2,
{"code": "2 * 3 - 1 // 7 + 4", "errorText": "", "outputText": "10\n"}],
["AddRecord", "_grist_REPL_Hist", 3,
{"code": "raise Exception('everything broke')", "errorText": "Traceback (most recent call last):\n File \"<input>\", line 1, in <module>\nException: everything broke\n", "outputText": ""}],
["UpdateRecord", "_grist_REPL_Hist", 1,
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
["UpdateRecord", "_grist_REPL_Hist", 1,
{"code": "print('dogs')", "errorText": "", "outputText": "dogs\n"}],
["AddRecord", "_grist_REPL_Hist", 4,
{"code": "def foo(x):\n\treturn x * 10\n", "errorText": "", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 5,
{"code": "foo(10)", "errorText": "", "outputText": "100\n"}]
],
"direct": [true, true, true, true, true, true, true],
"undo" : [
["RemoveRecord", "_grist_REPL_Hist", 1],
["RemoveRecord", "_grist_REPL_Hist", 2],
["RemoveRecord", "_grist_REPL_Hist", 3],
["UpdateRecord", "_grist_REPL_Hist", 1,
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
["UpdateRecord", "_grist_REPL_Hist", 1,
{"code": "print('cats')", "errorText": "", "outputText": "cats\n"}],
["RemoveRecord", "_grist_REPL_Hist", 4],
["RemoveRecord", "_grist_REPL_Hist", 5]
],
"retValue" : [true,true,true,false,true,true,true,true]
}
}],
["APPLY", {
"USER_ACTIONS": [
// Access to usercode before and after re-generation
["EvalCode", "list(map(str, Students.all.firstName))", null],
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
["ModifyColumn", "Students", "firstName", { "type" : "Numeric" }],
["EvalCode", "list(Students.all.firstName)", 6],
// Make sure that other tables are still usable as well after a schema change.
["EvalCode", "len(Schools.all)", 6]
],
"ACTIONS": {
"stored": [
["AddRecord", "_grist_REPL_Hist", 6,
{"code": "list(map(str, Students.all.firstName))", "errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
["ModifyColumn", "Students", "firstName", {"type": "Numeric"}],
["UpdateRecord", "Students", 1, {"firstName": 2e6}],
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Numeric"}],
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
"errorText": "",
"outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}],
["UpdateRecord", "_grist_REPL_Hist", 6,
{"code": "len(Schools.all)", "errorText": "", "outputText": "6\n"}],
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], {
"fullName": [["E", "TypeError"], ["E","TypeError"], ["E","TypeError"],
["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]]
}],
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8], {
"fullNameLen": [["E","TypeError"], ["E","TypeError"], ["E","TypeError"],
["E","TypeError"], ["E","TypeError"], ["E","TypeError"], ["E","TypeError"]]
}]
],
"direct": [true, true, true, false, true, true, true, false, false],
"undo": [
["RemoveRecord", "_grist_REPL_Hist", 6],
["UpdateRecord", "Students", 1, {"firstName": "Barack"}],
["UpdateRecord", "Students", 1, {"firstName": "2e6"}],
["ModifyColumn", "Students", "firstName", {"type": "Text"}],
["UpdateRecord", "_grist_Tables_column", 1, {"type": "Text"}],
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(map(str, Students.all.firstName))",
"errorText": "", "outputText": "['Barack', 'George W', 'Bill', 'George H', 'Ronald', 'Jimmy', 'Gerald']\n"}],
["UpdateRecord", "_grist_REPL_Hist", 6, {"code": "list(Students.all.firstName)",
"errorText": "",
"outputText": "[2000000.0, AltText('George W'), AltText('Bill'), AltText('George H'), AltText('Ronald'), AltText('Jimmy'), AltText('Gerald')]\n"}],
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8],
{"fullName": ["Barack Obama", "George W Bush", "Bill Clinton", "George H Bush", "Ronald Reagan", "Jimmy Carter", "Gerald Ford"]}],
["BulkUpdateRecord", "Students", [1, 2, 3, 4, 5, 6, 8],
{"fullNameLen": [12, 13, 12, 13, 13, 12, 11]}]
],
"retValue": [true,null,null,true,true]
}
}],
["APPLY", {
"USER_ACTIONS": [
// Syntax Error
["EvalCode", "not correct c", null],
// Other continuations
["EvalCode", "map(filter, ", null],
["EvalCode", "[1,2,3,", null],
// sys.exit
["EvalCode", "import sys", null],
["EvalCode", "sys.exit(0)", null],
// User reassignment of sys.stdout/sys.stderr
["EvalCode", "sys.stdout = None", null],
["EvalCode", "setattr(sys.stdout, 'getvalue', lambda : 2)", null],
["EvalCode", "def foo():\n global stdout\n exec('stdout = 2')\n", null],
["EvalCode", "setattr(sys.stderr, 'close', foo)", null]
],
"ACTIONS": {
"stored": [
["AddRecord", "_grist_REPL_Hist", 7,
{"code": "not correct c", "errorText": " File \"<input>\", line 1\n not correct c\n ^\nSyntaxError: invalid syntax\n", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 8,
{"code": "import sys", "errorText": "", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 9,
{"code": "sys.exit(0)", "errorText": "Traceback (most recent call last):\n File \"<input>\", line 1, in <module>\nSystemExit: 0\n", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 10,
{"code": "sys.stdout = None", "errorText": "", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 11,
{"code": "setattr(sys.stdout, 'getvalue', lambda : 2)", "errorText": "", "outputText": 2}],
["AddRecord", "_grist_REPL_Hist", 12,
{"code": "def foo():\n global stdout\n exec('stdout = 2')\n", "errorText": "", "outputText": ""}],
["AddRecord", "_grist_REPL_Hist", 13,
{"code": "setattr(sys.stderr, 'close', foo)", "errorText": "", "outputText": ""}]
],
"direct": [true, true, true, true, true, true, true],
"undo": [
["RemoveRecord", "_grist_REPL_Hist", 7],
["RemoveRecord", "_grist_REPL_Hist", 8],
["RemoveRecord", "_grist_REPL_Hist", 9],
["RemoveRecord", "_grist_REPL_Hist", 10],
["RemoveRecord", "_grist_REPL_Hist", 11],
["RemoveRecord", "_grist_REPL_Hist", 12],
["RemoveRecord", "_grist_REPL_Hist", 13]
],
"retValue" : [true,false,false,true,true,true,true,true,true ]
}
}]
]
}, {
//------------------------------------------------------------------------
"TEST_CASE" : "reserved_keywords",
//------------------------------------------------------------------------

View File

@@ -17,7 +17,6 @@ import schema
from schema import RecalcWhen
import summary
import import_actions
import repl
import textbuilder
import usertypes
import treeview
@@ -239,37 +238,6 @@ class UserActions(object):
"""
pass
#--------------------------------------
# User Actions on usercode
#--------------------------------------
@useraction
def EvalCode(self, code, row_id):
"""
Evaluates code in the REPL.
a return value of false indicates that the user's code was incomplete
(and in this case, we do not create any docactions)
otherwise, we either add a new record to the REPL_hist (row_id=null) or Update an existing
record (row_id=num) with the results of evaluating the code.
"""
evaluation = self._engine.eval_user_code(code)
if evaluation.status == repl.INCOMPLETE:
return False
hist = self._engine.tables["_grist_REPL_Hist"]
record = { "code" : code, "outputText" : evaluation.output, "errorText" : evaluation.error }
if row_id is None:
# This is a new evaluation, append it to the REPL history
action = actions.AddRecord(hist.table_id, hist.next_row_id(), record)
else:
# This is a re-evaluation, update the old retValue
action = actions.UpdateRecord(hist.table_id, row_id, record)
self._do_doc_action(action)
return True
#----------------------------------------
# User actions on records.
#----------------------------------------