1 | | % (c) 2009-2025 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(b_trace_checking, [tcltk_check_state_sequence_from_file/1, |
7 | | tcltk_check_state_sequence_from_file_state_id/3, |
8 | | tcltk_check_sequence_from_file/3, |
9 | | check_default_trace_for_specfile/1, |
10 | | get_default_trace_file/2, get_default_trace_file/3, |
11 | | print_trace_as_fdr_check/0, |
12 | | tcltk_save_history_as_trace_file/2, tcltk_save_history_as_trace_file/3, |
13 | | perform_initialisation/0, |
14 | | find_successor_state/4 |
15 | | ]). |
16 | | |
17 | | :- use_module(library(lists)). |
18 | | |
19 | | :- use_module(tools). |
20 | | :- use_module(module_information,[module_info/2]). |
21 | | :- module_info(group,testing). |
22 | | :- module_info(description,'Replay saved traces; raise error if not possible. Used in regression testing.'). |
23 | | |
24 | | :- use_module(bsyntaxtree, [get_texpr_type/2, conjunct_predicates/2]). |
25 | | :- use_module(bmachine,[b_machine_name/1, b_is_operation_name/1, b_get_machine_operation_typed_parameters/2]). |
26 | | :- use_module(b_global_sets,[lookup_global_constant/2]). |
27 | | :- use_module(error_manager). |
28 | | :- use_module(debug). |
29 | | :- use_module(preferences,[get_preference/2,preference/2, |
30 | | temporary_set_preference/3,reset_temporary_preference/2, get_prob_application_type/1]). |
31 | | :- use_module(specfile,[b_or_z_mode/0, csp_mode/0, get_operation_name/2, get_operation_return_values_and_arguments/3]). |
32 | | :- use_module(translate,[translate_event/2]). |
33 | | :- use_module(state_space,[current_options/1, current_state_id/1, current_expression/2, |
34 | | max_reached_for_node/1, not_all_transitions_added/1,time_out_for_node/3]). |
35 | | :- use_module(junit_tests). |
36 | | :- use_module(tools_printing,[print_red/1, print_green/1, format_with_colour_nl/4]). |
37 | | :- use_module(tools_files,[read_file_codes/2]). |
38 | | :- use_module(store,[lookup_value_for_existing_id/3]). |
39 | | :- use_module(tcltk_interface,[tcltk_reset/0,tcltk_add_user_executed_operation_typed/4, |
40 | | tcltk_get_options/1]). |
41 | | |
42 | | :- use_module(runtime_profiler,[profile_single_call/3]). |
43 | | :- use_module(tools,[start_ms_timer/1, stop_ms_timer_with_msg/2]). |
44 | | :- use_module(bsyntaxtree, [def_get_texpr_ids/2, def_get_texpr_id/2]). |
45 | | |
46 | | % -------------------------------- |
47 | | |
48 | | % replay trace with state predicates; one predicate per line |
49 | | |
50 | | % b_trace_checking:tcltk_check_state_sequence_from_file('~/Desktop/scheduler6.ptrace'). |
51 | | |
52 | | tcltk_check_state_sequence_from_file(File) :- |
53 | | current_state_id(ID), |
54 | | tcltk_check_state_sequence_from_file_state_id(File,ID,_DestID). |
55 | | |
56 | | tcltk_check_state_sequence_from_file_state_id(File,ID,DestID) :- nonvar(ID), |
57 | | open(File,read,Stream,[encoding(utf8)]), |
58 | | format(user_output,'Opened predicate list file: ~w~n Starting from state ~w.~n',[File,ID]), |
59 | | call_cleanup(tcltk_check_state_sequence_from_file_aux(File,0,Stream,ID,DestID), |
60 | | (format(user_output,'Finished processing list file: ~w~n',[File]), |
61 | | close(Stream))). |
62 | | |
63 | | tcltk_check_state_sequence_from_file_aux(File,LineNr,Stream,ID,DestID) :- read_line(Stream,Codes), |
64 | | Codes \= end_of_file, |
65 | | L1 is LineNr+1, |
66 | | !, %print(codes(Codes)),nl, |
67 | | process_line(File,L1,Stream,Codes,ID,DestID). |
68 | | tcltk_check_state_sequence_from_file_aux(_,LineNr,_,ID,ID) :- printsilent('reached EOF at line: '), printsilent(LineNr),nls. |
69 | | |
70 | | :- use_module(bmachine,[b_parse_wo_type_machine_predicate_from_codes_to_raw_expr/2, |
71 | | b_type_check_raw_expr/4]). |
72 | | :- use_module(translate_keywords). |
73 | | |
74 | | % TO DO: also allow operation names, parameters to be specified? |
75 | | process_line(File,LineNr,Stream,Codes,ID,DestID) :- |
76 | | % insert newlines to ensure error message correct: |
77 | | insert_nl_prefix(LineNr,Codes,CodesNL), |
78 | | b_parse_wo_type_machine_predicate_from_codes_to_raw_expr(CodesNL,Parsed), |
79 | | % in case we did translate keywords (e.g., in Event-B mode); translate them back: |
80 | | translate_keywords:raw_backtranslate_keyword_ids(Parsed,Parsed2), |
81 | | % now type check the raw expression: |
82 | | b_type_check_raw_expr(Parsed2,[prob_ids(all),variables],Pred,closed), |
83 | | % bmachine:b_parse_machine_predicate_from_codes(CodesNL,[prob_ids(all),variables],Pred), |
84 | | !, |
85 | | format('Line ~w : ~w --?--> ',[LineNr,ID]),translate:print_bexpr(Pred),nl, |
86 | | find_successor_state(ID,Pred,SuccID,LineNr), |
87 | | tcltk_check_state_sequence_from_file_aux(File,LineNr,Stream,SuccID,DestID). |
88 | | process_line(_File,LineNr,_Stream,Codes,_ID,_DestID) :- |
89 | | atom_codes(Atom,Codes), |
90 | | add_error(tcltk_check_state_sequence_from_file,'Could not parse state predicate: ',Atom), |
91 | | add_error(tcltk_check_state_sequence_from_file,'Line: ',LineNr), |
92 | | %add_error(tcltk_check_state_sequence_from_file,'File: ',File), |
93 | | fail. |
94 | | |
95 | | insert_nl_prefix(N,Acc,Acc) :- N =< 1,!. |
96 | | insert_nl_prefix(N,Acc,[10,13|Res]) :- N1 is N-1, insert_nl_prefix(N1,Acc,Res). |
97 | | |
98 | | :- use_module(b_global_sets,[add_prob_deferred_set_elements_to_store/3]). |
99 | | :- use_module(specfile,[expand_const_and_vars_to_full_store/2]). |
100 | | :- use_module(state_space,[visited_expression/2, |
101 | | max_reached_for_node/1, max_reached_or_timeout_for_node/1, time_out_for_node/1]). |
102 | | :- use_module(library(codesio),[format_to_codes/3]). |
103 | | :- use_module(eventhandling, [announce_event/1]). |
104 | | |
105 | | find_successor_state(ID,Pred,NewID,_) :- |
106 | | % transition(ID,ActionOpAsTerm,OpID,SuccID), |
107 | | tcltk_get_options(_), |
108 | | current_options(Options), % print(Options),nl, |
109 | ? | member( (_,Action,ActionOpAsTerm,NewID), Options), |
110 | | state_space:visited_expression(NewID,State), |
111 | | expand_const_and_vars_to_full_store(State,EState), |
112 | | % print(try(NewID,State)),nl, |
113 | | add_prob_deferred_set_elements_to_store(EState,DEState,all), |
114 | | announce_event(start_solving), |
115 | | b_interpreter:b_test_boolean_expression_cs(Pred,[],DEState,'find successor state',ID), % deferred |
116 | | announce_event(end_solving), |
117 | | debug_println(19,successor_found(NewID)), |
118 | | print(' --> '),my_print_event(ActionOpAsTerm,_),nl,nl, |
119 | | my_perform_action_as_string(Action,ActionOpAsTerm,NewID), |
120 | | !. |
121 | | find_successor_state(ID,Pred,NewID,_LineNr) :- |
122 | ? | max_reached_or_timeout_for_node(ID), % we have not computed all options |
123 | | get_op_name_for_state(ID,OpName), print(try_providing_custom_predicate(OpName)),nl, |
124 | | tcltk_add_user_executed_operation_typed(OpName,_,Pred,NewID), % The Pred can only talk about parameters and the state before; thus this only works for setup_constants and initialise_machine |
125 | | (silent_mode(on) -> true |
126 | | ; print_green(successor_found(NewID)),nl, |
127 | | print(' --> '),print(OpName),nl), |
128 | | !. |
129 | | find_successor_state(ID,Pred,_,LineNr) :- |
130 | | % we could try and execute operation by predicate here; in case max_reached_for_node(ID) or time_out_for_node(ID) |
131 | | format('State ID = ~w; line number = ~w~n',[ID,LineNr]), |
132 | | (max_reached_for_node(ID) |
133 | | -> format_to_codes('Try increasing the MAX_OPERATIONS preference. No successor found satisfying predicate at step ~w of replay: ',[LineNr],Codes) |
134 | | ; time_out_for_node(ID) -> format_to_codes('TIME-OUT occurred. No successor found satisfying predicate at step ~w of replay: ',[LineNr],Codes) |
135 | | ; format_to_codes('No successor found satisfying predicate at step ~w of replay: ',[LineNr],Codes) |
136 | | ), |
137 | | atom_codes(Msg,Codes), |
138 | | translate:translate_bexpression(Pred,TP), |
139 | | add_error(tcltk_check_state_sequence_from_file,Msg,TP), |
140 | | fail. |
141 | | |
142 | | :- use_module(bmachine,[b_machine_has_constants_or_properties/0]). |
143 | | get_op_name_for_state(root,OP) :- !, |
144 | | (b_machine_has_constants_or_properties -> OP='$setup_constants' ; OP='$initialise_machine'). |
145 | | get_op_name_for_state(ID,'$initialise_machine') :- |
146 | | visited_expression(ID,concrete_constants(_)),!. |
147 | | %get_op_name_for_state(ID,OpName) :- bmachine:b_top_level_operation(OpName). % not supported yet |
148 | | |
149 | | % -------------------------------- |
150 | | |
151 | | check_default_trace_for_specfile(File) :- get_default_trace_file(File,TFile), |
152 | | tcltk_check_sequence_from_file(prolog,TFile,default_trace_replay). |
153 | | |
154 | | get_default_trace_file(File,TraceFile) :- |
155 | | get_default_trace_file(File,'.trace',TraceFile). |
156 | | get_default_trace_file(File,Ext,TraceFile) :- |
157 | | split_filename(File,FN,_MchExt), |
158 | | string_concatenate(FN,Ext,TraceFile). |
159 | | |
160 | | :- use_module(tools_printing,[print_error/1]). |
161 | | % valid Style: prolog, json |
162 | | % valid Mode: deterministic_trace_replay or backtracking_trace_replay or default_trace_replay |
163 | | tcltk_check_sequence_from_file(Style,File,Mode) :- get_mode_value(Mode,Val,Msg),!, |
164 | | temporary_set_preference(deterministic_trace_replay,Val,CHNG), |
165 | | format(Msg,[File]), |
166 | | (tcltk_check_sequence_from_file2(Style,File) |
167 | | -> reset_temporary_preference(deterministic_trace_replay,CHNG) |
168 | | ; reset_temporary_preference(deterministic_trace_replay,CHNG)). |
169 | | tcltk_check_sequence_from_file(Style,File,_DetOrBacktrack) :- |
170 | | tcltk_check_sequence_from_file2(Style,File). |
171 | | |
172 | | get_mode_value(deterministic_trace_replay,true,Msg) :- |
173 | | Msg = 'Performing deterministic replay (lower memory usage, but no backtracking in case replay fails) for file: ~w~n'. |
174 | | get_mode_value(backtracking_trace_replay,false,Msg) :- |
175 | | Msg = 'Performing replay with backtracking for file: ~w~n'. |
176 | | get_mode_value(Other,_,_) :- Other \= default_trace_replay, |
177 | | add_error(b_trace_checking,'Illegal replay mode: ',Other),fail. |
178 | | |
179 | | :- use_module(tools, [get_modulename_filename/2]). |
180 | | tcltk_check_sequence_from_file2(Style,File) :- |
181 | | read_trace_file(Style,File,MachineName,Trace), |
182 | | length(Trace,Len), |
183 | | (b_or_z_mode -> b_machine_name(OurName) ; true), |
184 | | (OurName=MachineName -> true |
185 | | ; MachineName = 'dummy(uses)' % old trace files have these |
186 | | -> printsilent_message('Checking file for machine: '), printsilent_message(OurName) |
187 | | ; atom_concat('MAIN_MACHINE_FOR_',MachineName,OurName) -> true |
188 | | ; print_error('### Trace File has been generated for different machine: '), |
189 | | print_error(MachineName), |
190 | | print_error('### Our machine: '), print_error(OurName) |
191 | | ), |
192 | | formatsilent('Starting trace check of length ~w~n',[Len]), |
193 | | statistics(walltime,[Time1,_]), |
194 | ? | (perform_sequence_of_operations(Trace) |
195 | | -> nls,print_green('Trace checking successful !'),nl |
196 | | ; nls, |
197 | | length(Trace,Len), max_nr_replayed(Replayed), |
198 | | ajoin(['Trace Checking was not successful for ', MachineName, |
199 | | ', replayed ',Replayed,'/',Len,' operations from:'],Msg), |
200 | | add_error(trace_checking_fails,Msg,File) |
201 | | %% ,throw(trace_checking_failed(MachineName)) % comment in if you want to stop immediately upon such an error |
202 | | ), |
203 | | statistics(walltime,[Time2,_]), Time is Time2-Time1, |
204 | | formatsilent('Walltime: ~w ms~n',[Time]), |
205 | | (csp_mode -> print_trace_as_fdr_check ; true), |
206 | | |
207 | | (junit_mode(_) -> (get_error(trace_checking,Errors) -> V=error([Errors]) ; V=pass), |
208 | | get_modulename_filename(MachineName, Module), |
209 | | create_and_print_junit_result(['Trace_checking',Module], File, Time, V) |
210 | | ; true). |
211 | | |
212 | | :- use_module(extrasrc(json_parser),[json_parse_file/3]). |
213 | | read_trace_file(prolog,File,MachineName,Trace) :- |
214 | | formatsilent('% Opening trace file: ~w~n',[File]), |
215 | | my_see(File), |
216 | | parse_trace_file(MachineName,Trace),!, |
217 | | seen. |
218 | | read_trace_file('B',File,MachineName,Trace) :- !, |
219 | | MachineName = 'dummy(uses)', % name not stored in B operation call files |
220 | | format('Parsing operation calls from file: ~w~n',[File]), |
221 | | read_file_codes(File,Codes), |
222 | | %parsercall:parse_substitution(Codes,Tree), tools_printing:nested_print_term(Tree),nl, |
223 | | bmachine:b_parse_machine_subsitutions_from_codes(Codes,[operation_bodies,prob_ids(visible)], |
224 | | Typed,_Type,true,Error), |
225 | | (Error=none -> true ; add_error(read_trace_file,'Error occured while parsing substitution: ',Error),fail), |
226 | | %translate:print_subst(Typed),nl, |
227 | | translate_substition_to_trace(Typed,T2), %print(t2(T2)),nl, |
228 | | Trace=['$initialise_machine'|T2]. |
229 | | read_trace_file('JSON',File,MachineName,Trace) :- !, |
230 | | read_trace_file(json,File,MachineName,Trace). |
231 | | read_trace_file(json,File,MachineName,Trace) :- |
232 | | MachineName = 'dummy(uses)', % name not stored in JSON files |
233 | | json_parse_file(File,Term,[rest(_),strings_as_atoms(false)]), |
234 | | !, |
235 | | (translate_json_term(Term,Trace) -> true |
236 | | ; add_error(trace_checking_fails,'Could not translate JSON transitionList: ',Term), |
237 | | %trace, translate_json_term(Term,Trace), |
238 | | Trace = []). |
239 | | read_trace_file(Style,_,_,_) :- Style \= json, Style \= prolog, !, |
240 | | add_error(trace_checking_fails,'Illegal trace file format (must be json, prolog or B): ',Style),fail. |
241 | | read_trace_file(_,File,_,_) :- |
242 | | add_error(trace_checking_fails,'Could not read trace file: ',File),fail. |
243 | | |
244 | | my_see(File) :- |
245 | | catch(see(File), |
246 | | error(existence_error(_,_),_), |
247 | | add_error_fail(my_see,'File does not exist: ',File)). |
248 | | |
249 | | % ------------------------ |
250 | | |
251 | | % translate a B substitution to a trace to be replayed: |
252 | | translate_substition_to_trace(b(sequence(T),subst,_),Trace) :- !, maplist(translate_b2p,T,Trace). |
253 | | translate_substition_to_trace(Subst,[T]) :- translate_b2p(Subst,T). |
254 | | |
255 | | % translate a single B substitution to one Prolog trace replay term |
256 | | translate_b2p(b(S,_,I),Res) :- translate_b2p(S,I,Res). |
257 | | translate_b2p(operation_call(Operation,OpCallResults,OpCallParas),_,Res) :- |
258 | | OpCallResults = [], % TODO: treat return values |
259 | | !, |
260 | | maplist(translate_para,OpCallParas,PrologParas), |
261 | | def_get_texpr_id(Operation,op(OpName)), |
262 | | Res =.. [OpName|PrologParas]. |
263 | | translate_b2p(A,I,_) :- |
264 | | add_error(trace_replay,'Uncovered substitution in trace file: ',b(A,subst,I),I),fail. |
265 | | |
266 | | translate_para(TExpr,Value) :- % TODO: maybe allow constants? variables ??? local variables in trace file?? |
267 | | b_interpreter:b_compute_expression_nowf(TExpr,[],[],Value,'none',0). |
268 | | |
269 | | % ------------------------ |
270 | | |
271 | | % parse Prolog trace file |
272 | | parse_trace_file(MName,Trace) :- |
273 | | safe_read(Term),!, |
274 | | (Term = end_of_file -> (Trace = []) |
275 | | ; (Term = machine(MName) |
276 | | -> Trace = T |
277 | | ; Trace = [Term|T] |
278 | | ), |
279 | | parse_trace_file(MName,T) |
280 | | ). |
281 | | |
282 | | safe_read(T) :- |
283 | | catch(read(T), E, ( |
284 | | add_error(safe_read,'Exception while reading input:',[E]), |
285 | | T=end_of_file |
286 | | )). |
287 | | |
288 | | %------------------------- |
289 | | |
290 | | translate_json_term(json(ObjList),Trace) :- |
291 | | member('='(transitionList,array(List)),ObjList), |
292 | | !, |
293 | | % TODO: why is this not a call_cleanup? |
294 | | temporary_set_preference(repl_cache_parsing,true,CHNG), |
295 | | eval_strings:turn_normalising_off, |
296 | | maplist(translate_json_operation,List,Trace), |
297 | | eval_strings:turn_normalising_on, |
298 | | reset_temporary_preference(repl_cache_parsing,CHNG). |
299 | | |
300 | | % no transitionList => just use empty list |
301 | | translate_json_term(json(_),[]) :- !. |
302 | | |
303 | | translate_json_operation(json(OperationObj), Operation) :- % new style |
304 | | member('='(name,string(OpNameC)), OperationObj), % name is required! |
305 | | !, |
306 | | %print(json(OperationObj)),nl, |
307 | | get_parameters(OperationObj,Paras), |
308 | | atom_codes(OpName,OpNameC), debug_println(9,json_translate_operation(OpName)), |
309 | | (bmachine:b_get_machine_operation_parameter_names(OpName,OpParameterNames) -> true |
310 | | ; add_error(translate_json_operation,'Unknown operation: ',OpName),fail), |
311 | | maplist(translate_json_para,Paras,Bindings), |
312 | | order_paras(OpParameterNames,OpName,Bindings,BValues), % put the parameters into the order expected |
313 | | Op =.. [OpName|BValues], |
314 | | (bmachine:b_get_machine_operation(OpName,OpResults,_P,_Body,_OType,_OpPos), |
315 | | OpResults \= [] % we have return values |
316 | | -> Operation = '-->'(Op,BResValues), |
317 | | (member('='(results,json(ResParas)),OperationObj) % we could provide this as an option: check results or not |
318 | | -> maplist(translate_json_para,ResParas,Bindings2), |
319 | | def_get_texpr_ids(OpResults,OpResultNames), |
320 | | order_paras(OpResultNames,OpName,Bindings2,BResValues) |
321 | | ; true) |
322 | | ; Operation = Op). %, print(translated(Op)),nl. |
323 | | |
324 | | get_parameters(OperationObj,Paras) :- member('='(params,json(Paras)),OperationObj),!. |
325 | | get_parameters(_,[]) :- !. % e.g., for initialise machine |
326 | | |
327 | | order_paras([],OpName,Rest,[]) :- (Rest=[] -> true ; format('*** Unknown parameters for ~w: ~w~n',[OpName,Rest])). |
328 | | order_paras([ID|T],OpName,Env,[Val|TVal]) :- |
329 | | (select(bind(ID,Val),Env,Env2) -> order_paras(T,OpName,Env2,TVal) |
330 | | ; %add_error(translate_json_operation,'Parameter not specified: ',ID:Env), % keep Val a variable |
331 | | format('*** Parameter not specified for ~w: ~w~n',[OpName,ID]), |
332 | | order_paras(T,OpName,Env,TVal) |
333 | | ). |
334 | | |
335 | | % TO DO: using eval_strings is very ugly, use a better API predicate |
336 | | translate_json_para('='(Name,string(C)),bind(Name,Value)) :- !, |
337 | | eval_strings:eval_expression_codes(C,_Res,_EnumWarning,_LocalState,_Typed,_TypeInfo,[silent_no_string_result]), |
338 | | eval_strings:last_expression_value(Value). |
339 | | translate_json_para(Para,_) :- add_error(translate_json_para,'Unknown JSON para:',Para),fail. |
340 | | |
341 | | is_a_reset_operation(start_cspm_MAIN). |
342 | | is_a_reset_operation(start_csp_MAIN). |
343 | | is_a_reset_operation(Op) :- functor(Op,'$setup_constants',_). |
344 | | is_a_reset_operation(Op) :- functor(Op,Functor,_), |
345 | | unify_functor(Functor,'$initialise_machine',root), |
346 | | b_or_z_mode, \+ b_machine_has_constants_or_properties. |
347 | | |
348 | | :- use_module(bmachine, [b_machine_has_constants_or_properties/0]). |
349 | | perform_initialisation :- b_or_z_mode, b_machine_has_constants_or_properties,!, |
350 | | perform_sequence_of_operations(['$setup_constants','$initialise_machine']). |
351 | | perform_initialisation :- perform_sequence_of_operations(['$initialise_machine']). |
352 | | |
353 | | :- dynamic max_nr_replayed/1. |
354 | | update_max_nr_replayed(Ident) :- |
355 | | (max_nr_replayed(Nr),Nr >= Ident -> true |
356 | | ; retract(max_nr_replayed(_)), assertz(max_nr_replayed(Ident))). |
357 | | |
358 | | |
359 | | perform_sequence_of_operations(Trace) :- |
360 | | retractall(max_nr_replayed(_)), |
361 | | assertz(max_nr_replayed(0)), |
362 | ? | perform_sequence_of_operations2(Trace,0). |
363 | | |
364 | | perform_sequence_of_operations2([],Ident) :- |
365 | | (silent_mode(on) -> true ; ident(Ident),print('SUCCESS'),nl). |
366 | | perform_sequence_of_operations2([Op|T],Ident) :- |
367 | | get_error_context(Context), |
368 | | current_state_id(ID),get_operation_name(Op,FOP), |
369 | | set_error_context(operation(FOP,ID)), |
370 | ? | perform_one_operation_in_sequence(Op,Ident), |
371 | | update_max_nr_replayed(Ident), |
372 | | restore_error_context(Context), |
373 | | Ident1 is Ident+1, |
374 | | (get_preference(deterministic_trace_replay,true) -> ! ; true), |
375 | ? | perform_sequence_of_operations2(T,Ident1). |
376 | | |
377 | | |
378 | | perform_one_operation_in_sequence('$non_det_modified_vars'(ActionAsTerm,NonDetVars),Ident) :- !, |
379 | | current_expression(_CurID,State), |
380 | | current_options(Options), |
381 | | member( (_Id,Action,ActionOpAsTerm,NewID), Options), |
382 | | unify_action_as_term(ActionAsTerm,ActionOpAsTerm,State), |
383 | | %ident(Ident), format('checking non_det_modified_vars for ~w in ~w~n',[ActionAsTerm,NewID]),nl, |
384 | | check_non_det_vars(NewID,NonDetVars), |
385 | | !, |
386 | | perform_action_tclk(Action,ActionOpAsTerm,NewID,Ident). |
387 | | perform_one_operation_in_sequence('$non_det_modified_vars'(Op,_),Ident) :- !, |
388 | | ident(Ident), format('ignoring non_det_modified_vars for ~w~n',[Op]), % TO DO: use constraint-based finding of successor |
389 | | perform_one_operation_in_sequence(Op,Ident). |
390 | | perform_one_operation_in_sequence('$check_value'(ID,VAL),Ident) :- !, |
391 | ? | ident(Ident), format('checking value of ~w ',[ID]), |
392 | | state_space:current_expression(_,State), |
393 | | expand_const_and_vars_to_full_store(State,EState), |
394 | | lookup_value_for_existing_id(ID,EState,StoredVal), |
395 | | translate:print_bvalue(StoredVal), |
396 | | (unify(StoredVal,VAL) -> print_green(' ok'),nl |
397 | | ; print_red(' failed'),nl, %print(StoredVal),nl, print(VAL),nl, |
398 | | fail). |
399 | | perform_one_operation_in_sequence(Op,Ident) :- |
400 | | (silent_mode(on) -> true |
401 | | ; ident(Ident),print('<- '),print(Ident), print(': '), my_print_event(Op,OpStr), |
402 | | print(' :: '), |
403 | | current_state_id(CurID), print(CurID),nl |
404 | | ), |
405 | ? | (is_a_reset_operation(Op) |
406 | | -> tcltk_reset, %tcltk_goto_state(reset,root), % reset animation history |
407 | | tcltk_get_options(_) |
408 | | ; true), |
409 | | convert_action(Op,COp), |
410 | ? | if(perform_single_operation(COp,Ident),true, |
411 | | (silent_mode(off), |
412 | | ident(Ident), print_red(' **** FAIL ****'),nl, |
413 | | fail)), |
414 | | (get_preference(deterministic_trace_replay,true) -> ! ; true), |
415 | | (silent_mode(on) -> true |
416 | | ; ident(Ident), print(' | '), print(Ident), print(': '), print(OpStr), |
417 | | %functor(Op,OpName,_), print(OpName), |
418 | | %((OpName = '-->', arg(1,Op,A1), functor(A1,RealOpName,_)) -> print(RealOpName) ; true), |
419 | | print_green(' success -->'), |
420 | | current_state_id(NewCurID), |
421 | | statistics(walltime,[Tot,Delta]), |
422 | | format('~w [~w (Delta ~w) ms]~n',[NewCurID,Tot,Delta]) |
423 | | ). |
424 | | |
425 | | my_print_event(Op,OpStrTA) :- |
426 | | temporary_set_preference(expand_avl_upto,4,CHNG), |
427 | | translate_event(Op,OpStr), |
428 | | truncate_atom(OpStr,100,OpStrTA), |
429 | | reset_temporary_preference(expand_avl_upto,CHNG), |
430 | | write(OpStrTA). |
431 | | |
432 | | /* convert global constants such as b with S={a,b,c} into fd(2,'S') */ |
433 | | convert_action(V,R) :- var(V),!,R=V. |
434 | | convert_action(io(V,Ch),R) :- !, strip_dots(V,SV),R=io(SV,Ch,_SRCSPAN). |
435 | | convert_action(io(V,Ch,_),R) :- !, strip_dots(V,SV),R=io(SV,Ch,_SRCSPAN). |
436 | | convert_action(tick(_SRCSPAN),R) :- !, R = tick(_). |
437 | | convert_action(X,Res) :- nonvar(X),X=..[F|Args],!, |
438 | | l_convert_term(Args,CArgs),Res=..[F|CArgs]. |
439 | | convert_action(X,X). |
440 | | |
441 | | convert_term(V,R) :- var(V),!,R=V. |
442 | | convert_term(closure(A,B,C),R) :- !, R=closure(A,B,C). % we may mess with identifiers; see test 1575 |
443 | | convert_term(term(bool(0)),R) :- !, R=pred_false /* bool_false */. |
444 | | convert_term(term(bool(1)),R) :- !, R=pred_true /* bool_true */. |
445 | | convert_term(bool_true,pred_true /* bool_true */) :- !. |
446 | | convert_term(bool_false,pred_false /* bool_false */) :- !. |
447 | | convert_term(string(S),R) :- !, R=string(S). |
448 | | convert_term(X,Representation) :- atomic(X), lookup_global_constant(X,Representation),!. |
449 | | convert_term(X,Res) :- nonvar(X),X=..[F|Args],!, |
450 | | l_convert_term(Args,CArgs),Res=..[F|CArgs]. |
451 | | convert_term(X,X). |
452 | | |
453 | | l_convert_term([],[]). |
454 | | l_convert_term([H|T],[CH|CT]) :- convert_term(H,CH), l_convert_term(T,CT). |
455 | | |
456 | | /* newer version of ProB no longer has dot() constructor */ |
457 | | strip_dots([],[]). |
458 | | strip_dots([H|T],[X|ST]) :- (H=dot(X) -> true ; X=H), strip_dots(T,ST). |
459 | | strip_dots(tail_in(X),[in(XR)]) :- |
460 | | (X=dotTuple(R) -> XR=tuple(R) ; XR = X). /* we also no longer use tail_in */ |
461 | | |
462 | | get_state(concrete_constants(X),R) :- !, R=X. |
463 | | get_state(const_and_vars(_,X),R) :- !, R=X. |
464 | | get_state(X,X). |
465 | | |
466 | | % check whether state NewID contains all the bindings in NonDetVars |
467 | | check_non_det_vars(NewID,NonDetVars) :- |
468 | | visited_expression(NewID,State), |
469 | | get_state(State,VarState), |
470 | | maplist(check_non_det_bind(VarState),NonDetVars). |
471 | | |
472 | | check_non_det_bind(State,bind(Var,Val)) :- member(bind(Var,StoredVal),State), unify(StoredVal,Val). |
473 | | |
474 | | :- use_module(state_space,[current_state_id/1, current_expression/2, visited_expression_id/1]). |
475 | | perform_single_operation('$jump'(TO,FROM),Ident) :- !, |
476 | | current_state_id(ID), |
477 | | print_red('Warning: trace file contains JUMP!'),nl, |
478 | | (ID=FROM -> true ; ident(Ident),format('Cannot perform jump from ~w (to ~w)~n',[FROM,TO])), |
479 | | (visited_expression_id(TO) -> tcltk_interface:tcltk_goto_state(jump,TO) |
480 | | ; add_error(trace_checking_fails,'State ID does not exist for jump:',TO),fail |
481 | | ). |
482 | | perform_single_operation(ActionAsTerm,Ident) :- |
483 | | % tools_printing:print_term_summary(perform_single_op(ActionAsTerm)), |
484 | | current_expression(_CurID,State), |
485 | | current_options(Options), |
486 | ? | ( if( find_action_in_options(ActionAsTerm,Options,State, Action,ActionOpAsTerm,NewID), |
487 | ? | perform_action_tclk(Action,ActionOpAsTerm,NewID,Ident), |
488 | ? | if(perform_single_operation_retry(Options,ActionAsTerm,Action,NewID,State), |
489 | ? | perform_action_tclk(Action,_AlternateActionOpAsTerm,NewID,Ident), |
490 | | if(perform_custom_operation_retry(ActionAsTerm,_NewID),true, |
491 | | (replace_result_by_variable(ActionAsTerm,A2), |
492 | ? | find_action_in_options(A2,Options,State, Action,_,NewID), |
493 | | (silent_mode(on) -> true ; format_with_colour_nl(user_output,[red],'Result of operation call in trace does not match, e.g., ~w leading to state ~w',[Action,NewID])), |
494 | | fail |
495 | | ) |
496 | | ) |
497 | | ) |
498 | | |
499 | | ) |
500 | | ; |
501 | | single_operation_skip(ActionAsTerm,Ident) |
502 | | ), |
503 | | (get_preference(deterministic_trace_replay,true) -> ! ; true). |
504 | | perform_single_operation(ActionAsTerm,Ident) :- |
505 | | current_options(Options), |
506 | | member( (_Id,PActionS,'*permute*',NewID), Options), /* permute action from symmetry reduction */ |
507 | | perform_action_tclk(PActionS,'*permute*',NewID,Ident), !, |
508 | | (silent_mode(on) -> true |
509 | | ; ident(Ident), print(' | ** PERMUTING **'),nl), |
510 | | perform_single_operation(ActionAsTerm,Ident). |
511 | | perform_single_operation(ActionAsTerm,Ident) :- |
512 | | %%user:current_expression(CurID,State), |
513 | | \+ functor(ActionAsTerm,tau,1), |
514 | | current_options(Options), %print(options(Options)),nl, |
515 | ? | member( (_Id,AX,tau(X),NewID), Options), |
516 | | (silent_mode(on) -> true |
517 | | ; ident(Ident), print(' | ** SKIPPING ADDITIONAL TAU in SPEC. **'),nl), |
518 | ? | perform_action_tclk(AX,tau(X),NewID,Ident), |
519 | ? | perform_single_operation(ActionAsTerm,Ident). |
520 | | %perform_single_operation(ActionAsTerm,Ident) :- |
521 | | % ident(Ident),print(unsuccessful(ActionAsTerm)),nl,fail. |
522 | | |
523 | | |
524 | | perform_action_tclk(Action,ActionOpAsTerm,NewID,_Ident) :- |
525 | ? | my_perform_action_as_string(Action,ActionOpAsTerm,NewID), |
526 | | tcltk_get_options(_). |
527 | | perform_action_tclk(_Action,_ActionOpAsTerm,_NewID,Ident) :- % undo action |
528 | | current_state_id(FromID), |
529 | | tcltk_interface:tcltk_backtrack, |
530 | | tcltk_get_options(_), |
531 | | current_state_id(ToID), |
532 | | silent_mode(off), |
533 | | ident(Ident), |
534 | | print(backtrack_from_to(FromID,ToID)),nl, |
535 | | fail. |
536 | | |
537 | | % a more flexible version: allow string representations of ProB to evolve compared to when test suites where stored |
538 | | my_perform_action_as_string(ActionStr,ActionOpAsTerm,NewID) :- |
539 | ? | tcltk_interface:tcltk_perform_action_string(ActionStr,ActionOpAsTerm,NewID). |
540 | | |
541 | | % succeeds if we have an operation with results and then replaces results by fresh variable |
542 | | replace_result_by_variable('-->'(Op,_),'-->'(Op,_)). |
543 | | |
544 | | find_action_in_options(ActionAsTerm,Options,State, Action,ActionOpAsTerm,NewID) :- |
545 | ? | member( (_Id,Action,ActionOpAsTerm,NewID), Options), |
546 | | unify_action_as_term(ActionAsTerm,ActionOpAsTerm,State). |
547 | | |
548 | | :- use_module(library(avl)). |
549 | | unify_action_as_term(A,A,_) :- !. |
550 | | unify_action_as_term('-->'(Op,Results),'-->'(Op2,Results2),State) :- !, |
551 | | unify_action_as_term(Op,Op2,State), |
552 | | l_unify(Results,Results2). |
553 | | unify_action_as_term(A,B,State) :- functor(A,F,N), functor(B,F2,N2), |
554 | | unify_functor(F,F2,State), |
555 | | (N=N2 -> true |
556 | | ; formatsilent('*** SAME EVENT WITH VARYING ARITY: ~w : ~w vs ~w arguments ***~n',[F,N,N2]), |
557 | | % Probably due to show_eventb_any_arguments differently set ? or initalisation old format |
558 | | fail), |
559 | | A=..[F|AA], B=..[F2|BB], |
560 | | l_unify(AA,BB). |
561 | | l_unify([],[]). |
562 | | l_unify([H|T],[I|S]) :- unify(H,I), l_unify(T,S). |
563 | | |
564 | | |
565 | | unify_functor('initialise_machine','$initialise_machine',_) :- !. % old-style format |
566 | | unify_functor('setup_constants','$setup_constants',root) :- !. % old-style format |
567 | | unify_functor(X,X,_). |
568 | | |
569 | | %:- use_module(custom_explicit_sets,[equal_explicit_sets/4]). |
570 | | :- use_module(debug). |
571 | | unify(A,A) :- !. |
572 | | unify(A,avl_set(B)) :- %print(exp(A,B)),nl, |
573 | | expand_explicit_set(A,AA), |
574 | | %nl,translate:print_bvalue(AA),nl, translate:print_bvalue(avl_set(B)),nl, % print(chck(A,AA,B)),nl, |
575 | | AA = avl_set(B),!. |
576 | | %unify(A,B) :- print(unify(A,B)),nl,fail. |
577 | | unify(C1,C2) :- is_a_set(C1),(C2=closure(_P,_T,_B) ; C2=global_set(_)), |
578 | | %print(unify_closure(C1,C2)),nl, |
579 | | (C1=[_|_] -> custom_explicit_sets:convert_to_avl(C1,C11) ; C11=C1), % TO DO: replace by proper expansion predicate |
580 | | (unify_explicit_sets(C11,C2) |
581 | | -> true |
582 | | ; print(ko_unify_closure),nl, translate:print_bvalue(C11),nl, translate:print_bvalue(C2),nl, |
583 | | %terms:term_hash((C1,C2),H), print(hash(H)),nl, (H=140674321 -> trace, unify_explicit_sets(C11,C2)), |
584 | | fail),!. |
585 | | %avl_domain(B,R), print(chck(A,B,R)),nl,l_unify(A,R). |
586 | | unify(C1,C2) :- %tools_printing:print_term_summary(fail(C1,C2)),nl, |
587 | | debug_println(9,unify_failed(C1,C2)),fail. |
588 | | |
589 | | is_a_set(closure(_,_,_)). |
590 | | is_a_set([]). |
591 | | is_a_set([_|_]). |
592 | | is_a_set(global_set(_)). |
593 | | is_a_set(avl_set(_)). |
594 | | |
595 | | expand_explicit_set([H|T],AA) :- !, custom_explicit_sets:convert_to_avl([H|T],AA). |
596 | | expand_explicit_set(A,AA) :- |
597 | | on_enumeration_warning(custom_explicit_sets:try_expand_and_convert_to_avl_if_smaller_than(A,AA,200), fail). |
598 | | unify_explicit_sets(C1,C2) :- |
599 | | ALLOW=no_expansion, %(C1 = avl_set(_) -> ALLOW=allow_expansion ; ALLOW=no_expansion), |
600 | | on_enumeration_warning(custom_explicit_sets:equal_explicit_sets4(C1,C2,ALLOW,no_wf_available), fail). |
601 | | |
602 | | |
603 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID,root) :- |
604 | | functor(ActionAsTerm,'setup_constants',_), !, % rename to new format |
605 | | nl,print('DEPRECATED TRACE FILE'),nl,nl,nl, |
606 | | ActionAsTerm =.. [_|Args], ActionAsTerm2 =..['$setup_constants'|Args], |
607 | ? | perform_single_operation_retry(Options,ActionAsTerm2,Action,NewID). |
608 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID,State) :- |
609 | | (State=root ; State=concrete_constants(_)), |
610 | | functor(ActionAsTerm,'initialise_machine',_), !, % rename to new format |
611 | | nl,print('DEPRECATED TRACE FILE'),nl,nl,nl, |
612 | | ActionAsTerm =.. [_|Args], ActionAsTerm2 =..['$initialise_machine'|Args], |
613 | ? | perform_single_operation_retry(Options,ActionAsTerm2,Action,NewID). |
614 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID,_State) :- |
615 | ? | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID). |
616 | | |
617 | | |
618 | | % check that parameters provided for setup_constants in trace file can be found in State |
619 | | %check_initialised_args([],_). |
620 | | %check_initialised_args([V|T],State) :- |
621 | | % select(bind(_,V2),State,Rest), |
622 | | % unify(V,V2), |
623 | | % %print('SELECTED: '),translate:print_bvalue(V2),nl, |
624 | | % !, |
625 | | % check_initialised_args(T,Rest). |
626 | | %check_initialised_args([V|T],State) :- |
627 | | % print('MISMATCH'),nl, %print(mismatch(V,C)),nl, |
628 | | % print('COULD NOT FIND: '), translate:print_bvalue(V),nl, |
629 | | % tools_printing:print_term_summary(V),nl, |
630 | | % %print(V),nl, print(State),nl, |
631 | | % V=closure(_,_,_), memberchk(bind(_,closure(_,_,_)),State), |
632 | | % check_initialised_args(T,State). |
633 | | |
634 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID) :- |
635 | | functor(ActionAsTerm,'$setup_constants',_), !, |
636 | | (get_prob_application_type(tcltk) -> OTHER = ['$partial_setup_constants'] ; OTHER = []), |
637 | ? | perform_alternative_op_with_same_functor(Options, ['$setup_constants' | OTHER], |
638 | | 'set up constants', 'initialisation of constants', |
639 | | ActionAsTerm, Action, NewID), |
640 | | %visited_expression(NewID,concrete_constants(C)), ActionAsTerm =.. [_|Args], |
641 | | % check that state NewID corresponds to parameters provided for setup_constants in trace file |
642 | | %print(check_args),nl, check_initialised_args(Args,C), print('argsok'),nl. % TO DO: try and enable this for all tests |
643 | | true. |
644 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID) :- |
645 | | functor(ActionAsTerm,'$initialise_machine',_),!, |
646 | ? | perform_alternative_op_with_same_functor(Options, ['$initialise_machine'], |
647 | | 'initialise', 'initialisation', |
648 | | ActionAsTerm, Action, NewID), |
649 | | true. %visited_expression(NewID,S), (S=const_and_vars(_,VS) -> true ; VS=S), ActionAsTerm =.. [_|Args], |
650 | | %check_initialised_args(Args,VS), print('initargsok'),nl. |
651 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID) :- |
652 | | functor(ActionAsTerm,tau,_), arg(1,ActionAsTerm,Arg), nonvar(Arg),!, |
653 | ? | perform_alternative_op_with_same_functor(Options, [tau], |
654 | | tau, tau, |
655 | | ActionAsTerm, Action, NewID). |
656 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID) :- |
657 | | functor(ActionAsTerm,F,0), preference(show_eventb_any_arguments,true), |
658 | | /* then allow also the same operation name but with more arguments */ |
659 | | member( (_Id,Action,ActionAsTerm2,NewID), Options), |
660 | | functor(ActionAsTerm2,F,FArity), FArity>0, |
661 | | formatsilent('% Allowing extra Event-B style ANY arguments.~n',[]). |
662 | | perform_single_operation_retry(Options,ActionAsTerm,Action,NewID) :- |
663 | | functor(ActionAsTerm,F,N),N>0, preference(show_eventb_any_arguments,false), |
664 | | /* then allow also the same operation name but with more arguments */ |
665 | ? | member( (_Id,Action,F,NewID), Options), |
666 | | formatsilent('% Allowing operation without Event-B style ANY arguments.~n',[]). |
667 | | %perform_single_operation_retry(Options,_ActionsAsTerm,_Action,_NewID) :- |
668 | | % print_message('No more options: '), print(Options),nl,fail. |
669 | | |
670 | | |
671 | | perform_custom_operation_retry(OpName,NewID) :- |
672 | | (OpName = '$initialise_machine' ; OpName = '$setup_constants'), |
673 | | !, |
674 | | current_state_id(CurID), |
675 | | (max_reached_for_node(CurID) ; not_all_transitions_added(CurID)), |
676 | | preferences:get_preference(maxNrOfInitialisations,0), |
677 | | tcltk_add_user_executed_operation_typed(OpName,_,b(truth,pred,[]),NewID), |
678 | | (get_preference(deterministic_trace_replay,true) -> ! ; true), |
679 | | print_green(init_found(NewID)),nl. |
680 | | perform_custom_operation_retry(ActionAsTerm,NewID) :- |
681 | | (ActionAsTerm = '-->'(_,_) % for operations with return types we cannot provide a predicate to solve yet |
682 | | -> preferences:get_preference(maxNrOfEnablingsPerOperation,0) |
683 | | ; true), |
684 | | current_state_id(CurID), |
685 | | get_operation_name(ActionAsTerm,OpName), |
686 | | (max_reached_for_node(CurID) ; time_out_for_node(CurID,OpName,_) ; not_all_transitions_added(CurID)), |
687 | | debug_println(9,try_custom(CurID,ActionAsTerm)), % Execute operation by predicate |
688 | | % (state_space:transition(CurID,AA,BB,CC),format('Successor of ~w: ~w --~w--> ~w~n',[CurID,AA,BB,CC]),fail ; true), |
689 | | get_operation_return_values_and_arguments(ActionAsTerm,ExpectedResults,OpArgs), |
690 | | % TO DO: optionally check return values |
691 | | (OpArgs = [] -> Pred = b(truth,pred,[]) |
692 | | ; \+ b_is_operation_name(OpName) |
693 | | -> add_error(trace_checking_fails,'Operation does not exist in loaded specification:',OpName),fail |
694 | ? | ; b_get_machine_operation_typed_parameters(OpName,TArgParas), |
695 | | l_generate_predicate(OpArgs,TArgParas,OpName,Conjuncts), |
696 | | conjunct_predicates(Conjuncts,Pred) |
697 | | ), |
698 | | !, |
699 | | %print('PRED: '),translate:print_bexpr(Pred),nl, |
700 | | start_ms_timer(Timer), |
701 | | profile_single_call(OpName,CurID, |
702 | | tcltk_interface:tcltk_add_user_executed_operation_typed(OpName,OpTerm,Pred,NewID) % The Pred can only talk about parameters and the state before; thus this only works for setup_constants and initialise_machine |
703 | | ), |
704 | | (get_preference(deterministic_trace_replay,true) -> ! ; true), % by preventing backtracking we can reduce memory consumption |
705 | | print_green(successor_found(NewID, OpName)),nl, |
706 | | get_operation_return_values_and_arguments(OpTerm,ActualResults,_), |
707 | | (l_unify(ExpectedResults,ActualResults) -> true |
708 | | ; ajoin(['Result values for ',OpName,' do not match for transition from ',CurID,' to ',NewID,':'],Msg), |
709 | | translate_bvalue(ExpectedResults,ER), |
710 | | translate_bvalue(ActualResults,AR), |
711 | | add_warning(b_trace_checking,Msg,[expected(ER),actual(AR)]) |
712 | | ), |
713 | | (debug_mode(on) -> stop_ms_timer_with_msg(Timer,'Custom: ') ; true), |
714 | | garbage_collect, % important to keep memory consumption down for long traces e.g. for Innotrans VBF traces |
715 | | (debug_mode(on) -> print_memory_used_wo_gc,nl ; true), |
716 | | tcltk_get_options(_). |
717 | | |
718 | | l_generate_predicate([],[],_,[]) :- !. |
719 | | l_generate_predicate([],_,Op,[]) :- !, add_warning(b_trace_checking,'Trace file contains too few parameters for: ',Op). |
720 | | l_generate_predicate(_,[],Op,[]) :- !, add_warning(b_trace_checking,'Trace file contains too many parameters for: ',Op). |
721 | | l_generate_predicate([ArgVal|TArg],[TypedPara|TPar],OpName,[Pred|TPred]) :- |
722 | | generate_predicate(ArgVal,TypedPara,Pred), |
723 | | l_generate_predicate(TArg,TPar,OpName,TPred). |
724 | | generate_predicate(ArgVal,TypedPara,b(equal(TypedPara,TVal),pred,[])) :- |
725 | | get_texpr_type(TypedPara,Type), |
726 | | TVal = b(value(ArgVal),Type,[]). |
727 | | |
728 | | perform_alternative_op_with_same_functor(Options,Functors,ActionMsg,AltMsg,ActionAsTerm,Action,NewID) :- |
729 | | \+ member( (_,_,ActionAsTerm,_), Options), |
730 | | (silent_mode(on) -> true |
731 | | ; ajoin(['Could not ', ActionMsg, ' with parameters from trace file.'], Msg1), |
732 | | ajoin(['Will attempt any possible ', AltMsg, '.'], Msg2), |
733 | | print_message(Msg1), print_message(Msg2) |
734 | | ), |
735 | | %print(ActionAsTerm),nl, print(Options),nl,nl, |
736 | ? | member( (_,ActionStr,MachineActionAsTerm,NewID), Options), |
737 | | functor(MachineActionAsTerm,Functor,_), |
738 | | member(Functor,Functors), |
739 | | (Action=ActionStr -> true ; print(cannot_match(Action,ActionStr)),nl,fail). |
740 | | |
741 | | single_operation_skip(tau(_),Ident) :- |
742 | | (silent_mode(on) -> true |
743 | | ; ident(Ident), print(' | *** TRYING TO SKIP TAU IN TRACE FILE'),nl). |
744 | | |
745 | | |
746 | | |
747 | | ident(0). |
748 | | ident(N) :- N > 40, !, N1 is N mod 40, ident(N1). |
749 | ? | ident(N) :- N>0, print('- '), N1 is N-1, ident(N1). |
750 | | |
751 | | |
752 | | /* ------------------------------------------------------------------ */ |
753 | | |
754 | | :- use_module(state_space,[get_action_trace/1]). |
755 | | print_trace_as_fdr_check :- silent_mode(on),!. |
756 | | print_trace_as_fdr_check :- |
757 | | print('-- Trace Check Generated by ProB:'),nl, |
758 | | get_action_trace(T), |
759 | | reverse(T,RT), |
760 | | print('PROB_TEST_TRACE = '),print_fdr(RT), |
761 | | nl, |
762 | | print('assert MAIN [T= PROB_TEST_TRACE'),nl. |
763 | | |
764 | | print_fdr([]) :- print('STOP'),nl. |
765 | | print_fdr([jump|T]) :- print_fdr(T). |
766 | | print_fdr([action(Str,Term)|T]) :- |
767 | | (Term = i(_) |
768 | | -> (nl,print(' ( STOP /\\ ')) |
769 | | ; (Term = tick(_) |
770 | | -> print('SKIP ; ') |
771 | | ; ((Term = tau(_) ; Term = 'start_cspm_MAIN') |
772 | | -> true |
773 | | ; (print(Str), print(' -> ')) |
774 | | ) |
775 | | )), |
776 | | print_fdr(T), |
777 | | (Term = i(_) |
778 | | -> print(' )') |
779 | | ; true |
780 | | ). |
781 | | |
782 | | % ----------------------- |
783 | | |
784 | | tcltk_save_history_as_trace_file(Style,File) :- |
785 | | tcltk_save_history_as_trace_file(Style,[],File). |
786 | | |
787 | | tcltk_save_history_as_trace_file(Style,Options,File) :- |
788 | | formatsilent('% Saving history (~w format) to: ~w~n',[Style,File]), |
789 | | open(File,write,Stream,[encoding(utf8)]), |
790 | | currently_opened_specification_name(OurName), |
791 | | (Style=prolog -> format(Stream,'machine(\'~w\').~n',[OurName]) ; true), |
792 | | call_cleanup(print_trace_for_replay(Style,Options,Stream), |
793 | | (close(Stream),print_message(done))). |
794 | | |
795 | | :- use_module(state_space,[transition/4, op_trace_ids/1]). % transition(CurID,Term,TransId,DestID) |
796 | | |
797 | | % print the animator's history trace for later replay |
798 | | % prolog style is currently used by ProB Tcl/Tk |
799 | | % json style is used by ProB2 UI |
800 | | print_trace_for_replay(Style,Options,Stream) :- |
801 | | op_trace_ids(OpIds), |
802 | | reverse(OpIds,Trace), |
803 | | print_trace_for_replay(Style,Trace,Options,Stream). |
804 | | |
805 | | print_trace_for_replay(prolog,Trace,_,Stream) :- !, print_transition_list_prolog(Trace,Stream). |
806 | | print_trace_for_replay(json,Trace,Options,Stream) :- !, print_transition_list_json(Trace,Options,Stream). |
807 | | print_trace_for_replay(Style,_,_,_Stream) :- |
808 | | add_internal_error('Style not supported: ',print_trace_for_replay(Style,_,_,_)). |
809 | | |
810 | | print_transition_list_prolog(Trace,Stream) :- |
811 | ? | member(OpId,Trace), |
812 | | transition(PrevId,Op,OpId,SuccId), |
813 | | (atomic(Op), |
814 | | ( b_or_z_mode, |
815 | | transition(PrevId,Op,_,SuccId2), SuccId2 \= SuccId -> true), % another transition with same label Op exists |
816 | | translate:get_non_det_modified_vars_in_target_id(Op,SuccId,NonDetVars) |
817 | | -> print_quoted(Stream,'$non_det_modified_vars'(Op,NonDetVars)) |
818 | | ; print_quoted(Stream, Op) |
819 | | ), |
820 | | format(Stream,'.~n',[]), |
821 | | fail. |
822 | | print_transition_list_prolog(_,_). |
823 | | |
824 | | :- use_module(version, [version_str/1, revision/1]). % , lastchangeddate/1 |
825 | | :- use_module(library(system),[ datime/1, environ/2]). |
826 | | :- use_module(specfile,[currently_opened_file/1, currently_opened_specification_name/1]). |
827 | | :- use_module(tools_strings,[number_codes_min_length/3]). |
828 | | :- use_module(tools_lists,[get_member_option/3]). |
829 | | :- use_module(tools,[get_tail_filename/2]). |
830 | | |
831 | | |
832 | | :- use_module(extrasrc(json_parser),[json_write_stream/3]). |
833 | | % print list in JSON format for ProB2 UI: |
834 | | % this is formatVersion 1 |
835 | | print_transition_list_json(Trace,Options,Stream) :- |
836 | | get_prob_application_type(ApplType), |
837 | | (get_member_option(description,Options,Desc) -> true |
838 | | ; ApplType=probcli -> Desc = 'File created by probcli' |
839 | | ; ApplType=tcltk -> Desc = 'File created by ProB Tcl/Tk' |
840 | | ; Desc = 'File created by ProB' |
841 | | ), |
842 | | temporary_set_preference(expand_avl_upto,-1,CHNG), |
843 | | call_cleanup( |
844 | | print_json_list(Trace,root,TransitionList), |
845 | | reset_temporary_preference(expand_avl_upto,CHNG)), |
846 | | format_current_iso_time(Timestamp), |
847 | | (nonmember(privacy_mode,Options),environ('USER',User) |
848 | | -> ajoin([ApplType,' (',User,')'],Creator) |
849 | | ; Creator = ApplType), |
850 | | version_str(Version), |
851 | | revision(Rev), |
852 | | currently_opened_file(File), |
853 | | (nonmember(privacy_mode,Options) -> StoredFile=File |
854 | | ; get_tail_filename(File,StoredFile) % do not store full path |
855 | | ), |
856 | | currently_opened_specification_name(Model), |
857 | | Json = json([ |
858 | | description=string(Desc), |
859 | | transitionList=array(TransitionList), |
860 | | metadata=json([ |
861 | | fileType=string('Trace'), |
862 | | formatVersion=number(1), |
863 | | savedAt=string(Timestamp), % TODO: rename to createdAt and make optional for testing |
864 | | creator=string(Creator), |
865 | | proBCliVersion=string(Version), |
866 | | proBCliRevision=string(Rev), % not used by Java Kernel |
867 | | modelName=string(Model), |
868 | | modelFile=string(StoredFile) % not used by Java Kernel |
869 | | ]) |
870 | | ]), |
871 | | (json_write_stream(Stream,Json,[pretty(true)]) |
872 | | -> true |
873 | | ; add_error(print_transition_list_json,'Writing json trace failed'), fail). |
874 | | % to do: maybe also save types of variables, constants, operation parameters, ... |
875 | | % and modification time of modelFile, SHA of model, ... |
876 | | |
877 | | % example: "2020-12-23T14:33:00Z", |
878 | | format_current_iso_time(TimestampAtom) :- |
879 | | % TODO: this is incorrect |
880 | | % datime is the time in the current timezone, but does not include that zone |
881 | | % we put Z behind the ISO-8601 datetime to mark it as UTC which is incorrect! |
882 | | datime(datime(Yr,Mon,Day,Hr,Min,Sec)), |
883 | | number_codes_min_length(Yr,4,YrC), |
884 | | number_codes_min_length(Mon,2,MonC), |
885 | | number_codes_min_length(Day,2,DayC), |
886 | | number_codes_min_length(Hr,2,HrC), |
887 | | number_codes_min_length(Min,2,MinC), |
888 | | number_codes_min_length(Sec,2,SecC), |
889 | | format_to_codes('~s-~s-~sT~s:~s:~sZ',[YrC,MonC,DayC,HrC,MinC,SecC],TimestampCodes), |
890 | | atom_codes(TimestampAtom,TimestampCodes). |
891 | | |
892 | | % print a list of transition ids in JSON format |
893 | | print_json_list([],_,[]). |
894 | | print_json_list([H|T],FromID,[JsonH|JsonT]) :- |
895 | | (H=jump(NewID) -> true |
896 | | ; print_json_opid(H,JsonH,FromID,NewID) -> true |
897 | | ; add_error(print_json_list,'Writing operation failed: ',H), NewID=FromID), |
898 | | print_json_list(T,NewID,JsonT). |
899 | | |
900 | | :- use_module(specfile,[get_operation_internal_name/2, |
901 | | get_operation_return_values_and_arguments/3, |
902 | | get_operation_description_for_transition/4, |
903 | | get_possible_language_specific_top_level_event/3]). |
904 | | |
905 | | % print an operation id (transition id) for a JSON Trace |
906 | | print_json_opid(OpId,Json,PrevId,SuccId) :- |
907 | | (transition(PrevId,Op,OpId,SuccId) -> PrevId1=PrevId |
908 | | ; add_warning(print_json_list,'Cannot find operation id from given source id: ',OpId:from(PrevId):to(SuccId)), |
909 | | transition(PrevId1,Op,OpId,SuccId) |
910 | | ), |
911 | | get_operation_internal_name(Op,OpName), % TO DO: currently this is INITIALISATION instead of $initialise_machine |
912 | | (get_possible_language_specific_top_level_event(OpName,ResultNames,ParaNames) -> true |
913 | | ; ResultNames=unknown, ParaNames=unknown), |
914 | | get_operation_return_values_and_arguments(Op,ReturnValues,Paras), |
915 | | % TO DO: extract variables and constants for $initialise_machine, $setup_constants |
916 | | Json = json([name=string(OpName)|T1]), |
917 | | (get_operation_description_for_transition(PrevId,Op,SuccId,Desc) |
918 | | -> T1=[description=string(Desc)|T2] |
919 | | ; T2=T1), |
920 | | (Paras = [] |
921 | | -> T3=T2 % do not print Paras to reduce memory in JSON file |
922 | | ; (get_preference(show_eventb_any_arguments,true), |
923 | | \+ same_length(Paras,ParaNames) |
924 | | -> % we cannot easily use the parameters during replay; ANY parameters are currently only added when there are no other parameters |
925 | | add_warning(json_export,'Parameter mismatch: ',OpName:ParaNames,Paras), |
926 | | T3=T2 |
927 | | ; print_json_paras(Paras,ParaNames,OpName,JsonParams), |
928 | | T2=[params=JsonParams|T3] |
929 | | ) |
930 | | ), |
931 | | (ReturnValues = [] |
932 | | -> T4=T3 % do not print results to reduce memory in JSON file |
933 | | ; print_json_paras(ReturnValues,ResultNames,OpName,JsonResults), |
934 | | T3=[results=JsonResults|T4] |
935 | | ), |
936 | | get_change_list(PrevId1,SuccId,ChangedVarNames,ChangedValues), |
937 | | (ChangedVarNames = [] |
938 | | -> T5=T4 |
939 | | ; print_json_paras(ChangedValues,ChangedVarNames,OpName,JsonDestState), |
940 | | T4=[destState=JsonDestState|T5] |
941 | | ), |
942 | | get_unchanged_list(PrevId1,SuccId,UnChangedVarNames), |
943 | | (UnChangedVarNames = [] |
944 | | -> T6=T5 |
945 | | ; print_json_names(UnChangedVarNames,JsonDestStateNotChanged), |
946 | | T5=[destStateNotChanged=JsonDestStateNotChanged|T6] |
947 | | ), |
948 | | T6=[]. |
949 | | |
950 | | :- use_module(translate, [translate_bvalue/2, translate_bvalue_to_parseable_classicalb/2]). |
951 | | mytranslate(H,S) :- |
952 | | % should we use unicode mode to avoid more ambiguities? |
953 | | translate_bvalue_to_parseable_classicalb(H,S). |
954 | | |
955 | | print_json_paras(Values,Names,OpName,json(JsonList)) :- |
956 | | filter_json_paras(Values,Names,OpName,FValues,FNames), |
957 | | print_json_paras_aux(FValues,1,FNames,JsonList). |
958 | | % filter out parameters (e.g., large constants or symbolic values that cannot be read back in) |
959 | | % TODO: complete |
960 | | filter_json_paras([],Names,OpName,[],[]) :- |
961 | | (Names = [] -> true |
962 | | ; ajoin(['Missing values for arguments to operation ',OpName,':'],Msg), |
963 | | add_error(filter_json_paras,Msg,Names)). |
964 | | filter_json_paras([Val|TV],InNames,OpName,Vals,Names) :- |
965 | | (InNames = [Name|TN] |
966 | | -> |
967 | | (do_not_print_value(Val,Name,OpName) |
968 | | -> filter_json_paras(TV,TN,OpName,Vals,Names) |
969 | | ; Vals = [Val|RV], Names=[Name|RN], |
970 | | filter_json_paras(TV,TN,OpName,RV,RN) |
971 | | ) |
972 | | ; add_error(filter_json_paras,'Too many values:',[Val|TV]), Vals=[] |
973 | | ). |
974 | | |
975 | | :- use_module(memoization,[is_memoization_closure/4]). |
976 | | :- use_module(probsrc(kernel_freetypes),[registered_freetype/2, freetype_case_db/3]). |
977 | | do_not_print_value(closure(P,T,B),Name,_OpName) :- is_memoization_closure(P,T,B,_), |
978 | | add_message(save_trace_file,'Not saving memoization closure value to trace file: ',Name). |
979 | | do_not_print_value(_,Name,'$setup_constants') :- % Do not save constants generated for freetypes and their cases |
980 | | ( registered_freetype(Name,_) -> add_debug_message(save_trace_file,'Not saving FREETYPE to trace file: ',Name) |
981 | | ; freetype_case_db(Name,_,_) -> add_debug_message(save_trace_file,'Not saving FREETYPE case to trace file: ',Name) |
982 | | ). |
983 | | % TODO: we could also improve pretty-printing for memoization closures |
984 | | % TO DO: maybe do not print huge AVL sets, in particular constants |
985 | | |
986 | | % print_json_paras_aux(ListOfValues,ParameterNr,ListOfParameterNames) |
987 | | print_json_paras_aux([],_,_,[]) :- !. |
988 | | print_json_paras_aux([H|T],Nr,[HName|NT],[JsonH|JsonT]) :- !, Nr1 is Nr+1, |
989 | | mytranslate(H,HValue), |
990 | | JsonH = (HName=string(HValue)), |
991 | | print_json_paras_aux(T,Nr1,NT,JsonT). |
992 | | print_json_paras_aux(P,Nr,PN,[]) :- |
993 | | add_internal_error('Cannot print JSON parameters:',print_json_paras_aux(P,Nr,PN)). |
994 | | |
995 | | % print a list of identifier names for JSON |
996 | | print_json_names(Names,array(JsonList)) :- print_json_names_aux(Names,JsonList). |
997 | | print_json_names_aux([],[]). |
998 | | print_json_names_aux([HName|T],[string(HName)|JsonT]) :- |
999 | | print_json_names_aux(T,JsonT). |
1000 | | |
1001 | | % find change bindings in the state |
1002 | | changed_binding(FromId,ToId,Bind) :- |
1003 | | visited_expression(FromId,FromState), |
1004 | | visited_expression(ToId,ToState), |
1005 | | get_id_binding(ToState,Bind), |
1006 | | \+ get_id_binding(FromState,Bind). |
1007 | | |
1008 | | % find variables which have not been modified in the state |
1009 | | unchanged_binding(FromId,ToId,Bind) :- |
1010 | | visited_expression(FromId,FromState), % slightly inefficient to expand states again |
1011 | | visited_expression(ToId,ToState), |
1012 | | get_id_binding(ToState,Bind), |
1013 | | get_id_binding(FromState,Bind). |
1014 | | |
1015 | | get_change_list(FromId,ToId,ChangedVarNames,ChangedValues) :- |
1016 | | findall(Bind,changed_binding(FromId,ToId,Bind),List), |
1017 | | maplist(decompose_binding,List,ChangedVarNames,ChangedValues). |
1018 | | get_unchanged_list(FromId,ToId,UnChangedVarNames) :- |
1019 | | findall(Bind,unchanged_binding(FromId,ToId,Bind),List), |
1020 | | maplist(decompose_binding,List,UnChangedVarNames,_). |
1021 | | |
1022 | | decompose_binding(bind(ID,Val),ID,Val). |
1023 | | |
1024 | | |
1025 | | get_id_binding(const_and_vars(_,List),Bind) :- !, member(Bind,List). |
1026 | | get_id_binding(concrete_constants(List),Bind) :- !, member(Bind,List). |
1027 | | get_id_binding(List,Bind) :- !, member(Bind,List). |
1028 | | |