def __init__(self, files=None, main_file='answer.py', main_code=None, user=None, assignment=None, course=None, execution=None, instructor_file='on_run.py', skip_tifa=False, set_success=True, report=MAIN_REPORT): # Possibly user passed in stuff via the command line. if files is None and main_code is None: (instructor_file, files, main_file, main_code, user, assignment, course, execution) = parse_argv() super().__init__(files=files, main_file=main_file, main_code=main_code, user=user, assignment=assignment, course=course, execution=execution, instructor_file=instructor_file, report=report) # Then default custom stuff verify(report=report) self.ast = parse_program(report=report) if skip_tifa: self.tifa = None else: from pedal.tifa import tifa_analysis self.tifa = tifa_analysis(report=report) self.student = run(threaded=True, report=report) self.set_success = set_success
def find_operation(op_name, root=None, report=MAIN_REPORT): """ Returns all the occurrences of the operator `op_name` in the source code. You can specify the operator as a string like `"+"` or `"<<"`. Supports all comparison, boolean, binary, and unary operators. """ root = root or parse_program(report=report) found = [] if op_name in COMPARE_OP_NAMES: compares = root.find_all("Compare") for compare in compares: for op in compare.ops: if op.ast_name == COMPARE_OP_NAMES[op_name]: found.append(compare) elif op_name in BOOL_OP_NAMES: boolops = root.find_all("BoolOp") for boolop in boolops: if boolop.op_name == BOOL_OP_NAMES[op_name]: found.append(boolop) elif op_name in BIN_OP_NAMES: binops = root.find_all("BinOp") for binop in binops: if binop.op_name == BIN_OP_NAMES[op_name]: found.append(binop) elif op_name in UNARY_OP_NAMES: unaryops = root.find_all("UnaryOp") for unaryop in unaryops: if unaryop.op_name == UNARY_OP_NAMES[op_name]: found.append(unaryop) return found
def ensure_assignment(variable_name, type=None, value=None, root=None, muted=False, report=MAIN_REPORT): """ Consumes a variable name TODO: Implement the value parameter :param variable_name: The variable name the student is expected to define. :type variable_name: str :param type: The string type of the node on the right side of the assignment. Check GreenTreeSnakes (e.g., "Num", or "Str"). :type type: str :return: False or str Args: root: value: """ if root is None: root = parse_program() # TODO: Tie in Tifa's custom types expected_type = get_tifa_type_from_str(type) # Find assignments matching the pattern pattern = "{variable_name} = __expr__".format(variable_name=variable_name) matches = root.find_matches(pattern) potentials = [] for match in matches: if type is None: return match assigned_type = get_tifa_type_from_ast(match["__expr__"].ast_node) if are_types_equal(assigned_type, expected_type): return match potentials.append(match) if all(is_literal(potential) for potential in potentials): explain( ("You needed to assign a literal value to {variable}, but you " "created an expression instead.").format(variable=variable_name), label="did_not_assign_literal", title="Expression Instead of Literal", report=report, muted=muted) elif type is None: explain(("You have not properly assigned anything to the variable " "{variable}.").format(variable=variable_name), label="no_assign", title="No Proper Assignment", report=report, muted=muted) else: explain(("You have not assigned a {type} to the variable {variable}." "").format(type=type, variable=variable_name), label="wrong_type_assign", title="Unexpected Variable Type", report=report, muted=muted) return False
def grade_records(): body = parse_program().body for statement in body: if statement.ast_name == "Expr": if statement.value.ast_name == "Str": contents = statement.value.s for line in contents.split("\n"): if line.strip().lower().startswith("records:"): give_partial(FUNCTION_VALUE) return True explain("You have not created a Records definition at the top level.") return False
def find_seed(python_code): try: ast = parse_program(python_code) for assign in ast.find_all("Assign"): if assign.targets[0].ast_name != "Name": continue if assign.targets[0].id == "__STUDENT_SEED__": if assign.value.ast_name == "Str": return assign.value.s elif assign.value.ast_name == "Num": return assign.value.n elif assign.value.ast_name == "List": return [e.n for e in assign.value.elts] except SyntaxError: return 0 return 0
def find_function_definition(name, root=None, report=MAIN_REPORT): """ Finds the given function definition based on the given ``name``. Args: name (str): The name of the function. root: A subtree of a parse tree to revert to. report (Report): The name of the Report to refer to. Returns: :py:class:`pedal.cait.cait_node.CaitNode`: The first occurrence of a function with the given name. """ root = root or parse_program(report=report) defs = root.find_all('FunctionDef') for a_def in defs: if a_def._name == name: return a_def return None
def ensure_functions_return(exceptions=None, report=MAIN_REPORT, **kwargs): if exceptions is None: exceptions = set() elif isinstance(exceptions, str): exceptions = {exceptions} root = parse_program(report=report) defs = root.find_all("FunctionDef") for adef in defs: name = adef._name if name in exceptions: continue if not adef.find_match("return"): return explain( f"The function {name} is not returning." f" However, that function is supposed to have a return statement.", label="function_does_not_return", title="Must Return in Function", fields={'name': name}) return False
def find_prior_initializations(node, report=MAIN_REPORT): """ DEPRECATED Given a Name node, returns a list of all the assignment statements that incorporate that Name node prior to that line. Returns None if no Name is given. """ if node.ast_name != "Name": return None ast = parse_program(report=report) 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 is_top_level(ast_node, report=MAIN_REPORT) -> bool: """ Determines if the `ast_node` is at the top-level of the program. Correctly handles expression statements (so a print call on its own will be considered a statement, even though its technically an expression). Args: ast_node (pedal.cait.cait_node.CaitNode): The CaitNode to check report (pedal.core.report.Report): Returns: bool: Whether the node is from the top level """ ast = parse_program(report=report) 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 prevent_printing_functions(exceptions=None, report=MAIN_REPORT, **kwargs): if exceptions is None: exceptions = set() elif isinstance(exceptions, str): exceptions = {exceptions} root = parse_program(report=report) defs = root.find_all("FunctionDef") for adef in defs: name = adef._name if name in exceptions: continue for call in find_function_calls("print", root=adef, report=report): location = call.locate() return explain( f"The function {name} is printing on line {location.line}." f" However, that function is not supposed to print.", label="function_is_printing", title="Do Not Print in Function", fields={'name': name}, location=location, priority='syntax') return False
def find_function_calls(name: str, root=None, report=MAIN_REPORT): """ Returns a list of CaitNodes representing all of the function calls that were found. This includes both methods and regular functions. Args: name (str): The name of the function to search. root: A subtree of a parse tree to revert to. report (Report): The name of the Report to refer to. Returns: List[CaitNode]: Relevant call nodes. """ root = root or parse_program(report=report) all_calls = root.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
import pedal click("Imported pedal") from pedal.source import set_source click("Imported source") set_source("a = 0") click("Set source") from pedal.tifa import tifa_analysis click("Imported Tifa") tifa_analysis() click("Ran Tifa") from pedal.cait import parse_program click("Imported cait") ast = parse_program() click("Parsed program") if ast.find_all("Assign"): print(ast.find_all("Assign")) click("Found assignments") from pedal.resolvers import simple click("Imported resolver") print(simple.resolve()) click("Resolved")
def __init__(self, root=None, **kwargs): fields = kwargs.setdefault('fields', {}) report = kwargs.setdefault('report', MAIN_REPORT) fields['root'] = root or parse_program(report=report) super().__init__(**kwargs)
set_source("a = 0") click("Set source") from pedal.tifa import tifa_analysis click("Imported Tifa") tifa_analysis() click("Ran Tifa") from pedal.cait import parse_program click("Imported cait") ast = parse_program() click("Parsed program") if ast.find_all("Assign"): print(ast.find_all("Assign")) click("Found assignments") from pedal.sandbox.sandbox import run student = run() print(student) click("Ran sandbox") from pedal.resolvers import simple click("Imported resolver")
def test_has_loop(question): ast = parse_program() if not ast.find_all("For"): gently("No for loop yet.") else: question.answer()