From 34ba119fc56404f6d7e3b010602f752438372347 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Thu, 11 Dec 2014 12:28:02 +0100 Subject: Use abolish/1 instead of erase/1 to remove clauses Using erase/1 keeps the dynamic property for all defined predicates in the testing module. This overrides the clauses in solution modules. For example, if the user defines conc/3 in the solution for the dup/2 problem, the correct conc/3 would not get used even after unloading all user's clauses. This commit also plugs a couple of memory leaks by opening (and later discarding) a new Prolog frame in functions test, load_solution and mark_solved. --- prolog/engine.py | 46 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) (limited to 'prolog') diff --git a/prolog/engine.py b/prolog/engine.py index 7c90d1a..d15704a 100644 --- a/prolog/engine.py +++ b/prolog/engine.py @@ -80,12 +80,13 @@ class PrologEngine(object): # Construct some predicates. self.p = { + 'abolish/1': PL_predicate(b'abolish', 1, None), 'add_import_module/3': PL_predicate(b'add_import_module', 3, None), + 'arg/3': PL_predicate(b'arg', 3, None), 'assertz/1': PL_predicate(b'assertz', 1, None), - 'assertz/2': PL_predicate(b'assertz', 2, None), - 'erase/1': PL_predicate(b'erase', 1, None), 'call_with_time_limit/2': PL_predicate(b'call_with_time_limit', 2, None), 'consult/1': PL_predicate(b'consult', 1, None), + 'functor/3': PL_predicate(b'functor', 3, None), 'message_to_string/2': PL_predicate(b'message_to_string', 2, None), 'read_term_from_atom/3': PL_predicate(b'read_term_from_atom', 3, None), 'safe_goal/1': PL_predicate(b'safe_goal', 1, None), @@ -131,7 +132,8 @@ class PrologEngine(object): return error_str - # Call the Prolog predicate [name]. Raise an exception on error. + # Call the Prolog predicate [name]. Raise an exception on error. Since this + # creates a Termv object, it should be called within an open foreign frame. def call(self, name, args): qid = PL_open_query(None, self.err_flags, self.p[name], Termv(args).ref) try: @@ -199,15 +201,40 @@ class PrologEngine(object): # Loads the correct solution [code] to problem [pid]. # TODO handle library loading. def load_solution(self, pid, code): + fid = PL_open_foreign_frame() module = 'solution{}'.format(pid) for rule in prolog.util.split(code): self.call('assertz/1', [Term('{}:({})'.format(module, rule))]) + PL_discard_foreign_frame(fid) # Import the correct solution for problem [pid] into module for user [uid]. def mark_solved(self, uid, pid): + fid = PL_open_foreign_frame() m_user = 'user{}'.format(uid) m_solution = 'solution{}'.format(pid) return self.call('add_import_module/3', [Term(m_user), Term(m_solution), Term('end')]) + PL_discard_foreign_frame(fid) + + # Return the main functor defined by [clause], e.g. dup/2. + def predicate_indicator(self, clause): + # Return the main functor for [term]. + def main_functor(term): + name = Term() + arity = Term() + self.call('functor/3', [term, name, arity]) + return '{}/{}'.format(name, arity) + + fid = PL_open_foreign_frame() + clause = Term(clause) + functor = main_functor(clause) + # Check whether [clause] is a rule or a fact. + if functor == ':-/2': + # [clause] is a rule, return the main functor for the head. + head = Term() + self.call('arg/3', [Term(1), clause, head]) + functor = main_functor(head) + PL_discard_foreign_frame(fid) + return functor # Test whether [code] gives the same answer to [query] as the correct # solution. The solution for problem [pid] should be loaded beforehand. @@ -216,6 +243,7 @@ class PrologEngine(object): m_user = 'user{}'.format(uid) m_solution = 'solution{}'.format(pid) + fid = PL_open_foreign_frame() # Find the correct answers if not already known. for query in queries: if (pid, query) not in self.answers: @@ -225,13 +253,12 @@ class PrologEngine(object): self.answers[(pid, query)] = result[0]['X'] # TODO maybe we could check all vars? correct = True - refs = [] + predicates = set() try: # Load the user program [code]. for rule in prolog.util.split(code): - ref = Term() - if self.call('assertz/2', [Term('{}:({})'.format(m_user, rule)), ref]): - refs.append(ref) + self.call('assertz/1', [Term('{}:({})'.format(m_user, rule))]) + predicates.add(self.predicate_indicator(rule)) for i, query in enumerate(queries): result = self.query(query, m_user) @@ -240,8 +267,9 @@ class PrologEngine(object): correct = False # Unload all loaded rules. - for ref in refs: - self.call('erase/1', [ref]) + for predicate in predicates: + self.call('abolish/1', [Term(m_user + ':' + predicate)]) + PL_discard_foreign_frame(fid) return correct -- cgit v1.2.1