| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- /**
- * An object that manages the feedback area, where users are told the state of their
- * program's execution and given guidance. Also manages the creation of the Trace Table.
- *
- * @constructor
- * @this {BlockPyFeedback}
- * @param {Object} main - The main BlockPy instance
- * @param {HTMLElement} tag - The HTML object this is attached to.
- */
- function BlockPyFeedback(main, tag) {
- this.main = main;
- this.tag = tag;
-
- this.body = this.tag.find('.blockpy-feedback-body');
- this.title = this.tag.find('.blockpy-feedback-title');
- this.original = this.tag.find('.blockpy-feedback-original');
- this.status = this.tag.find('.blockpy-feedback-status');
- this.trace = this.tag.find('.blockpy-feedback-trace');
-
- // Reload the tracetable on click
- this.trace.click(this.buildTraceTable.bind(this));
-
- this.original.hide();
- };
- BlockPyFeedback.prototype.isFeedbackVisible = function () {
- var top_of_element = this.tag.offset().top;
- var bottom_of_element = this.tag.offset().top + this.tag.outerHeight();
- var bottom_of_screen = $(window).scrollTop() + $(window).height();
- var top_of_screen = $(window).scrollTop();
- //bottom_of_element -= 40; // User friendly padding
- return ((bottom_of_screen > top_of_element) && (top_of_screen < bottom_of_element));
- }
- /**
- * Reload the trace table, showing it if it was hidden and
- * resetting its position to the last step.
- */
- BlockPyFeedback.prototype.buildTraceTable = function() {
- var execution = this.main.model.execution;
- execution.show_trace(true);
- execution.trace_step(execution.last_step());
- this.main.components.server.logEvent('editor', 'trace');
- }
- /**
- * Raises a generic warning. This might not be used anymore.
- *
- * @param {String} html - Some HTML content to render to the user.
- */
- BlockPyFeedback.prototype.error = function(html) {
- this.tag.html(html);
- this.tag.removeClass("alert-success");
- this.tag.addClass("alert-warning");
- this.main.components.printer.print("Execution stopped - there was an error!");
- }
- /**
- * Clears any output currently in the feedback area. Also resets the printer and
- * any highlighted lines in the editor.
- */
- BlockPyFeedback.prototype.clear = function(printer) {
- this.title.html("Ready");
- this.original.hide();
- this.body.html("");
- this.main.model.status.error("none");
- this.main.components.editor.unhighlightLines();
- if (printer !== undefined && printer) {
- this.main.components.printer.resetPrinter()
- }
- };
- /**
- * Clears any errors from the editor area.
- */
- BlockPyFeedback.prototype.clearEditorErrors = function() {
- if (this.main.model.status.error() == "editor") {
- this.clear();
- }
- }
- /**
- * Show an error message related to a problem with the editor. This will appear in
- * the Feedback area, the Printer, and also log to the server. The relevant line of
- * code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.editorError = function(original, message, line) {
- original = this.prettyPrintError(original);
- this.title.html("Editor Error");
- this.original.show().html(original);
- this.body.html(message);
- this.main.model.status.error("editor");
- this.main.components.editor.highlightError(line-1);
- //this.main.components.printer.print("Editor error - could not make blocks!");
- this.main.components.server.logEvent('feedback', "Editor Error", original+"\n|\n"+message);
- }
- /**
- * Mark this problem as completed for the student. This will appear in the Feedback area,
- * and will also unhighlight lines in the editor and log to the server.
- */
- BlockPyFeedback.prototype.complete = function() {
- this.title.html("Complete!");
- this.original.hide();
- this.body.html("Great work!");
- this.main.model.status.error("complete");
- this.main.components.editor.unhighlightLines();
- this.main.components.server.logEvent('feedback', "Success");
- }
- /**
- * Mark this problem as finished for the student. This will appear in the Feedback area,
- * and will also unhighlight lines in the editor and log to the server.
- */
- BlockPyFeedback.prototype.finished = function() {
- this.title.html("Ran");
- this.original.hide();
- this.body.html("Your program ran successfully, without any errors. However, this problem does not have a correct solution. When you are satisfied with your program, you may stop working.");
- this.main.model.status.error("no errors");
- this.main.components.editor.unhighlightLines();
- this.main.components.server.logEvent('feedback', "Finished");
- }
- /**
- * This notifies the student that their code ran without errors, but that there was no
- * Success raised by the Checker. This will appear in the Feedback area,
- * and will also unhighlight lines in the editor and log to the server.
- */
- BlockPyFeedback.prototype.noErrors = function() {
- this.title.html("Ran");
- this.original.hide();
- this.body.html("No errors reported. View your output on the left.");
- this.main.model.status.error("no errors");
- this.main.components.editor.unhighlightLines();
- this.main.components.server.logEvent('feedback', "No Errors", '');
- }
- /**
- * Show an error message related to syntax issue. This will appear in
- * the Feedback area, the Printer, and also log to the server. The relevant line of
- * code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.syntaxError = function(original, message, line) {
- original = this.prettyPrintError(original);
- this.title.html("Syntax Error");
- this.original.show().html(original);
- this.body.html(message);
- this.main.model.status.error("syntax");
- this.main.components.editor.highlightError(line-1);
- this.main.components.printer.print("Execution stopped - there was an error!");
- this.main.components.server.logEvent('feedback', "Syntax Error", original+"\n|\n"+message);
- }
- /**
- * Show an error message related to semantic error with the program (e.g., unused variable).
- * This will appear in the Feedback area, the Printer, and also log to the server. The
- * relevant line of code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.semanticError = function(name, message, line) {
- this.title.html(name);
- this.original.hide();
- this.body.html(message);
- this.main.model.status.error("semantic");
- if (line !== null) {
- this.main.components.editor.highlightError(line-1);
- }
- this.main.components.printer.print("Execution stopped - there was an error!");
- this.main.components.server.logEvent('feedback', "Semantic Error", name+"\n|\n"+message);
- }
- /**
- * Show an error message related to a serious internal BlockPy program. Under normal conditions,
- * this should never appear to a student. This will appear in
- * the Feedback area, the Printer, and also log to the server. The relevant line of
- * code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.internalError = function(original, name, message) {
- original = this.prettyPrintError(original);
- this.title.html(name);
- this.original.show().html(original);
- this.body.html(message);
- this.main.model.status.error("internal");
- this.main.components.printer.print("Internal error! Please show this to an instructor!");
- this.main.components.server.logEvent('feedback', "Internal Error", name+"\n|\n"+original+"\n|\n"+message);
- console.error(original);
- }
- /**
- * Show an incorrect code message related to a problem as specified by the Checker. This will appear in
- * the Feedback area, the Printer, and also log to the server. The relevant line of
- * code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.instructorFeedback = function(name, message, line) {
- this.title.html(name);
- this.original.hide();
- this.body.html(message);
- this.main.model.status.error("feedback");
- if (line !== undefined && line != null) {
- this.main.components.editor.highlightError(line-1);
- }
- this.main.components.server.logEvent('feedback', "Instructor Feedback", name+"\n|\n"+"\n|\n"+message);
- }
- /**
- * Show "Empty Program" error, indicating the student hasn't written any code. This will appear in
- * the Feedback area, the Printer, and also log to the server. The relevant line of
- * code or block will also be highlighted.
- *
- * @param {String} original - HTML content that represents the original error message generated by the system.
- * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
- * @param {number} line - What line the error occurred on.
- */
- BlockPyFeedback.prototype.emptyProgram = function() {
- this.title.html("Blank Program");
- this.original.hide().html("");
- this.body.html("You have not written any code yet.");
- this.main.model.status.error("runtime");
- this.main.components.server.logEvent('feedback', "Empty Program");
- }
- /**
- * Converts any kind of error (usually a Skulpt one) into a prettier version that's ready
- * for users to see. If it's already a string, it is passed along unchanged. But Skulpt
- * errors have to be processed more closely.
- */
- BlockPyFeedback.prototype.prettyPrintError = function(error) {
- if (typeof error === "string") {
- return error;
- } else {
- // A weird skulpt thing?
- if (error.tp$str !== undefined) {
- return error.tp$str().v;
- } else {
- return ""+error.name + ": " + error.message;
- }
- }
- }
- /**
- * Print an error to the printers -- the on screen one and the browser one. This
- * will attempt to provide extra explanation and context for an error.
- * Notice that this is largely for Run-time errors that will be thrown when the code
- * is executed, as opposed to ones raised elsewhere in the environment.
- *
- * @param {String} error - The error message to be analyzed and printed.
- */
- BlockPyFeedback.prototype.printError = function(error) {
- //console.log(error);
- original = this.prettyPrintError(error);
- this.title.html(error.tp$name);
- this.original.show().html(original);
- if (error.tp$name == "ParseError") {
- this.body.html("While attempting to convert the Python code into blocks, I found a syntax error. In other words, your Python code has a spelling or grammatical mistake. 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 "+ error.args.v[2]+', where it says:<br><code>'+error.args.v[3][2]+'</code>', error.args.v[2]);
- } else if (error.constructor == Sk.builtin.NameError
- && error.args.v.length > 0
- && error.args.v[0].v == "name '___' is not defined") {
- this.body.html("You have incomplete blocks. Make sure that you do not have any dangling blocks or blocks that are connected incorrectly.<br><br>If you look at the text view of your Python code, you'll see <code>___</code> in the code. The converter will create these <code>___</code> to show that you have a block that's missing a piece.");
- } else if (error.tp$name in EXTENDED_ERROR_EXPLANATION) {
- this.body.html(EXTENDED_ERROR_EXPLANATION[error.tp$name]);
- } else {
- this.body.html(error.enhanced);
- }
- console.error(error);
- if (error.stack) {
- console.error(error.stack);
- }
- this.main.model.status.error("runtime");
- if (error.traceback && error.traceback.length) {
- this.main.components.editor.highlightError(error.traceback[0].lineno-1);
- }
- this.main.components.server.logEvent('feedback', "Runtime", original);
- }
- /**
- * Static method to convert a priority level into a number.
- */
- BlockPyFeedback.priorityNumConvert = function(priority){
- switch(priority){
- case 'low': return 1;
- case 'high': return 3;
- default: case 'medium': return 2;
- }
- };
- /**
- * Static method to compare two priority labels.
- */
- BlockPyFeedback.priorityComparator = function(a, b) {
- var priorityA = BlockPyFeedback.priorityNumConvert(a.priority);
- var priorityB = BlockPyFeedback.priorityNumConvert(b.priority);
- if (priorityA > priorityB) {
- return -1;
- } else if (priorityB > priorityA) {
- return 1;
- } else {
- return 0;
- }
- };
- /**
- * Present any accumulated feedback
- */
- BlockPyFeedback.prototype.presentFeedback = function() {
- this.clear(false);
- var report = this.main.model.execution.reports;
- var suppress = this.main.model.execution.suppressions;
-
- // Organize complaints
- var complaint = report['instructor'].complaint;
- var gentleComplaints = [];
- var verifierComplaints = [];
- if (complaint) {
- moveElements(complaint, gentleComplaints, function(e) { return e.priority == 'student' });
- moveElements(complaint, verifierComplaints, function(e) { return e.priority == 'verifier' });
- }
-
- // Verifier
- if (!suppress['verifier'] && !report['verifier'].success) {
- this.emptyProgram();
- return 'verifier';
- }
- // Parser
- if (verifierComplaints.length) {
- this.instructorFeedback(verifierComplaints[0].name,
- verifierComplaints[0].message,
- verifierComplaints[0].line);
- return 'instructor';
- }
- if (!suppress['parser'] && !report['parser'].success) {
- var parserReport = report['parser'].error;
- this.convertSkulptSyntax(parserReport);
- return 'parser';
- }
- // Error in Instructor Feedback code
- if (!report['instructor'].success) {
- var error = report['instructor'].error;
- if (!error.traceback) {
- this.internalError(error, "Instructor Feedback Error", "Error in instructor feedback. Please show the above message to an instructor!");
- console.error(error);
- return 'instructor';
- } else if (error.traceback[0].filename == "__main__.py") {
- this.printError(report['instructor'].error);
- return 'student';
- } else {
- if (error.traceback[0].filename == report['instructor'].filename) {
- error.traceback[0].lineno -= report['instructor']['line_offset'];
- }
- //report['instructor']['line_offset']
- this.internalError(error, "Instructor Feedback Error", "Error in instructor feedback. Please show the above message to an instructor!");
- console.error(error);
- return 'instructor';
- }
- }
- if (report['instructor'].compliments && report['instructor'].compliments.length) {
- //this.compliment(report['instructor'].compliments);
- console.log(report['instructor'].compliments);
- }
- if (suppress['instructor'] !== true && complaint && complaint.length) {
- complaint.sort(BlockPyFeedback.sortPriorities);
- this.instructorFeedback(complaint[0].name, complaint[0].message, complaint[0].line);
- return 'instructor';
- }
- // Analyzer
- if (!report['instructor'].hide_correctness &&
- suppress['analyzer'] !== true) {//if a subtype is specified, or no suppression requested, present feedback
- if (!report['analyzer'].success) {
- this.internalError(report['analyzer'].error, "Analyzer Error", "Error in analyzer. Please show the above message to an instructor!");
- return 'analyzer';
- }
- var wasPresented = this.presentAnalyzerFeedback();
- if (wasPresented) {
- return 'analyzer';
- }
- }
- // Student runtime errors
- if (!suppress['student']) {
- if (!report['student'].success) {
- this.printError(report['student'].error);
- return 'student';
- }
- }
- // No instructor feedback if hiding correctness
- if (report['instructor'].hide_correctness == true) {
- this.noErrors()
- return 'no errors';
- }
- // Gentle instructor feedback
- if (suppress['instructor'] !== true && gentleComplaints.length) {
- this.instructorFeedback(gentleComplaints[0].name,
- gentleComplaints[0].message,
- gentleComplaints[0].line);
- return 'instructor';
- }
- //instructor completion flag
- if (suppress['instructor'] !== true && report['instructor'].complete) {
- this.complete();
- return 'success';
- }
- if (!suppress['no errors']) {
- this.noErrors()
- return 'no errors';
- }
- return 'completed';
- }
- BlockPyFeedback.prototype.convertSkulptSyntax = function(skulptError) {
- var convertedError = Sk.ffi.remapToJs(skulptError.args);
- console.log(convertedError);
- var codeLine = '.';
- if (convertedError.length > 3 && convertedError[4]) {
- codeLine = ', where it says:<br><code>'+convertedError[4]+'</code>';
- }
- this.editorError(skulptError, "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 "+ convertedError[2]+codeLine, convertedError[2]);
- }
- BlockPyFeedback.prototype.OPERATION_DESCRIPTION = {
- "Pow": "an exponent",
- "Add": "an addition",
- "Mult": "a multiplication",
- "Sub": "a subtraction",
- "Div": "a division",
- "Mod": "a modulo"
- };
- BlockPyFeedback.prototype.TYPE_DESCRIPTION = {
- "Num": "a number",
- "Str": "a string",
- "Tuple": "a tuple",
- "List": "a list",
- "Bool": "a boolean",
- "File": "a file",
- "None": "a None",
- "Set": "a set",
- "Function": "a function"
- };
- BlockPyFeedback.prototype.presentAnalyzerFeedback = function() {
- var report = this.main.model.execution.reports['analyzer'].issues;
- var suppress = this.main.model.execution.suppressions['analyzer'] || {};
- if (suppress === true) {
- // Suppress all types of analyzer errors
- return false;
- } else if (!suppress["Action after return"] && report["Action after return"].length >= 1) {
- var variable = report["Action after return"][0];
- this.semanticError("Action after return", "You performed an action after already returning from a function, on line "+variable.position.line+". You can only return on a path once.", variable.position.line)
- return true;
- } else if (!suppress['Return outside function'] && report['Return outside function'].length >= 1) {
- var first = report['Return outside function'][0];
- this.semanticError("Return outside function", "You attempted to return outside of a function on line "+first.position.line+". But you can only return from within a function.", first.position.line)
- return true;
- /*} else if (!suppress['Write out of scope'] && report['Write out of scope'].length >= 1) {
- var first = report['Write out of scope'][0];
- this.semanticError("Write out of scope", "You attempted to write a variable from a higher scope (outside the function) on line "+first.position.line+". You should only use variables inside the function they were declared in.", first.position.line)
- return true;*/
- } else if (!suppress['Read out of scope'] && report['Read out of scope'].length >= 1) {
- var first = report['Read out of scope'][0];
- this.semanticError("Read out of scope", "You attempted to read a variable from a different scope on line "+first.position.line+". You should only use variables inside the function they were declared in.", first.position.line)
- return true;
- } else if (!suppress['Unconnected blocks'] && report["Unconnected blocks"].length >= 1) {
- var variable = report['Unconnected blocks'][0];
- this.semanticError("Unconnected blocks", "It looks like you have unconnected blocks on line "+variable.position.line+". Before you run your program, you must make sure that all of your blocks are connected and that there are no unfilled holes.", variable.position.line)
- return true;
- } else if (!suppress['Iteration variable is iteration list'] && report['Iteration variable is iteration list'].length >= 1) {
- var variable = report['Iteration variable is iteration list'][0];
- this.semanticError("Iteration Problem", "The variable <code>"+variable.name+"</code> was iterated on line "+variable.position.line+", but you used the same variable as the iteration variable. You should choose a different variable name for the iteration variable. Usually, the iteration variable is the singular form of the iteration list (e.g., <code>for dog in dogs:</code>).", variable.position.line)
- return true;
- } else if (!suppress["Undefined variables"] && report["Undefined variables"].length >= 1) {
- var variable = report["Undefined variables"][0];
- this.semanticError("Initialization Problem", "The variable <code>"+variable.name+"</code> was used on line "+variable.position.line+", but it was not given a value on a previous line. You cannot use a variable until it has been given a value.", variable.position.line)
- return true;
- } else if (!suppress["Possibly undefined variables"] && report["Possibly undefined variables"].length >= 1) {
- var variable = report["Possibly undefined variables"][0];
- this.semanticError("Initialization Problem", "The variable <code>"+variable.name+"</code> was used on line "+variable.position.line+", but it was possibly not given a value on a previous line. You cannot use a variable until it has been given a value. Check to make sure that this variable was declared in all of the branches of your decision.", variable.position.line);
- return true;
- } else if (!suppress["Unread variables"] && report["Unread variables"].length >= 1) {
- var variable = report["Unread variables"][0];
- var kindName = 'variable', kindBody = 'value';
- if (variable.type && variable.type.name == 'Function') {
- kindName = 'function';
- kindBody = 'definition';
- }
- this.semanticError("Unused Variable", "The "+kindName+" <code>"+variable.name+"</code> was given a "+kindBody+", but was never used after that.", null)
- return true;
- } else if (!suppress["Overwritten variables"] && report["Overwritten variables"].length >= 1) {
- var variable = report["Overwritten variables"][0];
- this.semanticError("Overwritten Variable", "The variable <code>"+variable.name+"</code> was given a value, but <code>"+variable.name+"</code> was changed on line "+variable.position.line+" before it was used. One of the times that you gave <code>"+variable.name+"</code> a value was incorrect.", variable.position.line)
- return true;
- } else if (!suppress["Empty iterations"] && report["Empty iterations"].length >= 1) {
- var variable = report["Empty iterations"][0];
- if (variable.name) {
- this.semanticError("Iterating over empty list", "The variable <code>"+variable.name+"</code> was set as an empty list, and then you attempted to use it in an iteration on line "+variable.position.line+". You should only iterate over non-empty lists.", variable.position.line)
- return true;
- }
- } else if (!suppress["Non-list iterations"] && report["Non-list iterations"].length >= 1) {
- var variable = report["Non-list iterations"][0];
- if (variable.name) {
- this.semanticError("Iterating over non-list", "The variable <code>"+variable.name+"</code> is not a list, but you used it in the iteration on line "+variable.position.line+". You should only iterate over sequences like lists.", variable.position.line)
- return true;
- }
- } else if (!suppress["Incompatible types"] && report["Incompatible types"].length >= 1) {
- var variable = report["Incompatible types"][0];
- var op = this.OPERATION_DESCRIPTION[variable.operation];
- var left = this.TYPE_DESCRIPTION[variable.left.name];
- var right = this.TYPE_DESCRIPTION[variable.right.name];
- this.semanticError("Incompatible types", "You used "+op+" operation with a "+left+" and a "+right+" on line "+variable.position.line+". But you can't do that with that operator. Make sure both sides of the operator are the right type.", variable.position.line)
- return true;
- }
- return false;
- }
- if (typeof exports !== 'undefined') {
- exports.BlockPyFeedback = BlockPyFeedback;
- }
|