instructor_utility.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. from instructor import *
  2. def is_top_level(ast_node):
  3. ast = parse_program()
  4. for element in ast.body:
  5. if element.ast_name == 'Expr':
  6. if element.value == ast_node:
  7. return True
  8. elif element == ast_node:
  9. return True
  10. return False
  11. def no_nested_function_definitions():
  12. ast = parse_program()
  13. defs = ast.find_all('FunctionDef')
  14. for a_def in defs:
  15. if not is_top_level(a_def):
  16. gently("You have defined a function inside of another block. For instance, you may have placed it inside another function definition, or inside of a loop. Do not nest your function definition!")
  17. return False
  18. return True
  19. def function_prints():
  20. ast = parse_program()
  21. defs = ast.find_all('FunctionDef')
  22. for a_def in defs:
  23. all_calls = a_def.find_all('Call')
  24. for a_call in all_calls:
  25. if a_call.func.ast_name == 'Name':
  26. if a_call.func.id == 'print':
  27. return True
  28. return False
  29. def find_function_calls(name):
  30. ast = parse_program()
  31. all_calls = ast.find_all('Call')
  32. calls = []
  33. for a_call in all_calls:
  34. if a_call.func.ast_name == 'Attribute':
  35. if a_call.func.attr == name:
  36. calls.append(a_call)
  37. elif a_call.func.ast_name == 'Name':
  38. if a_call.func.id == name:
  39. calls.append(a_call)
  40. return calls
  41. def function_is_called(name):
  42. return len(find_function_calls(name))
  43. def no_nonlist_nums():
  44. pass
  45. def only_printing_variables():
  46. ast = parse_program()
  47. all_calls = ast.find_all('Call')
  48. count = 0
  49. for a_call in all_calls:
  50. if a_call.func.ast_name == 'Name' and a_call.func.id == "print":
  51. for arg in a_call.args:
  52. if arg.ast_name != "Name":
  53. return False
  54. return True
  55. def find_prior_initializations(node):
  56. if node.ast_name != "Name":
  57. return None
  58. ast = parse_program()
  59. assignments = ast.find_all("Assign")
  60. cur_line_no = node.lineno
  61. all_assignments = []
  62. for assignment in assignments:
  63. if assignment.has(node):
  64. if assignment.lineno < cur_line_no:
  65. all_assignments.append(assignment)
  66. return all_assignments
  67. def prevent_unused_result():
  68. ast = parse_program()
  69. exprs = ast.find_all('Expr')
  70. for expr in exprs:
  71. if expr.value.ast_name == "Call":
  72. a_call = expr.value
  73. if a_call.func.ast_name == 'Attribute':
  74. if a_call.func.attr == 'append':
  75. pass
  76. elif a_call.func.attr in ('replace', 'strip', 'lstrip', 'rstrip'):
  77. gently("Remember! You cannot modify a string directly. Instead, you should assign the result back to the string variable.")
  78. def prevent_builtin_usage(function_names):
  79. # Prevent direction calls
  80. ast = parse_program()
  81. all_calls = ast.find_all('Call')
  82. for a_call in all_calls:
  83. if a_call.func.ast_name == 'Name':
  84. if a_call.func.id in function_names:
  85. explain("You cannot use the builtin function <code>{}</code>.".format(a_call.func.id))
  86. return a_call.func.id
  87. # Prevent tricky redeclarations!
  88. names = ast.find_all('Name')
  89. seen = set()
  90. for name in names:
  91. if name.id not in seen:
  92. if name.ctx == "Load" and name.id in function_names:
  93. explain("You cannot use the builtin function <code>{}</code>. If you are naming a variable, consider a more specific name.".format(name.id))
  94. seen.add(name.id)
  95. return name.id
  96. return None
  97. def prevent_literal(*literals):
  98. ast = parse_program()
  99. str_values = [s.s for s in ast.find_all("Str")]
  100. num_values = [n.n for n in ast.find_all("Num")]
  101. for literal in literals:
  102. if isinstance(literal, (int, float)):
  103. if literal in num_values:
  104. explain("Do not use the literal value <code>{}</code> in your code.".format(repr(literal)))
  105. return literal
  106. elif isinstance(literal, str):
  107. if literal in str_values:
  108. explain("Do not use the literal value <code>{}</code> in your code.".format(repr(literal)))
  109. return literal
  110. return False
  111. def ensure_literal(*literals):
  112. ast = parse_program()
  113. str_values = [s.s for s in ast.find_all("Str")]
  114. num_values = [n.n for n in ast.find_all("Num")]
  115. for literal in literals:
  116. if isinstance(literal, (int, float)):
  117. if literal not in num_values:
  118. explain("You need the literal value <code>{}</code> in your code.".format(repr(literal)))
  119. return literal
  120. elif isinstance(literal, str):
  121. if literal not in str_values:
  122. explain("You need the literal value <code>{}</code> in your code.".format(repr(literal)))
  123. return literal
  124. return False
  125. def prevent_advanced_iteration():
  126. ast = parse_program()
  127. if ast.find_all('While'):
  128. explain("You should not use a <code>while</code> loop to solve this problem.")
  129. prevent_builtin_usage(['sum', 'map', 'filter', 'reduce', 'len', 'max', 'min',
  130. 'max', 'sorted', 'all', 'any', 'getattr', 'setattr',
  131. 'eval', 'exec', 'iter'])
  132. COMPARE_OP_NAMES = {
  133. "==": "Eq",
  134. "<": "Lt",
  135. "<=": "Lte",
  136. ">=": "Gte",
  137. ">": "Gt",
  138. "!=": "NotEq",
  139. "is": "Is",
  140. "is not": "IsNot",
  141. "in": "In_",
  142. "not in": "NotIn"}
  143. BOOL_OP_NAMES = {
  144. "and": "And",
  145. "or": "Or"}
  146. BIN_OP_NAMES = {
  147. "+": "Add",
  148. "-": "Sub",
  149. "*": "Mult",
  150. "/": "Div",
  151. "//": "FloorDiv",
  152. "%": "Mod",
  153. "**": "Pow",
  154. ">>": "LShift",
  155. "<<": "RShift",
  156. "|": "BitOr",
  157. "^": "BitXor",
  158. "&": "BitAnd",
  159. "@": "MatMult"}
  160. UNARY_OP_NAMES = {
  161. #"+=": "UAdd",
  162. #"-=": "USub",
  163. "not": "Not",
  164. "~": "Invert"
  165. }
  166. def ensure_operation(op_name, root=None):
  167. if root is None:
  168. root = parse_program()
  169. result = find_operation(op_name, root)
  170. if result == False:
  171. gently("You are not using the <code>{}</code> operator.".format(op_name))
  172. return result
  173. def prevent_operation(op_name, root=None):
  174. if root is None:
  175. root = parse_program()
  176. result = find_operation(op_name, root)
  177. if result != False:
  178. gently("You may not use the <code>{}</code> operator.".format(op_name))
  179. return result
  180. def find_operation(op_name, root):
  181. if op_name in COMPARE_OP_NAMES:
  182. compares = root.find_all("Compare")
  183. for compare in compares:
  184. for op in compare.ops:
  185. if op == COMPARE_OP_NAMES[op_name]:
  186. return compare
  187. elif op_name in BOOL_OP_NAMES:
  188. boolops = root.find_all("BoolOp")
  189. for boolop in boolops:
  190. if boolop.op == BOOL_OP_NAMES[op_name]:
  191. return boolop
  192. elif op_name in BIN_OP_NAMES:
  193. binops = root.find_all("BinOp")
  194. for binop in binops:
  195. if binop.op == BIN_OP_NAMES[op_name]:
  196. return binop
  197. elif op_name in UNARY_OP_NAMES:
  198. unaryops = root.find_all("UnaryOp")
  199. for unaryop in unaryops:
  200. if unaryop.op == UNARY_OP_NAMES[op_name]:
  201. return unaryop
  202. return False
  203. '''
  204. mod.no_nonlist_nums = new Sk.builtin.func(function(source) {
  205. Sk.builtin.pyCheckArgs("no_nonlist_nums", arguments, 1, 1);
  206. Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source));
  207. source = source.v;
  208. var num_list = getNonListNums(source);
  209. var count = 0;
  210. for (var i = 0, len = num_list.length; i < len; i = i+1) {
  211. if (num_list[i].v != 0 && num_list[i].v != 1) {
  212. return Sk.ffi.remapToPy(true);
  213. }
  214. }
  215. return Sk.ffi.remapToPy(false);
  216. });
  217. /**
  218. * Given source code as a string, return a list of all of the AST elements
  219. * that are Num (aka numeric literals) but that are not inside List elements.
  220. *
  221. * @param {String} source - Python source code.
  222. * @returns {Array.number} The list of JavaScript numeric literals that were found.
  223. */
  224. function getNonListNums(source) {
  225. if (!(source in parses)) {
  226. var parse = Sk.parse("__main__", source);
  227. parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags);
  228. }
  229. var ast = parses[source];
  230. var visitor = new NodeVisitor();
  231. var insideList = false;
  232. var nums = [];
  233. visitor.visit_List = function(node) {
  234. insideList = true;
  235. this.generic_visit(node);
  236. insideList = false;
  237. }
  238. visitor.visit_Num = function(node) {
  239. if (!insideList) {
  240. nums.push(node.n);
  241. }
  242. this.generic_visit(node);
  243. }
  244. visitor.visit(ast);
  245. return nums;
  246. }
  247. '''