1 | % (c) 2009-2019 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
2 | % Heinrich Heine Universitaet Duesseldorf | |
3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
4 | ||
5 | :- module(delay,[delay_setof_wf/7, | |
6 | delay_setof_check_wf/9, | |
7 | delay_setof_list/4, | |
8 | delay_call/2, delay_call/3, | |
9 | delay_not/3, not_with_enum_warning/2, | |
10 | compute_wait_variables/3]). | |
11 | ||
12 | % meta_predicate annotations should appear before loading any code: | |
13 | :- meta_predicate my_findall(-,0,-,-). | |
14 | :- meta_predicate my_findall_catch(-,0,-,-,-). | |
15 | :- meta_predicate my_findall_catch(-,0,-,-,-,-). | |
16 | :- meta_predicate my_findall_check(-,0,-,-,0,0,-). | |
17 | ||
18 | :- meta_predicate delay_setof_wf(-,0,-,-,-,-,-). | |
19 | %:- meta_predicate block_my_findall_catch_wf(-,-,0,-,-,-). | |
20 | ||
21 | :- meta_predicate delay_setof_check_wf(-,0,-,-,-,0,0,-,-). | |
22 | :- meta_predicate block_findall_check(-,-,0,-,0,0,-,-,-,-). | |
23 | ||
24 | :- meta_predicate delay_setof_list(-,0,-,-). | |
25 | :- meta_predicate block_my_findall_sort(-,-,0,-). | |
26 | ||
27 | ||
28 | :- meta_predicate delay_call(0,-,-). | |
29 | :- meta_predicate delay_call(0,-). | |
30 | ||
31 | :- meta_predicate delay_not(0,-,-). | |
32 | :- meta_predicate not_with_enum_warning(0,-). | |
33 | :- meta_predicate not_with_enum_warning2(0,-,-). | |
34 | ||
35 | % -------------- | |
36 | ||
37 | ||
38 | :- use_module(tools). | |
39 | ||
40 | :- use_module(module_information,[module_info/2]). | |
41 | :- module_info(group,kernel). | |
42 | :- module_info(description,'Utilities to delay calls until sufficiently instantiated.'). | |
43 | ||
44 | %:- use_module(self_check). | |
45 | ||
46 | :- use_module(debug). /* so that calls can call unqualified debug_prints */ | |
47 | ||
48 | :- use_module(error_manager). | |
49 | :- use_module(kernel_tools). | |
50 | ||
51 | ||
52 | :- use_module(tools_printing,[print_term_summary/1]). | |
53 | :- load_files(library(system), [when(compile_time), imports([environ/2])]). | |
54 | ||
55 | :- if(debug:global_debug_flag). | |
56 | my_findall(X,P,L,_) :- | |
57 | statistics(runtime,[Start,_]), | |
58 | findall(X,P,L), | |
59 | statistics(runtime,[End,_]), | |
60 | Tot is End-Start, | |
61 | (Tot>50 -> nl, %nl,print(Call),nl, | |
62 | print('*** FINDALL: '),print_term_summary(P), | |
63 | print('*** exceeded limit: '), print(Tot), print(' ms'),nl, | |
64 | length(L,Len), | |
65 | print('*** SOLUTIONS: '), print(Len), nl | |
66 | ; true). | |
67 | :- elif(environ(prob_safe_mode,xtrue)). | |
68 | :- use_module(tools_meta,[call_residue/2]). | |
69 | :- meta_predicate call_residue_check(-). | |
70 | call_residue_check(Call) :- | |
71 | call_residue(Call,Residue), | |
72 | (Residue = [] -> true | |
73 | ; add_internal_error('Call had residue: ',residue(Residue)/call(Call))). | |
74 | my_findall(X,P,L,ExpectedFinalResult) :- | |
75 | (ExpectedFinalResult==[] | |
76 | -> \+(P),L=[] % we know the end result to be empty; not necessary to try and find all solutions; <--- we should probably treat this somewhere else to ensure that we do not throw all_solutions enumeration warnings | |
77 | ; findall(X,call_residue_check(P),L)). | |
78 | :- else. | |
79 | my_findall(X,P,L,ExpectedFinalResult) :- | |
80 | (ExpectedFinalResult==[] | |
81 | -> \+(P),L=[] % we know the end result to be empty; not necessary to try and find all solutions; <--- we should probably treat this somewhere else to ensure that we do not throw all_solutions enumeration warnings | |
82 | ; findall(X,P,L)). | |
83 | %; findall(X,(P,print(sol(X)),nl,nl,trace),L)). | |
84 | :- endif. | |
85 | ||
86 | ||
87 | ||
88 | ||
89 | my_findall_catch(X,P,L,ExpectedFinalResult,Span) :- | |
90 | my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span), | |
91 | (enum_warning_occ(EnumWarningWhichOcc) -> throw(EnumWarningWhichOcc) ; true). | |
92 | ||
93 | enum_warning_occ(EnumWarningWhichOcc) :- | |
94 | nonvar(EnumWarningWhichOcc),EnumWarningWhichOcc=enumeration_warning(_,_,_,_,_). | |
95 | ||
96 | my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span) :- | |
97 | enter_new_error_scope(Level,my_findall_catch), | |
98 | throw_enumeration_warnings_in_scope(Level,critical,Span), % critical enumeration warnings are thrown straight away | |
99 | call_cleanup( | |
100 | my_findall(X,P,L,ExpectedFinalResult), | |
101 | (event_occurred_in_error_scope(enumeration_warning(A,B,C,D,E)) | |
102 | -> exit_error_scope(Level,_,my_findall_catch_error), | |
103 | % do we need to throw the enumeration warning ? | |
104 | EnumWarningWhichOcc=enumeration_warning(A,B,C,D,E), | |
105 | debug_println(9,my_findall_catch(enumeration_warning(A,B,C,D,E))), %trace, | |
106 | true %throw(enumeration_warning(A,B,C,D,E)) | |
107 | ; exit_error_scope(Level,_,my_findall_catch_no_error), | |
108 | EnumWarningWhichOcc=false)). | |
109 | ||
110 | :- use_module(runtime_profiler,[observe_runtime/3]). | |
111 | my_findall_check(X,P,L,ExpectedFinalResult,TimeOutCode,VirtualTimeOutCode,Span) :- | |
112 | statistics(runtime,[Start,_]), | |
113 | get_total_number_of_errors(Nr1), % now also does count stored wd errors, see WD_SetCompr_Error | |
114 | %format('Start closure expansion at ~w ms~n',[Start]),print_message_span(Span),nl, | |
115 | call_cleanup((my_findall_catch(X,P,L,ExpectedFinalResult,EnumWarningWhichOcc,Span),Ok=true), | |
116 | (Ok==true,EnumWarningWhichOcc==false | |
117 | -> get_total_number_of_errors(Nr2), %print(f(Nr1,Nr2)),nl, | |
118 | (Nr2>Nr1 -> add_error(well_definedness_error,'Error(s) occurred during expansion of set comprehension','',Span), | |
119 | fail % TO DO: see comment below in delay_setof_wf | |
120 | ; observe_runtime(Start,'Long closure expansion: ',Span) %, print(done),nl | |
121 | %,statistics(runtime,[End,_]), Tot is End-Start, | |
122 | %(Tot>10 -> length(L,Len),add_message(delay,'Long closure expansion:',ms_sols(Tot,Len),Span) ; true) | |
123 | ) | |
124 | ; /* failure or time-out; --> time-out as findall should never fail */ | |
125 | statistics(runtime,[End,_]), Tot is End-Start, | |
126 | (enum_warning_occ(EnumWarningWhichOcc) | |
127 | -> (VirtualTimeOutCode = _:true -> true | |
128 | ; print('### VIRTUAL TIME-OUT after: '), print(Tot), print(' ms'),nl, | |
129 | call(VirtualTimeOutCode) | |
130 | ), %print(throw(EnumWarningWhichOcc)),nl, | |
131 | throw(EnumWarningWhichOcc) % ??? this is a set-comprehension expression; failure would mean not-well-defined | |
132 | ; Tot>1000 -> % TO DO: could also be CTRL-C ! we could use catch_interrupt_assertion_call(my_findall_catch) or on_exception(user_interrupt_signal,my_finall_catch() | |
133 | call(TimeOutCode), % Warning: this call is also called if an outer-time-out is triggered ! | |
134 | print('Runtime until TIME-OUT: '),print(Tot),nl | |
135 | % WHY DON'T WE FAIL HERE ---> because exception is passed on to outer calls after call_cleanup | |
136 | ; true) % check if the findall is probably responsible for the time-out | |
137 | )). | |
138 | ||
139 | /* ----------------------------------------- */ | |
140 | ||
141 | /* Below is a findall that delays until all | |
142 | non-output variables have become ground. | |
143 | Ideally, one would want a delay_findall that | |
144 | figures out by itself when it has all the answers | |
145 | (by looking at call_residue) and that may | |
146 | even progressively instantiate the output list | |
147 | such as in: | |
148 | delay_findall(X,delay_member(X,[a|T]),R), delay_member(Z,R) | |
149 | But this will require more involved machinery. | |
150 | */ | |
151 | ||
152 | ||
153 | :- use_module(tools,[remove_variables/3]). | |
154 | :- use_module(kernel_objects,[equal_object_wf/4]). | |
155 | :- if(current_prolog_flag(dialect,sicstus)). | |
156 | :- if((current_prolog_flag(version_data,sicstus(4,X,_,_,_)),X<3)). | |
157 | :- use_module(library(terms),[term_variables/2]). % is built-in in SICSTUS 4.3 | |
158 | :- endif. | |
159 | :- endif. | |
160 | ||
161 | ||
162 | :- use_module(kernel_tools,[ground_value_check/2]). | |
163 | ||
164 | % the following is called by expand_normal_closure_direct | |
165 | delay_setof_wf(V,G,FullSetResult,WVars,Done,WF,Span) :- %print(delay_set_of(V)),nl, | |
166 | ground_value_check(WVars,GW), | |
167 | block_my_findall_catch_wf2(GW,V,G,FullSetResult,Done,WF,Span). | |
168 | % comment in to perform idling to allow outer co-routines to run; useful when WD condition attached to Body G | |
169 | %:- use_module(kernel_waitflags,[get_idle_wait_flag/3]). | |
170 | %:- block block_my_findall_catch_wf(-,?,?,?,?,?). | |
171 | %block_my_findall_catch_wf(_,V,G,FullSetResult,Done,WF) :- | |
172 | % get_idle_wait_flag(delay_setof_wf,WF,LWF), | |
173 | % print(idling(WF,LWF)),nl,trace, | |
174 | % block_my_findall_catch_wf2(LWF,V,G,FullSetResult,Done,WF,Span). | |
175 | :- block block_my_findall_catch_wf2(-,?,?,?,?,?,?). | |
176 | block_my_findall_catch_wf2(_,V,G,FullSetResult,Done,WF,Span) :- | |
177 | ? | get_total_number_of_errors(Nr1), |
178 | ? | my_findall_catch(V,G,RRes,FullSetResult,Span), |
179 | ? | transform_result_into_set(RRes,SetRes), |
180 | ? | get_total_number_of_errors(Nr2), %print(f(Nr1,Nr2)),nl, |
181 | ? | (Nr2>Nr1 |
182 | -> add_error(well_definedness_error,'Error(s) occurred during expansion of set comprehension','',Span), | |
183 | fail | |
184 | % TO DO?: we could add_abort error here instead of adding a real error | |
185 | % for this, however, the inner abort_errors should only be recorded, not yet raised for the user | |
186 | ; true), | |
187 | ? | equal_object_wf(SetRes,FullSetResult,delay_setof_wf,WF), |
188 | Done=true. | |
189 | ||
190 | ||
191 | delay_setof_check_wf(V,G,FullSetResult,WVars,Done,TimeOutCode,VirtualTimeOutCode,WF,Span) :- %print(delay_set_of(V)),nl, | |
192 | %% print(wait_vars(WVars,G)),nl, %% | |
193 | ground_value_check(WVars,GW), | |
194 | block_findall_check(GW,V,G,FullSetResult,TimeOutCode,VirtualTimeOutCode,Done,FullSetResult,WF,Span). | |
195 | ||
196 | :- block block_findall_check(-,?,?,?,?,?,?,?,?,?). | |
197 | block_findall_check(_,V,G,FullSetResult,TimeOutCode,VirtualTimeOutCode,Done,FullSetResult,WF,Span) :- | |
198 | ? | my_findall_check(V,G,RRes,FullSetResult,TimeOutCode,VirtualTimeOutCode,Span), |
199 | ? | transform_result_into_set(RRes,SetRes), |
200 | % will generate AVL tree | |
201 | % print(delay_setof_result(SetRes,FullSetResult)),nl, translate:print_bvalue(SetRes),nl, | |
202 | ? | equal_object_wf(SetRes,FullSetResult,delay_setof_check_wf,WF), |
203 | ? | Done=true. |
204 | ||
205 | :- use_module(tools_printing,[print_term_summary/1]). | |
206 | :- use_module(custom_explicit_sets,[convert_to_avl/2]). | |
207 | transform_result_into_set(RRes,SetRes) :- % print(transform_result_into_set(RRes,SetRes)), | |
208 | ? | (convert_to_avl(RRes,SetRes) % we are sure that RRes is ground !!?? |
209 | -> true | |
210 | ; print_term_summary(convert_to_avl_failed(RRes,SetRes)), | |
211 | convert_list_of_expressions_into_set(RRes,SetRes)). | |
212 | ||
213 | :- use_module(kernel_waitflags,[init_wait_flags/1,ground_wait_flags/1]). | |
214 | :- use_module(b_interpreter,[convert_list_of_expressions_into_set_wf/4]). | |
215 | convert_list_of_expressions_into_set(List,Set) :- | |
216 | init_wait_flags(WF), | |
217 | convert_list_of_expressions_into_set_wf(List,Set,set(any),WF), | |
218 | ground_wait_flags(WF). | |
219 | ||
220 | /* same as above but Result is a list of elements; */ | |
221 | /* the list should not be interpreted as a set, but each element is a value for a parameter */ | |
222 | /* There is a very small chance that the same value is represented twice in the list (e.g., once as closure once as explicit list) */ | |
223 | delay_setof_list(V,G,FullSetResult,OutputVars) :- | |
224 | term_variables(G,Vars), | |
225 | term_variables(OutputVars,RealOutputVars), | |
226 | remove_variables(Vars,RealOutputVars,WVars), | |
227 | ground_value_check(WVars,GW), | |
228 | block_my_findall_sort(GW,V,G,FullSetResult). | |
229 | ||
230 | :- block block_my_findall_sort(-,?,?,?). | |
231 | block_my_findall_sort(_,V,G,FullSetResult) :- | |
232 | %print_quoted(findall(V,G,RRes)),nl, | |
233 | ? | my_findall(V,G,RRes,FullSetResult), |
234 | %% print_message(delay_seotof_list_rres(RRes)), %% | |
235 | ? | ll_norm(RRes,NormRRes), |
236 | ? | sort(NormRRes,SNormRRes), % removes duplicates |
237 | ? | FullSetResult=SNormRRes. |
238 | ||
239 | ||
240 | % normalise a list of list of values; the inner lists are not sets but values for parameters | |
241 | ll_norm([],[]). | |
242 | ll_norm([El1|T1],[El2|T2]) :- l_check_norm_required(El1,El2,NormRequired), | |
243 | (var(NormRequired) -> T1=T2 % no normalisation required: simply copy rest of list | |
244 | % TO DO: we could also statically determine based on parameter types whether normalising is required | |
245 | ; ll_norm2(T1,T2)). | |
246 | ||
247 | :- use_module(store,[l_normalise_values/2]). | |
248 | ll_norm2([],[]). | |
249 | ll_norm2([El1|T1],[El2|T2]) :- l_normalise_values(El1,El2), ll_norm2(T1,T2). | |
250 | ||
251 | % normalise a value and return norm_required in last argument if it is needed | |
252 | l_check_norm_required([],[],_). | |
253 | l_check_norm_required([H|T],[NH|NT],NormRequired) :- | |
254 | check_norm_required(H,NH,NormRequired), | |
255 | l_check_norm_required(T,NT,NormRequired). | |
256 | ||
257 | check_norm_required(pred_true,R,_) :- !, R=pred_true. | |
258 | check_norm_required(pred_false,R,_) :- !, R=pred_false. | |
259 | check_norm_required(int(S),R,_) :- !, R=int(S). | |
260 | check_norm_required(fd(X,T),R,_) :- !, R=fd(X,T). | |
261 | check_norm_required(string(S),R,_) :- !, R=string(S). | |
262 | check_norm_required((A,B),(NA,NB),NormRequired) :- !, | |
263 | check_norm_required(A,NA,NormRequired), check_norm_required(B,NB,NormRequired). | |
264 | % to do: add record and freeval | |
265 | check_norm_required(V,NV,norm_required) :- store:normalise_value(V,NV). | |
266 | ||
267 | ||
268 | ||
269 | ||
270 | ||
271 | compute_wait_variables(WaitTerm,OutputVars,WaitVars) :- | |
272 | term_variables(WaitTerm,Vars), | |
273 | term_variables(OutputVars,RealOutputVars), | |
274 | remove_variables(Vars,RealOutputVars,WaitVars). | |
275 | ||
276 | delay_call(Call,WaitTerm,OutputVars) :- | |
277 | compute_wait_variables(WaitTerm,OutputVars,WaitVars), | |
278 | %print_message(delay(Call,WaitVars)), | |
279 | when(ground(WaitVars),Call). | |
280 | ||
281 | delay_call(Call,OutputVars) :- | |
282 | delay_call(Call,Call,OutputVars). | |
283 | ||
284 | % LocalWF: created by create_inner_wait_flags; enumeration finished waitflag should not be grounded here | |
285 | delay_not(Call,OutputVars, LocalWF) :- % print_message(informational,delay_not(Call,OutputVars,LocalWF)), | |
286 | delay_call( not_with_enum_warning(Call, LocalWF) , OutputVars). | |
287 | ||
288 | not_with_enum_warning(Call, LocalWF) :- | |
289 | %% print_term_summary(neg_call(Call, LocalWF)), %% | |
290 | enter_new_clean_error_scope(Level), | |
291 | call_cleanup(not_with_enum_warning2(Call,Level,LocalWF), | |
292 | exit_error_scope(Level,_ErrOccured,not_with_enum_warn)). | |
293 | ||
294 | :- use_module(kernel_waitflags,[pending_abort_error/1, ground_wait_flags/1]). | |
295 | not_with_enum_warning2(Call,Level,LocalWF) :- | |
296 | ? | (Call -> %% print(clearing_warnings(Level)),nl, %% |
297 | (pending_abort_error(LocalWF) | |
298 | -> debug_println(19,pending_abort_error(LocalWF)), | |
299 | PENDING_ABORT = true | |
300 | % we do not fail in this case, so that abort errors can be raised | |
301 | % relevant for test 1966 or predicate f={1|->2} & not(#s.(s:1..2 & f(s)=3)) with -p TRY_FIND_ABORT TRUE | |
302 | ; clear_events_in_error_scope(Level,enumeration_warning(_,_,_,_,_)), fail | |
303 | % call has succeeded without pending abort errors | |
304 | ) | |
305 | ; otherwise -> true | |
306 | ), | |
307 | % Note: everything else is ground; so Call should not trigger any other co-routines/enumeration ?! | |
308 | Event = enumeration_warning(_,_,_,_,_), | |
309 | (event_occurred_in_error_scope(Event) % critical ? only at this level ? or also below ? | |
310 | -> PENDING_ABORT==true, %print(grd(LocalWF)),nl, kernel_waitflags:portray_waitflags(LocalWF),nl, | |
311 | ground_wait_flags(LocalWF), % raise any potential abort errors | |
312 | debug_println(19,grounded_wait_flag_for_pending_abort_error), % THIS print is important due to issue SPRM-20473 in SICStus Prolog which will otherwise *not* activate the pending co-routines attached to LocalWF ! | |
313 | fail | |
314 | %, print_message(throwing(Event)), throw(Event) % or simply re-post enum warning ? | |
315 | % we could also simply fail and raise an enumeration warning; meaning we havent explored all possibilities | |
316 | % Note: at the moment the error is copied into the outer scope by copy_error_scope_events | |
317 | ; true) | |
318 | %% ,print_term_summary(done_neg_call(Call)),nl %% | |
319 | . | |
320 | ||
321 | ||
322 |