sk_mod_instructor.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. /**
  2. * Skulpt Module for holding the Instructor API.
  3. *
  4. * This module is loaded in by getting the functions' source code from toString.
  5. * Isn't that crazy?
  6. *
  7. * @param {String} name - The name of the module (should always be 'instructor')
  8. *
  9. */
  10. var $sk_mod_instructor = function(name) {
  11. // Main module object that gets returned at the end.
  12. var mod = {};
  13. /**
  14. * Skulpt Exception that forces the program to exit, but gracefully.
  15. *
  16. * @param {Array} args - A list of optional arguments to pass to the Exception.
  17. * Usually this will include a message for the user.
  18. */
  19. Sk.builtin.GracefulExit = function (args) {
  20. var o;
  21. if (!(this instanceof Sk.builtin.GracefulExit)) {
  22. o = Object.create(Sk.builtin.GracefulExit.prototype);
  23. o.constructor.apply(o, arguments);
  24. return o;
  25. }
  26. Sk.builtin.Exception.apply(this, arguments);
  27. };
  28. Sk.abstr.setUpInheritance("GracefulExit", Sk.builtin.GracefulExit, Sk.builtin.Exception);
  29. /**
  30. * Give complimentary feedback to the user
  31. */
  32. mod.compliment = new Sk.builtin.func(function(message) {
  33. Sk.builtin.pyCheckArgs("compliment", arguments, 1, 1);
  34. Sk.builtin.pyCheckType("message", "string", Sk.builtin.checkString(message));
  35. Sk.executionReports.instructor.compliments.push(Sk.ffi.remapToJs(message));
  36. });
  37. /**
  38. * Mark problem as completed
  39. */
  40. mod.set_success = new Sk.builtin.func(function() {
  41. Sk.builtin.pyCheckArgs("set_success", arguments, 0, 0);
  42. Sk.executionReports.instructor.complete = true;
  43. throw new Sk.builtin.GracefulExit();
  44. });
  45. /**
  46. * Mark problem as partially completed
  47. */
  48. mod.give_partial = new Sk.builtin.func(function(value, message) {
  49. Sk.builtin.pyCheckArgs("give_partial", arguments, 1, 2);
  50. Sk.builtin.pyCheckType("value", "float", Sk.builtin.checkFloat(value));
  51. value = Sk.ffi.remapToJs(value);
  52. if (message != undefined) {
  53. Sk.builtin.pyCheckType("message", "string", Sk.builtin.checkString(message));
  54. message = Sk.ffi.remapToJs(message);
  55. } else {
  56. message = '';
  57. }
  58. if (!Sk.executionReports.instructor.partials){
  59. Sk.executionReports.instructor.partials = [];
  60. }
  61. Sk.executionReports.instructor.partials.push({'value': value, 'message': message});
  62. });
  63. mod.hide_correctness = new Sk.builtin.func(function() {
  64. Sk.builtin.pyCheckArgs("hide_correctness", arguments, 0, 0);
  65. Sk.executionReports.instructor.hide_correctness = true;
  66. });
  67. /**
  68. * Let user know about an issue
  69. */
  70. mod.explain = new Sk.builtin.func(function(message, priority, line) {
  71. Sk.builtin.pyCheckArgs("explain", arguments, 1, 3);
  72. Sk.builtin.pyCheckType("message", "string", Sk.builtin.checkString(message));
  73. if (priority != undefined){
  74. Sk.builtin.pyCheckType("priority", "string", Sk.builtin.checkString(priority));
  75. priority = Sk.ffi.remapToJs(priority);
  76. } else {
  77. priority = 'medium';
  78. }
  79. if (line !== undefined) {
  80. Sk.builtin.pyCheckType("line", "integer", Sk.builtin.checkInt(line));
  81. line = Sk.ffi.remapToJs(line);
  82. } else {
  83. line = null;
  84. }
  85. if (!Sk.executionReports.instructor.complaint){
  86. Sk.executionReports.instructor.complaint = [];
  87. }
  88. var newComplaint = {
  89. 'name': 'Instructor Feedback',
  90. 'message': Sk.ffi.remapToJs(message),
  91. 'priority': priority,
  92. 'line': line
  93. }
  94. Sk.executionReports.instructor.complaint.push(newComplaint);
  95. });
  96. mod.gently = new Sk.builtin.func(function(message, line) {
  97. return Sk.misceval.callsimOrSuspend(mod.explain, message, Sk.ffi.remapToPy('student'), line);
  98. });
  99. /**
  100. * Prevent a certain kind of error from percolating where type is the phase that's being suppressed and
  101. * subtype is a specific error in the report of that phase.
  102. */
  103. mod.suppress = new Sk.builtin.func(function(type, subtype) {
  104. Sk.builtin.pyCheckArgs("suppress", arguments, 1, 2);
  105. Sk.builtin.pyCheckType("type", "string", Sk.builtin.checkString(type));
  106. type = Sk.ffi.remapToJs(type);
  107. if (subtype !== undefined) {
  108. Sk.builtin.pyCheckType("subtype", "string", Sk.builtin.checkString(subtype));
  109. subtype = Sk.ffi.remapToJs(subtype);
  110. if (Sk.feedbackSuppressions[type] === false) {
  111. Sk.feedbackSuppressions[type] = {};
  112. Sk.feedbackSuppressions[type][subtype] = true;
  113. } else if (Sk.feedbackSuppressions[type] !== false) {
  114. Sk.feedbackSuppressions[type][subtype] = true;
  115. }
  116. } else {
  117. Sk.feedbackSuppressions[type] = true;
  118. }
  119. });
  120. /**
  121. * Logs feedback to javascript console
  122. */
  123. mod.log = new Sk.builtin.func(function(message) {
  124. Sk.builtin.pyCheckArgs("log", arguments, 1, 1);
  125. console.log(Sk.ffi.remapToJs(message));
  126. });
  127. /**
  128. * Logs debug to javascript console
  129. */
  130. mod.debug = new Sk.builtin.func(function(message) {
  131. Sk.builtin.pyCheckArgs("log", arguments, 1, 1);
  132. console.log(message);
  133. });
  134. // get_ast()
  135. // get_trace()
  136. // get_types()
  137. // get_types_raw()
  138. var create_logger = function(phase, report_item) {
  139. return new Sk.builtin.func(function() {
  140. Sk.builtin.pyCheckArgs('log_'+report_item, arguments, 0, 0);
  141. var report = Sk.executionReports[phase];
  142. if (report.success) {
  143. console.log(report[report_item]);
  144. } else {
  145. console.log('Execution phase "'+phase+'" failed, '+report_item+' could not be found.');
  146. }
  147. });
  148. };
  149. mod.log_ast = create_logger('parser', 'ast');
  150. mod.log_variables = create_logger('analyzer', 'variables');
  151. mod.log_behavior = create_logger('analyzer', 'behavior');
  152. mod.log_issues = create_logger('analyzer', 'issues');
  153. mod.log_trace = create_logger('student', 'trace');
  154. // Provides `student` as an object with all the data that the student declared.
  155. mod.StudentData = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  156. $loc.__init__ = new Sk.builtin.func(function(self) {
  157. //self.data = Sk.builtin.dict();
  158. var newDict = Sk.builtin.dict();
  159. Sk.abstr.sattr(self, 'data', newDict, true);
  160. self.module = Sk.executionReports['student'].module;
  161. if (self.module !== undefined) {
  162. self.module = self.module.$d;
  163. for (var key in self.module) {
  164. if (self.module.hasOwnProperty(key)) {
  165. newDict.mp$ass_subscript(Sk.ffi.remapToPy(key),
  166. self.module[key]);
  167. }
  168. }
  169. } else {
  170. self.module = {};
  171. }
  172. });
  173. $loc.get_names_by_type = new Sk.builtin.func(function(self, type, exclude_builtins) {
  174. Sk.builtin.pyCheckArgs("get_names_by_type", arguments, 2, 3);
  175. if (exclude_builtins === undefined) {
  176. exclude_builtins = true;
  177. } else {
  178. Sk.builtin.pyCheckType("exclude_builtins", "boolean", Sk.builtin.checkBool(exclude_builtins));
  179. exclude_builtins = Sk.ffi.remapToJs(exclude_builtins);
  180. }
  181. var result = [];
  182. for (var property in self.module) {
  183. if (self.module[property].tp$name == type.tp$name) {
  184. //console.log(exclude_builtins);
  185. if (exclude_builtins && property.startsWith("__")) {
  186. continue;
  187. }
  188. result.push(Sk.ffi.remapToPy(property));
  189. }
  190. }
  191. return Sk.builtin.list(result);
  192. });
  193. $loc.get_values_by_type = new Sk.builtin.func(function(self, type, exclude_builtins) {
  194. Sk.builtin.pyCheckArgs("get_values_by_type", arguments, 2, 3);
  195. if (exclude_builtins === undefined) {
  196. exclude_builtins = true;
  197. } else {
  198. Sk.builtin.pyCheckType("exclude_builtins", "boolean", Sk.builtin.checkBool(exclude_builtins));
  199. exclude_builtins = Sk.ffi.remapToJs(exclude_builtins);
  200. }
  201. var result = [];
  202. for (var property in self.module) {
  203. if (self.module[property].tp$name == type.tp$name) {
  204. if (exclude_builtins && property.startsWith("__")) {
  205. continue;
  206. }
  207. result.push(self.module[property]);
  208. }
  209. }
  210. return Sk.builtin.list(result);
  211. });
  212. });
  213. mod.student = Sk.misceval.callsimOrSuspend(mod.StudentData);
  214. //Enhanced feedback functions and objects starts here
  215. //variable used for easy reidentification of nodes so we don't have to recreate every node type
  216. var flatTree = [];
  217. //variable used for accumulating interrupting feedback AS A LIST OF PYTHON OBJECTS
  218. var accInterruptFeedback = [];
  219. //variable used for accumulating complementary feedback AS A LIST OF PYTHON OBJECTS
  220. var accCompFeedback = [];
  221. /**
  222. * Generates a flat ast tree and store it in the local variable.
  223. * This function is meant to be used to avoid extra coding by recreating every AST node type
  224. *
  225. **/
  226. function generateFlatTree(){
  227. var parser = Sk.executionReports['parser'];
  228. //Tree's already been built, don't do anything else
  229. if(flatTree.length > 0){
  230. return;
  231. }
  232. var ast;
  233. if (parser.success) {
  234. ast = parser.ast;
  235. } else {
  236. var filename = "__main__"
  237. var parse = Sk.parse(filename,"");
  238. ast = Sk.astFromParse(parse.cst, filename, parse.flags);
  239. }
  240. var visitor = new NodeVisitor();
  241. visitor.visit = function(node){
  242. flatTree.push(node);
  243. /** Visit a node. **/
  244. var method_name = 'visit_' + node._astname;
  245. //console.log(flatTree.length - 1 + ": " + node._astname)
  246. if (method_name in this) {
  247. return this[method_name](node);
  248. } else {
  249. return this.generic_visit(node);
  250. }
  251. }
  252. visitor.visit(ast);
  253. //console.log(flatTree);
  254. }
  255. function parseProgram(){
  256. if (Sk.executionReports['verifier'].success) {
  257. generateFlatTree(Sk.executionReports['verifier'].code);
  258. return true;
  259. } else {
  260. return null;
  261. }
  262. }
  263. /**
  264. * This function coverts the output in the student report to a python
  265. * list and returns it.
  266. **/
  267. mod.get_output = new Sk.builtin.func(function() {
  268. Sk.builtin.pyCheckArgs("get_output", arguments, 0, 0);
  269. if (Sk.executionReports['student'].success) {
  270. return mixedRemapToPy(Sk.executionReports['student']['output']());
  271. } else {
  272. return Sk.ffi.remapToPy([]);
  273. }
  274. });
  275. /**
  276. * This function resets the output, particularly useful if the student
  277. * code is going to be rerun.
  278. */
  279. mod.reset_output = new Sk.builtin.func(function() {
  280. Sk.builtin.pyCheckArgs("reset_output", arguments, 0, 0);
  281. if (Sk.executionReports['student'].success) {
  282. Sk.executionReports['student']['output'].removeAll();
  283. }
  284. });
  285. mod.queue_input = new Sk.builtin.func(function() {
  286. Sk.builtin.pyCheckArgs("queue_input", arguments, 1, Infinity);
  287. var args = arguments;
  288. for (var i = args.length-1; i >= 0; i--) {
  289. var input = args[i];
  290. Sk.builtin.pyCheckType("input", "string", Sk.builtin.checkString(input));
  291. Sk.queuedInput.push(Sk.ffi.remapToJs(input));
  292. }
  293. });
  294. /**
  295. * This function is called by instructors to get the students' code as a string.
  296. **/
  297. mod.get_program = new Sk.builtin.func(function() {
  298. Sk.builtin.pyCheckArgs("get_program", arguments, 0, 0);
  299. return Sk.ffi.remapToPy(Sk.executionReports['verifier'].code);
  300. });
  301. /**
  302. * This function is called by instructors to construct the python version of the AST
  303. **/
  304. mod.parse_program = new Sk.builtin.func(function() {
  305. var result = parseProgram();
  306. if(result == null){
  307. return Sk.builtin.none.none$;
  308. }else{
  309. return Sk.misceval.callsimOrSuspend(mod.AstNode, 0);
  310. }
  311. });
  312. mod.had_execution_time_error = new Sk.builtin.func(function() {
  313. Sk.builtin.pyCheckArgs("had_execution_time_error", arguments, 0, 0);
  314. return !Sk.executionReports['student'].success &&
  315. Sk.executionReports['student'].error &&
  316. Sk.executionReports['student'].error.tp$name == 'TimeLimitError';
  317. });
  318. var backupTime = undefined;
  319. mod.limit_execution_time = new Sk.builtin.func(function() {
  320. Sk.builtin.pyCheckArgs("limit_execution_time", arguments, 0, 0);
  321. backupTime = Sk.execLimit;
  322. if (Sk.execLimitFunction) {
  323. Sk.execLimit = Sk.execLimitFunction();
  324. Sk.execStart = Date.now();
  325. }
  326. });
  327. mod.unlimit_execution_time = new Sk.builtin.func(function() {
  328. Sk.builtin.pyCheckArgs("unlimit_execution_time", arguments, 0, 0);
  329. Sk.execLimit = backupTime;
  330. Sk.execStart = Date.now()
  331. });
  332. /**
  333. * This function is called by instructors to construct the python version of the AST
  334. **/
  335. mod.analyze_program = new Sk.builtin.func(function() {
  336. Sk.analyzeParse();
  337. });
  338. mod.def_use_error = new Sk.builtin.func(function(py_node) {
  339. var id = py_node.id;
  340. //console.log(id);
  341. var node = flatTree[id];
  342. //"Undefined variables": []
  343. //"Possibly undefined variables": []
  344. if((node instanceof Object) && ("_astname" in node) && node._astname == "Name"){
  345. var undefVars = Sk.executionReports['analyzer'].issues["Undefined variables"];
  346. var hasError = false;
  347. var name = Sk.ffi.remapToJs(node.id);
  348. for(var i = 0; i < undefVars.length; i += 1){
  349. if(undefVars[i].name == name){
  350. hasError = true;
  351. break;
  352. }
  353. }
  354. return Sk.ffi.remapToPy(hasError);
  355. }else{
  356. return Ski.ffi.remapToPy(false);
  357. }
  358. });
  359. /**
  360. * This function takes an AST node and if it's a name node, finds the type of the object
  361. * @param {Skulpt AST node} node - the node to check
  362. **/
  363. function checkNameNodeType(node){
  364. if((node instanceof Object) && ("_astname" in node) && node._astname == "Name"){
  365. var analyzer = Sk.executionReports['analyzer'];
  366. var typesList = analyzer.variables;
  367. var name = Sk.ffi.remapToJs(node.id);
  368. if (typesList[name] === undefined) {
  369. return Sk.ffi.remapToPy(null);
  370. } else {
  371. return Sk.ffi.remapToPy(typesList[name]["type"]["name"]);
  372. }
  373. }else{
  374. return Sk.ffi.remapToPy(null);
  375. }
  376. }
  377. /**
  378. * When passed a python AST node, returns the next node that isn't in this node's
  379. * subtree. If such a node does not exist, returns Sk.ffi.remapToPy(null)
  380. **/
  381. function getNextTree(self){
  382. var visitor = new NodeVisitor();
  383. var currentId = self.id;//-1 to offset first iteration
  384. visitor.visit = function(node) {
  385. currentId += 1;
  386. /** Visit a node. **/
  387. var method_name = 'visit_' + node._astname;
  388. return this.generic_visit(node);
  389. }
  390. visitor.visit(flatTree[currentId]);
  391. if(currentId >= flatTree.length){
  392. return Sk.ffi.remapToPy(null);
  393. }
  394. return Sk.misceval.callsimOrSuspend(mod.AstNode, currentId);
  395. }
  396. /**
  397. * TODO: Make this a subclass of AstNode that can be returned when a user
  398. parses a broken program. This would fail silently for most kinds
  399. of traversals (e.g., "ast.find_all" or "ast.body"). Perhaps it
  400. has some kind of special flag.
  401. */
  402. mod.CorruptedAstNode = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  403. $loc.__init__ = new Sk.builtin.func(function(self) {
  404. self.id = -1;
  405. self.type = '';
  406. Sk.abstr.sattr(self, 'type', Sk.ffi.remapToPy(self.type), true);
  407. });
  408. });
  409. /**
  410. * Returns javascript equivalent string representation of python operator
  411. * given a function that represents a python operator.
  412. **/
  413. function transPyOps(field){
  414. var op = field.name;
  415. var transOp = null;
  416. switch(op){
  417. case "Add":
  418. transOp = "+";
  419. break;
  420. case "Div":
  421. transOp = "/";
  422. break;
  423. case "Mult":
  424. transOp = "*";
  425. break;
  426. case "Sub":
  427. transOp = "-";
  428. break;
  429. case "Gt":
  430. transOp = ">";
  431. break;
  432. case "Lt":
  433. transOp = "<";
  434. break;
  435. case "LtE":
  436. transOp = "<=";
  437. break;
  438. case "GtE":
  439. transOp = ">=";
  440. break;
  441. case "And":
  442. transOp = "&&";
  443. break;
  444. case "Or":
  445. transOp = "||";
  446. break;
  447. case "Not":
  448. transOp = "!";
  449. break;
  450. case "Eq":
  451. transOp = "==";
  452. break;
  453. case "NotEq":
  454. transOp = "!=";
  455. break;
  456. default:
  457. break;
  458. }
  459. return transOp;
  460. }
  461. function findMatches(insCode){
  462. if(flatTree.length == 0)
  463. parseProgram();
  464. var insMatcher = new StretchyTreeMatcher(insCode);
  465. if(insMatcher.rootNode == null){
  466. console.error("instructor code didn't parse");
  467. return null;
  468. }
  469. var stdAST = flatTree[0];
  470. var matches = insMatcher.findMatches(stdAST);
  471. if(!matches){
  472. //console.log("match not found");
  473. return null;
  474. }
  475. //console.log("match found");
  476. //console.log(matches);
  477. return matches;
  478. }
  479. mod.find_match = new Sk.builtin.func(function(insCode) {
  480. Sk.builtin.pyCheckType("insCode", "string", Sk.builtin.checkString(insCode));
  481. insCode = Sk.ffi.remapToJs(insCode);
  482. var matches = findMatches(insCode);
  483. if(matches)
  484. return Sk.misceval.callsimOrSuspend(mod.ASTMAp, matches[0]);
  485. else
  486. return Sk.ffi.remapToPy(null);
  487. });
  488. mod.find_matches = new Sk.builtin.func(function(insCode) {
  489. Sk.builtin.pyCheckType("insCode", "string", Sk.builtin.checkString(insCode));
  490. insCode = Sk.ffi.remapToJs(insCode);
  491. var matches = findMatches(insCode);
  492. if(matches){
  493. var converts = [];
  494. for(var i = 0; i < matches.length; i += 1){
  495. converts.push(Sk.misceval.callsimOrSuspend(mod.ASTMAp, matches[i]));
  496. }
  497. //console.log(converts);
  498. return new Sk.builtin.list(converts);
  499. }
  500. else
  501. return Sk.ffi.remapToPy(null);
  502. });
  503. /**
  504. * This type of object should ONLY be generated in javascript!
  505. **/
  506. mod.ASTMAp = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  507. $loc.__init__ = new Sk.builtin.func(function(self, jsASTMap) {
  508. //the map
  509. self.astMap = jsASTMap;
  510. //console.log(self.astMap);
  511. });
  512. $loc.get_std_name = new Sk.builtin.func(function(self, id) {
  513. var insKey = Sk.ffi.remapToJs(id);
  514. //not a key
  515. if(typeof insKey != "string"){
  516. return Sk.ffi.remapToPy(null);
  517. }
  518. var value = self.astMap.symbolTable.get(insKey);
  519. //symbol doesn't exist
  520. if(value == null){//actually probably undefined
  521. return Sk.ffi.remapToPy(null);
  522. }else{
  523. //console.log(value[0].node);
  524. //console.log(flatTree.indexOf(value[0].node));
  525. return Sk.misceval.callsimOrSuspend(mod.AstNode, flatTree.indexOf(value[0].node));
  526. }
  527. });
  528. $loc.get_std_exp = new Sk.builtin.func(function(self, id) {
  529. var insKey = Sk.ffi.remapToJs(id);
  530. //not a key
  531. if(typeof insKey != "string"){
  532. return Sk.ffi.remapToPy(null);
  533. }
  534. var value = self.astMap.expTable.get(insKey);
  535. //symbol doesn't exist
  536. if(value == null){//actually probably undefined
  537. return Sk.ffi.remapToPy(null);
  538. }else{
  539. //console.log(value);
  540. //console.log(flatTree.indexOf(value));
  541. return Sk.misceval.callsimOrSuspend(mod.AstNode, flatTree.indexOf(value));
  542. }
  543. });
  544. //$loc.__getattr__ = new Sk.builtin.func(function(self, key) {
  545. // key = Sk.ffi.remapToJs(key);
  546. //});
  547. });
  548. /**
  549. * Python representation of the AST nodes w/o recreating the entire thing. This class assumes that parse_program
  550. * is called first
  551. * @property {number} self.id - the javascript id number of this object
  552. * @property {string} self.type - the javascript string representing the type of the node (_astname)
  553. * @property {Sk.abstr.(s/g)attr} id - the python version of self.id
  554. * @property {Sk.abstr.(s/g)attr} type - the python version of self.type
  555. **/
  556. mod.AstNode = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  557. $loc.__init__ = new Sk.builtin.func(function(self, id) {
  558. self.id = Sk.ffi.remapToJs(id);//note that id is passed from PYTHON as a default type already
  559. self.type = flatTree[self.id]._astname;
  560. //Sk.abstr.sattr(self, 'type', Sk.ffi.remapToPy(self.type), true);
  561. });
  562. $loc.__eq__ = new Sk.builtin.func(function(self, other){
  563. return Sk.ffi.remapToPy(self.id == other.id);
  564. });
  565. /**
  566. * If this node is a Compare or BoolOp node, sees if the logic in expr (a javascript string being a logical statement)
  567. * matches the logic of self. This assumes that we are only comparing numerical values to a single variable
  568. * @property {number} mag - the order of magnitude that should be added to numbers to check logic, 1 is usually
  569. * a good value, especially when working with the set of integers.
  570. **/
  571. $loc.numeric_logic_check = new Sk.builtin.func(function(self, mag, expr){
  572. if(self.type != "Compare" && self.type != "BoolOp"){
  573. return Sk.ffi.remapToPy(null);
  574. }
  575. expr = Sk.ffi.remapToJs(expr);
  576. var actualAstNode = flatTree[self.id];
  577. //test values for the boolean expression
  578. var consArray = [];
  579. var expConsArray = []
  580. var consRegex = /-?(?:\d{1,})\.?(?:\d{1,})?/;
  581. var varRegex = /[a-zA-Z_]\w{1,}/g;
  582. var extracts = expr.match(consRegex);
  583. for(var i = 0; i < extracts.length; i += 1){
  584. var cons = extracts[i] * 1;
  585. consArray.push(cons);
  586. expConsArray.push(cons);
  587. expConsArray.push(cons + mag * -1);
  588. expConsArray.push(cons + mag);
  589. }
  590. var compVarArray = expr.match(varRegex);
  591. var compVar = [];
  592. for(var i = 0; i < compVarArray.length; i += 1){
  593. if(compVar.indexOf(compVarArray[i]) == -1){
  594. compVar.push(compVarArray[i]);
  595. }
  596. }
  597. if(compVar.length != 1){
  598. return Sk.ffi.remapToPy(null);
  599. }else{
  600. compVar = "varPlaceHolder";
  601. }
  602. expr = expr.replace(varRegex, "varPlaceHolder");
  603. //build sudent expression
  604. var otherExpr = "";
  605. var prevWasOp = false;
  606. var boolOpstack = [];
  607. var studentVars = [];
  608. var fastFail = false;
  609. var visitor = new NodeVisitor();
  610. visitor.visit_BinOp = function(node){
  611. this.visit(node.left);
  612. otherExpr += transPyOps(node.op);
  613. this.visit(node.right);
  614. }
  615. visitor.visit_BoolOp = function(node){
  616. otherExpr += "(";
  617. var values = node.values;
  618. for(var i = 0; i < values.length; i += 1){
  619. this.visit(values[i]);
  620. if(i < values.length - 1){
  621. otherExpr += transPyOps(node.op) + " ";
  622. }
  623. }
  624. otherExpr += ")";
  625. }
  626. visitor.visit_Name = function(node){
  627. if(studentVars.length == 0){
  628. studentVars.push(node.id);
  629. }
  630. if(studentVars.indexOf(node.id) == -1){
  631. var fastFail = true;
  632. }
  633. otherExpr += compVar + " ";
  634. }
  635. visitor.visit_Num = function(node){
  636. otherExpr += Sk.ffi.remapToJs(node.n) + " ";
  637. }
  638. visitor.visit_Compare = function(node){
  639. //left op comp op comp
  640. otherExpr += "(";
  641. this.visit(node.left);
  642. var comparators = node.comparators;
  643. var ops = node.ops;
  644. for(var i = 0; i < comparators.length; i += 1){
  645. if(i % 2 == 1){
  646. otherExpr += " && ";
  647. this.visit(comparators[i-1]);
  648. }
  649. otherExpr += transPyOps(ops[i]);
  650. this.visit(comparators[i]);
  651. }
  652. otherExpr += ")";
  653. }
  654. visitor.visit(actualAstNode);
  655. var varPlaceHolder = 0;
  656. if(fastFail){
  657. return Sk.ffi.remapToPy(null);
  658. }
  659. var otherCons = otherExpr.match(consRegex);
  660. for(var i = 0; i < otherCons.length; i += 1){
  661. var cons = otherCons[i] * 1;
  662. expConsArray.push(cons);
  663. expConsArray.push(cons + mag * -1);
  664. expConsArray.push(cons + mag);
  665. }
  666. for(var i = 0; i < expConsArray.length; i += 1){
  667. varPlaceHolder = expConsArray[i];
  668. if(eval(expr) != eval(otherExpr)){
  669. return Sk.ffi.remapToPy(false);
  670. }
  671. }
  672. return Sk.ffi.remapToPy(true);
  673. });
  674. $loc.__str__ = new Sk.builtin.func(function(self) {
  675. return Sk.ffi.remapToPy('<AST '+self.type+'>');
  676. });
  677. $loc.__repr__ = new Sk.builtin.func(function(self) {
  678. return Sk.ffi.remapToPy('<AST '+self.type+'>');
  679. });
  680. /**
  681. * This function dynamically looks to see if the ast node has a given property and does
  682. * remapping where it can
  683. * @param {obj} self - the javascript object representing the python AST node (which is also a python object)
  684. * @param {string} key - the property the user is trying to look up
  685. **/
  686. $loc.__getattr__ = new Sk.builtin.func(function(self, key) {
  687. var actualAstNode = flatTree[self.id];
  688. key = Sk.ffi.remapToJs(key);
  689. if (key == "data_type"){
  690. //if it's a name node, returns the data type, otherwise returns null
  691. return checkNameNodeType(actualAstNode);
  692. }
  693. if (key == "next_tree"){
  694. return getNextTree(self);
  695. }
  696. if (key == "ast_name"){
  697. key = "_astname";
  698. }
  699. if (key == "_name"){
  700. key = "name";
  701. }
  702. if (key in actualAstNode){
  703. var field = actualAstNode[key];
  704. //@TODO: check for flag to see if chain assignments are allowed, otherwise return first item
  705. if (actualAstNode._astname == "Assign" && key == "targets"){//this means its an assignment node
  706. var childId = flatTree.indexOf(field[0]);//get the relevant node
  707. //console.log("Assign and targets case!" + childId);
  708. return Sk.misceval.callsimOrSuspend(mod.AstNode, childId);
  709. } else if (field === null) {
  710. return Sk.ffi.remapToPy(null);
  711. } else if (field.constructor === Array && key != "ops"){
  712. var astNodeCount = 0
  713. var fieldArray = [];
  714. //this will likely always be a mixed array
  715. for(var i = 0; i < field.length; i++){
  716. var subfield = field[i];
  717. //if AST node, use callism and push new object
  718. if(isAstNode(subfield)){//an AST node)
  719. var childId = flatTree.indexOf(subfield);//get the relevant node
  720. fieldArray.push(Sk.misceval.callsimOrSuspend(mod.AstNode, childId));
  721. }else{//else smart remap
  722. var tranSubfield = mixedRemapToPy(subfield);
  723. if(tranSubfield != undefined){
  724. fieldArray.push(tranSubfield);
  725. }
  726. }
  727. }
  728. return new Sk.builtin.list(fieldArray);
  729. } else if (isSkBuiltin(field)){//probably already a python object
  730. return field;
  731. } else if (isAstNode(field)){//an AST node
  732. var childId = flatTree.indexOf(field);//get the relevant node
  733. return Sk.misceval.callsimOrSuspend(mod.AstNode, childId);
  734. } else {
  735. switch(key){//looking for a function
  736. case "ctx"://a load or store
  737. case "ops"://an operator
  738. case "op"://an operator
  739. //the above 3 cases are functions, extract the function name
  740. return mixedRemapToPy(field);
  741. default:
  742. break;
  743. }
  744. //console.log(field)
  745. //console.log(mixedRemapToPy(field));
  746. //hope this is a basic type
  747. return mixedRemapToPy(field);
  748. }
  749. }
  750. return Sk.ffi.remapToPy(null);
  751. });
  752. /**
  753. * Given the python Name ast node (variable) and self (which is automatically filled), checks
  754. * the AST on the javascript side to see if the node has the specified variable using the name
  755. * @TODO: change this so it can handle any data type as opposed to just numbers and ast nodes
  756. * @param {???} self - the javascript reference of this object, which is self in python.
  757. * @param {mod.AstNode} pyAstNode - the python object representing the variable node to look for
  758. **/
  759. $loc.has = new Sk.builtin.func(function(self, pyAstNode) {
  760. var rawVariableName = null;
  761. var rawNum = null;
  762. var nodeId = self.id;
  763. var thisNode = flatTree[nodeId];
  764. //got a number instead of an AST node
  765. if (Sk.builtin.checkNumber(pyAstNode)){
  766. rawNum = Sk.ffi.remapToJs(pyAstNode);
  767. } else {//assume it's an AST node
  768. //@TODO: should handle exceptions/do type checking
  769. var otherId = Sk.ffi.remapToJs(pyAstNode.id);
  770. var otherNode = flatTree[otherId];
  771. if(otherNode._astname != "Name"){
  772. return Sk.ffi.remapToPy(false);
  773. }
  774. rawVariableName = Sk.ffi.remapToJs(otherNode.id);
  775. }
  776. var hasVar = false;
  777. var visitor = new NodeVisitor();
  778. if (rawVariableName != null){
  779. visitor.visit_Name = function(node){
  780. var otherRawName = Sk.ffi.remapToJs(node.id);
  781. if (rawVariableName == otherRawName){
  782. hasVar = true;
  783. return;
  784. }
  785. return this.generic_visit(node);
  786. }
  787. }
  788. if (rawNum != null){
  789. visitor.visit_Num = function(node){
  790. var otherNum = Sk.ffi.remapToJs(node.n);
  791. if (rawNum == otherNum){
  792. hasVar = true;
  793. return;
  794. }
  795. return this.generic_visit(node);
  796. }
  797. }
  798. visitor.visit(flatTree[nodeId]);
  799. return Sk.ffi.remapToPy(hasVar);
  800. });
  801. /**
  802. * Given a type of ast node as a string, returns all in the ast that are nodes of the specified "type"
  803. * valid options include BinOp, For, Call, If, Compare, Assign, Expr, note that these ARE case sensitive
  804. * @param {???} self - the javascript reference of this object, which is self in python.
  805. * @param {Sk.builtin.str} type - the python string representing the "type" of node to look for
  806. **/
  807. $loc.find_all = new Sk.builtin.func(function(self, type) {
  808. var items = [];
  809. var currentId = self.id - 1;
  810. var funcName = 'visit_' + Sk.ffi.remapToJs(type);
  811. var visitor = new NodeVisitor();
  812. visitor.visit = function(node) {
  813. currentId += 1;
  814. /** Visit a node. **/
  815. var method_name = 'visit_' + node._astname;
  816. if (method_name in this) {
  817. return this[method_name](node);
  818. } else {
  819. return this.generic_visit(node);
  820. }
  821. }
  822. visitor[funcName] = function(node){
  823. var skulptNode = Sk.misceval.callsimOrSuspend(mod.AstNode, currentId);
  824. items.push(skulptNode);
  825. return this.generic_visit(node);
  826. }
  827. var nodeId = self.id;
  828. visitor.visit(flatTree[nodeId]);
  829. //Don't use Sk.ffi because the objects in the array are already python objects
  830. return new Sk.builtin.list(items);
  831. });
  832. });
  833. return mod;
  834. }