old_engine_code.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /**
  2. Parse student code
  3. Analyze student code
  4. Run student code
  5. Run instructor code
  6. Output feedback
  7. # Within the instructor code you can surpress certain kinds of feedback
  8. surpress_syntax()
  9. surpress_empty()
  10. surpress_algorithm()
  11. surpress_runtime()
  12. surpress_runtime('NameError')
  13. # Or explicitly run certain kinds of analyses
  14. run_student()
  15. run_abstract()
  16. */
  17. /**
  18. * Given source code as a string, return a list of all of the AST elements
  19. * that are Num (aka numeric literals) but that are not inside List elements.
  20. *
  21. * @param {String} source - Python source code.
  22. * @returns {Array.number} The list of JavaScript numeric literals that were found.
  23. */
  24. function getNonListNums(source) {
  25. if (!(source in parses)) {
  26. var parse = Sk.parse("__main__", source);
  27. parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags);
  28. }
  29. var ast = parses[source];
  30. var visitor = new NodeVisitor();
  31. var insideList = false;
  32. var nums = [];
  33. visitor.visit_List = function(node) {
  34. insideList = true;
  35. this.generic_visit(node);
  36. insideList = false;
  37. }
  38. visitor.visit_Num = function(node) {
  39. if (!insideList) {
  40. nums.push(node.n);
  41. }
  42. this.generic_visit(node);
  43. }
  44. visitor.visit(ast);
  45. return nums;
  46. }
  47. /**
  48. * Given source code as a string, return a list of all of the AST elements
  49. * that are being printed (using the print function) but are not variables.
  50. *
  51. * @param {String} source - Python source code.
  52. * @returns {Array.<Object>} The list of AST elements that were found.
  53. */
  54. function getPrintedNonProperties(source) {
  55. if (!(source in parses)) {
  56. var parse = Sk.parse("__main__", source);
  57. parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags);
  58. }
  59. var ast = parses[source];
  60. var visitor = new NodeVisitor();
  61. var nonVariables = [];
  62. visitor.visit_Call = function(node) {
  63. var func = node.func;
  64. var args = node.args;
  65. if (func._astname == 'Name' && func.id.v == 'print') {
  66. for (var i =0; i < args.length; i+= 1) {
  67. if (args[i]._astname != "Name") {
  68. nonVariables.push(args[i]);
  69. }
  70. }
  71. }
  72. this.generic_visit(node);
  73. }
  74. visitor.visit(ast);
  75. return nonVariables;
  76. }
  77. /**
  78. * Skulpt function to iterate through the final state of
  79. * all the variables in the program, and check to see if they have
  80. * a given value.
  81. */
  82. mod.get_value_by_name = new Sk.builtin.func(function(name) {
  83. Sk.builtin.pyCheckArgs("get_value_by_name", arguments, 1, 1);
  84. Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name));
  85. name = name.v;
  86. var final_values = Sk.builtins._final_values;
  87. if (name in final_values) {
  88. return final_values[name];
  89. } else {
  90. return Sk.builtin.none.none$;
  91. }
  92. });
  93. mod.get_value_by_type = new Sk.builtin.func(function(type) {
  94. Sk.builtin.pyCheckArgs("get_value_by_type", arguments, 1, 1);
  95. var final_values = Sk.builtins._final_values;
  96. var result = [];
  97. for (var property in final_values) {
  98. if (final_values[property].tp$name == type.tp$name) {
  99. result.push(final_values[property]);
  100. }
  101. }
  102. return Sk.builtin.list(result);
  103. });
  104. mod.parse_json = new Sk.builtin.func(function(blob) {
  105. Sk.builtin.pyCheckArgs("parse_json", arguments, 1, 1);
  106. Sk.builtin.pyCheckType("blob", "string", Sk.builtin.checkString(blob));
  107. blob = blob.v;
  108. return Sk.ffi.remapToPy(JSON.parse(blob));
  109. });
  110. mod.get_property = new Sk.builtin.func(function(name) {
  111. Sk.builtin.pyCheckArgs("get_property", arguments, 1, 1);
  112. Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name));
  113. name = name.v;
  114. var trace = Sk.builtins._trace;
  115. if (trace.length <= 0) {
  116. return Sk.builtin.none.none$;
  117. }
  118. var properties = trace[trace.length-1]["properties"];
  119. for (var i = 0, len = properties.length; i < len; i += 1) {
  120. if (properties[i]['name'] == name) {
  121. return Sk.ffi.remapToPy(properties[i])
  122. }
  123. }
  124. return Sk.builtin.none.none$;
  125. });
  126. mod.calls_function = new Sk.builtin.func(function(source, name) {
  127. Sk.builtin.pyCheckArgs("calls_function", arguments, 2, 2);
  128. Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source));
  129. Sk.builtin.pyCheckType("name", "string", Sk.builtin.checkString(name));
  130. source = source.v;
  131. name = name.v;
  132. var ast_list = getParseList(source);
  133. var count = 0;
  134. for (var i = 0, len = ast_list.length; i < len; i = i+1) {
  135. if (ast_list[i]._astname == 'Call') {
  136. if (ast_list[i].func._astname == 'Attribute') {
  137. count += Sk.ffi.remapToJs(ast_list[i].func.attr) == name | 0;
  138. } else if (ast_list[i].func._astname == 'Name') {
  139. count += Sk.ffi.remapToJs(ast_list[i].func.id) == name | 0;
  140. }
  141. }
  142. }
  143. return Sk.ffi.remapToPy(count > 0);
  144. });
  145. mod.count_components = new Sk.builtin.func(function(source, component) {
  146. Sk.builtin.pyCheckArgs("count_components", arguments, 2, 2);
  147. Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source));
  148. Sk.builtin.pyCheckType("component", "string", Sk.builtin.checkString(component));
  149. source = source.v;
  150. component = component.v;
  151. var ast_list = getParseList(source);
  152. var count = 0;
  153. for (var i = 0, len = ast_list.length; i < len; i = i+1) {
  154. if (ast_list[i]._astname == component) {
  155. count = count+1;
  156. }
  157. }
  158. return Sk.ffi.remapToPy(count);
  159. });
  160. mod.no_nonlist_nums = new Sk.builtin.func(function(source) {
  161. Sk.builtin.pyCheckArgs("no_nonlist_nums", arguments, 1, 1);
  162. Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source));
  163. source = source.v;
  164. var num_list = getNonListNums(source);
  165. var count = 0;
  166. for (var i = 0, len = num_list.length; i < len; i = i+1) {
  167. if (num_list[i].v != 0 && num_list[i].v != 1) {
  168. return Sk.ffi.remapToPy(true);
  169. }
  170. }
  171. return Sk.ffi.remapToPy(false);
  172. });
  173. mod.only_printing_properties = new Sk.builtin.func(function(source) {
  174. Sk.builtin.pyCheckArgs("only_printing_properties", arguments, 1, 1);
  175. Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source));
  176. source = source.v;
  177. var non_var_list = getPrintedNonProperties(source);
  178. return Sk.ffi.remapToPy(non_var_list.length == 0);
  179. });
  180. /**
  181. * Given a feedback string, records the corrective feedback string for later printing
  182. * @param {string} feedback - the piece of feedback to save
  183. **/
  184. mod.add_interrupt_feedback = new Sk.builtin.func(function(feedback) {
  185. Sk.builtin.pyCheckArgs("add_interupt_feedback", arguments, 1, 1);
  186. Sk.builtin.pyCheckType("feedback", "string", Sk.builtin.checkString(feedback));
  187. accInterruptFeedback.push(feedback);
  188. });
  189. /**
  190. * Given a feedback string, records the complementary feedback string for later printing
  191. * @param {string} feedback - the piece of feedback to save
  192. **/
  193. mod.add_comp_feedback = new Sk.builtin.func(function(feedback) {
  194. Sk.builtin.pyCheckArgs("add_comp_feedback", arguments, 1, 1);
  195. Sk.builtin.pyCheckType("feedback", "string", Sk.builtin.checkString(feedback));
  196. accCompFeedback.push(feedback);
  197. });
  198. /**
  199. * This resolves all of the feedback and posts it to the appropriate places
  200. * @TODO: actually implement this functionality
  201. **/
  202. mod.post_feedback = new Sk.builtin.func(function() {
  203. var allFeedback = accInterruptFeedback.concat(accCompFeedback);
  204. completePythonAll = Sk.builtin.list(allFeedback);
  205. jsPureFeedback = Sk.ffi.remapToJs(completePythonAll);
  206. console.log("" + jsPureFeedback);
  207. });
  208. Sk.builtins.value = new Sk.builtin.func(function() {
  209. return Sk.ffi.remapToPy(GLOBAL_VALUE === undefined ? 5 : GLOBAL_VALUE);
  210. });
  211. Sk.builtins.set_value = new Sk.builtin.func(function(v) {
  212. GLOBAL_VALUE = v.v;
  213. });
  214. /**
  215. * Runs the given python code, resetting the console and Trace Table.
  216. */
  217. BlockPyEngine.prototype.run = function() {
  218. // Reset everything
  219. this.resetExecution();
  220. if (!this.main.model.settings.disable_semantic_errors() &&
  221. !this.main.model.assignment.disable_algorithm_errors()) {
  222. var success = this.analyze();
  223. if (success === false) {
  224. this.executionEnd_();
  225. this.main.components.server.markSuccess(0.0);
  226. return;
  227. }
  228. }
  229. this.main.model.execution.status("running");
  230. var feedback = this.main.components.feedback;
  231. // Get the code
  232. var code = this.main.model.programs['__main__']();
  233. if (code.trim() == "") {
  234. feedback.emptyProgram();
  235. this.main.model.execution.status("error");
  236. this.executionEnd_();
  237. this.main.components.server.markSuccess(0.0);
  238. return;
  239. }
  240. // Actually run the python code
  241. var executionPromise = Sk.misceval.asyncToPromise(function() {
  242. return Sk.importMainWithBody("<stdin>", false, code, true);
  243. });
  244. var engine = this;
  245. var server = this.server;
  246. var execution = this.main.model.execution;
  247. executionPromise.then(
  248. function (module) {
  249. // Run the afterSingleExecution one extra time for final state
  250. Sk.afterSingleExecution(module.$d, -1, 0, "<stdin>.py");
  251. engine.lastStep();
  252. // Handle checks
  253. feedback.noErrors()
  254. engine.check(code, execution.trace(), execution.output(), execution.ast, module.$d);
  255. // Reenable "Run"
  256. engine.main.model.execution.status("waiting");
  257. engine.executionEnd_();
  258. },
  259. function(error) {
  260. feedback.printError(error);
  261. engine.main.model.execution.status("error");
  262. engine.executionEnd_();
  263. server.markSuccess(0.0);
  264. //server.logEvent('blockly_error', error);
  265. }
  266. );
  267. }
  268. BlockPyEngine.prototype.setupEnvironment = function(student_code, traceTable, output, ast, final_values) {
  269. var model = this.main.model;
  270. this._backup_execution = Sk.afterSingleExecution;
  271. Sk.afterSingleExecution = undefined;
  272. Sk.builtins.get_output = new Sk.builtin.func(function() {
  273. Sk.builtin.pyCheckArgs("get_output", arguments, 0, 0);
  274. return Sk.ffi.remapToPy(model.execution.output());
  275. });
  276. Sk.builtins.reset_output = new Sk.builtin.func(function() {
  277. Sk.builtin.pyCheckArgs("reset_output", arguments, 0, 0);
  278. model.execution.output.removeAll();
  279. });
  280. Sk.builtins.log = new Sk.builtin.func(function(data) {
  281. Sk.builtin.pyCheckArgs("log", arguments, 1, 1);
  282. console.log(data)
  283. });
  284. //Sk.builtins.trace = Sk.ffi.remapToPy(traceTable);
  285. Sk.builtins._trace = traceTable;
  286. Sk.builtins._final_values = final_values;
  287. Sk.builtins.code = Sk.ffi.remapToPy(student_code);
  288. Sk.builtins.set_success = this.instructor_module.set_success;
  289. Sk.builtins.set_feedback = this.instructor_module.set_feedback;
  290. Sk.builtins.set_finished = this.instructor_module.set_finished;
  291. Sk.builtins.count_components = this.instructor_module.count_components;
  292. Sk.builtins.no_nonlist_nums = this.instructor_module.no_nonlist_nums;
  293. Sk.builtins.only_printing_properties = this.instructor_module.only_printing_properties;
  294. Sk.builtins.calls_function = this.instructor_module.calls_function;
  295. Sk.builtins.get_property = this.instructor_module.get_property;
  296. Sk.builtins.get_value_by_name = this.instructor_module.get_value_by_name;
  297. Sk.builtins.get_value_by_type = this.instructor_module.get_value_by_type;
  298. Sk.builtins.parse_json = this.instructor_module.parse_json;
  299. Sk.builtins.Stack = this.instructor_module.Stack;
  300. Sk.skip_drawing = true;
  301. model.settings.mute_printer(true);
  302. }
  303. BlockPyEngine.prototype.disposeEnvironment = function() {
  304. Sk.afterSingleExecution = this._backup_execution;
  305. Sk.builtins.get_output = undefined;
  306. Sk.builtins.reset_output = undefined;
  307. Sk.builtins.log = undefined;
  308. Sk.builtins._trace = undefined;
  309. Sk.builtins.trace = undefined;
  310. Sk.builtins.code = undefined;
  311. Sk.builtins.set_success = undefined;
  312. Sk.builtins.set_feedback = undefined;
  313. Sk.builtins.set_finished = undefined;
  314. Sk.builtins.count_components = undefined;
  315. Sk.builtins.calls_function = undefined;
  316. Sk.builtins.get_property = undefined;
  317. Sk.builtins.get_value_by_name = undefined;
  318. Sk.builtins.get_value_by_type = undefined;
  319. Sk.builtins.no_nonlist_nums = undefined;
  320. Sk.builtins.only_printing_properties = undefined;
  321. Sk.builtins.parse_json = undefined;
  322. Sk.skip_drawing = false;
  323. GLOBAL_VALUE = undefined;
  324. this.main.model.settings.mute_printer(false);
  325. }
  326. BlockPyEngine.prototype.check = function(student_code, traceTable, output, ast, final_values) {
  327. var engine = this;
  328. var server = this.main.components.server;
  329. var model = this.main.model;
  330. var on_run = model.programs['give_feedback']();
  331. if (on_run !== undefined && on_run.trim() !== "") {
  332. on_run = 'def run_code():\n'+indent(student_code)+'\n'+on_run;
  333. this.setupEnvironment(student_code, traceTable, output, ast, final_values);
  334. var executionPromise = Sk.misceval.asyncToPromise(function() {
  335. return Sk.importMainWithBody("<stdin>", false, on_run, true);
  336. });
  337. executionPromise.then(
  338. function (module) {
  339. engine.main.components.feedback.noErrors();
  340. engine.disposeEnvironment();
  341. }, function (error) {
  342. engine.disposeEnvironment();
  343. console.log(error.tp$name, error.tp$name == "Success");
  344. if (error.tp$name == "Success") {
  345. server.markSuccess(1.0, model.settings.completedCallback);
  346. engine.main.components.feedback.complete();
  347. } else if (error.tp$name == "Feedback") {
  348. server.markSuccess(0.0);
  349. engine.main.components.feedback.instructorFeedback("Incorrect Answer", error.args.v[0].v);
  350. } else if (error.tp$name == "Finished") {
  351. server.markSuccess(1.0, model.settings.completedCallback);
  352. engine.main.components.feedback.finished();
  353. } else {
  354. console.error(error);
  355. engine.main.components.feedback.internalError(error, "Feedback Error", "Error in instructor's feedback. Please show the above message to an instructor!");
  356. server.logEvent('blockly_instructor_error', ''+error);
  357. server.markSuccess(0.0);
  358. }
  359. });
  360. }
  361. }
  362. /**
  363. * Runs the AbstractInterpreter to get some static information about the code,
  364. * in particular the variables' types. This is needed for type checking.
  365. *
  366. * @returns {Object<String, AIType>} Maps variable names (as Strings) to types as constructed by the AbstractIntepreter.
  367. */
  368. BlockPyEngine.prototype.analyzeVariables = function() {
  369. // Get the code
  370. var code = this.main.model.programs['__main__']();
  371. if (code.trim() == "") {
  372. return {};
  373. }
  374. var analyzer = new AbstractInterpreter(code);
  375. report = analyzer.report;
  376. return analyzer.variableTypes;
  377. }
  378. /**
  379. * Runs the AbstractInterpreter to get some static information about the code,
  380. * including potential semantic errors. It then parses that information to give
  381. * feedback.
  382. *
  383. * @returns {Boolean} Whether the code was successfully analyzed.
  384. */
  385. BlockPyEngine.prototype.analyze = function() {
  386. this.main.model.execution.status("analyzing");
  387. var feedback = this.main.components.feedback;
  388. // Get the code
  389. var code = this.main.model.programs['__main__']();
  390. if (code.trim() == "") {
  391. this.main.components.feedback.emptyProgram("You haven't written any code yet!");
  392. //this.main.model.feedback.status("semantic");
  393. return false;
  394. }
  395. var analyzer = new AbstractInterpreter(code);
  396. this.main.model.execution.ast = analyzer.ast;
  397. report = analyzer.report;
  398. // Syntax error
  399. if (report.error !== false) {
  400. console.log(report.error.args.v)
  401. var codeLine = '.';
  402. if (report.error.args.v.length > 3) {
  403. codeLine = ', where it says:<br><code>'+report.error.args.v[3][2]+'</code>';
  404. }
  405. 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]);
  406. return false;
  407. }
  408. return true;
  409. }
  410. var GLOBAL_VALUE;