diff options
-rw-r--r-- | monkey/__init__.py | 2 | ||||
-rw-r--r-- | monkey/edits.py | 59 | ||||
-rwxr-xr-x | monkey/test.py | 5 | ||||
-rw-r--r-- | prolog/util.py | 6 | ||||
-rw-r--r-- | server/prolog_session.py | 6 |
5 files changed, 47 insertions, 31 deletions
diff --git a/monkey/__init__.py b/monkey/__init__.py index 5408061..42e0691 100644 --- a/monkey/__init__.py +++ b/monkey/__init__.py @@ -147,7 +147,7 @@ def fix_hints(code, steps): for h1, h2 in product(hints, hints): if h1 is h2 or h2['start'] <= h1['start']: continue - if h1['id'] == h2['id'] == 'monkey_insert' and h2['start'] - h1['start'] < 3: + if h1['id'] == h2['id'] == 'monkey_insert' and h2['start'] - h1['start'] < 5: hints.remove(h1) hints.remove(h2) hints.append({'id': 'monkey_change', 'start': h1['start'], 'end': h2['end']-1}) diff --git a/monkey/edits.py b/monkey/edits.py index 0f3a3e4..461f16b 100644 --- a/monkey/edits.py +++ b/monkey/edits.py @@ -31,7 +31,7 @@ def get_edits_from_trace(trace, test, id): def add_edit(path, start, end, tree): if start == end: return - if len(end) > 2*len(start): + if len(end) > 3*len(start): return edits[(path, start, end)].add(id) @@ -62,30 +62,42 @@ def get_edits_from_trace(trace, test, id): passed, total = test(code) correct = passed == total submissions.add((code, correct)) + + # Add edits for fragments we started tracking in submissions + # that passed fewer tests. + for start_tree, start_tokens, path, n_tests, pos_start, pos_end in open_edits: + if passed <= n_tests: + continue + end_tokens = tokenize(code[pos_start:pos_end]) + names = {} + start_normal = rename_vars_list(start_tokens, names) + end_normal = rename_vars_list(end_tokens, names) + norm_tree = rename_vars_ast(start_tree, names) + add_edit(path, tuple(start_normal), tuple(end_normal), norm_tree) if correct: # Ignore actions after the first correct version. done = True break - tree = prolog_parse(code) - if tree and tree.leaves() and tree != prev_tree: - for terminals, path in interesting_ranges(tree): - pos_start = terminals[0].pos - pos_end = terminals[-1].pos + len(terminals[-1].val) - # If there is an open edit with the same range, don't add a new one. - found = False - for e_start_tree, e_start_tokens, e_path, e_pos_start, e_pos_end in open_edits: - if e_pos_start == pos_start and e_pos_end == pos_end: - found = True - break - if not found: - #print('OPENING {}'.format(terminals)) - open_edits.append([tree, terminals, path, pos_start, pos_end]) - prev_tree = tree + tree = prolog_parse(code) + if tree and tree.leaves() and tree != prev_tree: + for terminals, path in interesting_ranges(tree): + pos_start = terminals[0].pos + pos_end = terminals[-1].pos + len(terminals[-1].val) + # If there is an open edit with the same range, don't + # add a new one. + found = False + for e_start_tree, e_start_tokens, e_path, n_tests, e_pos_start, e_pos_end in open_edits: + if e_pos_start == pos_start and e_pos_end == pos_end: + found = True + break + if not found: + open_edits.append([tree, terminals, path, passed, pos_start, pos_end]) + prev_tree = tree if action.type in {'insert', 'remove'}: new_open_edits = [] - for start_tree, start_tokens, path, pos_start, pos_end in open_edits: + for start_tree, start_tokens, path, n_tests, pos_start, pos_end in open_edits: new_pos_start, new_pos_end = pos_start, pos_end if action.type == 'remove': if action.offset < pos_end: @@ -102,6 +114,8 @@ def get_edits_from_trace(trace, test, id): new_pos_end += 1 elif action.offset == pos_end: if (prev_action is None or + (start_tokens[0].pos == pos_start and + start_tokens[-1].pos + len(start_tokens[-1].val) == pos_end) or prev_action.type == 'insert' and prev_action.offset == action.offset-1 or prev_action.type == 'remove' and prev_action.offset == action.offset): orig_next = None @@ -112,19 +126,10 @@ def get_edits_from_trace(trace, test, id): if not (orig_next and orig_next.val[0] == action.text): new_pos_end += 1 if new_pos_start != new_pos_end: - new_open_edits.append([start_tree, start_tokens, path, new_pos_start, new_pos_end]) + new_open_edits.append([start_tree, start_tokens, path, n_tests, new_pos_start, new_pos_end]) open_edits = new_open_edits prev_action = action - if done: - for start_tree, start_tokens, path, pos_start, pos_end in open_edits: - end_tokens = tokenize(code[pos_start:pos_end]) - names = {} - start_normal = rename_vars_list(start_tokens, names) - end_normal = rename_vars_list(end_tokens, names) - norm_tree = rename_vars_ast(start_tree, names) - add_edit(path, tuple(start_normal), tuple(end_normal), norm_tree) - return edits, submissions, queries def get_edits_from_solutions(solutions, test): diff --git a/monkey/test.py b/monkey/test.py index 9267782..b88942b 100755 --- a/monkey/test.py +++ b/monkey/test.py @@ -145,8 +145,9 @@ elif sys.argv[2] == 'info': # Print all observed edits and their costs. elif sys.argv[3] == 'edits': for (path, before, after), (cost, uids) in sorted(edits.items(), key=lambda x: x[1]): - print(' {:.4f}\t{} → {}'.format(cost, stringify(before) if before else 'ε', - stringify(after) if after else 'ε')) + print(' {:.4f}\t{}: {} → {}'.format(cost, '▹'.join(path), + stringify(before) if before else 'ε', + stringify(after) if after else 'ε')) # Print all student queries and their counts. elif sys.argv[3] == 'queries': diff --git a/prolog/util.py b/prolog/util.py index e1ce36b..402b920 100644 --- a/prolog/util.py +++ b/prolog/util.py @@ -144,6 +144,12 @@ def rename_vars_ast(root, fixed_names=None): # Yield "interesting" parts of a Prolog AST as lists of tokens. def interesting_ranges(ast, path=()): if ast.label() in {'clause', 'head', 'or', 'if', 'and'}: + if ast.label() == 'clause': + # Special case for clause with one goal. + if len(ast) == 4 and ast[2].label() == 'term': + terminals = ast[2].leaves() + yield terminals, path + (ast.label(), 'and') + if ast.label() == 'and': for i in range(0, len(ast), 2): for j in range(i, len(ast), 2): diff --git a/server/prolog_session.py b/server/prolog_session.py index 6cf6651..6cb1592 100644 --- a/server/prolog_session.py +++ b/server/prolog_session.py @@ -171,8 +171,12 @@ class PrologSession(server.LanguageSession): def _aux_code(self, user_id, problem, program): problem_module = load_problem(problem.language, problem.group, problem.identifier, 'common') used_predicate_identifiers = {make_identifier(name) for name in used_predicates(program)} + # FIXME this currently loads *all* solved predicates, since monkey.fix can + # add goals that require other predicates, which causes the modified + # program to fail if the predicate is not loaded (because it did not appear + # in the initial program). dependencies = {p for p in CodeqUser.solved_problems(user_id, problem.language) - if p[1] != problem.identifier and p[1] in used_predicate_identifiers} + if p[1] != problem.identifier} # TODO and p[1] in used_predicate_identifiers} return ('\n' + solutions_for_problems(problem.language, dependencies) + '\n' + get_facts(problem.language, problem_module)) |