summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--monkey/__init__.py2
-rw-r--r--monkey/edits.py59
-rwxr-xr-xmonkey/test.py5
-rw-r--r--prolog/util.py6
-rw-r--r--server/prolog_session.py6
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))