# 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))