1 % (c) 2009-2026 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
6 :- module(tools, [exact_member/2,
7 exact_member_lookup/4, exact_member_replace/5,
8 remove/3, remove_all/3, insert/3,
9 remove_variables/3,
10 list_intersection/3, list_difference/3,
11 disjoint_list_union/3, lists_are_disjoint/2,
12 string_concatenate/3, is_upper_case_name/1,
13 %write_to_file/2, write_to_utf8_file/2, put_codes/2, % now in tools_files
14 %print_error/1,
15 print_message/1, print_message_with_max_depth/2,
16 print_short_msg/1,
17 print_bt_message/1, print_bt_trace_message/1, bt_trace/1,
18 format_bt/2,
19 print_wtime/1,
20 print_mb/1, print_gb/1,
21 prints/1,
22 is_absolute_path/1,
23 split_common_path/4,
24 gen_relative_path/3, gen_relative_path_to_cur_dir/2,
25 get_parent_directory/2, get_parent_directory_name/2,
26 get_parent_directory_of_directory/2,
27 get_tail_filename/2, get_modulename_filename/2,
28 get_filename_extension/2,
29 get_option_from_list/4, get_options_from_list/2,
30 get_options/4,get_options/5,
31 arg_is_number/2, arg_is_number_or_wildcard/2,
32 check_filename_arg/2,
33 arg_is_integer/2,
34 split_filename/3,
35 safe_absolute_file_name/2, safe_absolute_file_name/3,
36 host_file_name_case_insensitive/0,
37 same_file_name/2,
38
39 filter/4,
40 flatten/2,
41 % count_occurences/2, % now in tools_lists
42 split_last/4,
43 split_atom/3, split_chars/3, split_complex_sep/3,
44 split_atom_at_separator/4,
45 latex_escape_atom/2,
46 b_escape_string_atom/2, b_string_escape_codes/2,
47 string_escape/2, print_escaped/1, % can also be used for dot
48 simple_dot_string_escape/2, % for dot, just quoting "
49 xml_escape/2, xml_escape_codes/2,
50 html_escape/2, html_escape_codes/2,
51 xml_attribute_escape/2,
52 read_term_from_file/2,
53 safe_read_term_from_atom/2, safe_read_term_from_codes/2,
54 read_string_from_file/2, read_string_from_file/3,
55 safe_read_string_from_file/3,
56 read_atom_from_file/3,
57 % write_lines_to_file/2, % now in tools_files
58 open_temp_file/3,
59 open_temp_file/4,
60 detect_xml_encoding/3,
61
62 ajoin/2,
63 ajoin_with_limit/3,
64 ajoin_with_sep/3,
65 substitute/4,
66 % call_residue/2, % now in tools_meta
67
68 catch_call/1,
69 observe_user_interrupt_signal/3,
70 %safe_on_exception/3, safe_on_exception_silent/3, % now in tools_meta
71 %reraise_important_exception/1, % now in tools_meta
72 % catch_matching/3, % now in tools_meta
73
74 convert_list_into_pairs/2, convert_pairs_into_list/3,
75
76 safe_univ/2, safe_univ_no_cutoff/2,
77 safe_sort/3,
78 safe_set_sort/3,
79 safe_functor/4,
80
81 safe_atom_codes/2, safe_atom_chars/3,
82 atom_codes_with_limit/2, atom_codes_with_limit/3,
83 truncate_atom/3, wrap_and_truncate_atom/4,
84 safe_number_codes/2,
85 atom_or_number_codes/2,
86 ensure_atom/2,
87 number_suffix/3,
88
89 print_size_of_table/1,
90 print_runtime/0, start_ms_timer/1, stop_ms_timer/2, stop_ms_timer/1,
91 stop_ms_timer_with_msg/2,
92 stop_ms_timer_with_silent_msg/2,
93 stop_ms_timer_with_debug_msg/2,
94 stop_ms_walltimer_with_msg/2,
95 bt_start_ms_timer/1, bt_stop_ms_timer/1,
96 get_elapsed_walltime/2,
97 get_elapsed_timer/2, combiner_timer/3,
98 get_elapsed_runtime/2,
99 cputime/1, walltime/1,
100 convert_ms_time_to_string/2,
101
102 retract_with_statistics/2,
103 statistics_memory_used/1,
104 get_memory_used/1, print_memory_used_difference/2, print_memory_used/1,
105 print_memory_used_wo_gc/0, print_memory_used_wo_gc/1,
106
107 split_list/4,
108 split_list_idx/5, re_split_list_idx/4,
109
110 minimize_lasso/4,
111
112 map_split_list/4,
113 foldl/4,foldl/5,foldl/6,
114 maplist5/5,
115 average/2,
116
117 map_optlist/2,
118 optlist_to_list/2,
119
120 (space_call)/1,
121 assert_once/1,
122
123 % safe_time_out/3, % now in tools_meta
124 % time_out_call/2, time_out_call/1, time_out_with_factor_call/3, % now in tools_timeout
125
126 unique_id/2,
127
128 get_PROBPATH/1,
129 atom_to_number/2,
130 get_set_optional_prolog_flag/3,
131
132 bb_safe_get/2,
133 bb_inc/1, bb_inc_by/2, bb_dec/1,
134
135 top_sort/3
136 ]).
137
138 :- meta_predicate foldl(3,?,?,?).
139 :- meta_predicate foldl2(?,3,?,?).
140 :- meta_predicate foldl(4,?,?,?,?).
141 :- meta_predicate foldl2(?,4,?,?,?).
142 :- meta_predicate foldl(5,?,?,?,?,?).
143 :- meta_predicate foldl2(?,5,?,?,?,?).
144 :- meta_predicate maplist5(4,-,-,-,-).
145 :- meta_predicate catch_call(0).
146 :- meta_predicate assert_once(0).
147 :- meta_predicate split_list_idx(1,?,?,?,?).
148 :- meta_predicate split_list_idx2(?,1,?,?,?).
149 :- meta_predicate map_split_list(2,?,?,?).
150 :- meta_predicate map_split_list2(?,2,?,?).
151 :- meta_predicate split_list(1,?,?,?).
152 :- meta_predicate split_list2(?,1,?,?).
153 :- meta_predicate observe_user_interrupt_signal(-,-,0).
154 :- meta_predicate filter(1,*,*,*).
155 :- meta_predicate get_options(+,4,-,-).
156 :- meta_predicate get_options(+,4,-,-,0).
157 :- meta_predicate retract_all_count(0,-,-).
158 :- meta_predicate space_call(0).
159 :- meta_predicate map_optlist(1,-).
160 :- meta_predicate map_optlist_aux(-,1).
161 :- meta_predicate call_optional(1,-).
162 :- meta_predicate call_optional_aux(-,1,-).
163
164 :- meta_predicate get_calls_for_table(:,-).
165 :- meta_predicate print_size_of_table(:).
166
167 :- meta_predicate string_escape(2,-,-).
168
169 :- meta_predicate bb_safe_get(0,-).
170 :- meta_predicate bb_inc(0).
171 :- meta_predicate bb_dec(0).
172 :- meta_predicate bb_inc_by(0,-).
173
174 :- use_module(module_information).
175
176 :- module_info(group,infrastructure).
177 :- module_info(description,'This module contains many general helper predicates.').
178
179 :- use_module(pathes,[runtime_application_path/1]). % we just import it to set-up pathes, we don't need any predicates
180
181 :- use_module(library(lists)).
182 :- use_module(library(system)).
183 %%:- use_module(library(file_systems)). %% not required ?
184 %% :- use_module(library(codesio)).
185
186
187 :- use_module(tools_meta,[reraise_important_exception/1, safe_on_exception/3]).
188
189 :- set_prolog_flag(double_quotes, codes).
190
191
192 catch_call(Call) :-
193 catch(call(Call), Exception, (
194 add_error(catch_call,'Call raised an exception: ',(Call:Exception)),
195 /* read(_), */
196 reraise_important_exception(Exception),
197 fail
198 )).
199
200 :- use_module(error_manager,[add_warning/4]).
201 % catch CTRL-C and print a message if it happens and re-throw the interrupt
202 observe_user_interrupt_signal(Context,Span,Call) :-
203 catch(Call, user_interrupt_signal, (
204 add_warning(user_interrupt_signal,'CTRL-C occurred in context: ',Context,Span),
205 %(pending_abort_error(WF,Msg,_ErrTerm,Span)
206 % % Unfortunately any abort errors induced by Call itself are already removed by the exception
207 % -> add_message(user,'Pending WD-Error could cause long runtimes: ',Msg,Span) ; true),
208 throw(user_interrupt_signal)
209 )).
210
211
212
213 :- use_module(self_check). % put after search paths have been set
214
215 % These predicates from tools_platform are tested here to avoid module load order issues.
216
217 :- use_module(tools_platform, [map_host_platform/2, map_host_processor/2, host_platform/1]).
218 :- assert_must_succeed(map_host_platform("x86-win32-nt-4",windows)).
219 :- assert_must_succeed(map_host_platform("x86_64-win32-nt-4",windows)).
220 :- assert_must_succeed(map_host_platform("x86_64-darwin-10.6.0",darwin)).
221 :- assert_must_succeed(map_host_platform("x86_64-darwin-18.7.0",darwin)).
222 :- assert_must_succeed(map_host_platform("arm64-darwin-20.1.0",darwin)).
223 :- assert_must_succeed(map_host_platform("x86-linux-glibc2.7",linux)).
224 :- assert_must_succeed(map_host_platform("x86_64-linux-glibc2.17",linux)).
225 :- assert_must_succeed(map_host_platform("aarch64-linux-glibc2.28",linux)).
226 :- assert_must_succeed(map_host_platform("nonsense-potatos-4.2",unknown)).
227 % On SWI, the arch/host_type flag doesn't include an OS version number.
228 :- assert_must_succeed(map_host_platform("i386-win32",windows)).
229 :- assert_must_succeed(map_host_platform("x64-win64",windows)).
230 :- assert_must_succeed(map_host_platform("x86_64-darwin",darwin)).
231 :- assert_must_succeed(map_host_platform("arm64-darwin",darwin)).
232 :- assert_must_succeed(map_host_platform("aarch64-linux",linux)).
233 :- assert_must_succeed(map_host_platform("nonsense-potatos",unknown)).
234
235 :- assert_must_succeed((host_platform(Platform), Platform \= unknown)).
236
237
238 :- assert_must_succeed(map_host_processor("x86-win32-nt-4",x86)).
239 :- assert_must_succeed(map_host_processor("x86_64-win32-nt-4",x86_64)).
240 :- assert_must_succeed(map_host_processor("x86_64-darwin-10.6.0",x86_64)).
241 :- assert_must_succeed(map_host_processor("x86_64-darwin-18.7.0",x86_64)).
242 :- assert_must_succeed(map_host_processor("arm64-darwin-20.1.0",aarch64)).
243 :- assert_must_succeed(map_host_processor("x86-linux-glibc2.7",x86)).
244 :- assert_must_succeed(map_host_processor("x86_64-linux-glibc2.17",x86_64)).
245 :- assert_must_succeed(map_host_processor("aarch64-linux-glibc2.28",aarch64)).
246 :- assert_must_succeed(map_host_processor("nonsense-potatos-4.2",unknown)).
247 % On SWI, the arch/host_type flag doesn't include an OS version number.
248 :- assert_must_succeed(map_host_processor("i386-win32",x86)).
249 :- assert_must_succeed(map_host_processor("x64-win64",x86_64)).
250 :- assert_must_succeed(map_host_processor("x86_64-darwin",x86_64)).
251 :- assert_must_succeed(map_host_processor("arm64-darwin",aarch64)).
252 :- assert_must_succeed(map_host_processor("aarch64-linux",aarch64)).
253 :- assert_must_succeed(map_host_processor("nonsense-potatos",unknown)).
254
255 % --------------------------------------
256
257 cputime(T) :-
258 statistics(runtime,[T,_]).
259
260 walltime(WT) :-
261 statistics(walltime,[WT,_]).
262 % --------------------------------------
263
264
265 :- use_module(error_manager,[add_error/3, add_internal_error/2]).
266 print_message(Msg) :- print_message_with_max_depth(Msg,20).
267 print_message_with_max_depth(Msg,MaxDepth) :-
268 safe_on_exception(E,print_message2(Msg,MaxDepth),
269 add_error(tools,'Exception in print_message: ',E)). % added because sometimes in Windows/Vista we get an exception here
270 print_message2(Msg,MaxDepth) :-
271 (var(Msg) -> print_message(informational,'_')
272 ; write(user_output,'% '),write_term(user_output,Msg,[max_depth(MaxDepth)]),nl(user_output) ).
273 print_short_msg(Msg) :-
274 write(user_output,Msg).
275
276 format_bt(Msg,P) :- format(Msg,P).
277 format_bt(Msg,P) :- print(' * BACKTRACK: '),format(Msg,P),
278 fail.
279
280 print_bt_message(Msg) :- print_message(Msg).
281 print_bt_message(Msg) :- print_message(backtrack(Msg)),
282 %(Msg = found_enumeration_of_constants(_,_) -> trace ; true),
283 fail.
284
285 % like print_bt_message but trace upon backtrack
286 print_bt_trace_message(Msg) :- print_message(Msg).
287 print_bt_trace_message(Msg) :- trace,
288 print_message(backtrack(Msg)),
289 fail.
290
291 % trace upon backtrack:
292 bt_trace(_) :- true.
293 bt_trace(PP) :- print(' * BACKTRACK: '), print(PP),nl, trace,fail.
294
295 print_wtime(PP) :- statistics(walltime,[WT,_]),
296 format('~w : ~w ms ~n', [PP,WT]).
297
298 % a print that will automatically stop after 25 prints and give the user the option to inspect the printed messages
299 :- dynamic prints_count/1.
300 prints_count(25).
301 prints(L) :- print_bt_message(L),
302 (retract(prints_count(X)) -> true ; X=25),
303 (X<1
304 -> print('*** Stopped printing >'),
305 read(RT),
306 (number(RT) -> X1=RT ; X1=25)
307 ; X1 is X-1
308 ), assertz(prints_count(X1)).
309
310 :- assert_pre(tools:exact_member(_Var,Vs),
311 (list_skeleton(Vs))).
312 :- assert_post(tools:exact_member(_Var,_Vs), true).
313 :- assert_must_succeed(tools:exact_member(V,[V])).
314 :- assert_must_succeed(tools:exact_member(V,[_X,_Z,V])).
315 :- assert_must_fail(tools:exact_member(_W,[_X,_Z,_V])).
316 :- assert_must_fail(tools:exact_member(_W,[])).
317
318 exact_member(X,[Y|T]) :-
319 (X==Y -> true ; exact_member(X,T)).
320
321
322 :- assert_pre(tools:exact_member_lookup(_Var,_ValRes,Vs,Vals),
323 (list_skeleton(Vs),list_skeleton(Vals))).
324 :- assert_post(tools:exact_member_lookup(_Var,_ValRes,_Vs,_Vals), true).
325 :- assert_must_succeed(tools:exact_member_lookup(V,2,[V],[2])).
326 :- assert_must_succeed(tools:exact_member_lookup(V,2,[_X,_Z,V],[1,3,2])).
327 :- assert_must_fail(tools:exact_member_lookup(V,3,[_X,_Z,V],[1,3,2])).
328 :- assert_must_fail(tools:exact_member_lookup(_W,3,[_X,_Z,_V],[1,3,2])).
329 :- assert_must_fail(tools:exact_member_lookup(_W,3,[],[1,3,2])).
330
331 exact_member_lookup(Var,ValRes,[V|TV],[Val|TVal]) :-
332 (Var==V -> ValRes=Val ; exact_member_lookup(Var,ValRes,TV,TVal)).
333
334
335 :- assert_pre(tools:exact_member_replace(_Var,_ValRes,Vs,Vals,_),
336 (list_skeleton(Vs),list_skeleton(Vals))).
337 :- assert_post(tools:exact_member_replace(_Var,_ValRes,_Vs,_Vals,NewVals),
338 list_skeleton(NewVals)).
339 :- assert_must_succeed(tools:exact_member_replace(V,44,[_X,_Z,V],[1,3,2],[1,3,44])).
340 :- assert_must_fail(tools:exact_member_replace(_V,44,[_X,_Z,_VV],[1,3,2],[1,3,44])).
341
342 exact_member_replace(Var,NewVal,[V|TV],[Val|TVal],[NV|TN]) :-
343 ((Var==V) -> (NV=NewVal,TN=TVal)
344 ; (NV=Val,exact_member_replace(Var,NewVal,TV,TVal,TN))).
345
346
347
348 remove([X|T],X,T).
349 ?remove([Y|T],X,[Y|DT]) :- \+(X=Y), remove(T,X,DT).
350
351 :- assert_must_succeed(tools:flatten([],[])).
352 :- assert_must_succeed(tools:flatten([[1,2,3]],[1,2,3])).
353 :- assert_must_succeed(tools:flatten([[1],[[99]],[2,3],[4,5]],[1,99,2,3,4,5])).
354 :- assert_must_succeed(tools:flatten([[]],[])).
355
356 flatten(List,FlatList) :- flatten1(List,[],FlatList).
357 flatten1([],L,L) :- !.
358 flatten1([H|T],Tail,List) :- !, flatten1(H,FlatList,List), flatten1(T,Tail,FlatList).
359 flatten1(NonList,Tail,[NonList|Tail]).
360
361
362 :- use_module(tools_lists,[count_occurences/2]).
363 :- assert_must_succeed((tools_lists:count_occurences([a,b,a,a,b],R),R == [a-3,b-2])).
364 :- assert_must_succeed((tools_lists:count_occurences([a],R),R == [a-1])).
365 :- assert_must_succeed((tools_lists:count_occurences([],R),R == [])).
366 :- assert_must_succeed((tools_lists:count_occurences([a,b],R),R == [a-1,b-1])).
367
368
369
370 :- assert_must_succeed((tools:filter(var,[1,2,X,3,Y],R,Out),R == [X,Y],Out == [1,2,3])).
371 :- assert_must_succeed((tools:filter(nonvar,[1,2,X,3,Y],R,Out),R == [1,2,3],Out == [X,Y])).
372
373 filter(_Pred,[],[],[]).
374 filter(Pred,[H|T],True,False) :-
375 ? (call(Pred,H) -> True = [H|TT], filter(Pred,T,TT,False)
376 ? ; False = [H|FF], filter(Pred,T,True,FF)).
377
378 :- public is_a_comment/3. % used for testing filter
379 :- assert_must_succeed((tools: filter(tools:is_a_comment('/*','*/'),['This comment ','/* comment */','will be ignored!'],R,Out),
380 R==['/* comment */'],Out==['This comment ','will be ignored!'])).
381 % checking whether an atom is a comment /* ... */
382 is_a_comment(Begin,End,Comment) :-
383 maplist(atom_codes,[Begin,End,Comment],[BL,EL,CL]),
384 ? prefix(CL,BL),suffix(CL,EL).
385
386 :- assert_must_succeed((tools:remove_variables([X,Y,Z],[Y],R),R==[X,Z])).
387 remove_variables(List,Vars,Remaining) :-
388 exclude(exact_member_rev(Vars),List,Remaining).
389 % a version of exact member with reversed parameters - usefull with
390 % higher-order functions
391 exact_member_rev(List,Member) :- exact_member(Member,List).
392
393 :- assert_must_succeed(tools:insert([],a,[a])).
394 :- assert_must_succeed(tools:insert([a,b,c,d],a,[a,b,c,d])).
395 :- assert_must_succeed(tools:insert([a,b,c,d],b,[a,b,c,d])).
396 :- assert_must_succeed(tools:insert([a,b,c,d],d,[a,b,c,d])).
397 :- assert_must_succeed(tools:insert([a,b,c,d],x,[a,b,c,d,x])).
398 insert([],X,[X]).
399 insert([H|T],X,R) :- (H=X -> R=[H|T] ; R=[H|R2],insert(T,X,R2)).
400
401
402 :- assert_must_succeed(tools:list_intersection([a,b,c,d],[d,f,b],[b,d])).
403
404 list_intersection([],_L,[]).
405 list_intersection([H|T],L,Res) :-
406 ? (remove(L,H,NL) -> (Res=[H|RR]) ; (Res=RR,NL=L)),
407 list_intersection(T,NL,RR).
408
409 insert_new([],X,[X]).
410 insert_new([H|T],X,R) :- (H=X -> fail ; R=[H|R2],insert_new(T,X,R2)).
411
412 :- assert_must_succeed(tools:disjoint_list_union([a,b,c,d],[e,f,g],[e,f,g,a,b,c,d])).
413 :- assert_must_fail(tools:disjoint_list_union([a,b,c,d],[d,f,b],_)).
414 :- assert_must_fail(tools:disjoint_list_union([a,b,c,d],[d,f,b],[d,f,b,a,c])).
415 disjoint_list_union([],L,L).
416 disjoint_list_union([H|T],L,Res) :- insert_new(L,H,L2), disjoint_list_union(T,L2,Res).
417
418 :- assert_must_succeed(tools:lists_are_disjoint([a,b,c,d],[e,f,g])).
419 :- assert_must_succeed(tools:lists_are_disjoint([a,b,c,d],[])).
420 :- assert_must_succeed(tools:lists_are_disjoint([],[e,f,g])).
421 :- assert_must_fail(tools:lists_are_disjoint([a,b,c,d],[e,f,g,d])).
422 lists_are_disjoint([],_).
423 lists_are_disjoint([H|T],List2) :- \+ member(H,List2), lists_are_disjoint(T,List2).
424
425 :- assert_must_succeed(tools:list_difference([a,b,c,d],[b,f,d],[a,c])).
426
427 list_difference([],_L,[]).
428 list_difference([H|T],L,Res) :-
429 ? (remove(L,H,NL) -> (Res=RR) ; (Res=[H|RR],NL=L)),
430 list_difference(T,NL,RR).
431
432 :- use_module(tools_strings,[string_concatenate/3]).
433 :- assert_must_succeed(( tools_strings:string_concatenate('5','.10',R), R=='5.10' )).
434
435
436
437
438 :- assert_must_succeed(tools:is_upper_case_name('GOODS')).
439 :- assert_must_succeed(tools:is_upper_case_name('ZZAA')).
440 :- assert_must_fail(tools:is_upper_case_name('capacity')).
441 :- assert_must_fail(tools:is_upper_case_name('PARAs')).
442
443 :- use_module(tools_strings,[safe_name/2]).
444 is_upper_case_name(Name) :-
445 safe_name(Name,AsciiList),
446 upper_case_list(AsciiList).
447
448 upper_case_list([]).
449 upper_case_list([H|T]) :- 0'A =< H, H =< 0'Z, upper_case_list(T).
450
451
452 :- assert_must_succeed(tools:split_common_path('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/','/aaaa/bbb/cc','d.app')).
453 :- assert_must_succeed(tools:split_common_path('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/e','/aaaa/bbb/cc','d.app')).
454 :- assert_must_succeed(tools:split_common_path('/aaaa/bbb/cc/d.app','/aaaa/bbb/ce/e','/aaaa/bbb','cc/d.app')).
455 :- assert_must_succeed(tools:split_common_path('/aaaa/bbb/cc/d.app','/aaaa/e','/aaaa','bbb/cc/d.app')).
456 split_common_path(Path1,Path2,CommonPrefix,Suffix1) :-
457 split_atom_with_empty(Path1,['/','\\'],PC1),
458 split_atom_with_empty(Path2,['/','\\'],PC2),
459 split_com_aux(PC1,PC2,Com,Suff),
460 ajoin_with_sep(Com,'/',CommonPrefix),
461 ajoin_with_sep(Suff,'/',Suffix1).
462
463 split_com_aux([],_,[],Suffix) :- !, Suffix=[].
464 split_com_aux([H|T1],[H|T2],[H|CT],Suffix) :- !,
465 split_com_aux(T1,T2,CT,Suffix).
466 split_com_aux(P1,_,[],P1).
467
468
469 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/e.app','bbb/cc/d.app')).
470 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/bbb/e.app','cc/d.app')).
471 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/e.app','d.app')).
472 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/xx/e.app','../bbb/cc/d.app')).
473 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/xx/yy/e.app','../../bbb/cc/d.app')).
474 :- assert_must_succeed(tools:gen_relative_path('/aaaa/bbb/cc/d.app','/aaaa/xx/yy/','../../bbb/cc/d.app')).
475 :- assert_must_succeed(tools:gen_relative_path('d.app','/aaaa/xx/yy/','d.app')).
476 :- assert_must_succeed(tools:gen_relative_path('d/e.app','/aaaa/xx/yy/','d/e.app')).
477 % can be used for already expanded full paths;
478 % the relative_to option absolute_file_name does not seem to work on already expanded full paths
479 gen_relative_path(Path,ReferencePath,RelPath) :-
480 split_atom_with_empty(Path,['/','\\'],PC1),
481 (PC1 = [File|_], File \= ''
482 -> % this is a relative path, not an absolute one; this is not the intended use of this predicate
483 RelPath = Path % but can happen, e.g., in error_manager
484 ; split_atom_with_empty(ReferencePath,['/','\\'],PC2),
485 split_com_aux(PC1,PC2,_,Suff1),
486 split_com_aux(PC2,PC1,_,Suff2),
487 (Suff2=[_|D2] -> true ; D2=Suff2), % remove last entry
488 maplist(replace_by_dotdot,D2,DotDot), % replace directory names by ..
489 append(DotDot,Suff1,Rel1),
490 ajoin_with_sep(Rel1,'/',RelPath)
491 ).
492
493 replace_by_dotdot(_,'..').
494
495 :- use_module(library(file_systems),[current_directory/1]).
496 gen_relative_path_to_cur_dir(Path,RelPath) :-
497 current_directory(CurDir),
498 gen_relative_path(Path,CurDir,RelPath).
499
500
501 :- assert_must_succeed(tools:is_absolute_path('/aaaa/bbb/cc/d.app')).
502 :- assert_must_fail(tools:is_absolute_path('cc/d.app')).
503 :- assert_must_fail(tools:is_absolute_path('./cc/')).
504 is_absolute_path(Path) :-
505 atom_chars(Path,PathAscii),
506 PathAscii = ['/'|_]. % TO DO: add other rules for windows C: ...
507
508 :- assert_must_succeed(tools:get_parent_directory('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/')).
509 :- assert_must_succeed(tools:get_parent_directory('/aaaa/bbb/cc/','/aaaa/bbb/cc/')).
510 :- assert_must_succeed(tools:get_parent_directory('d.app','')).
511 :- assert_must_succeed(tools:get_parent_directory('/a/b/cc/(machine from Jupyter cell).mch','/a/b/cc/')).
512 :- assert_must_succeed(tools:get_parent_directory('D:\\Users\\OneDrive - hhu\\[Master] PA\\models\\r\\RF.mch',
513 'D:\\Users\\OneDrive - hhu\\[Master] PA\\models\\r\\')).
514
515 get_parent_directory(Path,NewPath) :-
516 atom_chars(Path,PathAscii),
517 strip_last(PathAscii,[],[],New),
518 atom_chars(NewPath,New).
519
520 :- assert_must_succeed(tools:get_parent_directory_of_directory('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/')).
521 :- assert_must_succeed(tools:get_parent_directory_of_directory('/aaaa/bbb/cc/','/aaaa/bbb/')).
522 :- assert_must_succeed(tools:get_parent_directory_of_directory('\\aaaa\\bb b\\cc\\','\\aaaa\\bb b\\')).
523 % just like get_parent_directory, except when last character is a slash
524 get_parent_directory_of_directory(Path,NewPath) :-
525 atom_chars(Path,PathAscii0),
526 (append(PathAscii,[Last],PathAscii0), is_path_slash(Last)
527 -> true ; PathAscii=PathAscii0),
528 strip_last(PathAscii,[],[],New),
529 atom_chars(NewPath,New).
530
531 is_path_slash('/').
532 is_path_slash('\\'). % Windows
533
534 :- assert_must_succeed(tools:get_parent_directory_name('/aaaa/bbb/cc/d.app','cc')).
535 :- assert_must_succeed(tools:get_parent_directory_name('/aaaa/bbb/cc/','cc')).
536 :- assert_must_succeed(tools:get_parent_directory_name('\\aaaa\\bbb\\cc\\','cc')).
537 :- assert_must_succeed(tools:get_parent_directory_name('\\aaaa\\bbb b\\cc c\\','cc c')).
538 :- assert_must_fail(tools:get_parent_directory_name('d.app',_)).
539 :- assert_must_succeed(tools:get_parent_directory_name('/a/b/cd/(machine from Jupyter cell).mch','cd')).
540
541 get_parent_directory_name(Path,DirName) :-
542 atom_chars(Path,P1),
543 split_last2_lst(P1, ['/','\\'], [],[], P2, _),
544 split_last2_lst(P2, ['/','\\'], [],[], _, DChars),
545 atom_chars(DirName,DChars).
546
547 :- use_module(library(lists)).
548
549 % strip part after last path slash
550 strip_last([],ResSoFar,_,Res) :- reverse(ResSoFar,Res).
551 strip_last([Slash|Tail],ResSoFar,StripSoFar,Res) :- is_path_slash(Slash), !,
552 append([Slash|StripSoFar],ResSoFar,NewRes),
553 strip_last(Tail,NewRes,[],Res).
554 strip_last([A|Tail],ResSoFar,StripSoFar,Res) :-
555 strip_last(Tail,ResSoFar,[A|StripSoFar],Res).
556
557 :- assert_must_succeed((tools:split_atom_at_separator('ef,g',',',L,R), L==ef,R==g)).
558 % a simple split into two parts using a single separator atom of length 1
559 split_atom_at_separator(Atom,Sep,Left,Right) :-
560 ? sub_atom(Atom, Before, 1, After, Sep),
561 sub_atom(Atom, 0, Before, _, Left),
562 sub_atom(Atom, _, After, 0, Right).
563
564 :- assert_must_succeed((tools:split_atom('ef,g',[','],R), R==[ef,g])).
565 :- assert_must_succeed((tools:split_atom('ef, g',[',',' '],R), R==[ef,g])).
566 :- assert_must_succeed((tools:split_atom('ab,cd,ef,g',[','],R), R==['ab','cd',ef,g])).
567 :- assert_must_succeed((tools:split_atom('ab,cd,ef;g',[',',';'],R), R==['ab','cd',ef,g])).
568 :- assert_must_succeed((tools:split_atom('/ef/g/',['/'],R), R==[ef,g])).
569
570 split_atom(Atom,SepList,SplitList) :-
571 atom_chars(Atom,ListAscii),
572 split2(ListAscii,SepList,SplitList).
573
574 split2([],_,R) :- !,R=[].
575 split2(List,Sep,Res) :- get_next_word(List,Sep,Word,Tail),!,
576 (Word=[]
577 -> split2(Tail,Sep,Res) % skip empty atom
578 ; Res=[Atom|TA], atom_chars(Atom,Word),
579 split2(Tail,Sep,TA)).
580
581 get_next_word([],_Sep,[],[]).
582 get_next_word([H|T],Sep,Word,Tail) :-
583 ? member(H,Sep) -> Word=[],Tail=T
584 ; Word=[H|TR], get_next_word(T,Sep,TR,Tail).
585
586
587 :- assert_must_succeed((tools:split_atom_with_empty('/ef/g/',['/'],R), R==['',ef,g,''])).
588 :- assert_must_succeed((tools:split_atom_with_empty('/ef/g',['/'],R), R==['',ef,g])).
589 :- assert_must_succeed((tools:split_atom_with_empty('ef/g',['/'],R), R==[ef,g])).
590 :- assert_must_succeed((tools:split_atom_with_empty('efg',['/'],R), R==[efg])).
591
592 % a version of split atom that also returns empty sub atoms
593 split_atom_with_empty(Atom,SepList,SplitList) :-
594 atom_chars(Atom,ListAscii),
595 split_wempty2(ListAscii,SepList,SplitList).
596
597 split_wempty2(List,Sep,Res) :-
598 get_next_match(List,Sep,Word,Tail),!,
599 Res=[Atom|TA], atom_chars(Atom,Word),
600 split_wempty2(Tail,Sep,TA).
601 split_wempty2(List,_,[Atom]) :- atom_chars(Atom,List).
602
603 get_next_match([H|T],Sep,Word,Tail) :-
604 ? member(H,Sep) -> Word=[],Tail=T
605 ; Word=[H|TR], get_next_match(T,Sep,TR,Tail).
606
607
608 :- assert_must_succeed((tools:split_complex_sep("ef,,g,h",",,",R), R==["ef","g,h"])).
609 % a version of split that allows longer seperators, % TO DO: make more efficient
610 split_complex_sep(L,Sep,[First|Rest]) :- append(Sep,LRest,SepR),
611 append(First,SepR,L),
612 !,
613 split_complex_sep(LRest,Sep,Rest).
614 split_complex_sep(H,_,[H]).
615
616
617 :- assert_must_succeed((tools:split_chars("ef,g",",",R), R==["ef","g"])).
618 :- assert_must_succeed((tools:split_chars("10",".",R), R==["10"])).
619 :- assert_must_succeed((tools:split_chars("",".",R), R==[""])).
620 :- assert_must_succeed((tools:split_chars("1.0",".",R), R==["1","0"])).
621 :- assert_must_succeed((tools:split_chars("1.",".",R), R==["1",""])).
622 :- assert_must_succeed((tools:split_chars(".1",".",R), R==["","1"])).
623
624 split_chars(List,Sep,Res) :- get_next_word_until_sep(List,Sep,Word,Tail),!,
625 Res=[Word|TA],
626 split_chars(Tail,Sep,TA).
627 split_chars(List,_,[List]).
628
629 get_next_word_until_sep([H|T],Sep,Word,Tail) :-
630 member(H,Sep) -> Word=[],Tail=T
631 ; Word=[H|TR], get_next_word_until_sep(T,Sep,TR,Tail).
632
633 :- assert_must_succeed(tools:split_last('/aaaa/bbb/cc/d.app','/','/aaaa/bbb/cc','d.app')).
634 :- assert_must_succeed(tools:split_last('/aaaa/bbb/cc/d.app','.','/aaaa/bbb/cc/d','app')).
635 split_last(Atom, Sep, Head, Tail) :- \+ atom(Sep),!,
636 add_internal_error('Separator not an atom: ', split_last(Atom, Sep, Head, Tail)),fail.
637 split_last(Atom, Sep, Head, Tail) :- atom_chars(Sep,SepACodes),
638 split_last_lst(Atom,SepACodes,Head,Tail).
639
640
641 :- assert_must_succeed(tools:split_last_lst('/aaaa/bbb;cc/d.app',['/',';'],'/aaaa/bbb;cc','d.app')).
642 :- assert_must_succeed(tools:split_last_lst('/aaaa/bbb/cc/d.app',['.'],'/aaaa/bbb/cc/d','app')).
643 % a list version of split_last: obtains a list of sperator chars
644 split_last_lst(Atom, Seps, Head, Tail) :- \+ atom(Atom),!,
645 add_internal_error('First arg not an atom: ', split_last_lst(Atom, Seps, Head, Tail)),fail.
646 split_last_lst(Atom, Seps, Head, Tail) :-
647 atom_chars(Atom,ListAscii),
648 split_last2_lst(ListAscii,Seps,[],[],HeadA, TailA),
649 atom_chars(Head,HeadA), atom_chars(Tail,TailA).
650
651 split_last2_lst([],_,CurSplit,[_|Head],ResH,ResT) :-
652 reverse(CurSplit,ResT),
653 reverse(Head,ResH).
654 ?split_last2_lst([Sep|Tail],Seps,CurSplit,Head,ResH,ResT) :- member(Sep,Seps), % TO DO: use ord_member ?
655 !,
656 append([Sep|CurSplit],Head,NewHead),
657 split_last2_lst(Tail,Seps,[],NewHead,ResH,ResT).
658 split_last2_lst([H|Tail],Seps,CurSplit,Head,ResH,ResT) :-
659 split_last2_lst(Tail,Seps,[H|CurSplit],Head,ResH,ResT).
660
661
662
663 :- assert_must_succeed(tools:split_filename('/aaaa/bbb/cc/d.app','/aaaa/bbb/cc/d','app')).
664 :- assert_must_succeed((Z='/aaaa/bbb/cc/d',tools:split_filename(Z,R,X),X=='',R==Z)).
665
666 split_filename(Filename,Base,Ext) :-
667 (split_last(Filename,'.',Base,Ext) -> true ; Base=Filename,Ext='').
668
669
670 :- assert_must_succeed(tools:get_tail_filename('/aaaa/bbb/cc/d.app','d.app')).
671 :- assert_must_succeed(tools:get_tail_filename('\\aaaa\\bbb\\c\\d.app','d.app')).
672 :- assert_must_succeed(tools:get_tail_filename('d.app','d.app')).
673 :- assert_must_succeed(tools:get_tail_filename('/aaaa/bbb/cc/','')).
674 get_tail_filename(Path,Tail) :- compound(Path),!,
675 add_internal_error('Not a filename: ',get_tail_filename(Path,Tail)),
676 Tail=Path.
677 get_tail_filename(Path,Tail) :- (split_last_lst(Path, ['/','\\'], _, T) -> Tail=T ; Tail=Path).
678
679 :- assert_must_succeed(tools:get_modulename_filename('/aaaa/bbb/cc/d.app','d')).
680 :- assert_must_succeed(tools:get_modulename_filename('d.app','d')).
681 get_modulename_filename(Path,Module) :-
682 get_tail_filename(Path,Tail),
683 (split_last(Tail, '.', M, _) -> Module=M ; Module=Tail).
684
685
686 :- assert_must_succeed(get_filename_extension('/aaaa/bbb/cc/d.app','app')).
687 get_filename_extension(Path,Ext) :- split_filename(Path,_,Ext).
688
689 % also works if numbers passed (can happen by accident in test_runner ...)
690 % absolute_file_name('$a',X) generates a permission error
691 safe_absolute_file_name(F,AF,Options) :-
692 ensure_atom(F,A),
693 catch(
694 absolute_file_name(A,AF,Options),
695 error(permission_error(_,_,_),ERR),
696 (format('*** Permission Error for absolute_file_name: ~w~n',[ERR]),AF=F)).
697
698 safe_absolute_file_name(F,AF) :- safe_absolute_file_name(F,AF,[]).
699
700 % host_platform specific check if file names match
701 same_file_name(F,F) :- !.
702 same_file_name(File1,File2) :- host_file_name_case_insensitive,
703 atom_codes(File1,FC1),
704 atom_codes(File2,FC2),
705 maplist(case_insensitive_match,FC1,FC2).
706
707 case_insensitive_match(Code,Code) :- !.
708 case_insensitive_match(C1,C2) :-
709 simple_lowcase(C1,L1),
710 simple_lowcase(C2,L1).
711
712 % TODO: are there other changes to be made in Windows filenames?
713 simple_lowcase(H,R) :- H >="A", H=<"Z", !, R is H+"a"-"A".
714 simple_lowcase(92,R) :- !, R=47. % convert Windows path divider \ to Unix one /
715 simple_lowcase(Code,Code).
716
717 host_file_name_case_insensitive :-
718 host_platform(Platform),
719 host_file_name_case_insensitive(Platform).
720 host_file_name_case_insensitive(windows).
721 host_file_name_case_insensitive(darwin). % by default case insensitive, but up/lower case stored
722 % TODO: can we check if main filesystem is case sensitive??
723
724 %*******************************************************************************
725 % remove_all(A,B,Result): Result is the list of elements of A which do not occur in B
726 remove_all([],_,[]).
727 remove_all([H|T],Remove,Result) :-
728 ? (member(H,Remove) -> !,Result = Rest ; Result = [H|Rest]),
729 remove_all(T,Remove,Rest).
730
731
732
733 %*******************************************************************************
734 % get options from Prolog list passed as parameter
735
736
737 :- assert_must_succeed((get_option_from_list(b,2,[a/false,b/true],B),B==true)).
738 get_option_from_list(Option,Default, OptionList,Value) :-
739 (memberchk(Option/V,OptionList) -> Value=V ; Value=Default).
740
741
742 :- assert_must_succeed(get_options_from_list([option(a,true,false)],[a/false,b/true])).
743 :- assert_must_succeed((get_options_from_list([option(a,true,A)],[a/false,b/true]),A==false)).
744 :- assert_must_succeed((get_options_from_list([option(c,3,C),option(b,1,B),option(a,2,A)],[a/false,b/true]),
745 A==false,B==true,C==3)).
746
747 :- use_module(library(lists), [select/3]).
748
749 get_options_from_list([],_OptionList). % TO DO: provide optional check that no other options left
750 get_options_from_list([option(OptionName,Default,Value)|T],OptionList) :-
751 ? (select(OptionName/V,OptionList,Rest) -> Value=V
752 ; Value=Default, Rest=OptionList),
753 get_options_from_list(T,Rest).
754
755
756 %*******************************************************************************
757 % get_options/4 for parsing command line arguments
758
759
760 get_options(List,Pred,Options,Rest) :-
761 get_options(List,Pred,Options,Rest,halt).
762 get_options([],_,[],[],_).
763 get_options([X|T],Recognised,Options,Args,HALTCMD) :-
764 ? ( call(Recognised,X,Opt,Values,Action)
765 ->
766 ( append(Values, Rest, T) -> true
767 ;
768 length(Values,Len),
769 length(T,TLen),
770 (TLen < Len
771 -> format('Command ~w expects ~w argument(s); ~w provided.~n',[X,Len,TLen])
772 ; format('~nInvalid argument(s) for option: ~w.~n',[X]) % will never happen ??
773 ),
774 HALTCMD),
775 ( call(Action) -> true
776 ;
777 format('~nInvalid argument(s) for option ~w : ~w.~n',[X,Values]),
778 HALTCMD),
779 RT = Rest,
780 Options = [Opt|OT], Args = AT
781 ; % option not recognised, keep in Args list (for probcli these are assumed to be files)
782 Options = OT, Args = [X|AT],
783 RT = T
784 ),
785 get_options(RT,Recognised,OT,AT,HALTCMD).
786
787 arg_is_number(Arg,Nr) :- number(Arg),!,Nr=Arg.
788 arg_is_number(Arg,Nr) :- atom(Arg),atom_codes(Arg,Str),safe_number_codes(Nr,Str),number(Nr).
789 arg_is_integer(Arg,Nr) :-
790 ( atom_concat("-",SPos,Arg) -> % negative number
791 arg_is_number(SPos,Pos),
792 Nr is -Pos
793 ;
794 arg_is_number(Arg,Nr)).
795
796 % utilities for command-line arguments
797 % allow Number, _, >Number, <Number
798 arg_is_number_or_wildcard('_',R) :- !, R=_.
799 arg_is_number_or_wildcard('*',R) :- !, R=_. % * on command-line is expanded to files unless quoted
800 arg_is_number_or_wildcard(Arg,R) :- arg_is_number(Arg,N),!,R=N.
801 arg_is_number_or_wildcard(Comparator,R) :- atom(Comparator), atom_codes(Comparator,AC),
802 comparator(AC,CompOp,NC), safe_number_codes(Nr,NC), R=comparison_operator(CompOp,Nr).
803 comparator([0'>|T],>,T). % Ascii code 62
804 comparator([0'<|T],<,T).
805 comparator([0'>,0'=|T],'>=',T).
806 comparator([0'=,0'<|T],'=<',T).
807
808
809 % TO DO: add comparator
810
811 :- use_module(error_manager,[add_warning/3]).
812 check_filename_arg(File,Command) :- tools:arg_is_number(File,_),!,
813 ajoin(['File argument to -',Command,' is a number: '],Msg),
814 add_warning(Command,Msg,File).
815 check_filename_arg(File,Command) :- atom(File),
816 sub_atom(File,0,1,_,'-'), % atom_concat('-',_,File),
817 !,
818 ajoin(['File argument to -',Command,' starts with a hypen: '],Msg),
819 add_warning(Command,Msg,File).
820 check_filename_arg(_,_).
821
822
823 :- assert_must_succeed( tools:convert_list_into_pairs([a],a)).
824 :- assert_must_succeed( (tools:convert_list_into_pairs([a,b,c],R), R = ((a,b),c) )).
825
826 convert_list_into_pairs([X|T],Res) :- !,conv2(T,X,Res).
827 convert_list_into_pairs([],Res) :- !, Res=[].
828 convert_list_into_pairs(X,R) :- add_internal_error('Not a list: ',convert_list_into_pairs(X,R)),R=X.
829 conv2([],X,X).
830 conv2([X|T],Acc,Res) :- conv2(T,(Acc,X),Res).
831
832 :- assert_must_succeed(( tools:convert_pairs_into_list([x],a,R), R==[a] )).
833 %:- assert_must_succeed(( b_interpreter:convert_pairs_into_list([x,y,z],((a,b),c),R), R == [a,b,c] )).
834 :- assert_must_succeed(( tools:convert_pairs_into_list([x,y,z],((a,b),c),R), R == [a,b,c] )).
835 :- assert_must_succeed(( tools:convert_pairs_into_list([x,y,z],A,B),nonvar(A),nonvar(B),A=((_,_),_),B=[_,_,_] )).
836
837 % the first argument just indicates the identifiers, the second is a constructed pair and the third the list of values
838 convert_pairs_into_list([_],X,R) :- !,R=[X].
839 convert_pairs_into_list([_|Guide],Pair,R) :- !, R = [A|TValList],
840 conv4(Guide,A,Pair,TValList).
841 convert_pairs_into_list(List,Pairs,Result) :-
842 add_internal_error('Illegal arguments:',convert_pairs_into_list(List,Pairs,Result)),
843 fail.
844
845 conv4([],X,X,[]).
846 conv4([_|T],Acc,Res,[X|TValList]) :- conv4(T,(Acc,X),Res,TValList).
847
848
849 /* ex: substitute(1, [1,2,3,4], 5, X). */
850 :- assert_must_succeed(( tools:substitute(1, [1,2,3,4], 5, X), X==[5,2,3,4])).
851 substitute(X,L,Y,Res) :- sub(L,X,Y,Res).
852 sub([],_,_,[]).
853 sub([H|T],X,Y,[SH|ST]) :-
854 (H=X -> SH=Y ; SH=H),
855 sub(T,X,Y,ST).
856
857
858
859 :- use_module(tools_strings,[ajoin/2, ajoin_with_sep/3, ajoin_with_limit/3]).
860 % tests are stored here to avoid cyclic module dependencies
861 :- assert_must_succeed((tools_strings: ajoin_with_sep([link,a,xa],'.',Text), Text == 'link.a.xa')).
862 :- assert_must_succeed((tools_strings: ajoin_with_sep([link],'.',Text), Text == 'link')).
863 :- assert_must_succeed((tools_strings: ajoin_with_sep(['',a,''],'.',Text), Text == '.a.')).
864
865 :- assert_must_succeed((tools_strings: ajoin_with_limit(['A','B','C','D'],100,Text), Text == 'ABCD')).
866 :- assert_must_succeed((tools_strings: ajoin_with_limit(['A','B','C','D'],2,Text), Text == 'AB...')).
867
868
869 :- use_module(error_manager,[add_error_and_fail/3]).
870 safe_univ(Term,List) :- nonvar(Term),!,Term=..List.
871 safe_univ(Term,List) :- var(List), !,add_error_and_fail(tools,'Arguments to safe_univ (=..) both var:', safe_univ(Term,List)).
872 safe_univ(Term,List) :- %Term is a variable
873 current_prolog_flag(max_arity,MA),
874 (MA = unbounded -> CL = List ; cut_off_list(List,MA,CL)),
875 !, % avoid pending choice points
876 Term =.. CL.
877
878 cut_off_list([],_,[]).
879 cut_off_list([H|T],MA,R) :- (MA<2 -> R=['...'] ; R=[H|TR],MA1 is MA-1, cut_off_list(T,MA1,TR)).
880
881
882 % a version of safe_univ which does not remove args; just puts the extra arguments into the last arg
883
884 safe_univ_no_cutoff(Term,List) :- nonvar(Term),!,Term=..List.
885 safe_univ_no_cutoff(Term,List) :- var(List), !,
886 add_error_and_fail(tools,'Arguments to safe_univ (=..) both var:', safe_univ_no_cutoff(Term,List)).
887 safe_univ_no_cutoff(Term,List) :- %Term is a variable
888 current_prolog_flag(max_arity,MA),
889 (MA = unbounded -> CL = List ; squash_list(List,MA,CL)),
890 !, % avoid pending choice points
891 Term =.. CL.
892 squash_list([],_,[]).
893 squash_list([H|T],MA,R) :- (MA<3,T\=[] -> R=[H,T] ; R=[H|TR],MA1 is MA-1, squash_list(T,MA1,TR)).
894
895 safe_atom_chars(A,B,Loc) :-
896 catch(atom_chars(A,B), error(E1,E2), (
897 add_internal_error('atom_chars error: ',Loc:E1),
898 throw(error(E1,E2))
899 )).
900
901 :- use_module(tools_strings,[atom_codes_with_limit/2, atom_codes_with_limit/3]).
902
903 safe_atom_codes(V,C) :- var(V),var(C),!,
904 add_internal_error('Variables in call: ',safe_atom_codes(V,C)), C='$VARIABLE$'.
905 safe_atom_codes(A,C) :-
906 catch(atom_codes(A,C), error(representation_error(max_atom_length),_), (
907 print(exception(max_atom_length)),nl,
908 atom_codes_with_limit(A,1000,C)
909 )).
910
911 safe_number_codes(V,C) :- var(V),var(C),!,
912 add_internal_error('Variables in call: ',safe_number_codes(V,C)), C='$VARIABLE$'.
913 safe_number_codes(A,C) :-
914 catch(number_codes(A,C), error(syntax_error(_N),_), (
915 %print(9,syntax_error_in_number_codes(_N)),nl,
916 % in this case safe_number_codes fails ; we cannot convert the codes into a number
917 fail
918 )).
919
920 % for an identifier "x" and a number N, create a new identifier "x$N"
921 number_suffix(Id,N,FullId) :-
922 safe_atom_chars(Id,IdChars,number_suffix1),number_chars(N,NChars),
923 append(IdChars,['$'|NChars],FullIdChars),
924 safe_atom_chars(FullId,FullIdChars,number_suffix2).
925
926
927 :- assert_must_succeed(ensure_atom(19,'19')).
928 % ensure that numbers get converted to atoms:
929 ensure_atom(Var,A) :- var(Var),!, A='_'.
930 ensure_atom(N,Res) :- number(N),!,number_codes(N,C), atom_codes(A,C), Res=A.
931 ensure_atom(A,A).
932
933
934 :- assert_must_succeed(atom_or_number_codes(19,"19")).
935 :- assert_must_succeed(atom_or_number_codes(aa,"aa")).
936 atom_or_number_codes(N,Res) :- number(N),!,number_codes(N,Res).
937 atom_or_number_codes(A,Res) :- atom(A),!,atom_codes(A,Res).
938 atom_or_number_codes(Other,Res) :-
939 add_internal_error('Not atom or number: ',atom_or_number_codes(Other,Res)),
940 Res = [].
941
942
943 :- use_module(tools_strings,[truncate_atom/3]).
944 :- assert_must_succeed((tools_strings:truncate_atom(abcd,100,Text), Text == 'abcd')).
945 :- assert_must_succeed((tools_strings:truncate_atom(abcd,2,Text), Text == 'ab...')).
946 :- assert_must_succeed((tools_strings:truncate_atom(abcd,0,Text), Text == '...')).
947
948
949 wrap_and_truncate_atom(Atom,LineLength,Limit,NewAtom) :- \+ atom(Atom),!,
950 add_internal_error('Argument to wrap_and_truncate_atom not atom: ',wrap_and_truncate_atom(Atom,LineLength,Limit,NewAtom)),
951 NewAtom=Atom.
952 wrap_and_truncate_atom(Atom,LineLength,Limit,NewAtom) :-
953 atom_codes(Atom,Codes),
954 wrap_and_truncate_codes(Codes,LineLength,LineLength,Limit,NewCodes,Chng),
955 (Chng=true -> atom_codes(NewAtom,NewCodes) ; NewAtom=Atom).
956
957 wrap_and_truncate_codes([],_,_,_,[],false).
958 wrap_and_truncate_codes([H|T],CharsOnLineCount,MaxLineLength,TotCount,Res,Chng) :-
959 (TotCount<1 -> Res = [46,46,46],Chng=true /* '...' */
960 ; (CharsOnLineCount<1 -> Res=[92,110,H|TT], /* add newline \n */
961 TR=T, L1 is MaxLineLength, Chng=true
962 ; H=92,T=[H2|T2] -> Res = [H,H2|TT], /* do not split escaped char */
963 TR=T2, L1 is CharsOnLineCount-1, Chng2=Chng
964 ; Res = [H|TT], TR=T, L1 is CharsOnLineCount-1, Chng2=Chng),
965 TC1 is TotCount-1, wrap_and_truncate_codes(TR,L1,MaxLineLength,TC1,TT,Chng2)
966 ).
967
968
969 % print_size_of_table counts the number of succeeded calls of the
970 % given predicate
971 get_calls_for_table(Module:P/N,Call) :- !,
972 functor(Call,P,N),
973 call(Module:Call).
974 get_calls_for_table(P/N,Call) :-
975 functor(Call,P,N),
976 call(Call).
977
978 :- volatile count/1.
979 :- dynamic count/1.
980 print_size_of_table(Pred) :- retractall(count(_)),
981 assertz(count(0)),
982 get_calls_for_table(Pred,_),inc_size_of_table,fail.
983 print_size_of_table(Pred) :-
984 print('% size of table for '), print(Pred), print(': '), count(X), print(X),nl.
985 inc_size_of_table :- retract(count(X)),X1 is X+1, assertz(count(X1)).
986
987
988 % ----------------- Sorting ---------------
989
990 safe_sort(Orig,A,B) :- var(A),!, add_internal_error('Illegal call: ',safe_sort(Orig,A,B)),A=B.
991 safe_sort(_,A,B) :- sort(A,B).
992
993 :- use_module(library(samsort)).
994 % a sorting function which checks that there were no multiples in the original list
995 safe_set_sort(Orig,S,Res) :- var(S),!, add_internal_error('Illegal call: ',safe_set_sort(Orig,S,Res)),S=Res.
996 %safe_set_sort(Orig,S,Res) :- !,sort(S,Res).
997 safe_set_sort(Orig,S,Res) :- samsort(S,SS),
998 (SS=[H|T] -> (check_for_multiples(T,H,Orig) -> Res=SS ; sort(SS,Res)) ; Res=SS).
999 check_for_multiples([],_,_).
1000 check_for_multiples([H|T],Prev,Origin) :-
1001 (H=Prev
1002 -> add_error(Origin,'Multiple occurrences in set list of: ',H),fail
1003 ; check_for_multiples(T,H,Origin)).
1004
1005 % ----------------------
1006
1007 safe_functor(Src,F,A,Term) :- var(F),var(A),var(Term),!,
1008 add_internal_error('Illegal functor call: ',safe_functor(Src,F,A,Term)),
1009 fail.
1010 safe_functor(_,F,A,T) :- functor(F,A,T).
1011
1012
1013 print_runtime :- statistics(runtime,[Tot,SinceLast]), print(' Total runtime: '), print(Tot),
1014 print(' ms, since last: '), print(SinceLast), print(' ms'),nl.
1015
1016 :- use_module(debug,[debug_mode/1, silent_mode/1]).
1017 start_ms_timer(timer(R,T,W)) :- statistics(runtime,[R,_]),
1018 statistics(total_runtime,[T,_]),
1019 statistics(walltime,[W,_]).
1020 stop_ms_timer(T) :- stop_ms_timer(T,[runtime/RT,total_runtime/RTT,walltime/WT]),
1021 format('% Runtime: ~w ms (with gc: ~w ms, walltime: ~w ms)~n',[RT,RTT,WT]).
1022 stop_ms_timer_with_debug_msg(T,Msg) :-
1023 (debug_mode(on) -> stop_ms_timer_with_msg(T,Msg) ; true).
1024 stop_ms_timer_with_silent_msg(T,Msg) :-
1025 (silent_mode(on) -> true ; stop_ms_timer_with_msg(T,Msg)).
1026 stop_ms_timer_with_msg(T,Msg) :- stop_ms_timer(T,[runtime/RT,total_runtime/RTT,walltime/WT]),
1027 statistics(walltime,[WE,_]),
1028 convert_ms_time_to_string(WE,WEStr),
1029 format('% Runtime for ~w: ~w ms (with gc: ~w ms, walltime: ~w ms); since start: ~w~n',[Msg,RT,RTT,WT,WEStr]).
1030 stop_ms_walltimer_with_msg(T,Msg) :- stop_ms_timer(T,[runtime/_RT,total_runtime/_RTT,walltime/WT]),
1031 format('% Walltime for ~w: ~w ms~n',[Msg,WT]).
1032 stop_ms_timer(timer(R,T,W),[runtime/RT,total_runtime/RTT,walltime/WT]) :-!,
1033 statistics(runtime,[RE,_]),
1034 % These refer to CPU time used while executing, excluding time spent in memory management tasks or or in system calls.
1035 statistics(total_runtime,[TE,_]),
1036 % These refer to total CPU time used while executing, including memory management tasks such as garbage collection but excluding system calls.
1037 statistics(walltime,[WE,_]),
1038 % These refer to absolute time elapsed.
1039 RT is RE-R, RTT is TE-T, WT is WE-W.
1040 stop_ms_timer(X,Y) :-
1041 add_internal_error('Illegal call: ', stop_ms_timer(X,Y)),
1042 Y = [].
1043 get_elapsed_walltime(timer(_R,_T,W),WTot) :-
1044 statistics(walltime,[W2,_]), WTot is W2-W.
1045
1046 % convert ms time to a user readable time string
1047 convert_ms_time_to_string(TimeMS,Str) :-
1048 convert_ms_time_to_hms(TimeMS,H,M,S,MS),
1049 (H > 0 -> ajoin([H,' h ', M, ' min ', S, ' sec ', MS, ' ms'],Str)
1050 ; M > 0 -> ajoin([M, ' min ', S, ' sec ', MS, ' ms'],Str)
1051 ; ajoin([S, ' sec ', MS, ' ms'],Str)).
1052
1053 convert_ms_time_to_hms(TimeMS,Hours,MinsMod,SecsMod,MilSecs) :-
1054 MilSecs is TimeMS mod 1000,
1055 Secs is TimeMS // 1000, SecsMod is Secs mod 60,
1056 Mins is Secs // 60, MinsMod is Mins mod 60,
1057 Hours is Mins // 60.
1058
1059 % get delta timer between old timer1 and current time
1060 get_elapsed_timer(timer(R1,T1,W1),timer(R,T,W)) :-
1061 statistics(runtime,[R2,_]),
1062 statistics(total_runtime,[T2,_]),
1063 statistics(walltime,[W2,_]),
1064 R is R2-R1, T is T2-T1, W is W2-W1.
1065 get_elapsed_runtime(timer(R1,_,_),Delta) :-
1066 statistics(runtime,[R2,_]),
1067 Delta is R2-R1.
1068
1069 % combine (add) two timers
1070 combiner_timer(0,T2,R) :- !, R=T2.
1071 combiner_timer(timer(R1,T1,W1),timer(R2,T2,W2),timer(R,T,W)) :-
1072 R is R1+R2, T is T1+T2, W is W1+W2.
1073
1074 % a timer that measures backtracking times
1075 :- dynamic last_bt_timer/2.
1076 bt_start_ms_timer(Msg) :- retractall(last_bt_timer(Msg,_)),
1077 start_ms_timer(T), assertz(last_bt_timer(Msg,T)).
1078 bt_start_ms_timer(Msg) :- retract(last_bt_timer(Msg,Timer)),
1079 stop_ms_timer(Timer,[runtime/RT,total_runtime/_RTT,walltime/WT]),
1080 format('% Runtime to FINALISE ~w: ~w ms (walltime: ~w ms)~n',[Msg,RT,WT]),
1081 fail.
1082
1083
1084 bt_stop_ms_timer(Msg) :-
1085 retract(last_bt_timer(Msg,Last)),
1086 stop_ms_timer(Last,[runtime/RT,total_runtime/_RTT,walltime/WT]),
1087 format('% Runtime for SOLUTION for ~w: ~w ms (walltime: ~w ms)~n',[Msg,RT,WT]).
1088 bt_stop_ms_timer(Msg) :- start_ms_timer(BT_Timer),
1089 assertz(last_bt_timer(Msg,BT_Timer)),
1090 fail.
1091 % ---------------------------
1092
1093
1094 retract_with_statistics(Module,ListOfFacts) :-
1095 nl,print('Retracting Facts'),nl,
1096 get_memory_used(M),
1097 print(' Memory usage: '),print_memory_used(M),nl,
1098 retract_with_statistics(Module,ListOfFacts,M).
1099
1100
1101 retract_with_statistics(_Module,[],_) :- nl.
1102 retract_with_statistics(Module,[Fact|T],Mem) :-
1103 format('~w : ',[Fact]),
1104 %retractall(Module:Fact),
1105 retract_all_count(Module:Fact,0,Nr), format(' ~w facts : ',[Nr]),
1106 get_memory_used(NewMem),
1107 print_memory_used(NewMem),
1108 print_memory_used_difference(Mem,NewMem),
1109 retract_with_statistics(Module,T,NewMem).
1110
1111 retract_all_count(Fact,Acc,Res) :- \+ (\+ (retract(Fact))),!, A1 is Acc+1, retract_all_count(Fact,A1,Res).
1112 retract_all_count(_,R,R).
1113
1114 :- if(current_prolog_flag(dialect, swi)).
1115 % this could possibly be moved to SWI compatibility code
1116 statistics_memory_used(M) :-
1117 statistics(stack,GL), statistics(trail,T), statistics(heapused,H), M is GL+T+H.
1118 % not sure this computes all of memory used; other keys: globalused, localused, codes
1119 :- else.
1120 statistics_memory_used(M) :- statistics(memory_used,M).
1121 :- endif.
1122
1123 get_memory_used([M,PU]) :- garbage_collect,garbage_collect_atoms, get_memory_used_wo_gc([M,PU]).
1124 get_memory_used_wo_gc([M,PU]) :- statistics(program,[PU,_]),statistics_memory_used(M).
1125 print_memory_used_wo_gc :- print_memory_used_wo_gc(user_output).
1126 print_memory_used_wo_gc(Stream) :- get_memory_used_wo_gc(M), print_memory_used(Stream,M).
1127
1128 print_memory_used(M) :- print_memory_used(user_output,M).
1129 print_memory_used(Stream,[M,PU]) :- print_mb(Stream,M),
1130 write(Stream,' ('), print_mb(Stream,PU), write(Stream,' program) ').
1131
1132 print_memory_used_difference([M1,_PU1],[NewM2,_PU2]) :- Diff is (M1)-(NewM2),
1133 (Diff >= 0 -> print(' freed: '), print_bytes(Diff)
1134 ; print(' allocated: '), D2 is -(Diff), print_bytes(D2)),nl.
1135 %print(' / '),Diff2 is (PU1)-(PU2), print_mb(Diff2).
1136 print_bytes(X) :- (X<50000 -> print_kb(X) ; print_mb(X)).
1137 print_gb(X) :- XGB is X / 1000000000, % used instead of deprecated 1048576
1138 format(' ~3f GB',[XGB]).
1139 print_mb(X) :- print_mb(user_output,X).
1140 print_mb(Stream,X) :- XMB is X / 1000000, % used instead of deprecated 1048576
1141 format(Stream,' ~3f MB',[XMB]).
1142 print_kb(X) :- XKB is X / 1000, % used instead of deprecated 1024
1143 format(' ~3f KB',[XKB]).
1144
1145 space_call(Call) :- get_memory_used(M1),
1146 call(Call),
1147 get_memory_used(M2),
1148 print_memory_used(M2),
1149 print_memory_used_difference(M1,M2).
1150
1151 % ---------------------------
1152
1153 :- use_module(library(fastrw),[fast_read/2]).
1154
1155 read_term_from_file(Filename,Term) :-
1156 absolute_file_name(Filename,AbsFilename,[]),
1157 open(AbsFilename,read,Stream),
1158 peek_code(Stream,Code),
1159 read_term_from_file2(Code,Stream,AbsFilename,Term).
1160
1161 read_term_from_file2(Code,Stream,File,Term) :-
1162 fastrw_start_code(Code), !,
1163 close(Stream),
1164 open(File,read,NewStream,[type(binary)]),
1165 fast_read(NewStream,Term),
1166 close(NewStream).
1167 read_term_from_file2(_,Stream,_,Term) :-
1168 read_term(Stream,Term,[]),
1169 close(Stream).
1170
1171 fastrw_start_code(0'D). % like for .prob in parsercall
1172
1173 :- use_module(library(codesio), [read_from_codes/2]).
1174 safe_read_term_from_atom(Atom,Term) :-
1175 safe_atom_codes(Atom,Codes),
1176 safe_read_term_from_codes(Codes,Term).
1177 safe_read_term_from_codes(Codes,Term) :-
1178 (append(_,[46],Codes) % add dot: 46 = '.' if required
1179 -> Codes1 = Codes
1180 ; append(Codes,[46],Codes1)
1181 ),
1182 catch(read_from_codes(Codes1,Term),
1183 error(syntax_error(StxMsg),_),
1184 (atom_codes(AT,Codes),
1185 ajoin(['Prolog syntax error: ',StxMsg,' in'],Msg),
1186 add_error(read_term_from_codes,Msg,AT), fail)
1187 ).
1188
1189
1190 read_string_from_file(Filename,String) :-
1191 absolute_file_name(Filename,AbsFilename,[]),
1192 open(AbsFilename,read,S), % utf
1193 read_string(S,String),
1194 close(S).
1195
1196 % Encoding can be any value of text_encoding preference category: auto, 'ISO-8859-1', 'UTF-8', ...
1197 read_string_from_file(Filename,auto,String) :- !, read_string_from_file(Filename,String).
1198 read_string_from_file(Filename,Encoding,String) :-
1199 absolute_file_name(Filename,AbsFilename,[]),
1200 open(AbsFilename,read,S,[encoding(Encoding)]),
1201 read_string(S,String),
1202 close(S).
1203
1204 read_string(S,String) :-
1205 get_code(S,C),!,
1206 (C= -1
1207 -> String = []
1208 ; String = [C|Rest], read_string(S,Rest)).
1209
1210 read_atom_from_file(Filename,Encoding,Atom) :-
1211 read_string_from_file(Filename,Encoding,String),
1212 atom_codes(Atom,String).
1213
1214 % version which catches exceptions
1215 safe_read_string_from_file(Filename,Encoding,String) :-
1216 catch(read_string_from_file(Filename,Encoding,String), E, (
1217 ajoin(['Could not read string from file ',Filename,':'],Msg),
1218 add_error(read_string_from_file,Msg,E),
1219 fail
1220 )).
1221
1222 :- use_module(debug,[debug_format/3]).
1223 % open a file using auto and try and read XML header
1224 % <?xml version="1.0" encoding="UTF-8"?>
1225 % useful to first open file and detect encoding and then re-open file using the found encoding
1226 detect_xml_encoding(Filename,Version,Encoding) :-
1227 absolute_file_name(Filename,AbsFilename,[]),
1228 open(AbsFilename,read,Stream),
1229 call_cleanup(
1230 (
1231 match(" <?xml_version = \"",Stream),
1232 read_quoted_xml_string_contents(Stream,Version),
1233 debug_format(19,'XML version ~s detected~n',[Version]),
1234 (match(" encoding = \"",Stream)
1235 -> read_quoted_xml_string_contents(Stream,EncodingCodes),
1236 debug_format(19,'XML encoding ~s detected~n',[EncodingCodes]),
1237 (EncodingCodes = "windows 1252" -> UEnc=Encoding % only encoding using lower case
1238 ; maplist(simple_upcase,EncodingCodes,UEnc) % convert utf-8 to UTF-8;
1239 ),
1240 atom_codes(Encoding,UEnc)
1241 ; debug_format(19,'No XML encoding detected in header, using auto ~w~n',[Filename]),
1242 Encoding = auto
1243 )
1244 ),
1245 close(Stream)).
1246
1247
1248 simple_upcase(H,R) :- H >="a", H=<"z", !, R is H+"A"-"a".
1249 simple_upcase(H,H).
1250
1251 % simple read contents of a string until end quote; no escaping detected (yet)
1252 read_quoted_xml_string_contents(Stream,String) :-
1253 get_code(Stream,C),!,
1254 (C= -1
1255 -> String = []
1256 ; C = 34 -> String = [] % string finished
1257 ; String=[C|Rest],read_quoted_xml_string_contents(Stream,Rest)).
1258
1259 match([],_).
1260 match([H|T],Stream) :- !, get_code(Stream,C),
1261 match2(H,T,C,Stream).
1262
1263 match2(32,T,C,Stream) :- !, % treat a space character as optional whitespace
1264 (is_ws(C) -> match([32|T],Stream)
1265 ; T = [H1|T1],
1266 match2(H1,T1,C,Stream)
1267 ).
1268 match2(95,T,C,Stream) :- !, % treat underscore as mandatory whitespace
1269 (is_ws(C) -> match([32|T],Stream)
1270 ; debug_format(19,'Not a valid xml header ("~s" instead of whitespace)~n',[[C]]),fail
1271 ).
1272 match2(H,T,C,Stream) :-
1273 (C=H -> match(T,Stream)
1274 ; format('Not a valid xml header ("~s" instead of "~s")~n',[[C],[H]]),fail
1275 ).
1276
1277 is_ws(32).
1278 is_ws(9). % tab
1279 is_ws(10).
1280 is_ws(13).
1281
1282 %! open_temp_file(+BaseName, -Path, -Stream, +Options) is det.
1283 %
1284 % Compatible way to create and open a temporary file.
1285 %
1286 % ReqName will be used as the file name if possible,
1287 % but the actual file name may be different
1288 % (e. g. it may have a unique suffix added).
1289 % Path is the temporary file's full path,
1290 % which is automatically opened for writing as Stream.
1291 % Supported Options are type(binary) and encoding(Encoding).
1292 % see also tmpdir preference, default /tmp/ on Unix
1293
1294 :- if(predicate_property(tmp_file_stream(_,_,_), _)). % SWI
1295
1296 :- use_module(library(lists), [select/4]).
1297 open_temp_file(ReqName, Path, Stream, Options) :-
1298 select(type(binary), Options, encoding(octet), OptionsMod),
1299 !,
1300 open_temp_file(ReqName, Path, Stream, OptionsMod).
1301 open_temp_file(ReqName, Path, Stream, Options) :-
1302 % tmp_file_stream only allows controlling the extension, not the name before it.
1303 get_filename_extension(ReqName, Extension),
1304 tmp_file_stream(Path, Stream, [extension(Extension)|Options]).
1305
1306 :- else. % SICStus
1307
1308 open_temp_file(ReqName, Path, Stream, Options) :-
1309 open(temp(ReqName), write, Stream, [if_exists(generate_unique_name)|Options]),
1310 stream_property(Stream, file_name(Path)).
1311
1312 :- endif.
1313
1314 open_temp_file(ReqName, Path, Stream) :- open_temp_file(ReqName, Path, Stream, []).
1315
1316 % ------------------------------
1317
1318 % encoding atoms for Latex:
1319
1320 latex_escape_atom(Atom,EscAtom) :- \+ atom(Atom),!,
1321 add_internal_error('Cannot escape: ',latex_escape_atom(Atom,EscAtom)),
1322 EscAtom=Atom.
1323 latex_escape_atom(Atom,EscAtom) :-
1324 atom_codes(Atom,Codes), latex_escape_codes(Codes,ECodes), atom_codes(EscAtom,ECodes).
1325
1326 latex_escape_codes([],[]).
1327 %latex_escape_codes([92,C|T],[92,C|ET]) :- !, % already escaped
1328 % latex_escape_codes(T,ET).
1329 latex_escape_codes([C|T],[92,C|ET]) :- latex_escape_code(C),!, % 95 = _ underscore, 92 = \ backslash
1330 latex_escape_codes(T,ET).
1331 latex_escape_codes([Code|T],ET) :- translate_code(Code,String),
1332 !,
1333 append(String,ET2,ET),
1334 latex_escape_codes(T,ET2).
1335 latex_escape_codes([H|T],[H|ET]) :- latex_escape_codes(T,ET).
1336
1337 latex_escape_code(35). % #
1338 latex_escape_code(36). % $
1339 latex_escape_code(37). % %
1340 latex_escape_code(38). % &
1341 latex_escape_code(95). % 95 = _ underscore
1342 latex_escape_code(123). % {
1343 latex_escape_code(125). % }
1344
1345 translate_code(92,"\\textbackslash{}"). % \ % \textbackslash seems to work in both math and normal mode
1346 translate_code(94,"\\textasciicircum{}"). % ^
1347 translate_code(126,"\\textasciitilde{}"). % ~
1348 %translate_code(126,"\\~{}"). % 126 = ~ tilde -> \~{} (\sim would be alternative in math mode)
1349
1350 % ------------------------------
1351
1352 % encoding atoms for B Strings:
1353 % escape special characters so that we can output the string between quotes "..." and obtain a valid value
1354
1355 :- assert_must_succeed(tools:b_escape_string_atom('{"a"}','{\\"a\\"}')).
1356 :- assert_must_succeed(tools:b_escape_string_atom('{"a \\/ b"}','{\\"a \\/ b\\"}')).
1357
1358 b_escape_string_atom(Atom,EscAtom) :- \+ atom(Atom),!,
1359 add_internal_error('Cannot escape: ',b_escape_string_atom(Atom,EscAtom)),
1360 EscAtom=Atom.
1361 b_escape_string_atom(Atom,EscAtom) :-
1362 atom_codes(Atom,Codes), b_string_escape_codes(Codes,ECodes), atom_codes(EscAtom,ECodes).
1363
1364 b_string_escape_codes([],R) :- !, R=[].
1365 b_string_escape_codes([C|T],Res) :-
1366 (C=92, T=[C2|_], % 92 = \ backslash
1367 \+ valid_backslash_escape(C2) % we do not need to escape the \ in \x for example, but we need to escape in \n
1368 -> Res = [92|ET]
1369 ; b_escape_code(C,EC) -> Res = [92,EC|ET]
1370 ; Res = [C|ET]
1371 ),
1372 b_string_escape_codes(T,ET).
1373
1374 b_escape_code(9,116). % tab, 116 = t
1375 b_escape_code(10,110). % newline, 110 = n
1376 b_escape_code(13,114). % return 114 = r
1377 b_escape_code(34,34). % "
1378 %b_escape_code(39,39). % ' % not necessary for "..." literals
1379 b_escape_code(92,92). % \
1380
1381 % these are the escape codes the parser / ProB currently supports:
1382 valid_backslash_escape(34). % "
1383 valid_backslash_escape(39). % '
1384 valid_backslash_escape(92). % \
1385 valid_backslash_escape(110). %n
1386 valid_backslash_escape(114). %r
1387 valid_backslash_escape(116). %t
1388
1389 % ------------------------------
1390
1391
1392 %%
1393 % Escape is needed for &, \/, /\, ", ', etc.
1394 % used mainly for dot output
1395 % It seems dotty does not understand escapes, neither C style nor HTML style.
1396 % Dot does render '&' correctly, no matter of escape.
1397 %
1398
1399 print_escaped(Atom) :- string_escape(Atom,E), write(E).
1400
1401 :- use_module(library(lists), [
1402 maplist/3,
1403 scanlist/4]).
1404
1405 string_escape(Atom, EscapedAtom):-
1406 string_escape(dot_string_escape_map, Atom, EscapedAtom).
1407
1408
1409 string_escape(_,Number, EscapedAtom):-
1410 number(Number),!, number_chars(Number,C), atom_chars(EscapedAtom,C). % to detect when label=value
1411 string_escape(Map, Atom, EscapedAtom):-
1412 atom(Atom),
1413 atom_chars(Atom, Chars),
1414 maplist(Map, Chars, EscapedChars),
1415 scanlist(x_atom_concat_rev, EscapedChars, '', EscapedAtom),!.
1416 string_escape(Map,Term, EscapedTerm):-
1417 Term =.. [Fkt|Args],
1418 string_escape(Map, Fkt, EscapedFkt),
1419 maplist(string_escape(Map), Args, EscapedArgs),!,
1420 EscapedTerm =.. [EscapedFkt|EscapedArgs].
1421 string_escape(_,X,X).
1422
1423
1424 x_atom_concat_rev(A,B,BA):-
1425 atom_concat(B,A,BA).
1426
1427
1428 % simple escape, just ensuring no syntax errors and not disturbing records already constructed as B string,...
1429 % however, not that fields of records are already escaped
1430 simple_dot_string_escape(Number, EscapedAtom) :- number(Number),!,
1431 number_chars(Number,C), atom_chars(EscapedAtom,C).
1432 simple_dot_string_escape(Atom, EscapedAtom) :- atom(Atom),!,
1433 atom_chars(Atom,Chars),
1434 simple_dot_esc( Chars, EC), atom_chars(EscapedAtom,EC).
1435 simple_dot_string_escape(Atom,Res) :-
1436 add_internal_error('Not atomic: ',simple_dot_string_escape(Atom,Res)),
1437 Res=Atom.
1438
1439 simple_dot_esc([],[]).
1440 simple_dot_esc(['\\','"' | T],['\\','"' |TR]) :- !, % already escaped
1441 simple_dot_esc(T,TR).
1442 simple_dot_esc(['\\','\\' | T],['\\','\\' |TR]) :- !, % already escaped
1443 simple_dot_esc(T,TR).
1444 simple_dot_esc(['"' | T],['\\','"' |TR]) :- !,
1445 simple_dot_esc(T,TR).
1446 simple_dot_esc([H | T],[H|TR]) :-simple_dot_esc(T,TR).
1447
1448
1449 % ----------------
1450
1451
1452 % escape for XML attribute values for use in double quotes
1453 %https://stackoverflow.com/questions/19766669/which-characters-are-permitted-in-xml-attributes
1454 % AttValue ::= '"' ([^<&"] | Reference)* '"'
1455 % | "'" ([^<&'] | Reference)* "'"
1456
1457 xml_attribute_escape(Codes,EscapedCodes) :- xml_attr_escape(Codes,EscapedCodes,[]).
1458 xml_attr_escape([]) --> "".
1459 xml_attr_escape([0'&|T]) --> !, "&", xml_attr_escape(T).
1460 xml_attr_escape([0'<|T]) --> !, "<", xml_attr_escape(T).
1461 xml_attr_escape([0'>|T]) --> !, ">", xml_attr_escape(T). % strictly speaking not necessary for attribute values
1462 xml_attr_escape([10|T]) --> !, " ", xml_attr_escape(T). % newline
1463 xml_attr_escape([9|T]) --> !, " ", xml_attr_escape(T). % tab
1464 xml_attr_escape([0'"|T]) --> !, """, xml_attr_escape(T).
1465 xml_attr_escape([H|T]) --> [H], xml_attr_escape(T).
1466
1467 % ----------------
1468
1469 xml_escape(Number, EscapedAtom) :-
1470 number(Number),!, number_chars(Number,C), atom_chars(EscapedAtom,C).
1471 xml_escape(Atom, EscapedAtom) :-
1472 atom_codes(Atom,Codes),
1473 xml_escape_codes(Codes,ECodes),
1474 atom_codes(EscapedAtom,ECodes).
1475
1476 xml_escape_codes([],[]).
1477 xml_escape_codes([H|T],Res) :-
1478 xml_string_escape_map(H,NewCodes), !,
1479 append(NewCodes,TR,Res),
1480 xml_escape_codes(T,TR).
1481 xml_escape_codes([H|T],[H|TR]) :-
1482 xml_escape_codes(T,TR).
1483
1484 xml_string_escape_map(34,[38,113,117,111,116,59]) :- !. % " -> "
1485 xml_string_escape_map(38,[38,97,109,112,59]) :- !. % & -> &
1486 xml_string_escape_map(39,[38,97,112,111,115,59]) :- !. % ' -> '
1487 xml_string_escape_map(60,[38,108,116,59]) :- !. % < -> <
1488 xml_string_escape_map(62,[38,103,116,59]) :- !. % > -> >
1489
1490
1491 html_escape(Number, EscapedAtom) :-
1492 number(Number),!, number_chars(Number,C), atom_chars(EscapedAtom,C). % to detect when label=value
1493 html_escape(Atom, EscapedAtom) :-
1494 atom_codes(Atom,Codes),
1495 html_escape_codes(Codes,ECodes),
1496 atom_codes(EscapedAtom,ECodes).
1497 % atom_chars(Atom, Chars),
1498 % maplist(html_string_escape_map, Chars, EscapedChars),!,
1499 % scanlist(x_atom_concat_rev, EscapedChars, '', EscapedAtom). % very inefficient for large atoms
1500
1501
1502 html_escape_codes([],[]).
1503 html_escape_codes([H|T],Res) :-
1504 \+ no_escape_necessary(H), % avoid calling atom_codes
1505 atom_codes(Atom,[H]),
1506 html_string_escape_map(Atom,NewAtom), % TODO: rewrite html_escape map to work with codes
1507 !,
1508 atom_codes(NewAtom,NewCodes),
1509 append(NewCodes,TR,Res),
1510 html_escape_codes(T,TR).
1511 html_escape_codes([H|T],[H|TR]) :-
1512 html_escape_codes(T,TR).
1513
1514 no_escape_necessary(32).
1515 no_escape_necessary(40). % (
1516 no_escape_necessary(41). % )
1517 no_escape_necessary(44). % ,
1518 no_escape_necessary(45). % -
1519 no_escape_necessary(46). % .
1520 no_escape_necessary(61). % =
1521 no_escape_necessary(95).
1522 no_escape_necessary(X) :- (X>=97,X=<122) ; (X>=65, X=<90) ; (X>=48, X=<57). % alphadigit
1523
1524 dot_string_escape_map('\n', '\\n').
1525 dot_string_escape_map('\\', '\\\\').
1526 dot_string_escape_map('"', '\\"').
1527 dot_string_escape_map('\'', '\\\'').
1528 dot_string_escape_map('{', '\\{'). % important if used inside dot records
1529 dot_string_escape_map('}', '\\}'). % important if used inside dot records
1530 dot_string_escape_map('|', '\\|'). % important if used inside dot records
1531 dot_string_escape_map('>', '\\>'). % important if used inside dot records
1532 dot_string_escape_map('<', '\\<'). % important if used inside dot records
1533 dot_string_escape_map(A,B) :- x_string_escape_map(A,B).
1534
1535 html_string_escape_map('&', '&') :- !. % michael: habe diese 3 Zeilen auskommentiert
1536 html_string_escape_map('<', '<') :- !.
1537 html_string_escape_map('>', '>') :- !.
1538 html_string_escape_map('"', '"') :- !.
1539 html_string_escape_map(A,B) :- x_string_escape_map(A,B),!.
1540
1541
1542 % this is the SICSTus encoding for Unicode either \octal_number\ or \xHexNumber\
1543 % See Section 4.1.7.6 Escape Sequences in SICStus Manual (page 60)
1544 x_string_escape_map('\344\', 'ä'). % a mit Umlaut
1545 x_string_escape_map('\366\', 'ö'). % o mit Umlaut
1546 x_string_escape_map('\374\', 'ü'). % u mit Umlaut
1547 x_string_escape_map('\304\', 'Ä'). % A mit Umlaut
1548 x_string_escape_map('\326\', 'Ö'). % O mit Umlaut
1549 x_string_escape_map('\334\', 'Ü'). % U mit Umlaut
1550
1551 x_string_escape_map('\353\', 'ë'). % e mit Umlaut
1552 x_string_escape_map('\313\', 'Ë'). % E mit Umlaut
1553
1554 x_string_escape_map('\350\', 'è'). % e mit Accent
1555 x_string_escape_map('\351\', 'é').
1556 x_string_escape_map('\352\', 'ê').
1557 x_string_escape_map('\310\', 'È'). % E mit Accent
1558 x_string_escape_map('\311\', 'É').
1559 x_string_escape_map('\312\', 'Ê').
1560
1561 x_string_escape_map('\340\', 'à'). % a mit Accent
1562 x_string_escape_map('\341\', 'á').
1563 x_string_escape_map('\342\', 'â').
1564 x_string_escape_map('\300\', 'À'). % A mit Accent
1565 x_string_escape_map('\301\', 'Á').
1566 x_string_escape_map('\302\', 'Â').
1567
1568 x_string_escape_map('\354\', 'ì'). % i mit Accent
1569 x_string_escape_map('\355\', 'í').
1570 x_string_escape_map('\356\', 'î').
1571 x_string_escape_map('\314\', 'Ì'). % I mit Accent
1572 x_string_escape_map('\315\', 'Í').
1573 x_string_escape_map('\316\', 'Î').
1574
1575 x_string_escape_map('\362\', 'ò'). % o mit Accent
1576 x_string_escape_map('\363\', 'ó').
1577 x_string_escape_map('\364\', 'ô').
1578 x_string_escape_map('\322\', 'Ò'). % O mit Accent
1579 x_string_escape_map('\323\', 'Ó').
1580 x_string_escape_map('\324\', 'Ô').
1581
1582 x_string_escape_map('\347\', 'ç'). % cedille
1583 x_string_escape_map('\307\', 'Ç'). % Cedille
1584
1585 x_string_escape_map('\337\', 'ß'). % scharfes S (sz)
1586 x_string_escape_map('\361\', 'ñ'). % n with tilde
1587
1588 x_string_escape_map('\1661\', 'α'). % Greek
1589 x_string_escape_map('\1662\', 'β').
1590 x_string_escape_map('\1663\', 'γ').
1591 x_string_escape_map('\1664\', 'δ').
1592 x_string_escape_map('\1665\', 'ε').
1593 x_string_escape_map('\1666\', 'ζ').
1594 x_string_escape_map('\1667\', 'η').
1595 x_string_escape_map('\1670\', 'θ').
1596 x_string_escape_map('\1671\', 'ι').
1597 x_string_escape_map('\1672\', 'κ').
1598 x_string_escape_map('\1673\', 'λ').
1599 x_string_escape_map('\1674\', 'μ').
1600 x_string_escape_map('\1675\', 'ν').
1601 x_string_escape_map('\1676\', 'ξ').
1602 x_string_escape_map('\1677\', 'ο').
1603 x_string_escape_map('\1700\', 'π').
1604 x_string_escape_map('\1701\', 'ρ').
1605 x_string_escape_map('\1702\', 'ς').
1606 x_string_escape_map('\1703\', 'σ').
1607 x_string_escape_map('\1704\', 'τ').
1608 x_string_escape_map('\1705\', 'υ').
1609 x_string_escape_map('\1706\', 'φ').
1610 x_string_escape_map('\1707\', 'χ').
1611 x_string_escape_map('\1710\', 'ψ').
1612 x_string_escape_map('\1711\', 'ω').
1613
1614 % to do: add missing upper-case Greek letters:
1615 x_string_escape_map('\1624\', 'Δ').
1616 x_string_escape_map('\1630\', 'Θ').
1617 x_string_escape_map('\1633\', 'Λ').
1618 x_string_escape_map('\1636\', 'Ξ').
1619 x_string_escape_map('\1645\', 'Υ').
1620 x_string_escape_map('\1647\', 'Χ').
1621 x_string_escape_map('\1650\', 'Ψ').
1622 x_string_escape_map('\1651\', 'Ω').
1623
1624 % other symbols (converted using http://www.online-toolz.com/tools/unicode-html-entities-convertor.php )
1625 x_string_escape_map('\21242\','⊢'). %vdash turnstyle decimal: 8866
1626 x_string_escape_map('\21250\','⊨'). %models turnstyle decimal: 8872
1627 x_string_escape_map('\x21D4\','⇔'). % equivalence
1628 x_string_escape_map('\x21D2\','⇒'). % implication
1629 x_string_escape_map('\x2203\','∃'). % exists
1630 x_string_escape_map('\x2200\','∀'). % forall
1631 x_string_escape_map('ยท','·'). % dot used for quantifiers, ASCII 183
1632 x_string_escape_map('\x2227\','∧'). % conjunct
1633 x_string_escape_map('\x2228\','∨'). % disjunct
1634 x_string_escape_map('\xAC\','¬'). % negation
1635 x_string_escape_map('\x21A6\','↦'). % maplet |->
1636 x_string_escape_map('\x2286\','⊆'). % <: subseteq
1637 x_string_escape_map('\x222A\','∪'). % union
1638 x_string_escape_map('\x2229\','∩'). % intersection
1639 x_string_escape_map('\x2205\','∅'). % empty set
1640 x_string_escape_map('\x2260\','≠'). % not equal
1641 x_string_escape_map('\x2264\','≤'). % less equal
1642 x_string_escape_map('\x2265\','≥'). % greater equal
1643 x_string_escape_map('\x2124\','ℤ'). % Z (INTEGER)
1644 x_string_escape_map('\x2115\','ℕ'). % NATURAL
1645
1646 % Numbers without HTML translation: .. 8229
1647
1648 % translate unknown unicode chars to lozenge
1649 x_string_escape_map(Unicode, Result) :- atom(Unicode),
1650 atom_codes(Unicode,[Nr]), Nr>127,
1651 !,
1652 ajoin(['&#',Nr,';'],Result).
1653 %x_string_escape_map(Unicode, '◊') :-
1654 % atom(Unicode), atom_codes(Unicode,[Code]), Code>127.
1655 % format(user_output,'Uni: ~w ~n',[Unicode]).
1656 x_string_escape_map(X, X).
1657
1658 %
1659 % split_list(Pred,List,ListA,ListB):
1660 % List contains exactly the same elements as ListA and ListB
1661 % An element E is member of ListA iff Pred(E) is true and E is element of List.
1662 % An element E is member of ListB iff Pred(E) is false and E is element of List.
1663 % combination of include/exclude from library(lists) (include_exclude)
1664 :- assert_must_succeed(( split_list(number,[1,a,2,b],S,F), S==[1,2], F==[a,b] )).
1665 split_list(Pred,List,A,B) :-
1666 split_list2(List,Pred,A,B).
1667 split_list2([],_Pred,[],[]).
1668 split_list2([Elem|Rest],Pred,A,B) :-
1669 ( call(Pred,Elem) -> A=[Elem|AR], B=BR
1670 ; A=AR, B=[Elem|BR]),
1671 split_list2(Rest,Pred,AR,BR).
1672
1673
1674 :- assert_must_succeed(( map_split_list(just_for_unit_test_add(0),[1,2,3],S,F), S==[10,20,30], F==[] )).
1675 % like map_list but put unsuccessful elements in a second list
1676 map_split_list(Pred,List,A,B) :-
1677 map_split_list2(List,Pred,A,B).
1678 map_split_list2([],_Pred,[],[]).
1679 map_split_list2([Elem|Rest],Pred,A,B) :-
1680 ( call(Pred,Elem,Res) -> A=[Res|AR], B=BR
1681 ; A=AR, B=[Elem|BR]),
1682 map_split_list2(Rest,Pred,AR,BR).
1683
1684 % a variation of split_list which also returns a list of predicate results
1685 % with re_split_list_idx(L,PredResult,A,B) : we can split another list using the same pattern
1686 split_list_idx(Pred,List,PredResult,A,B) :-
1687 split_list_idx2(List,Pred,PredResult,A,B).
1688 split_list_idx2([],_Pred,[],[],[]).
1689 split_list_idx2([Elem|Rest],Pred,[PredTrue|PT],A,B) :-
1690 (call(Pred,Elem) -> PredTrue=true, A=[Elem|AR], B=BR
1691 ; PredTrue=false, A=AR, B=[Elem|BR]),
1692 split_list_idx2(Rest,Pred,PT,AR,BR).
1693
1694
1695 re_split_list_idx([],[],[],[]).
1696 re_split_list_idx([Elem|Rest],[PredTrue|PT],A,B) :-
1697 (PredTrue=true -> A=[Elem|AR], B=BR
1698 ; A=AR, B=[Elem|BR]),
1699 re_split_list_idx(Rest,PT,AR,BR).
1700
1701
1702
1703 % try and avoid going through same transitions twice
1704 % works with simple transition ids, or with atom/3 entries from ltl model_checker
1705 :- assert_must_succeed((minimize_lasso([0,1,2,4,8,14,7,12],[15,4,8,14,7,12],P,L),
1706 P==[0,1,2], L==[4,8,14,7,12,15])).
1707 minimize_lasso(Prefix,Loop,NewPrefix,NewLoop) :- reverse(Prefix,RP), reverse(Loop,RL),
1708 common_prefix(RP,RL,Common,RestP,RestLoop),
1709 append(RestLoop,Common,NewLR), reverse(NewLR,NewLoop),
1710 reverse(RestP,NewPrefix).
1711 common_prefix([H|T1],[H|T2],[H|Res],Rest1,Rest2) :- !, common_prefix(T1,T2,Res,Rest1,Rest2).
1712 common_prefix(T1,T2,[],T1,T2).
1713
1714
1715
1716 just_for_unit_test_add(A,B,C) :- C is A+(10*B).
1717 :- assert_must_succeed(( foldl(just_for_unit_test_add,[],0,R), R==0 )).
1718 :- assert_must_succeed(( foldl(just_for_unit_test_add,[3,6,7,2,6],0,R), R==36726 )).
1719
1720 foldl(MPred,List,Start,Result) :-
1721 foldl2(List,MPred,Start,Result).
1722 foldl2([],_Pred,Value,Value).
1723 foldl2([Elem|Rest],MPred,OldValue,NewValue) :-
1724 call(MPred,Elem,OldValue,Value),
1725 foldl2(Rest,MPred,Value,NewValue).
1726
1727
1728 just_for_unit_test_add2(A,B,C,D) :- D is 100*C+10*A+B.
1729 :- assert_must_succeed(( foldl(just_for_unit_test_add2,[],[],0,R), R==0 )).
1730 :- assert_must_succeed(( foldl(just_for_unit_test_add2,[3,6,7,2,6],[5,2,9,0,8],0,R), R==3562792068 )).
1731
1732 foldl(MPred,List,List1,Start,Result) :-
1733 foldl2(List,MPred,List1,Start,Result).
1734 foldl2([],_Pred,[],Value,Value).
1735 foldl2([Elem|Rest],MPred,[H1|R1],OldValue,NewValue) :-
1736 call(MPred,Elem,H1,OldValue,Value),
1737 foldl2(Rest,MPred,R1,Value,NewValue).
1738
1739
1740 just_for_unit_test_add3(A,B,C,D,E) :- E is D*1000+100*A+10*B+C.
1741 :- assert_must_succeed(( foldl(just_for_unit_test_add3,[],[],[],0,R), R==0 )).
1742 :- assert_must_succeed(( foldl(just_for_unit_test_add3,[3,6,7],[5,2,9],[4,1,0],0,R),
1743 R==354621790 )).
1744
1745 foldl(MPred,A,B,C,Start,Result) :-
1746 foldl2(A,MPred,B,C,Start,Result).
1747 foldl2([],_Pred,[],[],Value,Value).
1748 foldl2([Elem|Rest],MPred,[H1|R1],[H2|R2],OldValue,NewValue) :-
1749 call(MPred,Elem,H1,H2,OldValue,Value),
1750 foldl2(Rest,MPred,R1,R2,Value,NewValue).
1751
1752
1753 :- assert_must_succeed(( maplist5(just_for_unit_test_add2,[],[],[],R), R==[] )).
1754 :- assert_must_succeed(( maplist5(just_for_unit_test_add2,[1],[2],[3],R), R==[312] )).
1755 :- assert_must_succeed(( maplist5(just_for_unit_test_add2,[1,1],[2,0],[3,0],R), R==[312,10] )).
1756
1757 maplist5(_P,[],[],[],[]).
1758 maplist5(P,[H1|T1],[H2|T2],[H3|T3],[H4|T4]) :-
1759 if(call(P,H1,H2,H3,H4),
1760 ? maplist5(P,T1,T2,T3,T4),
1761 (add_internal_error('Call fails: ',maplist5(P,H1,H2,H3,H4)),
1762 fail)).
1763
1764
1765 :- assert_must_succeed(( average([2,4,8,10],Avg), D is abs(Avg-6),D<0.001 )).
1766 :- assert_must_succeed(( average([2],Avg), D is abs(Avg-2), D<0.001 )).
1767 average(List,Avg) :-
1768 length(List,N),sumlist(List,Sum),Avg is Sum/N.
1769
1770
1771 % assert_once works like assertz, but checks if the fact has already been stored before and
1772 % will not store it a second time
1773 assert_once(MPredicate) :-
1774 (call(MPredicate) -> true ; assertz(MPredicate)).
1775
1776
1777
1778 :- dynamic id_counter/1.
1779 unique_id(Prefix,Id) :-
1780 (id_counter(V) -> retractall(id_counter(_)) ; V is 0),
1781 N is V+1,
1782 assertz( id_counter(N) ),
1783 number_codes(N,NCodes),
1784 append(Prefix,NCodes,ICodes),
1785 atom_codes(Id,ICodes).
1786
1787 % PROBPATH is the runtime search path for defintion files not found relative to the original machine.
1788 % By default this is the stdlib directory relative to the prob base dir.
1789 % The user can provide a list of : separated directories in the PROBPATH
1790 % environment variables which are prepended to the default path.
1791 get_PROBPATH(PROBPATH) :-
1792 environ('PROBPATH', CustomPATH),
1793 atom_length(CustomPATH, L),
1794 L > 0, !,
1795 get_path_separator(PS),
1796 get_stdlib_path(STDLIB), ajoin([CustomPATH, PS, STDLIB], PROBPATH).
1797
1798 get_PROBPATH(PROBPATH) :- get_stdlib_path(PROBPATH).
1799
1800 get_stdlib_path(STDLIB) :- runtime_application_path(Base), atom_concat(Base, '/stdlib', STDLIB).
1801
1802 % Platform specific path separator char
1803 map_path_separator(windows, ';').
1804 map_path_separator(_, ':').
1805
1806 get_path_separator(PS) :- host_platform(Platform), map_path_separator(Platform, PS), !.
1807
1808 %% atom_to_number(?Atom, ?Number).
1809 atom_to_number(Atom, Number) :-
1810 atom(Atom),
1811 catch((
1812 atom_codes(Atom, Codes),
1813 number_codes(Number, Codes)
1814 ), _, fail).
1815 atom_to_number(Atom, Number) :-
1816 number(Number),
1817 number_codes(Number, Codes),
1818 atom_codes(Atom, Codes).
1819
1820 % ----------
1821
1822 % useful for e.g. SWI Prolog where redefine_warnings is not defined
1823 get_set_optional_prolog_flag(Flag,Old,New) :-
1824 (catch(current_prolog_flag(Flag, Old), error(domain_error(_, _), _), fail) ->
1825 set_prolog_flag(Flag, New)
1826 ;
1827 current_prolog_flag(dialect, Prolog),
1828 format('Prolog flag ~w not supported on ~w~n', [Flag, Prolog])
1829 ).
1830
1831
1832 % ----------
1833
1834 % predicates for lists with optional items represented using optional_value(V,pred_true/pred_false)
1835 % we assume that optional values constructors (optional_value/2) are instantiated *before* calling maplist_optional
1836
1837 map_optlist(Pred,List) :- map_optlist_aux(List,Pred).
1838
1839 :- block map_optlist_aux(-,?).
1840 map_optlist_aux([],_) :- !.
1841 map_optlist_aux([H|T],Pred) :- !,
1842 call_optional(Pred,H),
1843 map_optlist_aux(T,Pred).
1844 map_optlist_aux(Other,Pred) :-
1845 add_internal_error('First argument not a list:',map_optlist_aux(Other,Pred)).
1846
1847 call_optional(Pred,OptVal) :-
1848 is_optional_value(OptVal,Val,Use),
1849 !,
1850 call_optional_aux(Use,Pred,Val).
1851 call_optional(Pred,Val) :- call(Pred,Val).
1852
1853 :- block call_optional_aux(-,?,?).
1854 call_optional_aux(pred_true,Pred,Val) :- !, call(Pred,Val).
1855 call_optional_aux(pred_false,_,_) :- !. % ignore optional item
1856 call_optional_aux(Other,Pred,_) :-
1857 add_internal_error('First argument not a BOOL:',call_optional_aux(Other,Pred)).
1858
1859 :- block optlist_to_list(-,?).
1860 optlist_to_list([],R) :- !, R=[].
1861 optlist_to_list([OptVal|T],R) :- !,
1862 (is_optional_value(OptVal,Val,Use)
1863 -> optlist_to_list_aux(Use,Val,T,R)
1864 ; R = [OptVal|RT], optlist_to_list(T,RT)
1865 ).
1866 optlist_to_list(Other,R) :-
1867 add_internal_error('First argument not a list:',optlist_to_list(Other,R)),
1868 R=Other.
1869
1870 :- block optlist_to_list_aux(-,?,?,?).
1871 optlist_to_list_aux(pred_true,Val,T,[Val|TR]) :- !, optlist_to_list(T,TR).
1872 optlist_to_list_aux(pred_false,_,T,R) :- !, optlist_to_list(T,R).
1873 optlist_to_list_aux(Other,V,T,R) :-
1874 add_internal_error('First argument not a BOOL:',optlist_to_list_aux(Other,V,T,R)),
1875 fail.
1876
1877 is_optional_value(OptVal,Val,Use) :- nonvar(OptVal), OptVal = optional_value(Val,Use).
1878
1879 % ---------
1880
1881 % blackboard utilities
1882
1883
1884 bb_safe_get(Counter,R) :- (bb_get(Counter,X) -> R=X ; R=0).
1885
1886 bb_inc(Counter) :- bb_inc_by(Counter,1).
1887 bb_dec(Counter) :- bb_inc_by(Counter,-1).
1888
1889 bb_inc_by(Counter,Inc) :-
1890 bb_safe_get(Counter,R),
1891 R1 is R+Inc, bb_put(Counter,R1).
1892
1893
1894 % ---------
1895
1896
1897 :- if(current_prolog_flag(dialect, swi)).
1898
1899 :- use_module(library(ugraphs),[top_sort/2]).
1900
1901 % this version fails if there are cycles and cannot report the number of unsorted nodes:
1902 top_sort(Graph,Sorted,UnsortedNr) :- top_sort(Graph, Sorted), UnsortedNr=0.
1903
1904 % disable code below currently for SWI, as top_sort below calls internal predicate of ugraphs: fanin_counts
1905 :- else.
1906 :- use_module(library(avl),[ord_list_to_avl/2]).
1907
1908 % a slight variation of top_sort from library(ugraphs) which allows the sorting to be not completed
1909 % top_sort(G,S,0) corresponds to top_sort in ugraphs
1910 top_sort(Graph,Sorted,UnsortedNr) :-
1911 ugraphs:fanin_counts(Graph, Counts),
1912 ugraphs:get_top_elements(Counts, Top, 0, I),
1913 ord_list_to_avl(Counts, Map),
1914 top_sort_aux(Top, I, Map, Sorted,UnsortedNr).
1915
1916 top_sort_aux([], I, _, [], UnsortedNr) :- UnsortedNr = I.
1917 top_sort_aux([V-VN|Top0], I, Map0, [V|Sorted],UnsortedNr) :-
1918 ugraphs:dec_counts(VN, I, J, Map0, Map, Top0, Top),
1919 top_sort_aux(Top, J, Map, Sorted,UnsortedNr).
1920 :- endif.