from instructor import * def is_top_level(ast_node): ast = parse_program() for element in ast.body: if element.ast_name == 'Expr': if element.value == ast_node: return True elif element == ast_node: return True return False def no_nested_function_definitions(): ast = parse_program() defs = ast.find_all('FunctionDef') for a_def in defs: if not is_top_level(a_def): 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!") return False return True def function_prints(): ast = parse_program() defs = ast.find_all('FunctionDef') for a_def in defs: all_calls = a_def.find_all('Call') for a_call in all_calls: if a_call.func.ast_name == 'Name': if a_call.func.id == 'print': return True return False def find_function_calls(name): ast = parse_program() all_calls = ast.find_all('Call') calls = [] for a_call in all_calls: if a_call.func.ast_name == 'Attribute': if a_call.func.attr == name: calls.append(a_call) elif a_call.func.ast_name == 'Name': if a_call.func.id == name: calls.append(a_call) return calls def function_is_called(name): return len(find_function_calls(name)) def no_nonlist_nums(): pass def only_printing_variables(): ast = parse_program() all_calls = ast.find_all('Call') count = 0 for a_call in all_calls: if a_call.func.ast_name == 'Name' and a_call.func.id == "print": for arg in a_call.args: if arg.ast_name != "Name": return False return True def find_prior_initializations(node): if node.ast_name != "Name": return None ast = parse_program() assignments = ast.find_all("Assign") cur_line_no = node.lineno all_assignments = [] for assignment in assignments: if assignment.has(node): if assignment.lineno < cur_line_no: all_assignments.append(assignment) return all_assignments def prevent_unused_result(): ast = parse_program() exprs = ast.find_all('Expr') for expr in exprs: if expr.value.ast_name == "Call": a_call = expr.value if a_call.func.ast_name == 'Attribute': if a_call.func.attr == 'append': pass elif a_call.func.attr in ('replace', 'strip', 'lstrip', 'rstrip'): gently("Remember! You cannot modify a string directly. Instead, you should assign the result back to the string variable.") def prevent_builtin_usage(function_names): # Prevent direction calls ast = parse_program() all_calls = ast.find_all('Call') for a_call in all_calls: if a_call.func.ast_name == 'Name': if a_call.func.id in function_names: explain("You cannot use the builtin function {}.".format(a_call.func.id)) return a_call.func.id # Prevent tricky redeclarations! names = ast.find_all('Name') seen = set() for name in names: if name.id not in seen: if name.ctx == "Load" and name.id in function_names: explain("You cannot use the builtin function {}. If you are naming a variable, consider a more specific name.".format(name.id)) seen.add(name.id) return name.id return None def prevent_literal(*literals): ast = parse_program() str_values = [s.s for s in ast.find_all("Str")] num_values = [n.n for n in ast.find_all("Num")] for literal in literals: if isinstance(literal, (int, float)): if literal in num_values: explain("Do not use the literal value {} in your code.".format(repr(literal))) return literal elif isinstance(literal, str): if literal in str_values: explain("Do not use the literal value {} in your code.".format(repr(literal))) return literal return False def ensure_literal(*literals): ast = parse_program() str_values = [s.s for s in ast.find_all("Str")] num_values = [n.n for n in ast.find_all("Num")] for literal in literals: if isinstance(literal, (int, float)): if literal not in num_values: explain("You need the literal value {} in your code.".format(repr(literal))) return literal elif isinstance(literal, str): if literal not in str_values: explain("You need the literal value {} in your code.".format(repr(literal))) return literal return False def prevent_advanced_iteration(): ast = parse_program() if ast.find_all('While'): explain("You should not use a while loop to solve this problem.") prevent_builtin_usage(['sum', 'map', 'filter', 'reduce', 'len', 'max', 'min', 'max', 'sorted', 'all', 'any', 'getattr', 'setattr', 'eval', 'exec', 'iter']) COMPARE_OP_NAMES = { "==": "Eq", "<": "Lt", "<=": "Lte", ">=": "Gte", ">": "Gt", "!=": "NotEq", "is": "Is", "is not": "IsNot", "in": "In_", "not in": "NotIn"} BOOL_OP_NAMES = { "and": "And", "or": "Or"} BIN_OP_NAMES = { "+": "Add", "-": "Sub", "*": "Mult", "/": "Div", "//": "FloorDiv", "%": "Mod", "**": "Pow", ">>": "LShift", "<<": "RShift", "|": "BitOr", "^": "BitXor", "&": "BitAnd", "@": "MatMult"} UNARY_OP_NAMES = { #"+=": "UAdd", #"-=": "USub", "not": "Not", "~": "Invert" } def ensure_operation(op_name, root=None): if root is None: root = parse_program() result = find_operation(op_name, root) if result == False: gently("You are not using the {} operator.".format(op_name)) return result def prevent_operation(op_name, root=None): if root is None: root = parse_program() result = find_operation(op_name, root) if result != False: gently("You may not use the {} operator.".format(op_name)) return result def find_operation(op_name, root): if op_name in COMPARE_OP_NAMES: compares = root.find_all("Compare") for compare in compares: for op in compare.ops: if op == COMPARE_OP_NAMES[op_name]: return compare elif op_name in BOOL_OP_NAMES: boolops = root.find_all("BoolOp") for boolop in boolops: if boolop.op == BOOL_OP_NAMES[op_name]: return boolop elif op_name in BIN_OP_NAMES: binops = root.find_all("BinOp") for binop in binops: if binop.op == BIN_OP_NAMES[op_name]: return binop elif op_name in UNARY_OP_NAMES: unaryops = root.find_all("UnaryOp") for unaryop in unaryops: if unaryop.op == UNARY_OP_NAMES[op_name]: return unaryop return False ''' mod.no_nonlist_nums = new Sk.builtin.func(function(source) { Sk.builtin.pyCheckArgs("no_nonlist_nums", arguments, 1, 1); Sk.builtin.pyCheckType("source", "string", Sk.builtin.checkString(source)); source = source.v; var num_list = getNonListNums(source); var count = 0; for (var i = 0, len = num_list.length; i < len; i = i+1) { if (num_list[i].v != 0 && num_list[i].v != 1) { return Sk.ffi.remapToPy(true); } } return Sk.ffi.remapToPy(false); }); /** * Given source code as a string, return a list of all of the AST elements * that are Num (aka numeric literals) but that are not inside List elements. * * @param {String} source - Python source code. * @returns {Array.number} The list of JavaScript numeric literals that were found. */ function getNonListNums(source) { if (!(source in parses)) { var parse = Sk.parse("__main__", source); parses[source] = Sk.astFromParse(parse.cst, "__main__", parse.flags); } var ast = parses[source]; var visitor = new NodeVisitor(); var insideList = false; var nums = []; visitor.visit_List = function(node) { insideList = true; this.generic_visit(node); insideList = false; } visitor.visit_Num = function(node) { if (!insideList) { nums.push(node.n); } this.generic_visit(node); } visitor.visit(ast); return nums; } '''