def find_matches(self, ast_or_code, filename="__main__", check_meta=True): """ Args: ast_or_code (str or AstNode): The students' code or a valid AstNode from `ast.parse`. If the code has invalid syntax, a SyntaxError will be raised. filename (str): The filename to parse with - only used for error reporting. check_meta (bool): Determine if the nodes came from the same AST field. Returns: list[AstMap]: A list of AstMaps that are suitable matches. """ if isinstance(ast_or_code, str): other_tree = CaitNode(ast.parse(ast_or_code, filename), report=self.report) elif isinstance(ast_or_code, CaitNode): other_tree = ast_or_code else: other_tree = CaitNode(ast_or_code, "none", report=self.report) explore_root = self.root_node if self.root_node is not None: while (len(explore_root.children) == 1 and explore_root.ast_name in ["Expr", "Module"]): explore_root = explore_root.children[0] explore_root.field = "none" return self.any_node_match(explore_root, other_tree, check_meta=check_meta)
def find_matches(self, ast_or_code, filename="__main__", check_meta=True, use_previous=None): """ Args: use_previous: ast_or_code (str or AstNode): The students' code or a valid AstNode from `ast.parse`. If the code has invalid syntax, a SyntaxError will be raised. filename (str): The filename to parse with - only used for error reporting. check_meta (bool): Determine if the nodes came from the same AST field. Returns: list[AstMap]: A list of AstMaps that are suitable matches. """ if isinstance(ast_or_code, str): other_tree = CaitNode(ast.parse(ast_or_code, filename), report=self.report) elif isinstance(ast_or_code, CaitNode): other_tree = ast_or_code else: other_tree = CaitNode(ast_or_code, _NONE_FIELD, report=self.report) explore_root = self.root_node trim_set = ["Expr", "Module"] explore_root_old_field = explore_root.field if self.root_node is not None: # Trimming ins_node while (len(explore_root.children) == 1 and explore_root.ast_name in trim_set): explore_root.field = explore_root_old_field explore_root = explore_root.children[0] explore_root_old_field = explore_root.field explore_root.field = _NONE_FIELD other_root = other_tree other_root_old_field = other_root.field if other_root is not None: # Trimming std_node while len(other_root.children ) == 1 and other_root.ast_name in trim_set: other_root.field = other_root_old_field other_root = other_root.children[0] other_root_old_field = other_root.field other_root.field = _NONE_FIELD matches = self.any_node_match(explore_root, other_root, check_meta=check_meta, use_previous=use_previous) explore_root.field = explore_root_old_field other_root.field = other_root_old_field return matches
def __init__(self, ast_or_code, report, filename="__main__"): """ The StretchyTreeMatcher is used to compare a pattern against some student code. It produces a set of potential mappings between them. Args: ast_or_code (str or AstNode): The students' code or a valid AstNode from `ast.parse`. If the code has invalid syntax, a SyntaxError will be raised. filename (str): The filename to parse with - only used for error reporting. report (Report): A report to obtain data from. """ self.report = report if isinstance(ast_or_code, str): ast_node = ast.parse(ast_or_code, filename) else: ast_node = ast_or_code # Build up root if ast_node is None: self.root_node = None elif isinstance(ast_node, CaitNode): self.root_node = ast_node else: self.root_node = CaitNode(ast_node, "none", report=self.report)
def parse_type_value(value, parse_strings=False): if isinstance(value, str): if parse_strings: return parse_type(CaitNode(ast.parse(value).body[0].value)) else: return repr(value) elif value in (int, str, bool, float, list, dict, object): return value.__name__ elif value is None: return "None" elif isinstance(value, list): if value == []: return "[]" else: return "[{}]".format(parse_type_value(value[0])) elif isinstance(value, tuple): if value == (): return "()" else: return "({})".format("".join( ["{}, ".format(parse_type_value(v)) for v in value])) elif isinstance(value, dict): if value == {}: return "{}" else: return "{" + (", ".join([ "{}: {}".format(parse_type_value(k), parse_type_value(v)) for k, v in value.items() ])) + "}"
def reparse_if_needed(student_code=None, report=MAIN_REPORT): """ Retrieves the current report for CAIT. If there is no CAIT report, it will generate one. If source code is given, that will be used instead of the report's submission. Args: student_code (str): The code to parse into the a CaitNode tree. If None, then it will use the code in the report's submission. report (Report): The report to attach data to. Returns: dict: Returns the Cait Report """ cait = report[TOOL_NAME] if student_code is not None: if student_code in cait['cache']: cait['ast'] = cait['cache'][student_code] return cait else: student_ast = _parse_source(student_code, report=report) else: student_code = report.submission.main_code # Have we already parsed this code? if student_code in cait['cache']: cait['ast'] = cait['cache'][student_code] return cait # Try to steal parse from Source module, if available if report[SOURCE_TOOL_NAME]['success']: student_ast = report[SOURCE_TOOL_NAME]['ast'] else: student_ast = _parse_source(student_code, report=report) cait['ast'] = cait['cache'][student_code] = CaitNode(student_ast, report=report) return cait
def _load_cait(student_code, report): """ Retrieves the current report for CAIT. If there is no CAIT report, it will generate one. If source code is given, that will be used instead of the report's source code. Args: student_code (str): The code to parse into the a CaitNode tree. If None, then it will use the code in the report's Source tool. report (Report): The report to attach data to. Returns: dict: Returns the Cait Report """ if 'cait' not in report: report['cait'] = { 'success': True, 'error': None, 'ast': None, 'cache': {} } cait = report['cait'] if student_code is not None: if student_code in cait['cache']: cait['ast'] = cait['cache'][student_code] return cait else: student_ast = _parse_source(student_code, cait) elif report['source']['success']: student_code = report['source']['code'] if student_code in cait['cache']: cait['ast'] = cait['cache'][student_code] return cait else: student_ast = report['source']['ast'] else: report.attach("Unparsable Source", tool='cait', category='analyzer') cait['success'] = False cait['ast'] = CaitNode(ast.parse(""), report=report) return cait cait['ast'] = cait['cache'][student_code] = CaitNode(student_ast, report=report) return cait
def __init__(self, ast_or_code, report, filename="__main__"): self.report = report if isinstance(ast_or_code, str): ast_node = ast.parse(ast_or_code, filename) else: ast_node = ast_or_code # Build up root if ast_node is None: self.root_node = None elif isinstance(ast_node, CaitNode): self.root_node = ast_node else: self.root_node = CaitNode(ast_node, _NONE_FIELD, report=self.report)