sk_mod_instructor.js.orig 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  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. * Let user know about an issue
  47. */
  48. mod.explain = new Sk.builtin.func(function(message, priority, line) {
  49. Sk.builtin.pyCheckArgs("explain", arguments, 1, 3);
  50. Sk.builtin.pyCheckType("message", "string", Sk.builtin.checkString(message));
  51. if (priority != undefined){
  52. Sk.builtin.pyCheckType("priority", "string", Sk.builtin.checkString(priority));
  53. priority = Sk.ffi.remapToJs(priority);
  54. } else {
  55. priority = 'medium';
  56. }
  57. if (line !== undefined) {
  58. Sk.builtin.pyCheckType("line", "integer", Sk.builtin.checkInt(line));
  59. } else {
  60. line = null;
  61. }
  62. if (!Sk.executionReports.instructor.complaint){
  63. Sk.executionReports.instructor.complaint = [];
  64. }
  65. var newComplaint = {
  66. 'name': 'Instructor Feedback',
  67. 'message': Sk.ffi.remapToJs(message),
  68. 'priority': priority,
  69. 'line': line
  70. }
  71. Sk.executionReports.instructor.complaint.push(newComplaint);
  72. });
  73. mod.gently = new Sk.builtin.func(function(message, line) {
  74. return Sk.misceval.callsimOrSuspend(mod.explain, message, Sk.ffi.remapToPy('student'), line);
  75. });
  76. /**
  77. * Prevent a certain kind of error from percolating where type is the phase that's being suppressed and
  78. * subtype is a specific error in the report of that phase.
  79. */
  80. mod.suppress = new Sk.builtin.func(function(type, subtype) {
  81. Sk.builtin.pyCheckArgs("suppress", arguments, 1, 2);
  82. Sk.builtin.pyCheckType("type", "string", Sk.builtin.checkString(type));
  83. type = Sk.ffi.remapToJs(type);
  84. if (subtype !== undefined) {
  85. Sk.builtin.pyCheckType("subtype", "string", Sk.builtin.checkString(subtype));
  86. subtype = Sk.ffi.remapToJs(subtype);
  87. if (Sk.feedbackSuppressions[type] === false) {
  88. Sk.feedbackSuppressions[type] = {};
  89. Sk.feedbackSuppressions[type][subtype] = true;
  90. } else if (Sk.feedbackSuppressions[type] !== false) {
  91. Sk.feedbackSuppressions[type][subtype] = true;
  92. }
  93. } else {
  94. Sk.feedbackSuppressions[type] = true;
  95. }
  96. });
  97. /**
  98. * Logs feedback to javascript console
  99. */
  100. mod.log = new Sk.builtin.func(function(message) {
  101. Sk.builtin.pyCheckArgs("log", arguments, 1, 1);
  102. console.log(Sk.ffi.remapToJs(message));
  103. });
  104. // get_ast()
  105. // get_trace()
  106. // get_types()
  107. // get_types_raw()
  108. var create_logger = function(phase, report_item) {
  109. return new Sk.builtin.func(function() {
  110. Sk.builtin.pyCheckArgs('log_'+report_item, arguments, 0, 0);
  111. var report = Sk.executionReports[phase];
  112. if (report.success) {
  113. console.log(report[report_item]);
  114. } else {
  115. console.log('Execution phase "'+phase+'" failed, '+report_item+' could not be found.');
  116. }
  117. });
  118. };
  119. mod.log_ast = create_logger('parser', 'ast');
  120. mod.log_variables = create_logger('analyzer', 'variables');
  121. mod.log_behavior = create_logger('analyzer', 'behavior');
  122. mod.log_issues = create_logger('analyzer', 'issues');
  123. mod.log_trace = create_logger('analyzer', 'student');
  124. // Provides `student` as an object with all the data that the student declared.
  125. mod.StudentData = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  126. $loc.__init__ = new Sk.builtin.func(function(self) {
  127. //self.data = Sk.builtin.dict();
  128. var newDict = Sk.builtin.dict();
  129. Sk.abstr.sattr(self, 'data', newDict, true);
  130. self.module = Sk.executionReports['student'].module;
  131. if (self.module !== undefined) {
  132. self.module = self.module.$d;
  133. for (var key in self.module) {
  134. if (self.module.hasOwnProperty(key)) {
  135. newDict.mp$ass_subscript(Sk.ffi.remapToPy(key),
  136. self.module[key]);
  137. }
  138. }
  139. } else {
  140. self.module = {};
  141. }
  142. });
  143. $loc.get_names_by_type = new Sk.builtin.func(function(self, type, exclude_builtins) {
  144. Sk.builtin.pyCheckArgs("get_names_by_type", arguments, 2, 3);
  145. if (exclude_builtins === undefined) {
  146. exclude_builtins = true;
  147. } else {
  148. Sk.builtin.pyCheckType("exclude_builtins", "boolean", Sk.builtin.checkBool(exclude_builtins));
  149. exclude_builtins = Sk.ffi.remapToJs(exclude_builtins);
  150. }
  151. var result = [];
  152. for (var property in self.module) {
  153. if (self.module[property].tp$name == type.tp$name) {
  154. console.log(exclude_builtins);
  155. if (exclude_builtins && property.startsWith("__")) {
  156. continue;
  157. }
  158. result.push(Sk.ffi.remapToPy(property));
  159. }
  160. }
  161. return Sk.builtin.list(result);
  162. });
  163. $loc.get_values_by_type = new Sk.builtin.func(function(self, type, exclude_builtins) {
  164. Sk.builtin.pyCheckArgs("get_values_by_type", arguments, 2, 3);
  165. if (exclude_builtins === undefined) {
  166. exclude_builtins = true;
  167. } else {
  168. Sk.builtin.pyCheckType("exclude_builtins", "boolean", Sk.builtin.checkBool(exclude_builtins));
  169. exclude_builtins = Sk.ffi.remapToJs(exclude_builtins);
  170. }
  171. var result = [];
  172. for (var property in self.module) {
  173. if (self.module[property].tp$name == type.tp$name) {
  174. if (exclude_builtins && property.startsWith("__")) {
  175. continue;
  176. }
  177. result.push(self.module[property]);
  178. }
  179. }
  180. return Sk.builtin.list(result);
  181. });
  182. });
  183. mod.student = Sk.misceval.callsimOrSuspend(mod.StudentData);
  184. //Enhanced feedback functions and objects starts here
  185. //variable used for easy reidentification of nodes so we don't have to recreate every node type
  186. var flatTree = [];
  187. //variable used for accumulating interrupting feedback AS A LIST OF PYTHON OBJECTS
  188. var accInterruptFeedback = [];
  189. //variable used for accumulating complementary feedback AS A LIST OF PYTHON OBJECTS
  190. var accCompFeedback = [];
  191. /**
  192. * Generates a flat ast tree and store it in the local variable.
  193. * This function is meant to be used to avoid extra coding by recreating every AST node type
  194. *
  195. **/
  196. function generateFlatTree(){
  197. var parser = Sk.executionReports['parser'];
  198. //Tree's already been built, don't do anything else
  199. if(flatTree.length > 0){
  200. return;
  201. }
  202. var ast;
  203. if (parser.success) {
  204. ast = parser.ast;
  205. } else {
  206. return;
  207. }
  208. var visitor = new NodeVisitor();
  209. visitor.visit = function(node){
  210. flatTree.push(node);
  211. /** Visit a node. **/
  212. var method_name = 'visit_' + node._astname;
  213. //console.log(flatTree.length - 1 + ": " + node._astname)
  214. if (method_name in this) {
  215. return this[method_name](node);
  216. } else {
  217. return this.generic_visit(node);
  218. }
  219. }
  220. visitor.visit(ast);
  221. }
  222. /**
  223. * This function checks if the given object is one of the Sk.builtin objects
  224. * TODO: make this so we don't have to explicitly put out eveyr option
  225. * one possible thing we could do is get a string version of the
  226. * of the constructor and look for the substring "return new Sk.builtin"
  227. * But I don't know how reliable that is. Rather, it's kind of hackish.
  228. * @param {object} obj - the object to be examined
  229. * @return {boolean} true if the object is one of the Sk.builtin types
  230. **/
  231. function isSkBuiltin(obj){
  232. return (obj instanceof Sk.builtin.dict) ||
  233. (obj instanceof Sk.builtin.list) ||
  234. (obj instanceof Sk.builtin.tuple) ||
  235. (obj instanceof Sk.builtin.bool) ||
  236. (obj instanceof Sk.builtin.int_) ||
  237. (obj instanceof Sk.builtin.float_) ||
  238. (obj instanceof Sk.builtin.str) ||
  239. (obj instanceof Sk.builtin.lng);
  240. //var cons_str = obj.constructor + "";
  241. //return cons_str.indexOf("return new Sk.builtin") !== -1;
  242. }
  243. /**
  244. * Should theoretically belong in Sk.ffi, but I put it here instead to not mess up the skulpt files
  245. * like the normal Sk.ffi.remapToPy, it doesn't work for functions or more complex objects, but it handles
  246. * cases where the types in obj ore a mix of python SIMPLE objects and SIMPLE normal javascript objects
  247. * @param {object} obj - the object to be converted
  248. * @return {Sk.builtin.???} - returns the corresponding python object, dropping all functions and things it can't convert
  249. **/
  250. function mixedRemapToPy(obj){
  251. var k;
  252. var kvs;
  253. var i;
  254. var arr;
  255. //@TODO: should theoretically check if the object is a pyhon dict or array with js objects
  256. if (isSkBuiltin(obj)){
  257. //object is already python ready
  258. return obj;
  259. } else if (Object.prototype.toString.call(obj) === "[object Array]") {
  260. //object is actually a javascript array
  261. arr = [];
  262. for (i = 0; i < obj.length; ++i) {
  263. //for each object, convert it to a python object if it isn't one already
  264. var subval = obj[i];
  265. if(!isSkBuiltin(subval)){
  266. arr.push(mixedRemapToPy(subval));
  267. }else{
  268. arr.push(subval)
  269. }
  270. }
  271. return new Sk.builtin.list(arr);
  272. } else if (obj === null) {//null object
  273. return Sk.builtin.none.none$;
  274. } else if (typeof obj === "object") {
  275. if(!isSkBuiltin(obj)){
  276. //assuming it's a standard dictionary
  277. kvs = [];//Sk.builtin.dict uses an array of key-value,key-value...
  278. for (k in obj) {
  279. //convert the key if it needs to be converted
  280. kvs.push(mixedRemapToPy(k));
  281. //covert corresponding value if it needs to be converted
  282. kvs.push(mixedRemapToPy(obj[k]));
  283. }
  284. //create the new dictionary
  285. return new Sk.builtin.dict(kvs);
  286. }else{
  287. return obj;
  288. }
  289. } else if (typeof obj === "string") {
  290. return new Sk.builtin.str(obj);
  291. } else if (typeof obj === "number") {
  292. return Sk.builtin.assk$(obj);
  293. } else if (typeof obj === "boolean") {
  294. return new Sk.builtin.bool(obj);
  295. } else if(typeof obj === "function") {
  296. return new Sk.builtin.str(obj.toString());
  297. }
  298. }
  299. /**
  300. * This function coverts the output in the student report to a python
  301. * list and returns it.
  302. **/
  303. mod.get_output = new Sk.builtin.func(function() {
  304. Sk.builtin.pyCheckArgs("get_output", arguments, 0, 0);
  305. if (Sk.executionReports['student'].success) {
  306. return mixedRemapToPy(Sk.executionReports['student']['output']());
  307. } else {
  308. return Sk.ffi.remapToPy([]);
  309. }
  310. });
  311. /**
  312. * This function resets the output, particularly useful if the student
  313. * code is going to be rerun.
  314. */
  315. mod.reset_output = new Sk.builtin.func(function() {
  316. Sk.builtin.pyCheckArgs("reset_output", arguments, 0, 0);
  317. if (Sk.executionReports['student'].success) {
  318. Sk.executionReports['student']['output'].removeAll();
  319. }
  320. });
  321. /**
  322. * This function is called by instructors to get the students' code as a string.
  323. **/
  324. mod.get_program = new Sk.builtin.func(function() {
  325. Sk.builtin.pyCheckArgs("get_program", arguments, 0, 0);
  326. return Sk.ffi.remapToPy(Sk.executionReports['verifier'].code);
  327. });
  328. /**
  329. * This function is called by instructors to construct the python version of the AST
  330. **/
  331. mod.parse_program = new Sk.builtin.func(function() {
  332. generateFlatTree(Sk.executionReports['verifier'].code);
  333. console.log(flatTree);
  334. return Sk.misceval.callsimOrSuspend(mod.AstNode, 0);
  335. });
  336. mod.def_use_error = new Sk.builtin.func(function(py_node) {
  337. var id = py_node.id;
  338. var node = flatTree[id];
  339. if((node instanceof Object) && ("_astname" in node) && node._astname == "Name"){
  340. var undefVars = Sk.executionReports['analyzer'].issues["Undefined variables"];
  341. var hasError = false;
  342. var name = Sk.ffi.remapToJs(node.id);
  343. for(var i = 0; i < undefVars.length; i += 1){
  344. if(undefVars[i].name == name){
  345. hasError = true;
  346. break;
  347. }
  348. }
  349. return Sk.ffi.remapToPy(hasError);
  350. }else{
  351. return Ski.ffi.remapToPy(false);
  352. }
  353. });
  354. /**
  355. * This function takes an AST node and if it's a name node, finds the type of the object
  356. * @param {Skulpt AST node} node - the node to check
  357. **/
  358. function checkNameNodeType(node){
  359. if((node instanceof Object) && ("_astname" in node) && node._astname == "Name"){
  360. var analyzer = Sk.executionReports['analyzer'];
  361. var typesList = analyzer.variables;
  362. var name = Sk.ffi.remapToJs(node.id);
  363. if (typesList[name] === undefined) {
  364. return Sk.ffi.remapToPy(null);
  365. } else {
  366. return Sk.ffi.remapToPy(typesList[name]["type"]);
  367. }
  368. }else{
  369. return Sk.ffi.remapToPy(null);
  370. }
  371. }
  372. /**
  373. * When passed a python AST node, returns the next node that isn't in this node's
  374. * subtree. If such a node does not exist, returns Sk.ffi.remapToPy(null)
  375. **/
  376. function getNextTree(self){
  377. var visitor = new NodeVisitor();
  378. var currentId = self.id;//-1 to offset first iteration
  379. visitor.visit = function(node) {
  380. currentId += 1;
  381. /** Visit a node. **/
  382. var method_name = 'visit_' + node._astname;
  383. return this.generic_visit(node);
  384. }
  385. visitor.visit(flatTree[currentId]);
  386. if(currentId > flatTree.length){
  387. return Sk.ffi.remapToPy(null);
  388. }
  389. return Sk.misceval.callsimOrSuspend(mod.AstNode, currentId);
  390. }
  391. /**
  392. * TODO: Make this a subclass of AstNode that can be returned when a user
  393. parses a broken program. This would fail silently for most kinds
  394. of traversals (e.g., "ast.find_all" or "ast.body"). Perhaps it
  395. has some kind of special flag.
  396. */
  397. mod.CorruptedAstNode = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  398. $loc.__init__ = new Sk.builtin.func(function(self) {
  399. self.id = -1;
  400. self.type = '';
  401. Sk.abstr.sattr(self, 'type', Sk.ffi.remapToPy(self.type), true);
  402. });
  403. });
  404. /**
  405. * Returns javascript equivalent string representation of python operator given a function that represents
  406. * a python operator
  407. **/
  408. function transPyOps(field){
  409. var op = field.name;
  410. console.log("op is: " + op);
  411. var transOp = null;
  412. switch(op){
  413. case "Add":
  414. transOp = "+";
  415. break;
  416. case "Div":
  417. transOp = "/";
  418. break;
  419. case "Mult":
  420. transOp = "*";
  421. break;
  422. case "Sub":
  423. transOp = "-";
  424. break;
  425. case "Gt":
  426. transOp = ">";
  427. break;
  428. case "Lt":
  429. transOp = "<";
  430. break;
  431. case "LtE":
  432. transOp = "<=";
  433. break;
  434. case "GtE":
  435. transOp = ">=";
  436. break;
  437. case "And":
  438. transOp = "&&";
  439. break;
  440. case "Or":
  441. transOp = "||";
  442. break;
  443. case "Not":
  444. transOp = "!";
  445. break;
  446. case "Eq":
  447. transOp = "==";
  448. break;
  449. case "NotEq":
  450. transOp = "!=";
  451. break;
  452. default:
  453. break;
  454. }
  455. return transOp;
  456. }
  457. /**
  458. * Python representation of the AST nodes w/o recreating the entire thing. This class assumes that parse_program
  459. * is called first
  460. * @property {number} self.id - the javascript id number of this object
  461. * @property {string} self.type - the javascript string representing the type of the node (_astname)
  462. * @property {Sk.abstr.(s/g)attr} id - the python version of self.id
  463. * @property {Sk.abstr.(s/g)attr} type - the python version of self.type
  464. **/
  465. mod.AstNode = Sk.misceval.buildClass(mod, function($gbl, $loc) {
  466. $loc.__init__ = new Sk.builtin.func(function(self, id) {
  467. self.id = Sk.ffi.remapToJs(id);//note that id is passed from PYTHON as a default type already
  468. self.type = flatTree[self.id]._astname;
  469. //Sk.abstr.sattr(self, 'type', Sk.ffi.remapToPy(self.type), true);
  470. });
  471. $loc.__eq__ = new Sk.builtin.func(function(self, other){
  472. return Sk.ffi.remapToPy(self.id == other.id);
  473. });
  474. /**
  475. * If this node is a Compare or BoolOp node, sees if the logic in expr (a javascript string being a logical statement)
  476. * matches the logic of self. This assumes that we are only comparing numerical values to a single variable
  477. * @property {number} mag - the order of magnitude that should be added to numbers to check logic, 1 is usually
  478. * a good value, especially when working with the set of integers.
  479. **/
  480. $loc.numeric_logic_check = new Sk.builtin.func(function(self, mag, expr){
  481. if(self.type != "Compare" && self.type != "BoolOp"){
  482. console.log("incorrect node type: " + self.type);
  483. return Sk.ffi.remapToPy(null);
  484. }
  485. expr = Sk.ffi.remapToJs(expr);
  486. var actualAstNode = flatTree[self.id];
  487. //test values for the boolean expression
  488. var consArray = [];
  489. var expConsArray = []
  490. var consRegex = /-?(?:\d{1,})\.?(?:\d{1,})?/;
  491. var varRegex = /[a-zA-Z_]\w{1,}/;
  492. var extracts = expr.match(consRegex);
  493. for(var i = 0; i < extracts.length; i += 1){
  494. var cons = extracts[i] * 1;
  495. consArray.push(cons);
  496. expConsArray.push(cons);
  497. expConsArray.push(cons + mag * -1);
  498. expConsArray.push(cons + mag);
  499. }
  500. var compVar = expr.match(varRegex);
  501. if(compVar.length != 1){
  502. console.log("too many variables");
  503. return Sk.ffi.remapToPy(null);
  504. }else{
  505. compVar = "varPlaceHolder";
  506. }
  507. expr = expr.replace(varRegex, "varPlaceHolder");
  508. //build sudent expression
  509. var otherExpr = "";
  510. var prevWasOp = false;
  511. var boolOpstack = [];
  512. var studentVars = [];
  513. var fastFail = false;
  514. var visitor = new NodeVisitor();
  515. visitor.visit_BoolOp = function(node){
  516. otherExpr += "(";
  517. var values = node.values;
  518. for(var i = 0; i < values.length; i += 1){
  519. this.visit(values[i]);
  520. if(i < values.length - 1){
  521. otherExpr += transPyOps(node.op);
  522. }
  523. }
  524. otherExpr += ")";
  525. }
  526. visitor.visit_Name = function(node){
  527. if(studentVars.length == 0){
  528. studentVars.push(node.id);
  529. }
  530. if(studentVars.indexOf(node.id) == -1){
  531. var fastFail = true;
  532. }
  533. otherExpr += compVar + " ";
  534. }
  535. visitor.visit_Num = function(node){
  536. otherExpr += Sk.ffi.remapToJs(node.n) + " ";
  537. }
  538. visitor.visit_Compare = function(node){
  539. //left op comp op comp
  540. otherExpr += "(";
  541. this.visit(node.left);
  542. var comparators = node.comparators;
  543. var ops = node.ops;
  544. for(var i = 0; i < comparators.length; i += 1){
  545. if(i % 2 == 1){
  546. otherExpr = " && ";
  547. this.visit(comparators[i-1]);
  548. }
  549. otherExpr += transPyOps(ops[i]);
  550. this.visit(comparators[i]);
  551. }
  552. otherExpr += ")";
  553. }
  554. visitor.visit(actualAstNode);
  555. var varPlaceHolder = 0;
  556. if(fastFail){
  557. console.log("fast fail!");
  558. return Sk.ffi.remapToPy(null);
  559. }
  560. console.log(expr);
  561. console.log(otherExpr);
  562. var otherCons = otherExpr.match(consRegex);
  563. for(var i =0 ; i < otherCons.length; i += 1){
  564. if(consArray.indexOf(otherCons[i] * 1) == -1){
  565. return Sk.ffi.remapToPy(false);
  566. }
  567. }
  568. for(var i = 0; i < expConsArray.length; i += 1){
  569. varPlaceHolder = expConsArray[i];
  570. if(eval(expr) != eval(otherExpr)){
  571. return Sk.ffi.remapToPy(false);
  572. }
  573. }
  574. return Sk.ffi.remapToPy(true);
  575. });
  576. /**
  577. * This function dynamically looks to see if the ast node has a given property and does
  578. * remapping where it can
  579. * @param {obj} self - the javascript object representing the python AST node (which is also a python object)
  580. * @param {string} key - the property the user is trying to look up
  581. **/
  582. $loc.__getattr__ = new Sk.builtin.func(function(self, key) {
  583. var actualAstNode = flatTree[self.id];
  584. key = Sk.ffi.remapToJs(key);
  585. if(key == "data_type"){
  586. //if it's a name node, returns the data type, otherwise returns null
  587. return checkNameNodeType(actualAstNode);
  588. }
  589. if(key == "next_tree"){
  590. return getNextTree(self);
  591. }
  592. if(key == "ast_name"){
  593. key = "_astname";
  594. }
  595. if(key in actualAstNode){
  596. var field = actualAstNode[key];
  597. //@TODO: check for flag to see if chain assignments are allowed, otherwise return first item
  598. if (actualAstNode._astname == "Assign" && key == "targets"){//this means its an assignment node
  599. var childId = flatTree.indexOf(field[0]);//get the relevant node
  600. //console.log("Assign and targets case!" + childId);
  601. return Sk.misceval.callsimOrSuspend(mod.AstNode, childId);
  602. } else if (field === null) {
  603. return Sk.ffi.remapToPy(null);
  604. } else if (field.constructor === Array && key != "ops"){
  605. var astNodeCount = 0
  606. var fieldArray = [];
  607. //this will likely always be a mixed array
  608. for(var i = 0; i < field.length; i++){
  609. var subfield = field[i];
  610. //if AST node, use callism and push new object
  611. if(subfield instanceof Object && "_astname" in subfield){//an AST node)
  612. var childId = flatTree.indexOf(subfield);//get the relevant node
  613. fieldArray.push(Sk.misceval.callsimOrSuspend(mod.AstNode, childId));
  614. }else{//else smart remap
  615. var tranSubfield = mixedRemapToPy(subfield);
  616. if(tranSubfield != undefined){
  617. fieldArray.push(tranSubfield);
  618. }
  619. }
  620. }
  621. return new Sk.builtin.list(fieldArray);
  622. } else if (isSkBuiltin(field)){//probably already a python object
  623. return field;
  624. } else if (field instanceof Object && "_astname" in field){//an AST node
  625. var childId = flatTree.indexOf(field);//get the relevant node
  626. return Sk.misceval.callsimOrSuspend(mod.AstNode, childId);
  627. } else {
  628. switch(key){//looking for a function
  629. case "ctx"://a load or store
  630. case "ops"://an operator
  631. case "op"://an operator
  632. //the above 3 cases are functions, extract the function name
  633. field = field.name;
  634. return Sk.ffi.remapToPy(field);
  635. default:
  636. break;
  637. }
  638. //console.log(field)
  639. //console.log(mixedRemapToPy(field));
  640. //hope this is a basic type
  641. return mixedRemapToPy(field);
  642. }
  643. }
  644. return Sk.ffi.remapToPy(null);
  645. });
  646. /**
  647. * Given the python Name ast node (variable) and self (which is automatically filled), checks
  648. * the AST on the javascript side to see if the node has the specified variable using the name
  649. * @TODO: change this so it can handle any data type as opposed to just numbers and ast nodes
  650. * @param {???} self - the javascript reference of this object, which is self in python.
  651. * @param {mod.AstNode} pyAstNode - the python object representing the variable node to look for
  652. **/
  653. $loc.has = new Sk.builtin.func(function(self, pyAstNode) {
  654. var rawVariableName = null;
  655. var rawNum = null;
  656. var nodeId = self.id;
  657. var thisNode = flatTree[nodeId];
  658. //got a number instead of an AST node
  659. if (Sk.builtin.checkNumber(pyAstNode)){
  660. rawNum = Sk.ffi.remapToJs(pyAstNode);
  661. } else {//assume it's an AST node
  662. //@TODO: should handle exceptions/do type checking
  663. var otherId = Sk.ffi.remapToJs(pyAstNode.id);
  664. var otherNode = flatTree[otherId];
  665. if(otherNode._astname != "Name"){
  666. return Sk.ffi.remapToPy(false);
  667. }
  668. rawVariableName = Sk.ffi.remapToJs(otherNode.id);
  669. }
  670. var hasVar = false;
  671. var visitor = new NodeVisitor();
  672. if (rawVariableName != null){
  673. visitor.visit_Name = function(node){
  674. var otherRawName = Sk.ffi.remapToJs(node.id);
  675. if (rawVariableName == otherRawName){
  676. hasVar = true;
  677. return;
  678. }
  679. return this.generic_visit(node);
  680. }
  681. }
  682. if (rawNum != null){
  683. visitor.visit_Num = function(node){
  684. var otherNum = Sk.ffi.remapToJs(node.n);
  685. if (rawNum == otherNum){
  686. hasVar = true;
  687. return;
  688. }
  689. return this.generic_visit(node);
  690. }
  691. }
  692. visitor.visit(flatTree[nodeId]);
  693. return Sk.ffi.remapToPy(hasVar);
  694. });
  695. /**
  696. * Given a type of ast node as a string, returns all in the ast that are nodes of the specified "type"
  697. * valid options include BinOp, For, Call, If, Compare, Assign, Expr, note that these ARE case sensitive
  698. * @param {???} self - the javascript reference of this object, which is self in python.
  699. * @param {Sk.builtin.str} type - the python string representing the "type" of node to look for
  700. **/
  701. $loc.find_all = new Sk.builtin.func(function(self, type) {
  702. var items = [];
  703. var currentId = self.id - 1;
  704. var funcName = 'visit_' + Sk.ffi.remapToJs(type);
  705. var visitor = new NodeVisitor();
  706. visitor.visit = function(node) {
  707. currentId += 1;
  708. /** Visit a node. **/
  709. var method_name = 'visit_' + node._astname;
  710. if (method_name in this) {
  711. return this[method_name](node);
  712. } else {
  713. return this.generic_visit(node);
  714. }
  715. }
  716. visitor[funcName] = function(node){
  717. var skulptNode = Sk.misceval.callsimOrSuspend(mod.AstNode, currentId);
  718. items.push(skulptNode);
  719. return this.generic_visit(node);
  720. }
  721. var nodeId = self.id;
  722. visitor.visit(flatTree[nodeId]);
  723. //Don't use Sk.ffi because the objects in the array are already python objects
  724. return new Sk.builtin.list(items);
  725. });
  726. });
  727. return mod;
  728. }