add tabling library, update README, make Cargo.toml non-local
This commit is contained in:
389
src/prolog/lib/tabling/batched_worklist.pl
Normal file
389
src/prolog/lib/tabling/batched_worklist.pl
Normal file
@@ -0,0 +1,389 @@
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
|
||||
Jan Wielemaker (SWI-Prolog port)
|
||||
Copyright (c) 2016, Benoit Desouter
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- module(batched_worklist,
|
||||
[ wkl_add_answer/2, % +WorkList, +Answer
|
||||
wkl_add_suspension/2, % +Worklist, +Suspension
|
||||
wkl_new_worklist/2, % +TableID, -WorkList
|
||||
unset_flag_executing_all_work/1, % +WorkList
|
||||
unset_global_worklist_presence_flag/1, % +WorkList
|
||||
set_flag_executing_all_work/1, % +WorkList
|
||||
wkl_p_get_rightmost_inner_answer_cluster_pointer/2, % +WorkList, -Cluster
|
||||
wkl_p_swap_answer_continuation/3, % +WorkList, +Cluster1, +Cluster2
|
||||
wkl_worklist_work_done/1 % +WorkList
|
||||
]).
|
||||
|
||||
:- use_module(global_worklist).
|
||||
:- use_module(double_linked_list).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
:- attribute executing_all_work/1, worklist_presence/1, wkl_answer_cluster/1, wkl_suspension_cluster/1, wkl_answer_cluster_pointer_flag/1.
|
||||
|
||||
/** <module> Tabling Worklist management
|
||||
|
||||
A batched worklist: a worklist that clusters suspensions and answers as
|
||||
much as possible. The idea is to minimize the number of swaps. This
|
||||
should be more efficient than the worklist implementation without
|
||||
clustering.
|
||||
|
||||
Argument positions for nb_setarg:
|
||||
|
||||
1. double linked list
|
||||
2. pointer to the list entry of the rightmost inner answer cluster
|
||||
3. flag indicating the execution of wkl_unfolded_do_all_work
|
||||
4. flag indicating whether the table identifier associated with this
|
||||
worklist is already in the global worklist. This is because more
|
||||
than one answer can be added due to the execution of other
|
||||
worklists. 5: table identifier for the table this worklist belongs
|
||||
to
|
||||
|
||||
Contents of a batched worklist:
|
||||
|
||||
- wkl_answer_cluster([Answer|RestAnswers]).
|
||||
- wkl_suspension([Suspension|RestSuspension]).
|
||||
|
||||
The difficulty is that you should not add new entries to a cluster once
|
||||
you started its execution. Probably the simplest way to do so is by
|
||||
swapping the answer cluster AC and suspension cluster SC before you take
|
||||
the cartesian product of all answers in AC with all suspensions in SC.
|
||||
|
||||
Illustration why you may need a complex procedure for finding the future
|
||||
rightmost inner answer cluster.
|
||||
|
||||
Assume all clusters have 2 entries.
|
||||
|
||||
1. AA1 CC1
|
||||
2. AA2 CC1 AA1 CC2 (swapped AA1 and CC1)
|
||||
3. AA2 CC1 CC2 AA1 (swapped AA1 and CC2)
|
||||
4. AA3 CC1 AA2 CC2 AA1 CC3 (swapped AA2 and CC1)
|
||||
|
||||
Now AA1 is the RIAC, but AA2 is the future RIAC.
|
||||
|
||||
Can you find the future RIAC smarter than by walking back? If you don't,
|
||||
then it doesn't make sense to use a future RIAC at all. You could use a
|
||||
stack, which should not grow too large because you use batches. But
|
||||
walking back also should not take too long, since you use batches.
|
||||
|
||||
So let's not use a future RIAC in the first place, and just walk back
|
||||
when we need a new RIAC. This is easy to implement, hence we can test
|
||||
more quickly.
|
||||
|
||||
Abbreviations:
|
||||
|
||||
- RIAC = rightmost inner answer cluster
|
||||
- FUTRIAC = future rightmost inner answer cluster
|
||||
*/
|
||||
|
||||
%% wkl_new_worklist(+TableID, -WorkList) is det.
|
||||
%
|
||||
% Create a new worklist for TableID and add it to the global
|
||||
% worklist list (global variable `table_global_worklist`.
|
||||
|
||||
wkl_new_worklist(TableIdentifier, wkl_worklist(List,AnswerClusterPointerFlag,ExecutingAllWork,WorklistPresence,TableIdentifier)) :-
|
||||
dll_new_double_linked_list(List),
|
||||
put_atts(AnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(List)),
|
||||
% We set the RIAC to the dummy element at the start of the double linked list, which is List.
|
||||
% Don't set all the rest for now.
|
||||
put_atts(ExecutingAllWork, executing_all_work(false)),
|
||||
put_atts(WorklistPresence, worklist_presence(true)),
|
||||
add_to_global_worklist(TableIdentifier).
|
||||
|
||||
%% wkl_worklist_work_done(+WorkList) is semidet.
|
||||
%
|
||||
% The work is done if the RIAC pointer points to the unused cell
|
||||
% at the beginning. The work is also done if the RIAC pointer
|
||||
% points to the sole answer cluster in a list dll_start -
|
||||
% wkl_answer_cluster, because in that case there are no
|
||||
% suspensions to swap with. This is a special case, which we only
|
||||
% discovered by testing. You can detect it by checking whether the
|
||||
% NEXT-pointer of the RIAC is the dummy pointer.
|
||||
|
||||
wkl_worklist_work_done(Worklist) :-
|
||||
wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,RiacPointer),
|
||||
( wkl_is_dummy_pointer(Worklist,RiacPointer) ->
|
||||
true
|
||||
;
|
||||
dll_get_pointer_to_next(RiacPointer,NextPointer),
|
||||
wkl_is_dummy_pointer(Worklist,NextPointer)
|
||||
).
|
||||
|
||||
set_flag_executing_all_work(wkl_worklist(_,_,ExecutingAllWork,_,_)) :-
|
||||
put_atts(ExecutingAllWork, executing_all_work(true)).
|
||||
|
||||
unset_flag_executing_all_work(wkl_worklist(_,_,ExecutingAllWork,_,_)) :-
|
||||
put_atts(ExecutingAllWork, executing_all_work(false)).
|
||||
|
||||
% Swap answer cluster and the adjacent continuation cluster.
|
||||
% Mode: + + -
|
||||
wkl_p_swap_answer_continuation(Worklist,InnerAnswerClusterPointer,SuspensionClusterPointer) :-
|
||||
% You can have a worklist containing only an answer cluster, but no continuations.
|
||||
% In that case SuspensionClusterPointer will be dll_start. We must take our precautions elsewhere.
|
||||
% Do not forget that the list of answers and the list of suspensions is wrapped in a predicate!
|
||||
dll_get_pointer_to_next(InnerAnswerClusterPointer,SuspensionClusterPointer),
|
||||
% For reasons of speed we don't use dll_swap: we only swap adjacent elements and we can be sure that they are in the order A,B.
|
||||
% Therefore we can use dll_p_swap_adjacent_elements_
|
||||
dll_p_swap_adjacent_elements_(InnerAnswerClusterPointer,SuspensionClusterPointer),
|
||||
% Update the necessary pointers
|
||||
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer).
|
||||
|
||||
% Update the pointer if the answer cluster it points to is no longer the rightmost inner answer cluster.
|
||||
wkl_p_update_righmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer) :-
|
||||
( wkl_p_answer_cluster_currently_moved_completely(Worklist,InnerAnswerClusterPointer) ->
|
||||
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,InnerAnswerClusterPointer,NewRiacPointer),
|
||||
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,NewRiacPointer)
|
||||
;
|
||||
true
|
||||
).
|
||||
|
||||
% Rationale for this implementation: see the top of the file.
|
||||
% Unify NewRiacPointer to the first pointer satisfying the following conditions:
|
||||
% - left of StartPointer (when viewing the list as DUMMY-ELEM POINTER POINTER POINTER START-POINTER)
|
||||
% - either an anwer pointer or the dummy element
|
||||
% When StartPointer is the dummy element, NewRiacPointer is also the dummy element. We never look "in front of" the dummy element.
|
||||
wkl_p_find_new_rightmost_inner_answer_cluster_pointer(Worklist,StartPointer,NewRiacPointer) :-
|
||||
( wkl_is_dummy_pointer(Worklist,StartPointer) ->
|
||||
NewRiacPointer = StartPointer
|
||||
;
|
||||
dll_get_pointer_to_previous(StartPointer,FirstCandidatePointer),
|
||||
wkl_p_find_new_riac_helper(Worklist,FirstCandidatePointer,NewRiacPointer)
|
||||
).
|
||||
|
||||
wkl_p_find_new_riac_helper(Worklist,CandidatePointer,NewRiacPointer) :-
|
||||
( is_answer_cluster_or_dummy_pointer(Worklist,CandidatePointer) ->
|
||||
NewRiacPointer = CandidatePointer
|
||||
;
|
||||
dll_get_pointer_to_previous(CandidatePointer,NewCandidate),
|
||||
wkl_p_find_new_riac_helper(Worklist,NewCandidate,NewRiacPointer)
|
||||
).
|
||||
|
||||
is_answer_cluster_or_dummy_pointer(Worklist,Pointer) :-
|
||||
( wkl_is_dummy_pointer(Worklist,Pointer) ->
|
||||
true
|
||||
;
|
||||
wkl_p_dereference_pointer(Worklist,Pointer,A),
|
||||
wkl_p_is_answer_cluster(A)
|
||||
).
|
||||
|
||||
% Failure-driven loop
|
||||
wkl_clusters_cartesian_product(AnswerCluster,SuspensionCluster) :-
|
||||
( member(Answer,AnswerCluster),
|
||||
member(Suspension,SuspensionCluster),
|
||||
% The meat
|
||||
run_worklist_helper(Suspension,Answer),
|
||||
% Trigger loop
|
||||
fail
|
||||
;
|
||||
% Loop base case
|
||||
true
|
||||
).
|
||||
|
||||
run_worklist_helper(_Suspension, _Answer) :- % FIXME: just silense
|
||||
throw('not implemented').
|
||||
|
||||
wkl_both_flags_unset(wkl_worklist(_Dll,_Riac,ExecutingAllWork,WorklistPresence,_TableIdentifier)) :-
|
||||
put_atts(ExecutingAllWork, executing_all_work(false)),
|
||||
put_atts(WorklistPresence, worklist_presence(false)).
|
||||
|
||||
set_global_worklist_presence_flag(wkl_worklist(_,_,_,WorklistPresence,_)) :-
|
||||
put_atts(WorklistPresence, worklist_presence(true)).
|
||||
|
||||
unset_global_worklist_presence_flag(wkl_worklist(_,_,_,WorklistPresence,_)) :-
|
||||
put_atts(WorklistPresence, worklist_presence(false)).
|
||||
|
||||
potentially_add_to_global_worklist(Worklist) :-
|
||||
( wkl_both_flags_unset(Worklist) ->
|
||||
% Set the flag for presence in the metaworklist
|
||||
set_global_worklist_presence_flag(Worklist),
|
||||
% Should add to the metaworklist
|
||||
arg(5,Worklist,TableIdentifier),
|
||||
add_to_global_worklist(TableIdentifier)
|
||||
;
|
||||
% Nothing to do.
|
||||
true
|
||||
).
|
||||
|
||||
wkl_add_answer(Worklist,Answer) :-
|
||||
% Add to global worklist if not executing during wkl_unfolded_do_all_work and not there yet as well.
|
||||
potentially_add_to_global_worklist(Worklist),
|
||||
( wkl_p_leftmost_cluster_is_answer_cluster(Worklist) ->
|
||||
wkl_add_to_existing_answer_cluster(Worklist,Answer)
|
||||
% If you add to an existing cluster, then obviously you should not change the RIAC.
|
||||
;
|
||||
wkl_add_to_new_answer_cluster(Worklist,Answer,AnswerClusterPointer),
|
||||
% If the RIAC is the dummy pointer, we need to change that.
|
||||
wkl_p_update_rightmost_inner_answer_cluster_pointer(Worklist,AnswerClusterPointer)
|
||||
).
|
||||
|
||||
wkl_p_update_rightmost_inner_answer_cluster_pointer(Worklist,NewAnswerClusterPointer) :-
|
||||
wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,CurrentRiac),
|
||||
( wkl_is_dummy_pointer(Worklist,CurrentRiac) -> %% <- debugging this.
|
||||
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,NewAnswerClusterPointer)
|
||||
;
|
||||
% Nothing to do.
|
||||
true
|
||||
).
|
||||
|
||||
wkl_add_suspension(Worklist,Suspension) :-
|
||||
% Add to global worklist if not executing during wkl_unfolded_do_all_work and not there yet as well.
|
||||
potentially_add_to_global_worklist(Worklist),
|
||||
( wkl_p_rightmost_cluster_is_suspension_cluster(Worklist) ->
|
||||
wkl_add_to_existing_suspension_cluster(Worklist,Suspension)
|
||||
;
|
||||
wkl_add_to_new_suspension_cluster(Worklist,Suspension,SuspensionClusterPointer),
|
||||
% If added to a new suspension cluster, we may need to change the righmost inner answer pointer
|
||||
wkl_p_potential_rias_update_add_contin(Worklist,SuspensionClusterPointer)
|
||||
).
|
||||
|
||||
% This predicate should not fail.
|
||||
wkl_p_potential_rias_update_add_contin(Worklist,SuspensionClusterPointer) :-
|
||||
% Look back one entry of the freshly inserted SuspensionClusterPointer
|
||||
dll_get_pointer_to_previous(SuspensionClusterPointer,PotentialNewRiacPointer),
|
||||
( wkl_p_is_answer_cluster_pointer(Worklist,PotentialNewRiacPointer) ->
|
||||
% We must indeed update the rightmost inner answer cluster pointer.
|
||||
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,PotentialNewRiacPointer)
|
||||
;
|
||||
% Nothing to do, but we should not fail.
|
||||
true
|
||||
).
|
||||
|
||||
wkl_add_to_existing_answer_cluster(Worklist, Answer) :-
|
||||
arg(1,Worklist,Dll),
|
||||
dll_get_pointer_to_next(Dll,AnswerClusterPointer),
|
||||
wkl_p_dereference_pointer(Worklist,AnswerClusterPointer,AnswerCluster),
|
||||
AnswerCluster = wkl_answer_cluster(AnswersFlag),
|
||||
get_atts(AnswersFlag, wkl_answer_cluster(AnswersAlreadyInCluster)),
|
||||
put_atts(AnswersFlag, wkl_answer_cluster([Answer|AnswersAlreadyInCluster])).
|
||||
|
||||
wkl_add_to_new_answer_cluster(
|
||||
wkl_worklist(Dll,_Ria,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier),
|
||||
Answer,AnswerClusterPointer
|
||||
) :-
|
||||
dll_append_left(Dll,wkl_answer_cluster(AnswerFlag),AnswerClusterPointer),
|
||||
put_atts(AnswerFlag, wkl_answer_cluster([Answer])).
|
||||
|
||||
wkl_add_to_existing_suspension_cluster(Worklist, Suspension) :-
|
||||
arg(1,Worklist,Dll),
|
||||
dll_get_pointer_to_previous(Dll,SuspensionClusterPointer),
|
||||
wkl_p_dereference_pointer(Worklist,SuspensionClusterPointer,SuspensionCluster),
|
||||
SuspensionCluster = wkl_suspension_cluster(SuspensionsFlag),
|
||||
get_atts(SuspensionsFlag, wkl_suspension_cluster(SuspensionsAlreadyInCluster)),
|
||||
put_atts(SuspensionsFlag, wkl_suspension_cluster([Suspension|SuspensionsAlreadyInCluster])).
|
||||
%% nb_linkarg(1,SuspensionCluster,[Suspension|SuspensionsAlreadyInCluster]).
|
||||
|
||||
wkl_add_to_new_suspension_cluster(
|
||||
wkl_worklist(Dll,_Ria,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier),
|
||||
Suspension,
|
||||
SuspensionClusterPointer
|
||||
) :-
|
||||
put_atts(SuspensionFlag, wkl_suspension_cluster([Suspension])),
|
||||
dll_append_right(Dll,wkl_suspension_cluster(SuspensionFlag),SuspensionClusterPointer).
|
||||
|
||||
wkl_p_is_answer_cluster(CandidateAnswerCluster) :-
|
||||
nonvar(CandidateAnswerCluster),
|
||||
CandidateAnswerCluster = wkl_answer_cluster(_).
|
||||
|
||||
wkl_p_is_suspension_cluster(CandidateSuspensionCluster) :-
|
||||
nonvar(CandidateSuspensionCluster),
|
||||
CandidateSuspensionCluster = wkl_suspension_cluster(_).
|
||||
|
||||
wkl_p_leftmost_cluster_is_answer_cluster(Worklist) :-
|
||||
arg(1,Worklist,Dll),
|
||||
dll_get_pointer_to_next(Dll,CandidateAnswerClusterPointer),
|
||||
wkl_p_is_answer_cluster_pointer(Worklist,CandidateAnswerClusterPointer).
|
||||
|
||||
wkl_p_rightmost_cluster_is_suspension_cluster(Worklist) :-
|
||||
arg(1,Worklist,Dll),
|
||||
dll_get_pointer_to_previous(Dll,CandidateSuspensionClusterPointer),
|
||||
wkl_p_is_suspension_cluster_pointer(Worklist,CandidateSuspensionClusterPointer).
|
||||
|
||||
|
||||
wkl_p_get_rightmost_inner_answer_cluster_pointer(wkl_worklist(_Dll,InnerAnswerClusterPointerFlag,_FlagExecutingWork,_AlreadyInMetaworklist,_TableIdentifier), InnerAnswerClusterPointer) :-
|
||||
get_atts(InnerAnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(InnerAnswerClusterPointer)).
|
||||
|
||||
% Succeed if there are currently no more continuation clusters on the right of the given position:
|
||||
% Why 'currently' in the name? Another continuation can be added.
|
||||
wkl_p_answer_cluster_currently_moved_completely(Worklist,AnswerClusterPointer) :-
|
||||
( wkl_p_at_right(Worklist,AnswerClusterPointer) ->
|
||||
true
|
||||
;
|
||||
wkl_p_answer_cluster_on_right(Worklist,AnswerClusterPointer)
|
||||
).
|
||||
|
||||
% Succeeds if the given pointer points to the last element in the list. That is, if its next pointer is the dummy element in the double linked list.
|
||||
wkl_p_at_right(Worklist,Pointer) :-
|
||||
dll_get_pointer_to_next(Pointer,NextPointer),
|
||||
wkl_is_dummy_pointer(Worklist,NextPointer).
|
||||
|
||||
wkl_p_answer_cluster_on_right(Worklist,Pointer) :-
|
||||
dll_get_pointer_to_next(Pointer,NextPointer),
|
||||
wkl_p_is_answer_cluster_pointer(Worklist,NextPointer).
|
||||
|
||||
wkl_is_dummy_pointer(Worklist,Pointer) :-
|
||||
wkl_p_get_double_linked_list(Worklist,Dll),
|
||||
dll_is_dummy_pointer(Dll,Pointer).
|
||||
|
||||
wkl_p_is_answer_cluster_pointer(Worklist,PointerCandidateAnswerCluster) :-
|
||||
( wkl_is_dummy_pointer(Worklist,PointerCandidateAnswerCluster) ->
|
||||
% Certainly not an answer cluster, should not dereference this
|
||||
fail
|
||||
;
|
||||
wkl_p_dereference_pointer(Worklist,PointerCandidateAnswerCluster,CandidateAnswerCluster),
|
||||
wkl_p_is_answer_cluster(CandidateAnswerCluster)
|
||||
).
|
||||
|
||||
wkl_p_is_suspension_cluster_pointer(Worklist,PointerCandidateSuspensionCluster) :-
|
||||
( wkl_is_dummy_pointer(Worklist,PointerCandidateSuspensionCluster) ->
|
||||
% Certainly not an answer cluster, should not dereference this
|
||||
fail
|
||||
;
|
||||
wkl_p_dereference_pointer(Worklist,PointerCandidateSuspensionCluster,CandidateSuspensionCluster),
|
||||
wkl_p_is_suspension_cluster(CandidateSuspensionCluster)
|
||||
).
|
||||
|
||||
wkl_p_get_double_linked_list(Worklist,Dll) :-
|
||||
arg(1,Worklist,Dll).
|
||||
|
||||
% One should not attempt to dereference the dummy pointer in the double linked list.
|
||||
wkl_p_dereference_pointer(_Worklist,Pointer,Data) :-
|
||||
dll_get_data(Pointer,Data).
|
||||
|
||||
% SETTING POINTERS
|
||||
%%%%%%%%%%%%%%%%%%
|
||||
|
||||
wkl_p_set_rightmost_inner_answer_cluster_pointer(Worklist,AnswerClusterPointer) :-
|
||||
arg(2, Worklist, AnswerClusterPointerFlag),
|
||||
put_atts(AnswerClusterPointerFlag, wkl_answer_cluster_pointer_flag(AnswerClusterPointer)).
|
||||
211
src/prolog/lib/tabling/double_linked_list.pl
Normal file
211
src/prolog/lib/tabling/double_linked_list.pl
Normal file
@@ -0,0 +1,211 @@
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
|
||||
Jan Wielemaker (SWI-Prolog port)
|
||||
Copyright (c) 2016, Benoit Desouter
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- module(double_linked_list,
|
||||
[ dll_new_double_linked_list/1, % -List
|
||||
dll_append_right/2, % !List, +Element
|
||||
dll_append_left/2, % !List, +Element
|
||||
dll_append_right/3, % !List, +Element, -Pointer
|
||||
dll_append_left/3, % !List, +Element, -Pointer
|
||||
dll_get_data/2, % +List, -Head
|
||||
dll_get_pointer_to_next/2, % +List, -Pointer
|
||||
dll_get_pointer_to_previous/2, % +List, -Pointer
|
||||
dll_is_dummy_pointer/2, % +List, +Pointer
|
||||
dll_p_swap_adjacent_elements_/2, % +Pointer1, +Pointer2
|
||||
dll_get_contents/2,
|
||||
dll_get_reverse_contents/2
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
|
||||
:- attribute dll_element/1, dll_next/1, dll_prev/1.
|
||||
|
||||
% A circular double linked list
|
||||
% =============================
|
||||
|
||||
% Always have a unused-cell at the beginning.
|
||||
|
||||
% I do not always inline unifications because the head is then more readable for users who don't need to know the details.
|
||||
|
||||
% Due to lack of modules in hProlog, the following predicate names should not be used elsewhere:
|
||||
% - the heads of all following rules (starting with dll_, I reserve "the namespace"!)
|
||||
|
||||
% dll_cell(Element,Next,Previous)
|
||||
|
||||
% The following is perhaps odd:
|
||||
%
|
||||
% Next link = more to the front (the left)
|
||||
% Previous link = more to the back (the right)
|
||||
%
|
||||
% List structure
|
||||
% --------------
|
||||
% front-of-the-list | ... | back-of-the-list
|
||||
|
||||
dll_new_double_linked_list(List) :-
|
||||
% Nonused cell dll_start at the beginning, points to itself (this is easy when adding elements).
|
||||
List = dll_cell(Start),
|
||||
put_atts(Start, [dll_next(List), dll_prev(List), dll_element(dll_start)]).
|
||||
|
||||
dll_append_right(List, Element) :-
|
||||
dll_append_right(List, Element, _).
|
||||
|
||||
dll_append_left(List, Element) :-
|
||||
dll_append_left(List, Element, _).
|
||||
|
||||
% Append at the back of the list
|
||||
% Mode: + + -
|
||||
dll_append_right(List, Element, Pointer) :-
|
||||
% Get pointer to cell currently at the back. Done by taking the previous element from the unused element representing the list.
|
||||
dll_get_pointer_to_previous(List, OldBack),
|
||||
% Make the new cell point to OldBack as predecessor
|
||||
% Make the new cell point to the unused cell as successor.
|
||||
Pointer = dll_cell(NewCell),
|
||||
put_atts(NewCell, [dll_element(Element), dll_next(List), dll_prev(OldBack)]),
|
||||
% Make OldBack point to the new cell as successor
|
||||
dll_p_set_next_pointer(OldBack, Pointer),
|
||||
% Make the unused cell point to the new cell as predecessor
|
||||
dll_p_set_previous_pointer(List, Pointer).
|
||||
|
||||
% Add to the front of the list
|
||||
% Mode: + + -
|
||||
dll_append_left(List, Element, Pointer) :-
|
||||
% Get pointer to cell currently at the front. Done by taking the next element from the unused element representing the list.
|
||||
dll_get_pointer_to_next(List, OldFront),
|
||||
% Make the new cell point to OldFront as successor
|
||||
% Make the new cell point to the unused cell as predecessor
|
||||
Pointer = dll_cell(NewCell),
|
||||
put_atts(NewCell, [dll_element(Element), dll_prev(List), dll_next(OldFront)]),
|
||||
% Make OldFront point to the new cell as predecessor
|
||||
dll_p_set_previous_pointer(OldFront, Pointer),
|
||||
% Make the unused cell point to the new cell as successor
|
||||
dll_p_set_next_pointer(List, Pointer).
|
||||
|
||||
% get_next_cell?
|
||||
dll_get_pointer_to_next(dll_cell(Cell), PointerNext) :-
|
||||
get_atts(Cell, dll_next(PointerNext)).
|
||||
|
||||
% get_previous_cell?
|
||||
dll_get_pointer_to_previous(dll_cell(Cell), PointerPrevious) :-
|
||||
get_atts(Cell, dll_prev(PointerPrevious)).
|
||||
|
||||
% Will happily give you the "data" from the unused cell at the beginning. (We use this odd behaviour below, f.e. in dll_p_foreach_element_/2.)
|
||||
dll_get_data(dll_cell(Cell), Element) :-
|
||||
get_atts(Cell, dll_element(Element)).
|
||||
|
||||
dll_is_dummy_pointer(List, Pointer) :-
|
||||
dll_get_contents(List, ListContents),
|
||||
dll_get_contents(Pointer, PointerContents),
|
||||
\+ PointerContents \= ListContents.
|
||||
|
||||
% Special case of swapping - used in dll_swap/2.
|
||||
% This is also the case used for swapping a freshly created list with itself.
|
||||
%
|
||||
% Sketch: APrevious <-> PointerA <-> PointerB <-> BNext etc.
|
||||
dll_p_swap_adjacent_elements(PointerA, PointerB) :-
|
||||
% Order B A?
|
||||
( dll_get_pointer_to_next(PointerB, PointerA) ->
|
||||
dll_p_swap_adjacent_elements_(PointerB, PointerA)
|
||||
;
|
||||
% Order A B!
|
||||
dll_p_swap_adjacent_elements_(PointerA, PointerB)
|
||||
).
|
||||
|
||||
% Assumes the order A B.
|
||||
dll_p_swap_adjacent_elements_(PointerA, PointerB) :-
|
||||
% Get A's previous and B's next
|
||||
dll_get_pointer_to_previous(PointerA, PointerAPrevious),
|
||||
dll_get_pointer_to_next(PointerB, PointerBNext),
|
||||
% Set A's previous to B
|
||||
dll_p_set_previous_pointer(PointerA, PointerB),
|
||||
% Set B's next to A
|
||||
dll_p_set_next_pointer(PointerB, PointerA),
|
||||
% Set A's next to BNext
|
||||
dll_p_set_next_pointer(PointerA, PointerBNext),
|
||||
% Set B's previous to APrevious
|
||||
dll_p_set_previous_pointer(PointerB, PointerAPrevious),
|
||||
% Set APrevious' next to B !!
|
||||
dll_p_set_next_pointer(PointerAPrevious, PointerB),
|
||||
% Set BNext's previous to A !!
|
||||
dll_p_set_previous_pointer(PointerBNext, PointerA).
|
||||
|
||||
% Private
|
||||
% Careful: make sure this is called on the actual cell, and not some copy.
|
||||
% Mode: + +
|
||||
dll_p_set_previous_pointer(dll_cell(Cell), PointerToNewPrevious) :-
|
||||
put_atts(Cell, dll_prev(PointerToNewPrevious)).
|
||||
|
||||
% Private
|
||||
% Careful: make sure this is called on the actual cell, and not some copy.
|
||||
% Mode: + +
|
||||
dll_p_set_next_pointer(dll_cell(Cell), PointerToNewNext) :-
|
||||
put_atts(Cell, dll_next(PointerToNewNext)).
|
||||
|
||||
dll_extract_element(ElementFlag, Element) :-
|
||||
( ElementFlag = wkl_suspension_cluster(SuspensionClusterFlag) ->
|
||||
get_atts(SuspensionClusterFlag, batched_worklist, wkl_suspension_cluster(SuspensionCluster)),
|
||||
Element = wkl_suspension_cluster(SuspensionCluster)
|
||||
; ElementFlag = wkl_answer_cluster(AnswerClusterFlag) ->
|
||||
get_atts(AnswerClusterFlag, batched_worklist, wkl_answer_cluster(AnswerCluster)),
|
||||
Element = wkl_answer_cluster(AnswerCluster)
|
||||
).
|
||||
|
||||
dll_get_contents(List, Contents) :-
|
||||
dll_get_pointer_to_next(List, Next),
|
||||
dll_get_contents_(Next, Contents).
|
||||
|
||||
dll_get_contents_(List, Contents) :-
|
||||
dll_get_data(List, ElementFlag),
|
||||
( ElementFlag == dll_start ->
|
||||
Contents = []
|
||||
; dll_extract_element(ElementFlag, Element),
|
||||
Contents = [Element | Rest],
|
||||
dll_get_pointer_to_next(List, Next),
|
||||
dll_get_contents_(Next, Rest)
|
||||
).
|
||||
|
||||
dll_get_reverse_contents(List, Contents) :-
|
||||
dll_get_pointer_to_previous(List, Prev),
|
||||
dll_get_reverse_contents_(Prev, Contents).
|
||||
|
||||
dll_get_reverse_contents_(List, Contents) :-
|
||||
dll_get_data(List, ElementFlag),
|
||||
( ElementFlag == dll_start ->
|
||||
Contents = []
|
||||
; dll_extract_element(ElementFlag, Element),
|
||||
Contents = [Element | Rest],
|
||||
dll_get_pointer_to_previous(List, Prev),
|
||||
dll_get_reverse_contents_(Prev, Rest)
|
||||
).
|
||||
|
||||
40
src/prolog/lib/tabling/global_worklist.pl
Normal file
40
src/prolog/lib/tabling/global_worklist.pl
Normal file
@@ -0,0 +1,40 @@
|
||||
/* Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
*/
|
||||
|
||||
:- module(global_worklist,
|
||||
[ put_new_global_worklist/0,
|
||||
add_to_global_worklist/1,
|
||||
worklist_empty/0,
|
||||
pop_worklist/1
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(non_iso)).
|
||||
|
||||
:- attribute table_global_worklist/1.
|
||||
|
||||
put_new_global_worklist :-
|
||||
( bb_get(table_global_worklist_initialized, _) ->
|
||||
true
|
||||
; put_atts(Worklist, table_global_worklist([])),
|
||||
bb_put(table_global_worklist, Worklist),
|
||||
bb_b_put(table_global_worklist_initialized, [])
|
||||
).
|
||||
|
||||
add_to_global_worklist(TableIdentifier) :-
|
||||
bb_get(table_global_worklist, TableGlobalWorklistFlag),
|
||||
get_atts(TableGlobalWorklistFlag, table_global_worklist(L1)),
|
||||
put_atts(TableGlobalWorklistFlag, table_global_worklist([TableIdentifier|L1])),
|
||||
bb_put(table_global_worklist, TableGlobalWorklistFlag).
|
||||
|
||||
worklist_empty :-
|
||||
bb_get(table_global_worklist,TableGlobalWorklistFlag),
|
||||
get_atts(TableGlobalWorklistFlag, table_global_worklist(L)),
|
||||
L == [].
|
||||
|
||||
pop_worklist(TableIdentifier) :-
|
||||
bb_get(table_global_worklist,TableGlobalWorklistFlag),
|
||||
get_atts(TableGlobalWorklistFlag, table_global_worklist(L1)),
|
||||
L1 = [TableIdentifier|L2],
|
||||
put_atts(TableGlobalWorklistFlag, table_global_worklist(L2)),
|
||||
bb_put(table_global_worklist, TableGlobalWorklistFlag).
|
||||
253
src/prolog/lib/tabling/table_data_structure.pl
Normal file
253
src/prolog/lib/tabling/table_data_structure.pl
Normal file
@@ -0,0 +1,253 @@
|
||||
:- module(table_datastructure,
|
||||
[ get_answer/2, % +TableID, -Answer
|
||||
add_answer/2, % +TableID, +Answer
|
||||
get_call_variant/2, % +TableID, -CallVariant
|
||||
set_complete_status/1, % +TableID
|
||||
set_active_status/1, % +TableID
|
||||
tbd_table_status/2, % +TableID, -Status
|
||||
table_for_variant/2, % +Variant, -TableID
|
||||
store_dependency/2, % +TableID, +Suspension
|
||||
cleanup_after_complete/1, % +TableID
|
||||
get_newly_created_table_identifiers/2, % NewlyCreatedTableIDs, NumIDs
|
||||
reset_newly_created_table_identifiers/0,
|
||||
answers_for_variant/2, % +Variant, -Answers
|
||||
put_new_table_identifiers/0,
|
||||
get_nb_identifiers/3 % +Table, -NbWorklistID, -NbAnswerTreeID
|
||||
]).
|
||||
|
||||
:- use_module(table_link_manager).
|
||||
:- use_module(trie).
|
||||
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
|
||||
Jan Wielemaker (SWI-Prolog port)
|
||||
Copyright (c) 2016, Benoit Desouter
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- use_module(batched_worklist).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(gensym)).
|
||||
:- use_module(library(non_iso)).
|
||||
|
||||
:- attribute table_status/1, newly_created_table_identifiers/1.
|
||||
|
||||
% This file defines the table datastructure.
|
||||
%
|
||||
% The table datastructure contains the following sub-structures:
|
||||
% - the answer trie
|
||||
% - the worklist
|
||||
%
|
||||
% Structure for tables:
|
||||
% table(CallVariant,Status,AnswerTrie,Worklist) or complete_table(CallVariant,AnswerTrie).
|
||||
% where AnswerTrie contains a trie of unique answers
|
||||
%
|
||||
% Remember that a table may also be nonexistent!
|
||||
% nb_getval(nonexistent,X) then gives [].
|
||||
|
||||
put_new_table_identifiers :-
|
||||
( bb_get(newly_created_table_identifiers_initialized, _) ->
|
||||
true
|
||||
; put_atts(NewlyCreatedFlag, newly_created_table_identifiers([]-0)),
|
||||
bb_b_put(newly_created_table_identifiers, NewlyCreatedFlag),
|
||||
bb_b_put(newly_created_table_identifiers_initialized, [])
|
||||
).
|
||||
|
||||
% Returns a list of newly created table identifiers since the last call to reset_newly_created_table_identifiers/0, as well as the length of the list.
|
||||
get_newly_created_table_identifiers(NewlyCreatedTableIdentifiers,NumIdentifiers) :-
|
||||
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
|
||||
get_atts(NewlyCreatedFlag, newly_created_table_identifiers(NewlyCreatedTableIdentifiers-NumIdentifiers)).
|
||||
|
||||
reset_newly_created_table_identifiers :-
|
||||
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
|
||||
put_atts(NewlyCreatedFlag, newly_created_table_identifiers([]-0)).
|
||||
|
||||
add_to_newly_created_table_identifiers(TableIdentifier) :-
|
||||
bb_get(newly_created_table_identifiers, NewlyCreatedFlag),
|
||||
get_atts(NewlyCreatedFlag, newly_created_table_identifiers(L1-Num1)),
|
||||
Num2 is Num1 + 1,
|
||||
put_atts(NewlyCreatedFlag, newly_created_table_identifiers([TableIdentifier|L1]-Num2)).
|
||||
|
||||
% PRIVATE
|
||||
% Mode: + -
|
||||
%
|
||||
% Created in the fresh status.
|
||||
p_create_table(CallVariant,TableIdentifier) :-
|
||||
% We use a copy_term here so that we can be sure not to corrupt our table if CallVariant is "changed" afterwards.
|
||||
copy_term(CallVariant,CallVariant2),
|
||||
% Generate a table identifier, create the table and do bookkeeping.
|
||||
gensym(table,TableIdentifier),
|
||||
% Create a trie and a worklist.
|
||||
trie_new(EmptyTrie),
|
||||
wkl_new_worklist(TableIdentifier,NewWorklist),
|
||||
put_atts(StatusFlag, table_status(fresh)),
|
||||
%% this is important! we don't want to copy the incomplete table every time we refer to it,
|
||||
%% which would occur if we used bb_put here.
|
||||
%% note that the complete_table variant is written to the blackboard using bb_get.
|
||||
atom_concat(TableIdentifier, nb_worklist, NbWorklistID),
|
||||
atom_concat(TableIdentifier, nb_answer_trie, NbAnswerTrieID),
|
||||
bb_put(TableIdentifier, table(CallVariant2,StatusFlag,NbWorklistID,NbAnswerTrieID)),
|
||||
bb_put(NbWorklistID, table_nb_worklist(NewWorklist)),
|
||||
bb_put(NbAnswerTrieID, table_nb_answer_trie(EmptyTrie)),
|
||||
p_link_variant_identifier(CallVariant2,TableIdentifier),
|
||||
add_to_newly_created_table_identifiers(TableIdentifier).
|
||||
|
||||
% Get the Status for table TableIdentifier
|
||||
% Throws exception if this table does not exist.
|
||||
tbd_table_status(TableIdentifier,Status) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
tbd_table_status_(Table,Status).
|
||||
|
||||
% Is also used in other predicates than tbd_table_status.
|
||||
tbd_table_status_(table(_CallVariant,StatusFlag,_NbWorklistID, _NbAnswerTrieID),Status) :-
|
||||
get_atts(StatusFlag, table_status(Status)).
|
||||
tbd_table_status_(complete_table(_,_,_),complete).
|
||||
|
||||
% PRIVATE
|
||||
% Table must already exist.
|
||||
p_get_table_for_identifier(TableIdentifier,Table) :-
|
||||
bb_get(TableIdentifier,Table).
|
||||
|
||||
% Get the table identifier (!!) for call variant V, creating a new one if necessary.
|
||||
%
|
||||
% More costly than directly passing the table identifier for already existing tables.
|
||||
%
|
||||
% Since this creates a new table, this predicate is NOT meant for users who should get access to existing tables - f.e. benchmark shortest_path.P
|
||||
%
|
||||
table_for_variant(V,TableIdentifier) :-
|
||||
( p_existing_table(V,TableIdentifier) ->
|
||||
true
|
||||
;
|
||||
p_create_table(V,TableIdentifier)
|
||||
).
|
||||
|
||||
% Get call variant for this table
|
||||
get_call_variant(TableIdentifier,CallVariant) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
get_call_variant_(Table,CallVariant).
|
||||
|
||||
get_call_variant_(table(CallVariant,_Status,_NbWorklistID,_NbAnswerTrieID),CallVariant).
|
||||
get_call_variant_(complete_table(CallVariant,_NbWorklistID,_NbAnswerTrieID),CallVariant).
|
||||
|
||||
add_answer(TableIdentifier,A) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
% arg(1,Table,CallVariant),
|
||||
arg(3,Table,NbWorklistID),
|
||||
arg(4,Table,NbAnswerTrieID),
|
||||
bb_get(NbWorklistID,table_nb_worklist(Worklist)),
|
||||
bb_get(NbAnswerTrieID,table_nb_answer_trie(AnswerTrie)),
|
||||
copy_term(A,A2),
|
||||
% This predicate succeeds if the answer was new, otherwise it fails.
|
||||
trie_insert(AnswerTrie,A2,A2), % Use answer both as key and as value. Having it as value uses memory, but greatly simplifies getting all the answers.
|
||||
% We got here, so trie_insert added a new answer.
|
||||
% We must also insert this answer in the worklist
|
||||
wkl_add_answer(Worklist,A2),
|
||||
bb_put(NbWorklistID, table_nb_worklist(Worklist)),
|
||||
bb_put(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
|
||||
|
||||
get_answer(TableIdentifier,A) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
get_answer_trie_(Table,AnswerTrie),
|
||||
% The trick is that we have stored the answers as values of the trie and that there is a method to get all the values.
|
||||
trie_get_all_values(AnswerTrie,A).
|
||||
|
||||
% get_answer_trie_(TableOrCompleteTable,AnswerTrie).
|
||||
% First argument is not a TableIdentifier.
|
||||
get_answer_trie_(table(_CallVariant,_Status,_NbWorklistID, NbAnswerTrieID),AnswerTrie) :-
|
||||
bb_get(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
|
||||
get_answer_trie_(complete_table(_CallVariant,_NbWorklistID, NbAnswerTrieID),AnswerTrie) :-
|
||||
bb_get(NbAnswerTrieID, table_nb_answer_trie(AnswerTrie)).
|
||||
|
||||
get_nb_identifiers(table(_CallVariant, _Status, NbWorklistID, NbAnswerTrieID), NbWorklistID, NbAnswerTrieID).
|
||||
get_nb_identifiers(complete_table(_CallVariant, NbWorklistID, NbAnswerTrieID), NbWorklistID, NbAnswerTrieID).
|
||||
|
||||
% Get a list of answers for the given call variant.
|
||||
% Used in compare_expected_for_variant/3 in testlib.pl
|
||||
% IMPORTANT: table must be filled already, this is not done in this predicate! Therefore can be called during execution.
|
||||
% V = variant
|
||||
% LA = list of answers.
|
||||
%
|
||||
% More costly operation than directly giving the table identifier.
|
||||
answers_for_variant(V,LA) :-
|
||||
table_for_variant(V,TableIdentifier),
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
get_answer_trie_(Table,AnswerTrie),
|
||||
findall(Value,trie_get_all_values(AnswerTrie,Value),LA).
|
||||
|
||||
% Set status of table TableIdentifier to active
|
||||
set_active_status(TableIdentifier) :-
|
||||
tbd_status_transition(TableIdentifier,active,fresh,'set_active_status').
|
||||
|
||||
cleanup_after_complete(TableIdentifier) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
cleanup_after_complete_(Table,TableIdentifier).
|
||||
|
||||
% Clause for a (noncomplete) table.
|
||||
cleanup_after_complete_(
|
||||
table(CallVariant,_ActualOldStatus, NbWorklistID, NbAnswerTrieID),
|
||||
TableIdentifier
|
||||
) :-
|
||||
bb_put(TableIdentifier,complete_table(CallVariant, NbWorklistID, NbAnswerTrieID)).
|
||||
% If necessary for debugging add second clause for complete_table.
|
||||
|
||||
% Set status of table TableIdentifier to complete.
|
||||
set_complete_status(TableIdentifier) :-
|
||||
% The transition must be active to complete, otherwise we have an invalid status transition.
|
||||
% Preexisting tables should have been cleaned-up, thus not have the form table/5 anymore, thus complete -> complete is not possible there.
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
set_complete_status_(Table,TableIdentifier).
|
||||
|
||||
% set_complete_status_(Table,TableIdentifier).
|
||||
set_complete_status_(table(_CallVariant,_OldStatus,_NbWorklistID, _NbAnswerTrieID),TableIdentifier) :-
|
||||
tbd_status_transition(TableIdentifier,complete,active,'set_complete_status').
|
||||
|
||||
tbd_status_transition_no_check(TableIdentifier,NewStatus) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus).
|
||||
|
||||
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus) :-
|
||||
Table = table(_,StatusFlag,_,_),
|
||||
put_atts(StatusFlag, table_status(NewStatus)),
|
||||
bb_put(TableIdentifier, Table).
|
||||
|
||||
% Set Table's status to NewStatus if current status is RequiredOldStatus, otherwise throw an exception mentioning CallerAsString: attempt to set NewStatus for table TableIdentifier, but current status was ActualOldStatus instead of RequiredOldStatus
|
||||
tbd_status_transition(TableIdentifier,NewStatus,_RequiredOldStatus,_CallerAsString) :-
|
||||
p_get_table_for_identifier(TableIdentifier,Table),
|
||||
tbd_status_transition_no_check_(TableIdentifier,Table,NewStatus).
|
||||
|
||||
store_dependency(TableIdentifier,Suspension) :-
|
||||
p_get_table_for_identifier(TableIdentifier, Table),
|
||||
get_nb_identifiers(Table, NbWorklistID, _NbAnswerTrieID),
|
||||
copy_term(Suspension, SuspensionCopy),
|
||||
bb_get(NbWorklistID, table_nb_worklist(Worklist)),
|
||||
wkl_add_suspension(Worklist, SuspensionCopy),
|
||||
bb_put(NbWorklistID, table_nb_worklist(Worklist)).
|
||||
121
src/prolog/lib/tabling/table_link_manager.pl
Normal file
121
src/prolog/lib/tabling/table_link_manager.pl
Normal file
@@ -0,0 +1,121 @@
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
|
||||
Jan Wielemaker (SWI-Prolog port)
|
||||
Copyright (c) 2016, Benoit Desouter
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- module(table_link_manager,
|
||||
[ get_existing_tables/1, % -Tables
|
||||
p_existing_table/2, % +Variant, -TableID
|
||||
p_link_variant_identifier/2, % +Variant, -TableID
|
||||
num_tables/1, % -Count
|
||||
get_trie_table_link/1, % -Trie
|
||||
put_new_trie_table_link/0
|
||||
]).
|
||||
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(lists)).
|
||||
:- use_module(library(non_iso)).
|
||||
:- use_module(library(terms)).
|
||||
|
||||
:- use_module(trie).
|
||||
|
||||
:- attribute trie_table_link/1.
|
||||
|
||||
% This file defines a call pattern trie.
|
||||
%
|
||||
% This data structure keeps the relation between a variant and the
|
||||
% corresponding table identifier using a trie. The trick is to make a
|
||||
% canonical representation of a given variant using the numbervars/3
|
||||
% predicate. The trie uses this canonical representation as key, and
|
||||
% the table identifier as value.
|
||||
|
||||
% Uses the (private) global variable trie_table_link
|
||||
|
||||
% This predicate should be called exactly once.
|
||||
% It throws an exception if it is called more than once.
|
||||
|
||||
%% table_link_manager_initialize
|
||||
%
|
||||
% Initializes the global variables `trie_table_link`. Normally
|
||||
% called from table_datastructure_initialize/0.
|
||||
|
||||
put_new_trie_table_link :-
|
||||
( bb_get(trie_table_link_initialized, _) ->
|
||||
true
|
||||
; trie_new(Trie),
|
||||
bb_b_put(trie_table_link, TrieFlag),
|
||||
bb_b_put(trie_table_link_initialized, []),
|
||||
put_atts(TrieFlag, trie_table_link(Trie))
|
||||
).
|
||||
|
||||
get_trie_table_link(Trie) :-
|
||||
bb_get(trie_table_link, TrieFlag),
|
||||
get_atts(TrieFlag, trie_table_link(Trie)).
|
||||
|
||||
% PRIVATE
|
||||
% mode: + -
|
||||
% Variant is not modified
|
||||
variant_canonical_representation(Variant, CanonicalRepresentation) :-
|
||||
copy_term(Variant, CanonicalRepresentation),
|
||||
numbervars(CanonicalRepresentation, 0 ,_).
|
||||
|
||||
% Succeeds if there is a table TableIdentifier in existance for the
|
||||
% given call variant Variant.
|
||||
p_existing_table(Variant, TableIdentifier) :-
|
||||
get_trie_table_link(Trie),
|
||||
variant_canonical_representation(Variant, CanonicalRepresentation),
|
||||
trie_lookup(Trie, CanonicalRepresentation, TableIdentifier).
|
||||
|
||||
% Important remark: we cannot use an out-of-the-box association list,
|
||||
% because we need a lookup based on variant checking, which is not
|
||||
% available for such lists. Converting the association list to a
|
||||
% regular list => why would you use an association list in the first
|
||||
% place...
|
||||
p_link_variant_identifier(Variant, TableIdentifier) :-
|
||||
get_trie_table_link(Trie),
|
||||
variant_canonical_representation(Variant, CanonicalRepresentation),
|
||||
trie_insert_succeed(Trie, CanonicalRepresentation, TableIdentifier).
|
||||
|
||||
% Returns a list of existing table identifiers.
|
||||
% Rather costly.
|
||||
get_existing_tables(Ts) :-
|
||||
get_trie_table_link(Trie),
|
||||
findall(T, trie_get_all_values(Trie, T), Ts).
|
||||
|
||||
% A very unefficient way of implementing this predicate. But it is
|
||||
% only used for unit testing, so it doesn't really matter. Also, it
|
||||
% doesn't require any additional bookkeeping during the actual
|
||||
% execution.
|
||||
num_tables(N) :-
|
||||
get_existing_tables(Ts),
|
||||
length(Ts, N).
|
||||
220
src/prolog/lib/tabling/trie.pl
Normal file
220
src/prolog/lib/tabling/trie.pl
Normal file
@@ -0,0 +1,220 @@
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Benoit Desouter <Benoit.Desouter@UGent.be>
|
||||
Jan Wielemaker (SWI-Prolog port)
|
||||
Copyright (c) 2016, Benoit Desouter
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- module(trie,
|
||||
[ trie_new/1, % -Trie
|
||||
trie_insert/3, % !Trie, +Key, +Value
|
||||
trie_insert_succeed/3,
|
||||
trie_lookup/3, % +Trie, +Key, -Value
|
||||
trie_get_all_values/2 % +Trie, -Value
|
||||
]).
|
||||
|
||||
:- use_module(library(assoc)).
|
||||
:- use_module(library(atts)).
|
||||
:- use_module(library(lists)).
|
||||
|
||||
:- attribute maybe_just/1, children/1.
|
||||
|
||||
% Implementation of a prefix tree, a.k.a. trie %
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
|
||||
% Desired complexity for lookup and insert: linear in the length of the key.
|
||||
|
||||
% ATTENTION: do not use the term functor_data/2; this is used internally here.
|
||||
|
||||
% Inspiration from http://en.wikipedia.org/wiki/Trie
|
||||
|
||||
% Structure of tries:
|
||||
% trie_inner_node(MaybeValue,Children).
|
||||
% where Children is an association list of nonvars to tries.
|
||||
% and where MaybeValue is maybe_none/0 or maybe_just(Value).
|
||||
|
||||
% PRIVATE
|
||||
% For a term of the form p(a,q(b)), "returns" functor_data(p,2) and [a,q(b)].
|
||||
% p_trie_arity_univ(+Term,-FunctorData,-ArgumentsList).
|
||||
p_trie_arity_univ(Term,functor_data(Name,Arity),Arguments) :-
|
||||
Term =.. [Name|Arguments],
|
||||
functor(Term,_,Arity).
|
||||
|
||||
% Returns a new empty trie.
|
||||
trie_new(Trie) :-
|
||||
empty_assoc(Assoc),
|
||||
put_atts(A, children(Assoc)),
|
||||
Trie = trie_inner_node(_,A).
|
||||
|
||||
% Succeeds if given trie does not contain any key-value pair.
|
||||
% trie_is_empty(+Trie)
|
||||
trie_is_empty(trie_inner_node(X,A)) :-
|
||||
get_atts(X, -maybe_just(_)),
|
||||
get_atts(A, children(Assoc)),
|
||||
empty_assoc(Assoc).
|
||||
|
||||
% For internal use.
|
||||
% For now, Children is an association list that can be manipulated using the assoc_ predicates.
|
||||
trie_get_children(trie_inner_node(_,ChildNode),Children) :-
|
||||
get_atts(ChildNode, children(Children)).
|
||||
|
||||
% For internal use.
|
||||
trie_get_maybe_value(trie_inner_node(MaybeNode,_),MaybeValue) :-
|
||||
get_atts(MaybeNode, maybe_just(MaybeValue)).
|
||||
|
||||
% Destructive update of the association list Children.
|
||||
% For internal use.
|
||||
trie_set_children(trie_inner_node(_,ChildNode),Children) :-
|
||||
put_atts(ChildNode, children(Children)).
|
||||
|
||||
trie_set_maybe_value(trie_inner_node(MaybeNode, _),MaybeValue) :-
|
||||
put_atts(MaybeNode, MaybeValue).
|
||||
|
||||
trie_insert_succeed(Trie,Key,Value) :-
|
||||
( trie_insert(Trie,Key,Value) ->
|
||||
true
|
||||
;
|
||||
true
|
||||
).
|
||||
|
||||
% Succeeds if the term was not present, fails if the term was present.
|
||||
% The term will be present now, whatever the outcome.
|
||||
% We don't use an extra argument to indicate earlier presence, as this increases the trail size.
|
||||
trie_insert(Trie,Key,Value) :-
|
||||
p_trie_arity_univ(Key,FunctorData,KeyList),
|
||||
trie_insert_1(KeyList,FunctorData,Trie,Value).
|
||||
|
||||
trie_insert_1([],FunctorData,Trie,Value) :-
|
||||
trie_get_children(Trie,Assoc),
|
||||
% You need Assoc twice: once to traverse through it, once keeping it as a whole for insertion using put_assoc/4.
|
||||
trie_insert_a(Assoc,Assoc,FunctorData,Trie,Value).
|
||||
|
||||
% Inline the failure and success continuation to avoid a growing trail stack.
|
||||
trie_insert_1([First|Rest],FunctorData,Trie,Value) :-
|
||||
trie_get_children(Trie,Assoc),
|
||||
% You need Assoc twice: once to traverse through it, once keeping it as a whole for insertion using put_assoc/4.
|
||||
trie_insert_1_1(Assoc,Assoc,FunctorData,Trie,First,Rest,Value).
|
||||
|
||||
% Else part, base case: empty assoc list.
|
||||
trie_insert_a(t,Assoc,FunctorData,Trie,Value) :-
|
||||
trie_new(Subtrie),
|
||||
trie_set_maybe_value(Subtrie,maybe_just(Value)),
|
||||
put_assoc(FunctorData,Assoc,Subtrie,NewAssoc),
|
||||
trie_set_children(Trie,NewAssoc).
|
||||
|
||||
% Then part, nonempty assoc tree.
|
||||
trie_insert_a(t(K,V,_,L,R),Assoc,FunctorData,Trie,Value) :-
|
||||
compare(Rel,FunctorData,K),
|
||||
trie_insert_b(Rel,V,L,R,Assoc,FunctorData,Trie,Value).
|
||||
|
||||
% Recursively look in the left part of the assoc tree.
|
||||
trie_insert_b(<,_V,L,_R,Assoc,FunctorData,Trie,Value) :-
|
||||
trie_insert_a(L,Assoc,FunctorData,Trie,Value).
|
||||
|
||||
% Recursively look in the right part of the assoc tree.
|
||||
trie_insert_b(>,_V,_L,R,Assoc,FunctorData,Trie,Value) :-
|
||||
trie_insert_a(R,Assoc,FunctorData,Trie,Value).
|
||||
|
||||
trie_insert_b(=,V,_L,_R,_Assoc,_FunctorData,_Trie,Value) :-
|
||||
trie_get_maybe_value(V,MaybeValue), % V is the Subtrie
|
||||
( MaybeValue == maybe_none ->
|
||||
trie_set_maybe_value(V,maybe_just(Value))
|
||||
% Use true to indicate that the answer was new.
|
||||
;
|
||||
MaybeValue = maybe_just(JustValue),
|
||||
( JustValue == Value ->
|
||||
% Fail to indicate earlier presence
|
||||
fail
|
||||
;
|
||||
throw('trie: attempt to update the value for a key')
|
||||
)
|
||||
).
|
||||
|
||||
% Else part, base case: empty assoc list
|
||||
trie_insert_1_1(t,Assoc,FunctorData,Trie,First,Rest,Value) :-
|
||||
% Assoc = t, % t is the empty assoc tree
|
||||
trie_new(Subtrie),
|
||||
put_assoc(FunctorData,Assoc,Subtrie,NewAssoc),
|
||||
trie_set_children(Trie,NewAssoc),
|
||||
trie_insert_2(First,Rest,Subtrie,Value).
|
||||
|
||||
% Then part, lookup in assoc list.
|
||||
trie_insert_1_1(t(K,V,_,L,R),Assoc,FunctorData,Trie,First,Rest,Value) :-
|
||||
compare(Rel,FunctorData,K),
|
||||
trie_insert_1_1_1(Rel,V,L,R,Assoc,FunctorData,Trie,First,Rest,Value).
|
||||
|
||||
trie_insert_1_1_1(=,V,_L,_R,_Assoc,_FunctorData,_Trie,First,Rest,Value) :-
|
||||
trie_insert_2(First,Rest,V,Value). % V is the Subtrie
|
||||
|
||||
trie_insert_1_1_1(<,_V,L,_R,Assoc,FunctorData,Trie,First,Rest,Value) :-
|
||||
% Look in the left part of the assoc tree.
|
||||
trie_insert_1_1(L,Assoc,FunctorData,Trie,First,Rest,Value).
|
||||
|
||||
trie_insert_1_1_1(>,_V,_L,R,Assoc,FunctorData,Trie,First,Rest,Value) :-
|
||||
% Look in the right part of the assoc tree.
|
||||
trie_insert_1_1(R,Assoc,FunctorData,Trie,First,Rest,Value).
|
||||
|
||||
trie_insert_2(RegularTerm,Rest,Trie,Value) :-
|
||||
p_trie_arity_univ(RegularTerm,FunctorData,KList),
|
||||
append(KList,Rest,KList2),
|
||||
trie_insert_1(KList2,FunctorData,Trie,Value).
|
||||
|
||||
trie_lookup(Trie,Key,Value) :-
|
||||
p_trie_arity_univ(Key,FunctorData,KeyList),
|
||||
trie_lookup_1(FunctorData,KeyList,Trie,Value).
|
||||
|
||||
trie_lookup_1(FunctorData,Rest,Trie,Value) :-
|
||||
% Select right subtree, fail if it isn't there, and do recursive call.
|
||||
trie_get_children(Trie,Assoc),
|
||||
get_assoc(FunctorData,Assoc,Subtrie), % Fails if not present
|
||||
trie_lookup_2(Rest,Subtrie,Value).
|
||||
|
||||
trie_lookup_2([],Trie,Value) :-
|
||||
% If the value at this trie is maybe_just(X), then X is our Value.
|
||||
% Otherwise, there is no value for this key, so we fail...
|
||||
trie_get_maybe_value(Trie,Value).
|
||||
% Regular term at the head, like p or p(a). Not functor_data/2.
|
||||
trie_lookup_2([RegularTerm|Rest],Trie,Value) :-
|
||||
% split RegularTerm
|
||||
p_trie_arity_univ(RegularTerm,FunctorData,KList),
|
||||
% Make a recursive call on KList ++ Rest.
|
||||
% Since we cannot implement p_trie_arity_univ so that "its result", KList, has a free variable at the end, without resorting to techniques that require linear time, we need a call to append/3. However, since KList will in general be rather short, I don't expect this to be a large problem in practice.
|
||||
append(KList,Rest,KList2),
|
||||
trie_lookup_1(FunctorData,KList2,Trie,Value).
|
||||
|
||||
|
||||
% Returns all values in the trie by backtracking - we don't provide any information about the associated key.
|
||||
trie_get_all_values(Trie,Value) :-
|
||||
trie_get_maybe_value(Trie,Value).
|
||||
trie_get_all_values(Trie,Value) :-
|
||||
trie_get_children(Trie,Children),
|
||||
gen_assoc(_Key, Children, ChildTrie),
|
||||
trie_get_all_values(ChildTrie,Value).
|
||||
120
src/prolog/lib/tabling/wrapper.pl
Normal file
120
src/prolog/lib/tabling/wrapper.pl
Normal file
@@ -0,0 +1,120 @@
|
||||
/* Part of SWI-Prolog
|
||||
|
||||
Author: Jan Wielemaker
|
||||
Copyright (c) 2016, VU University Amsterdam
|
||||
All rights reserved.
|
||||
|
||||
Ported to Scryer Prolog by Mark Thom (2019/2020).
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
:- module(table_wrapper,
|
||||
[ %(table)/1, % +Predicates
|
||||
op(1150, fx, table)
|
||||
]).
|
||||
|
||||
:- use_module(library(dcgs)).
|
||||
:- use_module(library(error)).
|
||||
|
||||
%%:- multifile
|
||||
%% system:term_expansion/2,
|
||||
%% tabled/2.
|
||||
%%:- dynamic
|
||||
%% system:term_expansion/2.
|
||||
|
||||
%% table(+PredicateIndicators)
|
||||
%
|
||||
% Prepare the given PredicateIndicators for tabling. Can only
|
||||
% be used as a directive.
|
||||
|
||||
%% table(PIList) :-
|
||||
%% throw(error(context_error(nodirective, table(PIList)), _)).
|
||||
|
||||
instantiation_error(Var) :-
|
||||
throw(error(instantiation_error(Var), _)).
|
||||
|
||||
wrappers(Var) -->
|
||||
{ var(Var), !,
|
||||
instantiation_error(Var)
|
||||
}.
|
||||
wrappers((A,B)) --> !,
|
||||
wrappers(A),
|
||||
wrappers(B).
|
||||
wrappers(Name//Arity) -->
|
||||
{ atom(Name), integer(Arity), Arity >= 0, !,
|
||||
Arity1 is Arity+2
|
||||
},
|
||||
wrappers(Name/Arity1).
|
||||
wrappers(Name/Arity) -->
|
||||
{ atom(Name), integer(Arity), Arity >= 0, !,
|
||||
functor(Head, Name, Arity),
|
||||
atom_concat(Name, ' tabled', WrapName),
|
||||
Head =.. [Name|Args],
|
||||
WrappedHead =.. [WrapName|Args],
|
||||
'$module_of'(Module, Name) %prolog_load_context(module, Module)
|
||||
},
|
||||
[ ( Head :-
|
||||
start_tabling(Module:Head, WrappedHead)
|
||||
),
|
||||
table_wrapper:tabled(Head, Module)
|
||||
].
|
||||
|
||||
rename(M:Term0, M:Term, _) :-
|
||||
atom(M), !,
|
||||
rename(Term0, Term, M).
|
||||
rename((Head :- Body), (NewHead :- Body), Module) :- !,
|
||||
rename(Head, NewHead, Module).
|
||||
rename((Head --> Body), (NewHead --> Body), Module) :- !,
|
||||
functor(Head, Name, Arity),
|
||||
PlainArity is Arity+1,
|
||||
functor(PlainHead, Name, PlainArity),
|
||||
table_wrapper:tabled(PlainHead, Module),
|
||||
rename_term(Head, NewHead).
|
||||
rename(Head, NewHead, Module) :-
|
||||
table_wrapper:tabled(Head, Module), !,
|
||||
rename_term(Head, NewHead).
|
||||
|
||||
rename_term(Compound0, Compound) :-
|
||||
compound(Compound0), !,
|
||||
Compound0 =.. [Name|Args],
|
||||
atom_concat(Name, ' tabled', WrapName),
|
||||
Compound =.. [WrapName|Args].
|
||||
rename_term(Name, WrapName) :-
|
||||
atom_concat(Name, ' tabled', WrapName).
|
||||
|
||||
|
||||
user:term_expansion(Term0,
|
||||
[ (:- multifile(table_wrapper:tabled/2))
|
||||
| Clauses
|
||||
]) :-
|
||||
nonvar(Term0),
|
||||
Term0 = (:- table Preds),
|
||||
phrase(wrappers(Preds), Clauses).
|
||||
user:term_expansion(Clause, NewClause) :-
|
||||
nonvar(Clause),
|
||||
'$module_of'(Module, Clause),
|
||||
rename(Clause, NewClause, Module).
|
||||
Reference in New Issue
Block a user