nix = "0.15.0"
num-rug-adapter = { optional = true, version = "0.1.1" }
ordered-float = "0.5.0"
-prolog_parser = { version = "0.8.38", path = "../prolog_parser", default-features = false }
+prolog_parser = { version = "0.8.38", default-features = false }
ref_thread_local = "0.0.0"
rug = { version = "1.4.0", optional = true }
rustyline = "5.0.3"
- [x] Built-in predicates for list processing and top-level declarative
control (`setup_call_cleanup/3`, `call_with_inference_limit/3`,
etc.)
-- [x] Default representation of strings as list of chars, using a packed
- internal representation.
- - A representation of 'partial strings' as difference lists
- of characters.
+- [x] ~~Default representation of strings as list of chars, using a packed
+ internal representation.~~
- [x] `term_expansion/2` and `goal_expansion/2`.
- [x] Definite Clause Grammars.
- [x] Attributed variables using the SICStus Prolog interface and
(backtrackable).
- [x] Delimited continuations based on reset/3, shift/1 (documented in
"Delimited Continuations for Prolog").
-- [ ] Add opt-in tabling library based on delimited continuations
- (documented in "Tabling as a Library with Delimited Control") (_in
- progress_).
+- [x] Add opt-in tabling library based on delimited continuations
+ (documented in "Tabling as a Library with Delimited Control").
+- [ ] A _redone_ representation of strings as difference list of
+ chars, using a packed internal representation (_in progress_).
- [ ] clp(B) and clp(ℤ) as builtin libraries (_in progress_).
- [ ] Streams and predicates for stream control (_in progress_).
- [ ] An incremental compacting garbage collector satisfying the five
--- /dev/null
+
+:- module(tabling,
+ [ start_tabling/2, % +Wrapper, :Worker.
+
+ abolish_all_tables/0,
+
+% (table)/1, % +PI ...
+ op(1150, fx, table)
+ ]).
+
+:- use_module('tabling/double_linked_list').
+:- use_module('tabling/table_data_structure').
+:- use_module('tabling/batched_worklist').
+:- use_module('tabling/wrapper').
+:- use_module('tabling/global_worklist').
+:- use_module('tabling/table_link_manager').
+
+:- use_module(library(cont)).
+:- use_module(library(lists)).
+%:- use_module(library(debug)).
+:- use_module(library(non_iso)).
+
+%% :- meta_predicate
+%% start_tabling(+, 0).
+
+%% user:exception(+Exception, +Var, -Action)
+%
+% Realises lazy initialization of table variables.
+
+%% user:exception(undefined_global_variable, Var, retry) :-
+%% ( table_gvar(Var)
+%% -> true
+%% ; format('Creating global var ~q~n', [Var]),
+%% nb_setval(Var, [])
+%% ).
+/*
+table_gvar(trie_table_link) :-
+ table_datastructure_initialize.
+table_gvar(newly_created_table_identifiers) :-
+ table_datastructure_initialize.
+table_gvar(table_global_worklist) :-
+ bb_put(table_global_worklist, []).
+table_gvar(table_leader) :-
+ bb_put(table_leader, []).
+*/
+
+%% abolish_all_tables
+%
+% Remove all tables. Should not be called when tabling is in
+% progress.
+%
+% @bug Check whether tabling is in progress
+
+
+abolish_all_tables :-
+ bb_put(trie_table_link, []),
+ bb_put(newly_created_table_identifiers, []),
+ bb_put(table_global_worklist,[]),
+ bb_put(table_leader, []).
+
+
+% Find table and status for the given call variant.
+%
+table_and_status_for_variant(V,T,S) :-
+ % Order of the two calls really important: first create, then get status
+ table_for_variant(V,T),
+ tbd_table_status(T,S).
+
+start_tabling(Wrapper,Worker) :-
+ put_new_trie_table_link,
+ put_new_global_worklist,
+ put_new_table_identifiers,
+ table_and_status_for_variant(Wrapper,T,S),
+ ( S == complete ->
+ get_answer(T,Wrapper)
+ ;
+ ( exists_scheduling_component ->
+ run_leader(Wrapper,Worker,T),
+ % Now answer the original query!
+ get_answer(T,Wrapper)
+ ;
+ run_follower(S,Wrapper,Worker,T)
+ )
+ ).
+
+run_follower(fresh,Wrapper,Worker,T) :-
+ activate(Wrapper,Worker,T),
+ shift(call_info(Wrapper,T)).
+
+run_follower(active,Wrapper,_Worker,T) :-
+ shift(call_info(Wrapper,T)).
+
+run_leader(Wrapper,Worker,T) :-
+ create_scheduling_component,
+ activate(Wrapper,Worker,T),
+ completion,
+ unset_scheduling_component.
+
+exists_scheduling_component :-
+ bb_get(table_leader, Leader),
+ Leader == [].
+
+create_scheduling_component :-
+ bb_b_put(table_leader, leaderCreated).
+
+unset_scheduling_component :-
+ bb_put(table_leader, []).
+
+set_all_complete :-
+ get_newly_created_table_identifiers(Ts, _),
+ set_all_complete_(Ts).
+
+set_all_complete_([]).
+set_all_complete_([T|Ts]) :-
+ set_complete_status(T),
+ set_all_complete_(Ts).
+
+cleanup_all_complete :-
+ get_newly_created_table_identifiers(Ts,_),
+ cleanup_all_complete_(Ts).
+
+cleanup_all_complete_([]).
+cleanup_all_complete_([T|Ts]) :-
+ cleanup_after_complete(T),
+ cleanup_all_complete_(Ts).
+
+activate(Wrapper,Worker,T) :-
+ set_active_status(T),
+ (
+ delim(Wrapper,Worker,T),
+ fail
+ ;
+ true
+ ).
+
+delim(Wrapper,Worker,Table) :-
+% debug(tabling, 'ACT: ~p on ~p', [Wrapper, Table]),
+ reset(Worker,SourceCall,Continuation),
+ ( Continuation == none, var(SourceCall) ->
+ ( add_answer(Table,Wrapper)
+ -> true %debug(tabling, 'ADD: ~p', [Wrapper])
+ ; %debug(tabling, 'DUP: ~p', [Wrapper]),
+ fail
+ )
+ ;
+ ( Continuation = cont(Cont) ->
+ true
+ ; Continuation = none ->
+ Cont = true
+ ),
+ SourceCall = call_info(_,SourceTable),
+ TargetCall = call_info(Wrapper,Table),
+ Dependency = dependency(SourceCall,Cont,TargetCall),
+ %debug(tabling, 'DEP: ~p: ~p', [SourceTable,Dependency]),
+ store_dependency(SourceTable,Dependency)
+ ).
+
+completion :-
+ ( worklist_empty ->
+ set_all_complete,
+ cleanup_all_complete,
+ % The place of the call to reset is really important: it must happen after the completion. If you do it before, you will wrongly remove yourself from the list of newly created table identifiers. On starting hProlog there are no newly created table identifiers, and nb_getval gives [] which is the perfect value.
+ reset_newly_created_table_identifiers
+ ;
+ pop_worklist(Table),
+ completion_step(Table),
+ completion
+ ).
+
+completion_step(SourceTableID) :-
+ bb_get(SourceTableID, Table),
+ get_nb_identifiers(Table, NBWorklistID, _),
+ (
+ table_get_work(NBWorklistID,Answer,dependency(Source,Continuation,Target)),
+ Source = call_info(Answer,_),
+ Target = call_info(Wrapper,TargetTable),
+ delim(Wrapper,Continuation,TargetTable),
+ fail
+ ;
+ true
+ ).
+
+table_get_work(NBWorklistID,Answer,Dependency) :-
+ % get_worklist(Table, Worklist),
+ % NOT IN PAPER (could be part of the definition of pop_worklist):
+ bb_get(NBWorklistID, table_nb_worklist(Worklist)),
+ unset_global_worklist_presence_flag(Worklist),
+ set_flag_executing_all_work(Worklist),
+ bb_put(NBWorklistID, table_nb_worklist(Worklist)),
+ table_get_work_(NBWorklistID,Answer,Dependency).
+
+table_get_work_(NBWorklistID,Answer,Dependency) :-
+ worklist_do_all_work(NBWorklistID,Answer,Dependency0), % This will eventually fail
+ copy_term(Dependency0,Dependency).
+
+table_get_work_(NBWorklistID,_Answer,_Dependency) :-
+ bb_get(NBWorklistID, table_nb_worklist(Worklist)),
+ unset_flag_executing_all_work(Worklist),
+ bb_put(NBWorklistID, table_nb_worklist(Worklist)),
+ fail.
+
+worklist_do_all_work(NBWorklistID,Answer,Dependency) :-
+ ( bb_get(NBWorklistID, table_nb_worklist(Worklist)),
+ wkl_worklist_work_done(Worklist) ->
+ fail
+ ;
+ worklist_do_step(NBWorklistID,Answer,Dependency)
+ ;
+ worklist_do_all_work(NBWorklistID,Answer,Dependency)
+ ).
+
+worklist_do_step(NBWorklistID,Answer,Dependency) :-
+ bb_get(NBWorklistID, table_nb_worklist(Worklist)),
+ wkl_p_get_rightmost_inner_answer_cluster_pointer(Worklist,ACP),
+ wkl_p_swap_answer_continuation(Worklist,ACP,SCP),
+ dll_get_data(ACP,wkl_answer_cluster(AListFlag)),
+ dll_get_data(SCP,wkl_suspension_cluster(SListFlag)),
+ get_atts(AListFlag, batched_worklist, wkl_answer_cluster(AList)),
+ get_atts(SListFlag, batched_worklist, wkl_suspension_cluster(SList)),
+ bb_put(NBWorklistID, table_nb_worklist(Worklist)),
+ member(Answer,AList),
+ member(Dependency,SList).
+
+:- initialization(bb_put(table_leader, [])).
--- /dev/null
+/* Part of SWI-Prolog
+
+ 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)).
--- /dev/null
+/* Part of SWI-Prolog
+
+ 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)
+ ).
+
--- /dev/null
+/* 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).
--- /dev/null
+:- 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
+
+ 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)).
--- /dev/null
+/* Part of SWI-Prolog
+
+ 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).
--- /dev/null
+/* Part of SWI-Prolog
+
+ 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).
--- /dev/null
+/* 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).