feedback.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /**
  2. * An object that manages the feedback area, where users are told the state of their
  3. * program's execution and given guidance. Also manages the creation of the Trace Table.
  4. *
  5. * @constructor
  6. * @this {BlockPyFeedback}
  7. * @param {Object} main - The main BlockPy instance
  8. * @param {HTMLElement} tag - The HTML object this is attached to.
  9. */
  10. function BlockPyFeedback(main, tag) {
  11. this.main = main;
  12. this.tag = tag;
  13. this.body = this.tag.find('.blockpy-feedback-body');
  14. this.title = this.tag.find('.blockpy-feedback-title');
  15. this.original = this.tag.find('.blockpy-feedback-original');
  16. this.status = this.tag.find('.blockpy-feedback-status');
  17. this.trace = this.tag.find('.blockpy-feedback-trace');
  18. // Reload the tracetable on click
  19. this.trace.click(this.buildTraceTable.bind(this));
  20. this.original.hide();
  21. };
  22. BlockPyFeedback.prototype.isFeedbackVisible = function () {
  23. var top_of_element = this.tag.offset().top;
  24. var bottom_of_element = this.tag.offset().top + this.tag.outerHeight();
  25. var bottom_of_screen = $(window).scrollTop() + $(window).height();
  26. var top_of_screen = $(window).scrollTop();
  27. //bottom_of_element -= 40; // User friendly padding
  28. return ((bottom_of_screen > top_of_element) && (top_of_screen < bottom_of_element));
  29. }
  30. /**
  31. * Reload the trace table, showing it if it was hidden and
  32. * resetting its position to the last step.
  33. */
  34. BlockPyFeedback.prototype.buildTraceTable = function() {
  35. var execution = this.main.model.execution;
  36. execution.show_trace(true);
  37. execution.trace_step(execution.last_step());
  38. this.main.components.server.logEvent('editor', 'trace');
  39. }
  40. /**
  41. * Raises a generic warning. This might not be used anymore.
  42. *
  43. * @param {String} html - Some HTML content to render to the user.
  44. */
  45. BlockPyFeedback.prototype.error = function(html) {
  46. this.tag.html(html);
  47. this.tag.removeClass("alert-success");
  48. this.tag.addClass("alert-warning");
  49. this.main.components.printer.print("Execution stopped - there was an error!");
  50. }
  51. /**
  52. * Clears any output currently in the feedback area. Also resets the printer and
  53. * any highlighted lines in the editor.
  54. */
  55. BlockPyFeedback.prototype.clear = function(printer) {
  56. this.title.html("Ready");
  57. this.original.hide();
  58. this.body.html("");
  59. this.main.model.status.error("none");
  60. this.main.components.editor.unhighlightLines();
  61. if (printer !== undefined && printer) {
  62. this.main.components.printer.resetPrinter()
  63. }
  64. };
  65. /**
  66. * Clears any errors from the editor area.
  67. */
  68. BlockPyFeedback.prototype.clearEditorErrors = function() {
  69. if (this.main.model.status.error() == "editor") {
  70. this.clear();
  71. }
  72. }
  73. /**
  74. * Show an error message related to a problem with the editor. This will appear in
  75. * the Feedback area, the Printer, and also log to the server. The relevant line of
  76. * code or block will also be highlighted.
  77. *
  78. * @param {String} original - HTML content that represents the original error message generated by the system.
  79. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  80. * @param {number} line - What line the error occurred on.
  81. */
  82. BlockPyFeedback.prototype.editorError = function(original, message, line) {
  83. original = this.prettyPrintError(original);
  84. this.title.html("Editor Error");
  85. this.original.show().html(original);
  86. this.body.html(message);
  87. this.main.model.status.error("editor");
  88. this.main.components.editor.highlightError(line-1);
  89. //this.main.components.printer.print("Editor error - could not make blocks!");
  90. this.main.components.server.logEvent('feedback', "Editor Error", original+"\n|\n"+message);
  91. }
  92. /**
  93. * Mark this problem as completed for the student. This will appear in the Feedback area,
  94. * and will also unhighlight lines in the editor and log to the server.
  95. */
  96. BlockPyFeedback.prototype.complete = function() {
  97. this.title.html("Complete!");
  98. this.original.hide();
  99. this.body.html("Great work!");
  100. this.main.model.status.error("complete");
  101. this.main.components.editor.unhighlightLines();
  102. this.main.components.server.logEvent('feedback', "Success");
  103. }
  104. /**
  105. * Mark this problem as finished for the student. This will appear in the Feedback area,
  106. * and will also unhighlight lines in the editor and log to the server.
  107. */
  108. BlockPyFeedback.prototype.finished = function() {
  109. this.title.html("Ran");
  110. this.original.hide();
  111. 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.");
  112. this.main.model.status.error("no errors");
  113. this.main.components.editor.unhighlightLines();
  114. this.main.components.server.logEvent('feedback', "Finished");
  115. }
  116. /**
  117. * This notifies the student that their code ran without errors, but that there was no
  118. * Success raised by the Checker. This will appear in the Feedback area,
  119. * and will also unhighlight lines in the editor and log to the server.
  120. */
  121. BlockPyFeedback.prototype.noErrors = function() {
  122. this.title.html("Ran");
  123. this.original.hide();
  124. this.body.html("No errors reported. View your output on the left.");
  125. this.main.model.status.error("no errors");
  126. this.main.components.editor.unhighlightLines();
  127. this.main.components.server.logEvent('feedback', "No Errors", '');
  128. }
  129. /**
  130. * Show an error message related to syntax issue. This will appear in
  131. * the Feedback area, the Printer, and also log to the server. The relevant line of
  132. * code or block will also be highlighted.
  133. *
  134. * @param {String} original - HTML content that represents the original error message generated by the system.
  135. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  136. * @param {number} line - What line the error occurred on.
  137. */
  138. BlockPyFeedback.prototype.syntaxError = function(original, message, line) {
  139. original = this.prettyPrintError(original);
  140. this.title.html("Syntax Error");
  141. this.original.show().html(original);
  142. this.body.html(message);
  143. this.main.model.status.error("syntax");
  144. this.main.components.editor.highlightError(line-1);
  145. this.main.components.printer.print("Execution stopped - there was an error!");
  146. this.main.components.server.logEvent('feedback', "Syntax Error", original+"\n|\n"+message);
  147. }
  148. /**
  149. * Show an error message related to semantic error with the program (e.g., unused variable).
  150. * This will appear in the Feedback area, the Printer, and also log to the server. The
  151. * relevant line of code or block will also be highlighted.
  152. *
  153. * @param {String} original - HTML content that represents the original error message generated by the system.
  154. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  155. * @param {number} line - What line the error occurred on.
  156. */
  157. BlockPyFeedback.prototype.semanticError = function(name, message, line) {
  158. this.title.html(name);
  159. this.original.hide();
  160. this.body.html(message);
  161. this.main.model.status.error("semantic");
  162. if (line !== null) {
  163. this.main.components.editor.highlightError(line-1);
  164. }
  165. this.main.components.printer.print("Execution stopped - there was an error!");
  166. this.main.components.server.logEvent('feedback', "Semantic Error", name+"\n|\n"+message);
  167. }
  168. /**
  169. * Show an error message related to a serious internal BlockPy program. Under normal conditions,
  170. * this should never appear to a student. This will appear in
  171. * the Feedback area, the Printer, and also log to the server. The relevant line of
  172. * code or block will also be highlighted.
  173. *
  174. * @param {String} original - HTML content that represents the original error message generated by the system.
  175. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  176. * @param {number} line - What line the error occurred on.
  177. */
  178. BlockPyFeedback.prototype.internalError = function(original, name, message) {
  179. original = this.prettyPrintError(original);
  180. this.title.html(name);
  181. this.original.show().html(original);
  182. this.body.html(message);
  183. this.main.model.status.error("internal");
  184. this.main.components.printer.print("Internal error! Please show this to an instructor!");
  185. this.main.components.server.logEvent('feedback', "Internal Error", name+"\n|\n"+original+"\n|\n"+message);
  186. console.error(original);
  187. }
  188. /**
  189. * Show an incorrect code message related to a problem as specified by the Checker. This will appear in
  190. * the Feedback area, the Printer, and also log to the server. The relevant line of
  191. * code or block will also be highlighted.
  192. *
  193. * @param {String} original - HTML content that represents the original error message generated by the system.
  194. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  195. * @param {number} line - What line the error occurred on.
  196. */
  197. BlockPyFeedback.prototype.instructorFeedback = function(name, message, line) {
  198. this.title.html(name);
  199. this.original.hide();
  200. this.body.html(message);
  201. this.main.model.status.error("feedback");
  202. if (line !== undefined && line != null) {
  203. this.main.components.editor.highlightError(line-1);
  204. }
  205. this.main.components.server.logEvent('feedback', "Instructor Feedback", name+"\n|\n"+"\n|\n"+message);
  206. }
  207. /**
  208. * Show "Empty Program" error, indicating the student hasn't written any code. This will appear in
  209. * the Feedback area, the Printer, and also log to the server. The relevant line of
  210. * code or block will also be highlighted.
  211. *
  212. * @param {String} original - HTML content that represents the original error message generated by the system.
  213. * @param {String} message - HTML content that is a hopefully friendlier message for the user explaining the error.
  214. * @param {number} line - What line the error occurred on.
  215. */
  216. BlockPyFeedback.prototype.emptyProgram = function() {
  217. this.title.html("Blank Program");
  218. this.original.hide().html("");
  219. this.body.html("You have not written any code yet.");
  220. this.main.model.status.error("runtime");
  221. this.main.components.server.logEvent('feedback', "Empty Program");
  222. }
  223. /**
  224. * Converts any kind of error (usually a Skulpt one) into a prettier version that's ready
  225. * for users to see. If it's already a string, it is passed along unchanged. But Skulpt
  226. * errors have to be processed more closely.
  227. */
  228. BlockPyFeedback.prototype.prettyPrintError = function(error) {
  229. if (typeof error === "string") {
  230. return error;
  231. } else {
  232. // A weird skulpt thing?
  233. if (error.tp$str !== undefined) {
  234. return error.tp$str().v;
  235. } else {
  236. return ""+error.name + ": " + error.message;
  237. }
  238. }
  239. }
  240. /**
  241. * Print an error to the printers -- the on screen one and the browser one. This
  242. * will attempt to provide extra explanation and context for an error.
  243. * Notice that this is largely for Run-time errors that will be thrown when the code
  244. * is executed, as opposed to ones raised elsewhere in the environment.
  245. *
  246. * @param {String} error - The error message to be analyzed and printed.
  247. */
  248. BlockPyFeedback.prototype.printError = function(error) {
  249. //console.log(error);
  250. original = this.prettyPrintError(error);
  251. this.title.html(error.tp$name);
  252. this.original.show().html(original);
  253. if (error.tp$name == "ParseError") {
  254. 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]);
  255. } else if (error.constructor == Sk.builtin.NameError
  256. && error.args.v.length > 0
  257. && error.args.v[0].v == "name '___' is not defined") {
  258. 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.");
  259. } else if (error.tp$name in EXTENDED_ERROR_EXPLANATION) {
  260. this.body.html(EXTENDED_ERROR_EXPLANATION[error.tp$name]);
  261. } else {
  262. this.body.html(error.enhanced);
  263. }
  264. console.error(error);
  265. if (error.stack) {
  266. console.error(error.stack);
  267. }
  268. this.main.model.status.error("runtime");
  269. if (error.traceback && error.traceback.length) {
  270. this.main.components.editor.highlightError(error.traceback[0].lineno-1);
  271. }
  272. this.main.components.server.logEvent('feedback', "Runtime", original);
  273. }
  274. /**
  275. * Static method to convert a priority level into a number.
  276. */
  277. BlockPyFeedback.priorityNumConvert = function(priority){
  278. switch(priority){
  279. case 'low': return 1;
  280. case 'high': return 3;
  281. default: case 'medium': return 2;
  282. }
  283. };
  284. /**
  285. * Static method to compare two priority labels.
  286. */
  287. BlockPyFeedback.priorityComparator = function(a, b) {
  288. var priorityA = BlockPyFeedback.priorityNumConvert(a.priority);
  289. var priorityB = BlockPyFeedback.priorityNumConvert(b.priority);
  290. if (priorityA > priorityB) {
  291. return -1;
  292. } else if (priorityB > priorityA) {
  293. return 1;
  294. } else {
  295. return 0;
  296. }
  297. };
  298. /**
  299. * Present any accumulated feedback
  300. */
  301. BlockPyFeedback.prototype.presentFeedback = function() {
  302. this.clear(false);
  303. var report = this.main.model.execution.reports;
  304. var suppress = this.main.model.execution.suppressions;
  305. // Organize complaints
  306. var complaint = report['instructor'].complaint;
  307. var gentleComplaints = [];
  308. var verifierComplaints = [];
  309. if (complaint) {
  310. moveElements(complaint, gentleComplaints, function(e) { return e.priority == 'student' });
  311. moveElements(complaint, verifierComplaints, function(e) { return e.priority == 'verifier' });
  312. }
  313. // Verifier
  314. if (!suppress['verifier'] && !report['verifier'].success) {
  315. this.emptyProgram();
  316. return 'verifier';
  317. }
  318. // Parser
  319. if (verifierComplaints.length) {
  320. this.instructorFeedback(verifierComplaints[0].name,
  321. verifierComplaints[0].message,
  322. verifierComplaints[0].line);
  323. return 'instructor';
  324. }
  325. if (!suppress['parser'] && !report['parser'].success) {
  326. var parserReport = report['parser'].error;
  327. this.convertSkulptSyntax(parserReport);
  328. return 'parser';
  329. }
  330. // Error in Instructor Feedback code
  331. if (!report['instructor'].success) {
  332. var error = report['instructor'].error;
  333. if (!error.traceback) {
  334. this.internalError(error, "Instructor Feedback Error", "Error in instructor feedback. Please show the above message to an instructor!");
  335. console.error(error);
  336. return 'instructor';
  337. } else if (error.traceback[0].filename == "__main__.py") {
  338. this.printError(report['instructor'].error);
  339. return 'student';
  340. } else {
  341. if (error.traceback[0].filename == report['instructor'].filename) {
  342. error.traceback[0].lineno -= report['instructor']['line_offset'];
  343. }
  344. //report['instructor']['line_offset']
  345. this.internalError(error, "Instructor Feedback Error", "Error in instructor feedback. Please show the above message to an instructor!");
  346. console.error(error);
  347. return 'instructor';
  348. }
  349. }
  350. if (report['instructor'].compliments && report['instructor'].compliments.length) {
  351. //this.compliment(report['instructor'].compliments);
  352. console.log(report['instructor'].compliments);
  353. }
  354. if (suppress['instructor'] !== true && complaint && complaint.length) {
  355. complaint.sort(BlockPyFeedback.sortPriorities);
  356. this.instructorFeedback(complaint[0].name, complaint[0].message, complaint[0].line);
  357. return 'instructor';
  358. }
  359. // Analyzer
  360. if (!report['instructor'].hide_correctness &&
  361. suppress['analyzer'] !== true) {//if a subtype is specified, or no suppression requested, present feedback
  362. if (!report['analyzer'].success) {
  363. this.internalError(report['analyzer'].error, "Analyzer Error", "Error in analyzer. Please show the above message to an instructor!");
  364. return 'analyzer';
  365. }
  366. var wasPresented = this.presentAnalyzerFeedback();
  367. if (wasPresented) {
  368. return 'analyzer';
  369. }
  370. }
  371. // Student runtime errors
  372. if (!suppress['student']) {
  373. if (!report['student'].success) {
  374. this.printError(report['student'].error);
  375. return 'student';
  376. }
  377. }
  378. // No instructor feedback if hiding correctness
  379. if (report['instructor'].hide_correctness == true) {
  380. this.noErrors()
  381. return 'no errors';
  382. }
  383. // Gentle instructor feedback
  384. if (suppress['instructor'] !== true && gentleComplaints.length) {
  385. this.instructorFeedback(gentleComplaints[0].name,
  386. gentleComplaints[0].message,
  387. gentleComplaints[0].line);
  388. return 'instructor';
  389. }
  390. //instructor completion flag
  391. if (suppress['instructor'] !== true && report['instructor'].complete) {
  392. this.complete();
  393. return 'success';
  394. }
  395. if (!suppress['no errors']) {
  396. this.noErrors()
  397. return 'no errors';
  398. }
  399. return 'completed';
  400. }
  401. BlockPyFeedback.prototype.convertSkulptSyntax = function(skulptError) {
  402. var convertedError = Sk.ffi.remapToJs(skulptError.args);
  403. console.log(convertedError);
  404. var codeLine = '.';
  405. if (convertedError.length > 3 && convertedError[4]) {
  406. codeLine = ', where it says:<br><code>'+convertedError[4]+'</code>';
  407. }
  408. 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]);
  409. }
  410. BlockPyFeedback.prototype.OPERATION_DESCRIPTION = {
  411. "Pow": "an exponent",
  412. "Add": "an addition",
  413. "Mult": "a multiplication",
  414. "Sub": "a subtraction",
  415. "Div": "a division",
  416. "Mod": "a modulo"
  417. };
  418. BlockPyFeedback.prototype.TYPE_DESCRIPTION = {
  419. "Num": "a number",
  420. "Str": "a string",
  421. "Tuple": "a tuple",
  422. "List": "a list",
  423. "Bool": "a boolean",
  424. "File": "a file",
  425. "None": "a None",
  426. "Set": "a set",
  427. "Function": "a function"
  428. };
  429. BlockPyFeedback.prototype.presentAnalyzerFeedback = function() {
  430. var report = this.main.model.execution.reports['analyzer'].issues;
  431. var suppress = this.main.model.execution.suppressions['analyzer'] || {};
  432. if (suppress === true) {
  433. // Suppress all types of analyzer errors
  434. return false;
  435. } else if (!suppress["Action after return"] && report["Action after return"].length >= 1) {
  436. var variable = report["Action after return"][0];
  437. 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)
  438. return true;
  439. } else if (!suppress['Return outside function'] && report['Return outside function'].length >= 1) {
  440. var first = report['Return outside function'][0];
  441. 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)
  442. return true;
  443. /*} else if (!suppress['Write out of scope'] && report['Write out of scope'].length >= 1) {
  444. var first = report['Write out of scope'][0];
  445. 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)
  446. return true;*/
  447. } else if (!suppress['Read out of scope'] && report['Read out of scope'].length >= 1) {
  448. var first = report['Read out of scope'][0];
  449. 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)
  450. return true;
  451. } else if (!suppress['Unconnected blocks'] && report["Unconnected blocks"].length >= 1) {
  452. var variable = report['Unconnected blocks'][0];
  453. 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)
  454. return true;
  455. } else if (!suppress['Iteration variable is iteration list'] && report['Iteration variable is iteration list'].length >= 1) {
  456. var variable = report['Iteration variable is iteration list'][0];
  457. 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)
  458. return true;
  459. } else if (!suppress["Undefined variables"] && report["Undefined variables"].length >= 1) {
  460. var variable = report["Undefined variables"][0];
  461. 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)
  462. return true;
  463. } else if (!suppress["Possibly undefined variables"] && report["Possibly undefined variables"].length >= 1) {
  464. var variable = report["Possibly undefined variables"][0];
  465. 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);
  466. return true;
  467. } else if (!suppress["Unread variables"] && report["Unread variables"].length >= 1) {
  468. var variable = report["Unread variables"][0];
  469. var kindName = 'variable', kindBody = 'value';
  470. if (variable.type && variable.type.name == 'Function') {
  471. kindName = 'function';
  472. kindBody = 'definition';
  473. }
  474. this.semanticError("Unused Variable", "The "+kindName+" <code>"+variable.name+"</code> was given a "+kindBody+", but was never used after that.", null)
  475. return true;
  476. } else if (!suppress["Overwritten variables"] && report["Overwritten variables"].length >= 1) {
  477. var variable = report["Overwritten variables"][0];
  478. 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)
  479. return true;
  480. } else if (!suppress["Empty iterations"] && report["Empty iterations"].length >= 1) {
  481. var variable = report["Empty iterations"][0];
  482. if (variable.name) {
  483. 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)
  484. return true;
  485. }
  486. } else if (!suppress["Non-list iterations"] && report["Non-list iterations"].length >= 1) {
  487. var variable = report["Non-list iterations"][0];
  488. if (variable.name) {
  489. 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)
  490. return true;
  491. }
  492. } else if (!suppress["Incompatible types"] && report["Incompatible types"].length >= 1) {
  493. var variable = report["Incompatible types"][0];
  494. var op = this.OPERATION_DESCRIPTION[variable.operation];
  495. var left = this.TYPE_DESCRIPTION[variable.left.name];
  496. var right = this.TYPE_DESCRIPTION[variable.right.name];
  497. 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)
  498. return true;
  499. }
  500. return false;
  501. }
  502. if (typeof exports !== 'undefined') {
  503. exports.BlockPyFeedback = BlockPyFeedback;
  504. }