/** Parse student code Analyze student code Run student code Run instructor code Output feedback # Within the instructor code you can surpress certain kinds of feedback surpress_syntax() surpress_empty() surpress_algorithm() surpress_runtime() surpress_runtime('NameError') # Or explicitly run certain kinds of analyses run_student() run_abstract() */ /** * Given source code as a string, return a list of all of the AST elements * that are Num (aka numeric literals) but that are not inside List elements. * * @param {String} source - Python source code. * @returns {Array.number} The list of JavaScript numeric literals that were found. */ function getNonListNums(source) { if (!(source in parses)) { var parse = Sk.parse("__main__", source); parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags); } var ast = parses[source]; var visitor = new NodeVisitor(); var insideList = false; var nums = []; visitor.visit_List = function(node) { insideList = true; this.generic_visit(node); insideList = false; } visitor.visit_Num = function(node) { if (!insideList) { nums.push(node.n); } this.generic_visit(node); } visitor.visit(ast); return nums; } /** * Given source code as a string, return a list of all of the AST elements * that are being printed (using the print function) but are not variables. * * @param {String} source - Python source code. * @returns {Array.} The list of AST elements that were found. */ function getPrintedNonProperties(source) { if (!(source in parses)) { var parse = Sk.parse("__main__", source); parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags); } var ast = parses[source]; var visitor = new NodeVisitor(); var nonVariables = []; visitor.visit_Call = function(node) { var func = node.func; var args = node.args; if (func._astname == 'Name' && func.id.v == 'print') { for (var i =0; i < args.length; i+= 1) { if (args[i]._astname != "Name") { nonVariables.push(args[i]); } } } this.generic_visit(node); } visitor.visit(ast); return nonVariables; } /** * Skulpt function to iterate through the final state of * all the variables in the program, and check to see if they have * a given value. */ mod.get_value_by_name = new Sk.builtin.func(function(name) { Sk.builtin.pyCheckArgs("get_value_by_name", arguments, 1, 1); Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name)); name = name.v; var final_values = Sk.builtins._final_values; if (name in final_values) { return final_values[name]; } else { return Sk.builtin.none.none$; } }); mod.get_value_by_type = new Sk.builtin.func(function(type) { Sk.builtin.pyCheckArgs("get_value_by_type", arguments, 1, 1); var final_values = Sk.builtins._final_values; var result = []; for (var property in final_values) { if (final_values[property].tp$name == type.tp$name) { result.push(final_values[property]); } } return Sk.builtin.list(result); }); mod.parse_json = new Sk.builtin.func(function(blob) { Sk.builtin.pyCheckArgs("parse_json", arguments, 1, 1); Sk.builtin.pyCheckType("blob", "string", Sk.builtin.checkString(blob)); blob = blob.v; return Sk.ffi.remapToPy(JSON.parse(blob)); }); mod.get_property = new Sk.builtin.func(function(name) { Sk.builtin.pyCheckArgs("get_property", arguments, 1, 1); Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name)); name = name.v; var trace = Sk.builtins._trace; if (trace.length <= 0) { return Sk.builtin.none.none$; } var properties = trace[trace.length-1]["properties"]; for (var i = 0, len = properties.length; i < len; i += 1) { if (properties[i]['name'] == name) { return Sk.ffi.remapToPy(properties[i]) } } return Sk.builtin.none.none$; }); mod.calls_function = new Sk.builtin.func(function(source, name) { Sk.builtin.pyCheckArgs("calls_function", arguments, 2, 2); Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source)); Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name)); source = source.v; name = name.v; var ast_list = getParseList(source); var count = 0; for (var i = 0, len = ast_list.length; i < len; i = i+1) { if (ast_list[i]._astname == 'Call') { if (ast_list[i].func._astname == 'Attribute') { count += Sk.ffi.remapToJs(ast_list[i].func.attr) == name | 0; } else if (ast_list[i].func._astname == 'Name') { count += Sk.ffi.remapToJs(ast_list[i].func.id) == name | 0; } } } return Sk.ffi.remapToPy(count > 0); }); mod.count_components = new Sk.builtin.func(function(source, component) { Sk.builtin.pyCheckArgs("count_components", arguments, 2, 2); Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source)); Sk.builtin.pyCheckType("component", "string", Sk.builtin.checkString(component)); source = source.v; component = component.v; var ast_list = getParseList(source); var count = 0; for (var i = 0, len = ast_list.length; i < len; i = i+1) { if (ast_list[i]._astname == component) { count = count+1; } } return Sk.ffi.remapToPy(count); }); mod.no_nonlist_nums = new Sk.builtin.func(function(source) { Sk.builtin.pyCheckArgs("no_nonlist_nums", arguments, 1, 1); Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source)); source = source.v; var num_list = getNonListNums(source); var count = 0; for (var i = 0, len = num_list.length; i < len; i = i+1) { if (num_list[i].v != 0 && num_list[i].v != 1) { return Sk.ffi.remapToPy(true); } } return Sk.ffi.remapToPy(false); }); mod.only_printing_properties = new Sk.builtin.func(function(source) { Sk.builtin.pyCheckArgs("only_printing_properties", arguments, 1, 1); Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source)); source = source.v; var non_var_list = getPrintedNonProperties(source); return Sk.ffi.remapToPy(non_var_list.length == 0); }); /** * Given a feedback string, records the corrective feedback string for later printing * @param {string} feedback - the piece of feedback to save **/ mod.add_interrupt_feedback = new Sk.builtin.func(function(feedback) { Sk.builtin.pyCheckArgs("add_interupt_feedback", arguments, 1, 1); Sk.builtin.pyCheckType("feedback", "string", Sk.builtin.checkString(feedback)); accInterruptFeedback.push(feedback); }); /** * Given a feedback string, records the complementary feedback string for later printing * @param {string} feedback - the piece of feedback to save **/ mod.add_comp_feedback = new Sk.builtin.func(function(feedback) { Sk.builtin.pyCheckArgs("add_comp_feedback", arguments, 1, 1); Sk.builtin.pyCheckType("feedback", "string", Sk.builtin.checkString(feedback)); accCompFeedback.push(feedback); }); /** * This resolves all of the feedback and posts it to the appropriate places * @TODO: actually implement this functionality **/ mod.post_feedback = new Sk.builtin.func(function() { var allFeedback = accInterruptFeedback.concat(accCompFeedback); completePythonAll = Sk.builtin.list(allFeedback); jsPureFeedback = Sk.ffi.remapToJs(completePythonAll); console.log("" + jsPureFeedback); }); Sk.builtins.value = new Sk.builtin.func(function() { return Sk.ffi.remapToPy(GLOBAL_VALUE === undefined ? 5 : GLOBAL_VALUE); }); Sk.builtins.set_value = new Sk.builtin.func(function(v) { GLOBAL_VALUE = v.v; }); /** * Runs the given python code, resetting the console and Trace Table. */ BlockPyEngine.prototype.run = function() { // Reset everything this.resetExecution(); if (!this.main.model.settings.disable_semantic_errors() && !this.main.model.assignment.disable_algorithm_errors()) { var success = this.analyze(); if (success === false) { this.executionEnd_(); this.main.components.server.markSuccess(0.0); return; } } this.main.model.execution.status("running"); var feedback = this.main.components.feedback; // Get the code var code = this.main.model.programs['__main__'](); if (code.trim() == "") { feedback.emptyProgram(); this.main.model.execution.status("error"); this.executionEnd_(); this.main.components.server.markSuccess(0.0); return; } // Actually run the python code var executionPromise = Sk.misceval.asyncToPromise(function() { return Sk.importMainWithBody("", false, code, true); }); var engine = this; var server = this.server; var execution = this.main.model.execution; executionPromise.then( function (module) { // Run the afterSingleExecution one extra time for final state Sk.afterSingleExecution(module.$d, -1, 0, ".py"); engine.lastStep(); // Handle checks feedback.noErrors() engine.check(code, execution.trace(), execution.output(), execution.ast, module.$d); // Reenable "Run" engine.main.model.execution.status("waiting"); engine.executionEnd_(); }, function(error) { feedback.printError(error); engine.main.model.execution.status("error"); engine.executionEnd_(); server.markSuccess(0.0); //server.logEvent('blockly_error', error); } ); } BlockPyEngine.prototype.setupEnvironment = function(student_code, traceTable, output, ast, final_values) { var model = this.main.model; this._backup_execution = Sk.afterSingleExecution; Sk.afterSingleExecution = undefined; Sk.builtins.get_output = new Sk.builtin.func(function() { Sk.builtin.pyCheckArgs("get_output", arguments, 0, 0); return Sk.ffi.remapToPy(model.execution.output()); }); Sk.builtins.reset_output = new Sk.builtin.func(function() { Sk.builtin.pyCheckArgs("reset_output", arguments, 0, 0); model.execution.output.removeAll(); }); Sk.builtins.log = new Sk.builtin.func(function(data) { Sk.builtin.pyCheckArgs("log", arguments, 1, 1); console.log(data) }); //Sk.builtins.trace = Sk.ffi.remapToPy(traceTable); Sk.builtins._trace = traceTable; Sk.builtins._final_values = final_values; Sk.builtins.code = Sk.ffi.remapToPy(student_code); Sk.builtins.set_success = this.instructor_module.set_success; Sk.builtins.set_feedback = this.instructor_module.set_feedback; Sk.builtins.set_finished = this.instructor_module.set_finished; Sk.builtins.count_components = this.instructor_module.count_components; Sk.builtins.no_nonlist_nums = this.instructor_module.no_nonlist_nums; Sk.builtins.only_printing_properties = this.instructor_module.only_printing_properties; Sk.builtins.calls_function = this.instructor_module.calls_function; Sk.builtins.get_property = this.instructor_module.get_property; Sk.builtins.get_value_by_name = this.instructor_module.get_value_by_name; Sk.builtins.get_value_by_type = this.instructor_module.get_value_by_type; Sk.builtins.parse_json = this.instructor_module.parse_json; Sk.builtins.Stack = this.instructor_module.Stack; Sk.skip_drawing = true; model.settings.mute_printer(true); } BlockPyEngine.prototype.disposeEnvironment = function() { Sk.afterSingleExecution = this._backup_execution; Sk.builtins.get_output = undefined; Sk.builtins.reset_output = undefined; Sk.builtins.log = undefined; Sk.builtins._trace = undefined; Sk.builtins.trace = undefined; Sk.builtins.code = undefined; Sk.builtins.set_success = undefined; Sk.builtins.set_feedback = undefined; Sk.builtins.set_finished = undefined; Sk.builtins.count_components = undefined; Sk.builtins.calls_function = undefined; Sk.builtins.get_property = undefined; Sk.builtins.get_value_by_name = undefined; Sk.builtins.get_value_by_type = undefined; Sk.builtins.no_nonlist_nums = undefined; Sk.builtins.only_printing_properties = undefined; Sk.builtins.parse_json = undefined; Sk.skip_drawing = false; GLOBAL_VALUE = undefined; this.main.model.settings.mute_printer(false); } BlockPyEngine.prototype.check = function(student_code, traceTable, output, ast, final_values) { var engine = this; var server = this.main.components.server; var model = this.main.model; var on_run = model.programs['give_feedback'](); if (on_run !== undefined && on_run.trim() !== "") { on_run = 'def run_code():\n'+indent(student_code)+'\n'+on_run; this.setupEnvironment(student_code, traceTable, output, ast, final_values); var executionPromise = Sk.misceval.asyncToPromise(function() { return Sk.importMainWithBody("", false, on_run, true); }); executionPromise.then( function (module) { engine.main.components.feedback.noErrors(); engine.disposeEnvironment(); }, function (error) { engine.disposeEnvironment(); console.log(error.tp$name, error.tp$name == "Success"); if (error.tp$name == "Success") { server.markSuccess(1.0, model.settings.completedCallback); engine.main.components.feedback.complete(); } else if (error.tp$name == "Feedback") { server.markSuccess(0.0); engine.main.components.feedback.instructorFeedback("Incorrect Answer", error.args.v[0].v); } else if (error.tp$name == "Finished") { server.markSuccess(1.0, model.settings.completedCallback); engine.main.components.feedback.finished(); } else { console.error(error); engine.main.components.feedback.internalError(error, "Feedback Error", "Error in instructor's feedback. Please show the above message to an instructor!"); server.logEvent('blockly_instructor_error', ''+error); server.markSuccess(0.0); } }); } } /** * Runs the AbstractInterpreter to get some static information about the code, * in particular the variables' types. This is needed for type checking. * * @returns {Object} Maps variable names (as Strings) to types as constructed by the AbstractIntepreter. */ BlockPyEngine.prototype.analyzeVariables = function() { // Get the code var code = this.main.model.programs['__main__'](); if (code.trim() == "") { return {}; } var analyzer = new AbstractInterpreter(code); report = analyzer.report; return analyzer.variableTypes; } /** * Runs the AbstractInterpreter to get some static information about the code, * including potential semantic errors. It then parses that information to give * feedback. * * @returns {Boolean} Whether the code was successfully analyzed. */ BlockPyEngine.prototype.analyze = function() { this.main.model.execution.status("analyzing"); var feedback = this.main.components.feedback; // Get the code var code = this.main.model.programs['__main__'](); if (code.trim() == "") { this.main.components.feedback.emptyProgram("You haven't written any code yet!"); //this.main.model.feedback.status("semantic"); return false; } var analyzer = new AbstractInterpreter(code); this.main.model.execution.ast = analyzer.ast; report = analyzer.report; // Syntax error if (report.error !== false) { console.log(report.error.args.v) var codeLine = '.'; if (report.error.args.v.length > 3) { codeLine = ', where it says:
'+report.error.args.v[3][2]+''; } feedback.editorError(report.error, "While attempting to process your Python code, I found a syntax error. In other words, your Python code has a mistake in it (e.g., mispelled a keyword, bad indentation, unnecessary symbol). You should check to make sure that you have written all of your code correctly. To me, it looks like the problem is on line "+ report.error.args.v[2]+codeLine, report.error.args.v[2]); return false; } return true; } var GLOBAL_VALUE;