# CodeQ: an online programming tutor.
# Copyright (C) 2015 UL FRI
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import os
import pickle
import sys
from termcolor import colored
from db.util import make_identifier
from db.models import Problem, Solution
from . import fix, fix_hints
from prolog.util import parse, stringify, used_predicates
from server.problems import get_facts, load_group, load_problem, solutions_for_problems
from .util import indent
# Get problem id from commandline.
if len(sys.argv) < 2:
print('usage: ' + sys.argv[0] + ' ')
sys.exit(1)
problem = Problem.get(id=sys.argv[1])
problem_name = load_problem(problem.language, problem.group, problem.identifier, 'en').name
problem_module = load_problem(problem.language, problem.group, problem.identifier, 'common')
other_problems = [p for p in Problem.filter_language(problem.language)
if p.identifier != problem.identifier]
facts = get_facts(problem.language, problem_module)
# Testing function.
def test(code):
# Find solutions to other problems that are used by this program.
used_predicate_identifiers = {make_identifier(name) for name in used_predicates(code)}
dependencies = sorted([p[2:] for p in other_problems
if p.identifier in used_predicate_identifiers])
aux_code = '\n' + solutions_for_problems('prolog', dependencies) + '\n' + facts
n_correct, n_all, _ = problem_module.test(code, aux_code)
return n_correct, n_all
traces = [s.trace for s in Solution.filter(problem_id=problem.id)]
# Load hint database stored in edits.pickle.
edits, submissions, queries = pickle.load(open('edits.pickle', 'rb'))
edits, submissions, queries = edits[problem.id], submissions[problem.id], queries[problem.id]
# Load current status (programs for which a hint was found).
try:
done = pickle.load(open('status-'+str(problem.id)+'.pickle', 'rb'))
except:
done = []
def print_hint(code, solution, steps, fix_time, n_tested):
if solution:
print(colored('Hint found! Tested {} programs in {:.1f} s.'.format(n_tested, fix_time), 'green'))
print(colored(' Edits', 'blue'))
for idx, a, b in steps:
print(' {}: {} → {}'.format(idx, stringify(a), stringify(b)))
print(colored(' Hints', 'blue'))
for hint in fix_hints(code, steps):
print(' {}'.format(hint))
print(colored(' Final version', 'blue'))
print(indent(stringify(parse(solution)), 2))
else:
print(colored('Hint not found! Tested {} programs in {:.1f} s.'.format(n_tested, fix_time), 'red'))
# Run interactive loop.
if len(sys.argv) == 2:
while True:
# Read the program from stdin.
print('Enter program, end with empty line:')
code = ''
try:
while True:
line = input()
if not line:
break
code += line + '\n'
except EOFError:
break
# Try finding a fix.
print(colored('Analyzing program…', 'yellow'))
solution, steps, fix_time, n_tested = fix(code, edits, test, debug=True)
print_hint(code, solution, steps, fix_time, n_tested)
# Test fix() on incorrect student submissions.
elif sys.argv[2] == 'test':
timeout = int(sys.argv[3]) if len(sys.argv) == 4 else 10
incorrect = []
for (code, correct), count in sorted(submissions.items()):
# Skip syntactically-incorrect submissions.
if parse(code) is None:
continue
if not correct:
incorrect += [code] * count
print('Fixing {}/{} programs (timeout={})…'.format(
len([code for code in incorrect if code not in done]),
len(incorrect), timeout))
undone = []
for i, program in enumerate(sorted(incorrect)):
if program in done:
done.append(program)
continue
if program in undone:
continue
print(colored('Analyzing program {0}/{1}…'.format(i+1, len(incorrect)), 'yellow'))
solution, steps, fix_time, n_tested = fix(program, edits, test, timeout=timeout, debug=True)
print_hint(program, solution, steps, fix_time, n_tested)
print()
if solution:
done.append(program)
else:
undone.append(program)
pickle.dump(done, open('status-'+str(problem.id)+'.pickle', 'wb'))
print('Found hints for ' + str(len(done)) + ' of ' + str(len(incorrect)) + ' incorrect programs')
# Print info for this problem.
elif sys.argv[2] == 'info':
# With no additional arguments, print some stats.
if len(sys.argv) == 3:
print('Problem {} ({}): {} edits and {} different submissions in {} traces'.format(
problem.id, colored(problem_name, 'yellow'),
colored(str(len(edits)), 'yellow'),
colored(str(len(submissions)), 'yellow'),
colored(str(len(traces)), 'yellow')))
# 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, '▹'.join(path),
stringify(before) if before else 'ε',
stringify(after) if after else 'ε'))
# Print all student queries and their counts.
elif sys.argv[3] == 'queries':
for query, count in queries.most_common():
print(' ' + str(count) + '\t' + query)
# Print all student submissions and their counts.
elif sys.argv[3] == 'submissions':
which = None
if len(sys.argv) > 4:
which = sys.argv[4] == 'good'
for (code, correct), uids in reversed(sorted(submissions.items(), key=lambda x: len(x[1]))):
if which is None or correct == which:
print('{}\t{}'.format(len(uids), code))