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 | | :- module(b_machine_hierarchy,[analyse_hierarchy/2 |
6 | | ,analyse_eventb_hierarchy/2 |
7 | | ,main_machine_name/1 % find out name of main machine |
8 | | ,machine_name/1 % can be used to find out machine names |
9 | | ,machine_type/2 % machine_type(Name, R) - get the type |
10 | | % (abstract_machine, abstract_model, refinement, implementation) for the machine named Name |
11 | | ,machine_references/2 % get a list of references and the type of the reference. |
12 | | % Example: if A refines B, then machine_references('A',X) returns X= [ref(refines'B','')]. |
13 | | % The last argument in ref/3 is the prefix |
14 | | ,machine_identifiers/7 % gets Params,Sets,Abstract Variables,Concrete Variables,Abstract Constants and Concrete |
15 | | % Constants for a machine. Abstract constants/Variables are introduced in the machine |
16 | | % using the ABSTRACT_VARAIABLES/ABSTARCT_CONSTANTS keyword. The concrete versions |
17 | | % analogously via CONCRETE_VARAIBLES,CONCRETE_CONSTANTS |
18 | | % Example output for |
19 | | % MACHINE xx(T,u) |
20 | | % CONSTRAINTS u:T |
21 | | % SETS A; B={foo, bar} |
22 | | % CONCRETE_CONSTANTS cc |
23 | | % PROPERTIES cc:B |
24 | | % ABSTRACT_VARIABLES xx |
25 | | % CONCRETE_VARIABLES yy |
26 | | % INVARIANT |
27 | | % xx:INT & |
28 | | % yy : T |
29 | | % INITIALISATION xx,yy:= ({1|->2};{2|->4})(1), u |
30 | | % END |
31 | | % |
32 | | % machine_identifiers(A,B,C,D,E,F,G). |
33 | | % A = xx, |
34 | | % B = [identifier(pos(4,1,1,12,1,12),'T'),identifier(pos(5,1,1,14,1,14),u)], |
35 | | % C = [deferred_set(pos(11,1,3,6,3,6),'A'),enumerated_set(pos(12,1,3,9,3,20),'B',[identifier(pos(13,1,3,12,3,14),foo),identifier(pos(14,1,3,17,3,19),bar)])], |
36 | | % D = [identifier(pos(22,1,6,20,6,21),xx)], |
37 | | % E = [identifier(pos(24,1,7,20,7,21),yy)], |
38 | | % F = [], |
39 | | % G = [identifier(pos(16,1,4,20,4,21),cc)] |
40 | | , get_machine_identifier_names/7 % a version returning atomic identifier names |
41 | | , machine_has_constants/1 % check if machine has some constants |
42 | | , get_machine_short_desc/2 |
43 | | ,abstract_constant/2, concrete_constant/2 |
44 | | ,possibly_abstract_constant/1 |
45 | | ,machine_operations/2 % machine_operations(M, Ops) gets the names of the Operations defined in Machine M |
46 | | ,machine_operation_calls/2 |
47 | | ,machine_hash/2 % stores a hash value for the machine |
48 | | ,properties_hash/2 % computes a hash over the constants and properties |
49 | | ,operation_hash/3 % computes a hash for the operation in a machine |
50 | | ,write_dot_hierarchy_to_file/1 |
51 | | ,write_dot_op_hierarchy_to_file/1 |
52 | | ,write_dot_event_hierarchy_to_file/1 |
53 | | ,write_dot_variable_hierarchy_to_file/1 |
54 | | ,get_machine_inclusion_graph/3 |
55 | | ,get_machine_topological_order/1 |
56 | | ,print_machine_topological_order/0 |
57 | | ]). |
58 | | |
59 | | :- use_module(library(lists)). |
60 | | :- use_module(library(ordsets)). |
61 | | :- use_module(bmachine,[b_get_definition/5, get_machine_file_number/4]). |
62 | | :- use_module(bmachine_construction). |
63 | | :- use_module(debug). |
64 | | :- use_module(self_check). |
65 | | :- use_module(specfile,[get_specification_description/2]). |
66 | | :- use_module(extension('probhash/probhash'),[raw_sha_hash/2]). |
67 | | :- use_module(input_syntax_tree). |
68 | | :- use_module(dotsrc(dot_graph_generator), [gen_dot_graph/6, dot_no_same_rank/1, |
69 | | use_new_dot_attr_pred/7, get_dot_cluster_name/2]). |
70 | | |
71 | | :- use_module(value_persistance,[cache_is_activated/0]). |
72 | | :- use_module(probsrc(tools),[split_list/4]). |
73 | | |
74 | | :- use_module(module_information,[module_info/2]). |
75 | | :- module_info(group,ast). |
76 | | :- module_info(description,'This module provides functionality to visualize the dependencies of a B machine (include and sees relations, etc.).'). |
77 | | |
78 | | :- volatile |
79 | | main_machine_name/1, |
80 | | machine_type/2, |
81 | | machine_package_directory/2, |
82 | | machine_references/2, |
83 | | machine_identifiers/7, |
84 | | machine_operations/2, |
85 | | machine_operation_calls/2, |
86 | | machine_values_identifiers/2, |
87 | | refines_event/4, |
88 | | machine_has_assertions/1, |
89 | | raw_machine/2, |
90 | | machine_hash_cached/2, |
91 | | properties_hash_cached/2, operation_hash_cached/3, |
92 | | basic_operation_hash/4, |
93 | | event_refinement_change/6. |
94 | | :- dynamic |
95 | | main_machine_name/1, |
96 | | machine_type/2, |
97 | | machine_package_directory/2, |
98 | | machine_references/2, |
99 | | machine_identifiers/7, |
100 | | machine_operations/2, |
101 | | machine_operation_calls/2, |
102 | | machine_values_identifiers/2, |
103 | | refines_event/4, |
104 | | machine_has_assertions/1, |
105 | | raw_machine/2, |
106 | | machine_hash_cached/2, |
107 | | properties_hash_cached/2, operation_hash_cached/3, |
108 | | basic_operation_hash/4, |
109 | | event_refinement_change/6. |
110 | | :- volatile abstract_constant/2, concrete_constant/2. |
111 | | :- dynamic abstract_constant/2. |
112 | | :- dynamic concrete_constant/2. |
113 | | |
114 | | :- use_module(specfile,[animation_minor_mode/1]). |
115 | | possibly_abstract_constant(ID) :- |
116 | | (abstract_constant(ID,_) ; animation_minor_mode(eventb),concrete_constant(ID,_) ). |
117 | | |
118 | | reset_hierarchy :- |
119 | | retract_all(main_machine_name/1), |
120 | | retract_all(machine_type/2), |
121 | | retract_all(machine_package_directory/2), |
122 | | retract_all(machine_references/2), |
123 | | retract_all(machine_identifiers/7), |
124 | | retract_all(abstract_constant/2), |
125 | | retract_all(concrete_constant/2), |
126 | | retract_all(machine_operations/2), |
127 | | retract_all(machine_operation_calls/2), |
128 | | retract_all(machine_values_identifiers/2), |
129 | | retract_all(refines_event/4), |
130 | | retract_all(machine_has_assertions/1), |
131 | | retract_all(machine_hash_cached/2), |
132 | | retract_all(raw_machine/2), |
133 | | retract_all(properties_hash_cached/2), |
134 | | retract_all(operation_hash_cached/3), |
135 | | retract_all(basic_operation_hash/4), |
136 | | retract_all(event_refinement_change/6). |
137 | | |
138 | | machine_name(Name) :- machine_type(Name,_). |
139 | | |
140 | | :- use_module(eventhandling,[register_event_listener/3]). |
141 | | :- register_event_listener(clear_specification,reset_hierarchy, |
142 | | 'Reset B Machine Hierarchy Facts.'). |
143 | | |
144 | | retract_all(Functor/Arity) :- |
145 | | functor(Pattern,Functor,Arity), |
146 | | retractall(Pattern). |
147 | | |
148 | | analyse_hierarchy(Main,Machines) :- (var(Main) ; var(Machines)),!, |
149 | | add_internal_error('Illegal call:',analyse_hierarchy(Main,Machines)). |
150 | | analyse_hierarchy(Main,Machines) :- |
151 | | reset_hierarchy, |
152 | | assertz(main_machine_name(Main)), |
153 | | analyse_machine(Main,Machines,main). |
154 | | |
155 | | :- use_module(error_manager). |
156 | | :- use_module(tools_strings,[ajoin/2]). |
157 | | :- use_module(bmachine,[b_filenumber/4]). |
158 | | :- public analyse_machine/3. |
159 | | analyse_machine(Name,_Machines,_) :- |
160 | | % machine already analysed |
161 | | machine_type(Name,_),!. |
162 | | analyse_machine(Name,Machines,_) :- |
163 | | debug:debug_println(19,analysing_machine(Name)), |
164 | | get_machine(Name,Machines,Type,Header,Refines,Body), |
165 | | !, |
166 | | ( cache_is_activated -> % we need the machines stored for later analysis |
167 | | assert_all_machines(Machines) |
168 | | ; true), |
169 | | assertz(machine_type(Name,Type)), |
170 | ? | (get_machine_parent_directory(Name,Dir) -> assertz(machine_package_directory(Name,Dir)) ; true), |
171 | | ( get_raw_section(assertions,Body,_) -> |
172 | | assertz(machine_has_assertions(Name)) |
173 | | ; true), |
174 | | store_identifiers(Name,Header,Body), |
175 | | store_operations(Name,Body), |
176 | | store_values(Name,Body), |
177 | | store_references(Name,Refines,Body,Machines). |
178 | | analyse_machine(Name,Machines,RefType) :- |
179 | | get_machine_file_number(Name,_Ext,Nr,File), |
180 | | get_ref_type_name(RefType,Clause), |
181 | | !, |
182 | | ( member(M,Machines),get_constructed_machine_name_and_filenumber(M,OtherName,Nr) |
183 | | -> ajoin(['Cannot use B machine "',Name,'" within ', Clause, |
184 | | ' clause. Rename machine "', OtherName,'" to "', Name, '" in file: '],Msg) |
185 | | ; findall(MN,b_filenumber(MN,_,_,_),List), |
186 | | ajoin(['Cannot find B machine "',Name,'" within ', Clause, |
187 | | ' clause (available: ',List,'). Check that machine name matches filename in: '],Msg) |
188 | | ), |
189 | | add_error_fail(invalid_machine_reference,Msg,File). |
190 | | analyse_machine(Name,_Machines,_RefType) :- |
191 | | bmachine:portray_filenumbers, |
192 | | add_error_fail(invalid_machine_reference, |
193 | | 'Could not find machine in parsed machine list (check that your machine names match your filenames): ',Name). |
194 | | |
195 | | :- use_module(tools,[get_parent_directory_name/2]). |
196 | | get_machine_parent_directory(Name,DirName) :- |
197 | | % try and get parent directory name; useful to distinguish different packages when using package pragma |
198 | ? | get_machine_file_number(Name,_Ext,_Nr,File), |
199 | | get_parent_directory_name(File,DirName). |
200 | | |
201 | | |
202 | | % store the un-typed input syntax tree for later analysis |
203 | | assert_all_machines(Machines) :- |
204 | | (raw_machine(_,_) |
205 | | -> true % machines already asserted |
206 | | ; maplist(assert_raw_machine,Machines)). |
207 | | assert_raw_machine(Machine) :- |
208 | | get_raw_machine_name(Machine,Name), |
209 | | (raw_machine(Name,_) -> add_warning(b_machine_hierarchy,'Raw machine already exists: ',Name) ; true), |
210 | | assertz( raw_machine(Name,Machine) ). |
211 | | |
212 | | machine_has_constants(MachName) :- |
213 | | machine_identifiers(MachName,_,_,_,_,AConsts,CConsts), |
214 | | (AConsts=[] -> CConsts = [_|_] ; true). |
215 | | |
216 | | get_machine_short_desc(MachName,Text) :- |
217 | | machine_identifiers(MachName,Params,Sets,AVars,CVars,AConsts,CConsts), |
218 | | length(Params,PL), length(Sets,SL), length(AVars,AVL), length(CVars,CVL), VL is AVL+CVL, |
219 | | length(AConsts,ACL), length(CConsts,CCL), CL is ACL+CCL, |
220 | | (machine_operations(MachName,Ops), length(Ops,OL) -> true ; OL=0), |
221 | | ajoin(['SETS:',SL,' CONSTANTS:',CL,' VARIABLES:',VL, ' PARAMETERS:',PL, ' OPERATIONS:',OL],Text). |
222 | | |
223 | | store_identifiers(Name,Header,Body) :- |
224 | | get_parameters(Header,Params), |
225 | | get_sets(Body,Sets), |
226 | | get_identifiers([abstract_variables,variables],Body,AVars), |
227 | | get_identifiers([concrete_variables],Body,CVars), |
228 | | get_identifiers([abstract_constants],Body,AConsts), |
229 | | get_identifiers([concrete_constants,constants],Body,CConsts), % fixed abstract -> concrete |
230 | | assertz(machine_identifiers(Name,Params,Sets,AVars,CVars,AConsts,CConsts)), |
231 | | maplist(assert_raw_id_with_position(abstract_constant),AConsts), |
232 | | maplist(assert_raw_id_with_position(concrete_constant),CConsts). |
233 | | |
234 | | |
235 | | raw_id_is_identifier2(description(_Pos,_Desc,Raw),ID) :- !, raw_id_is_identifier2(Raw,ID). |
236 | | raw_id_is_identifier2(deferred_set(_,ID),ID) :- !. |
237 | | raw_id_is_identifier2(enumerated_set(_,ID,_Elements),ID) :- !. |
238 | | raw_id_is_identifier2(Raw,ID) :- raw_id_is_identifier(Raw,_,ID). |
239 | | |
240 | | get_machine_identifier_names(Name,Params,Sets,AVars,CVars,AConsts,CConsts) :- |
241 | | machine_identifiers(Name,RawParams,RawSets,RawAVars,RawCVars,RawAConsts,RawCConsts), |
242 | | maplist(raw_id_is_identifier2,RawParams,Params), |
243 | | maplist(raw_id_is_identifier2,RawSets,Sets), |
244 | | maplist(raw_id_is_identifier2,RawAVars,AVars), |
245 | | maplist(raw_id_is_identifier2,RawCVars,CVars), |
246 | | maplist(raw_id_is_identifier2,RawAConsts,AConsts), |
247 | | maplist(raw_id_is_identifier2,RawCConsts,CConsts). |
248 | | |
249 | | |
250 | | machine_hash(Name,Hash) :- |
251 | | machine_hash_cached(Name,Hash1),!,Hash=Hash1. |
252 | | machine_hash(Name,Hash) :- |
253 | | compute_machine_hash(Name,Hash1), |
254 | | assertz( machine_hash_cached(Name,Hash1) ), |
255 | | Hash=Hash1. |
256 | | compute_machine_hash(Name,Digest) :- |
257 | | if(raw_machine(Name,Machine), |
258 | | raw_sha_hash(Machine,Digest), |
259 | | add_error_and_fail(compute_machine_hash,'Machine does not exist or has not been processed:',Name) |
260 | | ). |
261 | | |
262 | | :- use_module(pathes_extensions_db, [compile_time_unavailable_extension/2]). |
263 | | :- if(\+ compile_time_unavailable_extension(probhash_extension, _)). |
264 | | store_eventb_hash(Name,ContextMachTerm) :- |
265 | | raw_sha_hash(ContextMachTerm,Digest), |
266 | | assertz(machine_hash_cached(Name,Digest)). |
267 | | :- else. |
268 | | store_eventb_hash(Name,_) :- |
269 | | assertz( (machine_hash_cached(Name,_) :- |
270 | | add_error(b_machine_hierarchy,'prob_hash_extension not available for: ',Name),fail) ). |
271 | | :- endif. |
272 | | |
273 | | :- use_module(tools_strings,[get_hex_bytes/2]). |
274 | | operation_hash(MachName,OpName,Hash) :- |
275 | | operation_hash_cached(MachName,OpName,Hash1),!,Hash=Hash1. |
276 | | operation_hash(MachName,OpName,Hash) :- |
277 | | computed_basic_operation_hashes, |
278 | | basic_operation_hash(OpName,MachName,Hash1,OpCalls), |
279 | | (OpCalls = [] |
280 | | -> FinalHash=Hash1 % no recursive call of other operations |
281 | | ; maplist(get_basic_op_hash,OpCalls,OpDigests), |
282 | | raw_sha_hash(op(Hash1,OpDigests),FinalHash) % also include hash of called operations |
283 | | ), |
284 | | assertz( operation_hash_cached(MachName,OpName,FinalHash) ), |
285 | | get_hex_bytes(FinalHash,Hex), |
286 | | debug_format(19,'value caching: operation ~w in machine ~w has hash: ~s~n',[OpName,MachName,Hex]), |
287 | | Hash=FinalHash. |
288 | | |
289 | | get_basic_op_hash(OpName,Digest) :- |
290 | | basic_operation_hash(OpName,_MachName,Digest,_OpCalls). |
291 | | |
292 | | % compute basic hash, without taking recursive operation calls into account |
293 | | computed_basic_operation_hashes :- |
294 | | basic_operation_hash(_,_,_,_),!. % already computed |
295 | | computed_basic_operation_hashes :- |
296 | | get_raw_machine_operation_hash(MachName,OpName,Digest,OpCalls), |
297 | | (basic_operation_hash(OpName,_,_,_) |
298 | | -> add_warning(computed_basic_operation_hashes,'Multiple hashes for operation: ',OpName) |
299 | | ; true), |
300 | | assertz(basic_operation_hash(OpName,MachName,Digest,OpCalls)), |
301 | | get_hex_bytes(Digest,Hex), |
302 | | debug_format(19,'value caching: basic operation hash ~w in machine ~w: ~s (calls ~w)~n', |
303 | | [OpName,MachName,Hex,OpCalls]), |
304 | | fail. |
305 | | computed_basic_operation_hashes. |
306 | | |
307 | | % first computed basic, independent operation hashes: |
308 | | % only look up used definitions, but not yet called operations |
309 | | get_raw_machine_operation_hash(MachName,OpName,Digest,SOpCalls) :- |
310 | | raw_machine(MachName,Machine), |
311 | | get_machine(MachName,[Machine],_Type,_Header,_Refines,MachBody), |
312 | | get_opt_section(operations,MachBody,Operations), |
313 | | Op = operation(_,identifier(_,OpName),_,_,_), |
314 | | member(Op,Operations), |
315 | | get_raw_operation_id_and_body(Op,identifier(_,OpName),OpBody), |
316 | | extract_used_np_definitions(Op,MachBody,UsedDefinitions,DefsWithPos), |
317 | | remove_raw_position_info(Op,RawOperation), |
318 | | raw_sha_hash(op(RawOperation,UsedDefinitions),Digest), |
319 | | findall(Id, (get_raw_operation_call_id(OpBody,Id) % extract op calls in body of operation |
320 | | ; get_def_body(Def,DefsWithPos), get_raw_operation_call_id(Def,Id) % extract op calls in definitions |
321 | | ), |
322 | | OpCalls), |
323 | | sort(OpCalls,SOpCalls). |
324 | | |
325 | | get_def_body(Body,Defs) :- member(definition(_,_DefName,_,Body),Defs). |
326 | | |
327 | | |
328 | | :- use_module(debug,[debug_println/2]). |
329 | | assert_raw_id_with_position(PredFunctor,Rid) :- |
330 | | (raw_id_is_identifier(Rid,Pos,ID) |
331 | | -> true |
332 | | ; peel_desc(Rid,PRid), PRid = definition(Pos,ID,_) |
333 | | -> add_error(illegal_definition_use,'Definition cannot be used as identifier here: ',ID,Pos) |
334 | | ; add_error_fail(assert_raw_id_with_position,'Not identifier: ',Rid) |
335 | | ), |
336 | | Fact =.. [PredFunctor,ID,Pos], |
337 | | assertz(Fact), debug_println(9,Fact). |
338 | | |
339 | | raw_id_is_identifier(identifier(Pos,ID),Pos,ID). |
340 | | raw_id_is_identifier(unit(_,_,identifier(Pos,ID)),Pos,ID). |
341 | | raw_id_is_identifier(new_unit(_,_,identifier(Pos,ID)),Pos,ID). |
342 | | raw_id_is_identifier(inferred_unit(_,_,identifier(Pos,ID)),Pos,ID). |
343 | | raw_id_is_identifier(inferredunit(_,_,identifier(Pos,ID)),Pos,ID). % the (new?) parser seems to generate the wrong pragma in the .prob file; TO DO: investigate |
344 | | raw_id_is_identifier(description(_,_,RawID),Pos,ID) :- |
345 | | raw_id_is_identifier(RawID,Pos,ID). |
346 | | |
347 | | peel_desc(description(_,_,E),R) :- !, peel_desc(E,R). |
348 | | peel_desc(R,R). |
349 | | |
350 | | get_raw_identifier(Raw,Res) :- raw_id_is_identifier(Raw,_Pos,Id),!, Res=Id. |
351 | | get_raw_identifier(definition(_Pos,DID,[]),ID) :- % see also expand_definition_to_variable_list |
352 | | atom(DID),!, |
353 | | ajoin([DID,'(DEFINITION)'],ID). |
354 | | get_raw_identifier(deferred_set(_Pos,DID),ID) :- atom(DID),!, ID=DID. |
355 | | get_raw_identifier(enumerated_set(_Pos,DID,_List),ID) :- atom(DID),!, ID=DID. |
356 | | get_raw_identifier(description(_,_,RawID),ID) :- !, |
357 | | get_raw_identifier(RawID,ID). |
358 | | get_raw_identifier(Raw,Res) :- add_internal_error('Cannot get identifier:',Raw),Res='???'. |
359 | | |
360 | | raw_identifier_member(ID,List) :- member(Raw,List), raw_id_is_identifier(Raw,_Pos,ID). |
361 | | |
362 | | store_operations(MachName,Body) :- |
363 | | get_opt_section(operations,Body,Operations), |
364 | | findall(I,(member(Op,Operations),get_raw_operation_id_and_body(Op,I,_)),Ids), |
365 | | assertz(machine_operations(MachName,Ids)), |
366 | | findall(calls(I1,I2),(member(Op,Operations),get_raw_operation_call(Op,I1,I2)),Calls), |
367 | | sort(Calls,SCalls), |
368 | | debug_println(9,machine_operation_calls(MachName,SCalls)), |
369 | | assertz(machine_operation_calls(MachName,SCalls)). |
370 | | |
371 | | get_raw_operation_id_and_body(operation(_Pos,Id,_,_,Body),Id,Body). |
372 | | get_raw_operation_id_and_body(refined_operation(_Pos,Id,_Results,_Args,_RefinesID,Body),Id,Body). |
373 | | get_raw_operation_id_and_body(description_operation(_Pos,_,Op),Id,Body) :- get_raw_operation_id_and_body(Op,Id,Body). |
374 | | |
375 | | % get operations called in body |
376 | | get_raw_operation_call(Op,Id,CallsId) :- |
377 | | get_raw_operation_id_and_body(Op,identifier(_,Id),Body), |
378 | | get_raw_operation_call(Body,identifier(_,CallsId)). |
379 | | |
380 | | :- use_module(debug,[debug_format/3]). |
381 | | get_raw_operation_call(block(_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
382 | | get_raw_operation_call(precondition(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
383 | | get_raw_operation_call(assertion(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
384 | | get_raw_operation_call(witness_then(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
385 | | get_raw_operation_call(var(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
386 | | get_raw_operation_call(select_when(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
387 | | get_raw_operation_call(if_elsif(_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
388 | | get_raw_operation_call(let(_,_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
389 | | get_raw_operation_call(any(_,_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
390 | | get_raw_operation_call(case(_,_,_,_,_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
391 | | get_raw_operation_call(while(_,_Cond,Body,_,_),ID) :- !, get_raw_operation_call(Body,ID). |
392 | ? | get_raw_operation_call(parallel(_,List),ID) :- !,member(A,List), get_raw_operation_call(A,ID). |
393 | ? | get_raw_operation_call(sequence(_,List),ID) :- !,member(A,List), get_raw_operation_call(A,ID). |
394 | ? | get_raw_operation_call(if(_,_Test,Then,List,Else),ID) :- !,member(A,[Then,Else|List]), get_raw_operation_call(A,ID). |
395 | | get_raw_operation_call(select(_,_Cond,Body,List),ID) :- !,member(A,[Body|List]), get_raw_operation_call(A,ID). |
396 | ? | get_raw_operation_call(select(_,_Cond,Body,List,Else),ID) :- !,member(A,[Body,Else|List]), get_raw_operation_call(A,ID). |
397 | ? | get_raw_operation_call(choice(_,List),ID) :- !,member(A,List), get_raw_operation_call(A,ID). |
398 | | get_raw_operation_call(choice_or(_,Body),ID) :- !, get_raw_operation_call(Body,ID). |
399 | | get_raw_operation_call(operation_call(_,ID,_,_),ID) :- !. |
400 | | get_raw_operation_call(skip(_),_) :- !, fail. |
401 | | get_raw_operation_call(assign(_,_,_),_) :- !, fail. |
402 | | get_raw_operation_call(becomes_element_of(_,_,_),_) :- !, fail. |
403 | | get_raw_operation_call(becomes_such(_,_,_),_) :- !, fail. |
404 | | get_raw_operation_call(definition(_,Name,_),_) :- !, % TO DO: improve |
405 | | debug_format(19,'Ignoring operation calls in DEFINITION ~w for operation call diagram~n',[Name]), |
406 | | % b_get_definition(Name,_DefType,_Args,DefBody,_Deps), not yet precompiled ! |
407 | | fail. |
408 | | get_raw_operation_call(Subst,_) :- functor(Subst,F,N),print(uncovered_subst(F,N,Subst)),nl,fail. |
409 | | % we also do not find operation calls in expressions |
410 | | |
411 | | :- use_module(input_syntax_tree,[raw_operator_term/1, raw_literal_term/1, raw_special_set_term/1]). |
412 | | % avoid spurious uncovered_subst messages in a context where we are not sure we have a subst |
413 | | try_get_raw_operation_call(Term,_) :- raw_literal_term(Term),!, fail. |
414 | | try_get_raw_operation_call(Term,_) :- raw_operator_term(Term),!, fail. |
415 | | try_get_raw_operation_call(Term,_) :- raw_special_set_term(Term),!, fail. |
416 | | try_get_raw_operation_call(F,_) :- functor(F,external_function_call,_),!, fail. % definitely expression |
417 | | try_get_raw_operation_call(F,_) :- functor(F,external_pred_call,_),!, fail. % definitely predicate |
418 | | try_get_raw_operation_call(Body,ID) :- get_raw_operation_call(Body,ID). |
419 | | |
420 | | get_raw_operation_call_id(OpBody,CalledId) :- |
421 | | try_get_raw_operation_call(OpBody,identifier(_,CalledId)). |
422 | | |
423 | | store_references(Name,Refines,Body,Machines) :- |
424 | | get_refinements(Refines,Refs1), |
425 | | get_references(Body,Refs2), |
426 | | append(Refs1,Refs2,Refs), |
427 | | assertz(machine_references(Name,Refs)), |
428 | | follow_refs(Refs,Machines). |
429 | | |
430 | | get_references(Body,Refs) :- |
431 | | findrefs(Body,includes,Includes), |
432 | | findrefs(Body,extends,Extends), |
433 | | findrefs(Body,imports,Imports), |
434 | | findusessees(Body,uses,Uses), |
435 | | findusessees(Body,sees,Sees), |
436 | | append([Includes,Imports,Extends,Uses,Sees],Refs). |
437 | | |
438 | | get_refinements([],[]). |
439 | | get_refinements([Name|NRest],[ref(refines,Name,'')|RRest]) :- |
440 | | get_refinements(NRest,RRest). |
441 | | |
442 | | findrefs(Body,Type,Refs) :- |
443 | | get_opt_section(Type,Body,RawRefs), |
444 | | findrefs2(RawRefs,Type,Refs). |
445 | | findrefs2([],_Type,[]). |
446 | | findrefs2([MID|MRest],Type,[ref(Type,Name,Prefix)|RRest]) :- |
447 | | get_machine_id(MID,FullName), % should be machine_reference |
448 | | bmachine_construction:split_prefix(FullName,Prefix,Name), |
449 | | findrefs2(MRest,Type,RRest). |
450 | | |
451 | | findusessees(Body,Type,Refs) :- |
452 | | get_opt_section(Type,Body,RawRefs), |
453 | | findusessees2(RawRefs,Type,Refs). |
454 | | findusessees2([],_Type,[]). |
455 | | findusessees2([MID|MRest],Type,[ref(Type,Name,Prefix)|RRest]) :- |
456 | | get_machine_id(MID,FullName), % should be machine_reference |
457 | | % USE and SEES do not have machine_reference in AST but identifier |
458 | | bmachine_construction:split_prefix(FullName,Prefix,Name), |
459 | | findusessees2(MRest,Type,RRest). |
460 | | |
461 | | get_machine_id(identifier(_Pos,FN),FullName) :- !, FullName=FN. |
462 | | get_machine_id(machine_reference(_Pos,FN,_Params),FullName) :- !, FullName=FN. |
463 | | get_machine_id(Other,_) :- add_internal_error('Illegal machine id: ',get_machine_id(Other,_)),fail. |
464 | | |
465 | | :- use_module(probsrc(tools),[split_atom/3]). |
466 | | follow_refs([],_Machines). |
467 | | follow_refs([ref(RefType,Name,_Prefix)|Rest],Machines) :- |
468 | | analyse_machine(Name,Machines,RefType), |
469 | | follow_refs(Rest,Machines). |
470 | | |
471 | | get_parameters(machine_header(_Pos,_Name,Params),Params). |
472 | | |
473 | | get_sets(Body,Sets) :- |
474 | | get_opt_section(sets,Body,Sets). |
475 | | |
476 | | get_identifiers([],_Body,[]). |
477 | | get_identifiers([Sec|Rest],Body,Ids) :- |
478 | | get_opt_section(Sec,Body,Ids1), |
479 | | append(Ids1,IRest,Ids), |
480 | | get_identifiers(Rest,Body,IRest). |
481 | | |
482 | | get_opt_sections([],_Body,[]). |
483 | | get_opt_sections([S|Srest],Body,Contents) :- |
484 | | get_opt_section(S,Body,L),append(L,Rest,Contents), |
485 | | get_opt_sections(Srest,Body,Rest). |
486 | | |
487 | | get_opt_section(Sec,Body,Result) :- |
488 | | ( get_raw_section(Sec,Body,Content) -> Result=Content; Result=[]). |
489 | | get_raw_section(Sec,Body,Content) :- % look for Sec(_Pos,Content) in Body list |
490 | | functor(Pattern,Sec,2),arg(2,Pattern,Content), |
491 | | memberchk(Pattern,Body). |
492 | | |
493 | | get_machine(Name,Machines,Type,Header,Refines,Body) :- |
494 | | get_machine1(Name,Machines,_Machine,Type,Header,Refines,Body). |
495 | | %get_raw_machine(Name,Machines,Machine) :- |
496 | | % get_machine1(Name,Machines,Machine,_Type,_Header,_Refines,_Body). |
497 | | get_machine1(Name,Machines,Machine,Type,Header,Refines,Body) :- |
498 | | Header = machine_header(_Pos,Name,_Params), |
499 | ? | member(Machine,Machines), |
500 | | get_machine2(Machine,Type,Header,Refines,Body),!. |
501 | | get_machine2(abstract_machine(_Pos,MS,Header,Body),TypeOfAbstractMachine,Header,[],Body) :- |
502 | | get_abstract_machine_type(MS,TypeOfAbstractMachine). |
503 | | get_machine2(refinement_machine(_Pos,Header,Refines,Body),refinement,Header,[Refines],Body). |
504 | | get_machine2(implementation_machine(_Pos,Header,Refines,Body),implementation,Header,[Refines],Body). |
505 | | get_machine2(generated(_Pos,Machine),A,B,C,D) :- % @generated Pragma used at top of file |
506 | | get_machine2(Machine,A,B,C,D). |
507 | | get_machine2(unit_alias(_Pos,_Name,_Alias,Machine),A,B,C,D) :- |
508 | | get_machine2(Machine,A,B,C,D). |
509 | | |
510 | | get_raw_machine_name(Machine,Name) :- |
511 | | get_machine2(Machine,_,machine_header(_,Name,_),_,_). |
512 | | |
513 | | get_abstract_machine_type(machine(_Pos2),R) :- !,R=abstract_machine. |
514 | | get_abstract_machine_type(system(_Pos2),R) :- !,R=abstract_machine. |
515 | | get_abstract_machine_type(model(_Pos2),R) :- !,R=abstract_model. |
516 | | get_abstract_machine_type(X,R) :- atomic(X),!, |
517 | | add_error(get_abstract_machine_type,'Your parser seems out-dated. Assuming abstract_machine: ',X), |
518 | | R=abstract_machine. |
519 | | |
520 | | |
521 | | get_values_id(values_entry(Pos,ID,_Val),identifier(Pos,ID)). |
522 | | % Store the identifiers assigned to in VALUES clauses |
523 | | store_values(Name,Body) :- |
524 | | get_opt_section('values',Body,Values),!, |
525 | | maplist(get_values_id,Values,ValuesIDs), |
526 | | assertz(machine_values_identifiers(Name,ValuesIDs)). |
527 | | store_values(_,_). |
528 | | |
529 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
530 | | |
531 | | % write dot operation call graph |
532 | | write_dot_op_hierarchy_to_file(File) :- |
533 | | (get_preference(dot_event_hierarchy_horizontal,true) -> PageOpts=[rankdir/'LR'] ; PageOpts=[]), |
534 | | gen_dot_graph(File,PageOpts,dot_operation_node,dot_op_calls_op,dot_no_same_rank,dot_subgraph(op_hierarchy)). |
535 | | |
536 | | |
537 | | :- use_module(bmachine,[b_top_level_operation/1, b_top_level_feasible_operation/1]). |
538 | | dot_operation_node(OpName,M,Desc,Shape,Style,Color) :- |
539 | | machine_operations(M,Operations), |
540 | | (machine_promotes_operations(M,Promotes) -> true ; Promotes=[]), |
541 | | raw_identifier_member(OpName,Operations), |
542 | | (b_top_level_feasible_operation(OpName) -> Color=lightgray |
543 | | ; b_top_level_operation(OpName) -> Color='OldLace' % commented out operation |
544 | | ; raw_identifier_member(OpName,Promotes) -> Color='gray98' % promoted but not to top_level |
545 | | ; Color=white), |
546 | | Desc=OpName, Shape=box, Style=filled. |
547 | | |
548 | | dot_op_calls_op(Op1,Label,Op2,Color,Style) :- Style=solid, Color=steelblue, |
549 | | Label = '', % TO DO: different colors for local operation calls, detect op calls in expressions? |
550 | | machine_operation_calls(_,Operations), |
551 | | member(calls(Op1,Op2),Operations). |
552 | | |
553 | | |
554 | | |
555 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
556 | | % print hierarchy to dot file |
557 | | |
558 | | maxlinelength(30). |
559 | | |
560 | | :- use_module(tools_io). |
561 | | |
562 | | % write hierarchy of machines, with inclusion/refinement links |
563 | | write_dot_hierarchy_to_file(Filename) :- |
564 | | findall(M,machine_type(M,_),Machines), |
565 | | safe_open_file(Filename,write,S,[]), |
566 | | ( print_header(S), |
567 | | print_machines(S,1,Machines,Ids), |
568 | | print_refs_for_dot(S,Ids,Ids), |
569 | | print_footer(S) |
570 | | -> true |
571 | | ; add_internal_error('Command failed:',write_dot_hierarchy_to_file(Filename)) |
572 | | ), |
573 | | close(S). |
574 | | |
575 | | print_header(S) :- |
576 | | write(S,'digraph module_hierarchy {\n'), |
577 | | write(S,' graph [page="8.5, 11",ratio=fill,size="7.5,10"];\n'). |
578 | | print_footer(S) :- |
579 | | write(S,'}\n'). |
580 | | |
581 | | print_machines(_S,_Nr,[],[]). |
582 | | print_machines(S,Nr,[M|Machines],[id(M,Nr)|Ids]) :- |
583 | | (print_machine(S,Nr,M) -> true |
584 | | ; add_internal_error('Printing machine for hierarchy failed: ',print_machine(S,Nr,M)) |
585 | | ), |
586 | | Nr2 is Nr + 1, |
587 | | print_machines(S,Nr2,Machines,Ids). |
588 | | print_machine(S,Nr,M) :- |
589 | | write(S,' '),write(S,Nr), |
590 | | (main_machine_name(M) -> |
591 | | write(S,' [shape=record, style=bold, color=darkgreen, label=\"|{') |
592 | | ; write(S,' [shape=record, color=steelblue, label=\"|{')), |
593 | | print_machine_header(S,M), |
594 | | machine_identifiers(M,_Params,Sets,AVars,CVars,AConsts,CConsts), |
595 | | %print_hash(S,M), |
596 | | print_sets(S,Sets), |
597 | | print_identifiers(S,'VARIABLES',AVars), |
598 | | print_identifiers(S,'CONCRETE VARIABLES',CVars), |
599 | | print_identifiers(S,'ABSTRACT CONSTANTS',AConsts), |
600 | | print_identifiers(S,'CONSTANTS',CConsts), |
601 | | (machine_values_identifiers(M,VConsts) |
602 | | -> print_identifiers(S,'VALUES',VConsts) |
603 | | ; true), |
604 | | ( machine_has_assertions(M) -> |
605 | | get_specification_description(assertions,AssStr), |
606 | | print_title(S,AssStr) |
607 | | ; true |
608 | | ), |
609 | | (machine_promotes_operations(M,Promotes) |
610 | | -> print_identifiers(S,'PROMOTES',Promotes) ; true), |
611 | | machine_operations(M,Operations), |
612 | | delete(Operations,identifier(_,'INITIALISATION'),Operations2), |
613 | | exclude(is_refinining_event(M),Operations2,RefiningOperations), |
614 | | include(is_refinining_event(M),Operations2,NewOperations), |
615 | | ((RefiningOperations=[] ; NewOperations=[]) |
616 | | -> get_specification_description(operations,OpStr), |
617 | | print_identifiers(S,OpStr,Operations2) |
618 | | ; print_identifiers(S,'EVENTS (refining)',RefiningOperations), |
619 | | print_identifiers(S,'EVENTS (new)',NewOperations) |
620 | | ), |
621 | | |
622 | | write(S,'}|\"];\n'). |
623 | | |
624 | | is_refinining_event(M,identifier(_,Event)) :- (refines_event(M,Event,_,_) -> true). |
625 | | |
626 | | %print_hash(S,M) :- machine_hash(M,Digest),!,print_title(S,'Digest'), |
627 | | % maplist(format(S,'~16r'),Digest),write(S,'\\n'). |
628 | | %print_hash(_S,_M). |
629 | | |
630 | | %get_machine_colour(M,steelblue) :- main_machine_name(M),!. |
631 | | %get_machine_colour(_M,steelblue). |
632 | | |
633 | | print_machine_header(S,M) :- |
634 | | machine_type(M,Type), |
635 | | get_machine_type_keyw(Type,P), |
636 | | write(S,P),write(S,' '), |
637 | | write(S,M), |
638 | | (machine_package_directory(M,Dir), machine_package_directory(_M2,D2), D2 \= Dir |
639 | | -> format(S,' (~w)',[Dir]) % show the name of the directory; probably the package pragma was used |
640 | | ; true), |
641 | | write(S,'\\n'). |
642 | | get_machine_type_keyw(abstract_machine,'MACHINE'). |
643 | | get_machine_type_keyw(abstract_model,'MODEL'). |
644 | | get_machine_type_keyw(refinement,'REFINEMENT'). |
645 | | get_machine_type_keyw(implementation,'IMPLEMENTATION'). |
646 | | get_machine_type_keyw(context,'CONTEXT'). |
647 | | |
648 | | print_sets(_,[]) :- !. |
649 | | print_sets(S,Sets) :- |
650 | | write(S,'|SETS\\n'), |
651 | | print_sets2(Sets,S). |
652 | | print_sets2([],_). |
653 | | print_sets2([Set|Rest],S) :- |
654 | | print_set(Set,S), |
655 | | write(S,'\\n'), |
656 | | print_sets2(Rest,S). |
657 | | print_set(description(_Pos,_Desc,Raw),S) :- print_set(Raw,S). |
658 | | print_set(deferred_set(_Pos,Name),S) :- |
659 | | write_id(S,Name). |
660 | | print_set(enumerated_set(_Pos,Name,List),S) :- |
661 | | write_id(S,Name),write(S,' = \\{'), |
662 | | maxlinelength(Max), |
663 | | preferences:get_preference(dot_hierarchy_max_ids,MaxIdsToPrint), |
664 | | print_set_elements(List,S,Max,MaxIdsToPrint), |
665 | | write(S,'\\}'). |
666 | | print_set_elements([],_,_,_). |
667 | | print_set_elements([RawID],S,_LenSoFar,_) :- |
668 | | raw_id_is_identifier(RawID,_,Name), |
669 | | !,write_id(S,Name). |
670 | | print_set_elements([RawID,B|Rest],S,LenSoFar,MaxIdsToPrint) :- |
671 | | raw_id_is_identifier(RawID,_,Name), |
672 | | dec_atom_length(LenSoFar,Name,NewLen), |
673 | | (NewLen<0 -> maxlinelength(NL0),NL is NL0-1,write(S,'\\n ') ; NL is NewLen-1), |
674 | | write_id(S,Name),write(S,','), |
675 | | M1 is MaxIdsToPrint-1, |
676 | | (M1 < 1, Rest \= [] |
677 | | -> write(S,'...,'), |
678 | | last([B|Rest],Last), print_set_elements([Last],S,NL,M1) |
679 | | ; print_set_elements([B|Rest],S,NL,M1) |
680 | | ). |
681 | | |
682 | | :- use_module(tools,[string_escape/2]). |
683 | | % write identifier and escape it for dot |
684 | | write_id(S,Name) :- string_escape(Name,EName), write(S,EName). |
685 | | |
686 | | dec_atom_length(Prev,Atom,New) :- atom_length(Atom,Len), |
687 | | New is Prev-Len. |
688 | | |
689 | | print_title(S,Title) :- |
690 | | write(S,'|'), |
691 | | write(S,Title),write(S,'\\n'). |
692 | | print_identifiers(_S,_,[]) :- !. |
693 | | print_identifiers(S,Title,List) :- |
694 | | print_title(S,Title), |
695 | | preferences:get_preference(dot_hierarchy_max_ids,MaxIdsToPrint), % how many do we print overall; -1 means print all of them |
696 | | print_identifiers2(List,0,_,S,MaxIdsToPrint). |
697 | | print_identifiers2([],Count,Count,_,_MaxIdsToPrint). |
698 | | print_identifiers2([RawID|Rest],Count,NCount,S,MaxIdsToPrint) :- |
699 | | get_raw_identifier(RawID,Name), |
700 | | (MaxIdsToPrint = 0,Rest\=[] |
701 | | -> length(Rest,Len), RN is Len+1, |
702 | | format(S,' (~w more)',[RN]) |
703 | | ; MaxIdsToPrint1 is MaxIdsToPrint-1, |
704 | | print_identifier(Name,Count,ICount,S), |
705 | | (Rest = [] -> true; write(S,',')), |
706 | | print_identifiers2(Rest,ICount,NCount,S,MaxIdsToPrint1) |
707 | | ). |
708 | | print_identifier(Name,Count,NewCount,S) :- |
709 | | atom_codes(Name,Codes), |
710 | | length(Codes,Length), |
711 | | NewCount1 is Count+Length, |
712 | | maxlinelength(Max), |
713 | | ( Count == 0 -> NewCount1=NewCount |
714 | | ; NewCount1 =< Max -> NewCount1=NewCount |
715 | | ; NewCount=0,write(S,'\\n')), |
716 | | write_id(S,Name). |
717 | | |
718 | | print_refs_for_dot(_S,[],_). |
719 | | print_refs_for_dot(S,[id(M,Nr)|Rest],Ids) :- |
720 | | machine_references(M,Refs), |
721 | | filter_redundant_refs(Refs,Refs,UsefulRefs), |
722 | | print_refs2(UsefulRefs,M,Nr,Ids,S), |
723 | | print_refs_for_dot(S,Rest,Ids). |
724 | | print_refs2([],_,_,_,_). |
725 | | print_refs2([ref(Type,Dest,_Prefix)|Rest],M,Nr,Ids,S) :- |
726 | | member(id(Dest,DestNr),Ids),!, |
727 | | get_ref_type(Type,Ref,Dir), !, |
728 | | (Dir=reverse -> |
729 | | format(S,' ~w -> ~w ~w;~n',[DestNr,Nr,Ref]) |
730 | | ; format(S,' ~w -> ~w ~w;~n',[Nr,DestNr,Ref]) |
731 | | ), |
732 | | print_refs2(Rest,M,Nr,Ids,S). |
733 | | get_ref_type(includes,'[label=\"INCLUDES\",color=navyblue]',normal). |
734 | | get_ref_type(imports,'[label=\"IMPORTS\",color=navyblue]',normal). |
735 | | get_ref_type(extends,'[label=\"EXTENDS\",color=navyblue]',normal). |
736 | | get_ref_type(uses,'[label=\"USES\",color=navyblue,style=dashed]',normal). |
737 | | get_ref_type(sees,'[label=\"SEES\",color=navyblue,style=dashed]',normal). |
738 | | %get_ref_type(sees,'[label=\"SEES\",color=navyblue,style=dashed,dir=back]',reverse). |
739 | | %get_ref_type(refines,'[label=\"REFINES\",color=navyblue,style=bold,dir=back]',reverse). |
740 | | get_ref_type(refines,'[label=\"REFINEMENT\",color=navyblue,style=bold]',reverse). % reverse so that abstract machines are shown on top |
741 | | get_ref_type(UNKNOWN,'[label=\"UNKNOWN\",color=navyblue]',normal) :- add_internal_error('Unknown : ',get_ref_type(UNKNOWN,_,_)). |
742 | | |
743 | | get_ref_type_name(includes,'INCLUDES'). |
744 | | get_ref_type_name(imports,'IMPORTS'). |
745 | | get_ref_type_name(extends,'EXTENDS'). |
746 | | get_ref_type_name(uses,'USES'). |
747 | | get_ref_type_name(sees,'SEES'). |
748 | | get_ref_type_name(refines,'REFINEMENT'). |
749 | | get_ref_type_name(main,'MACHINE'). |
750 | | get_ref_type_name(UNKNOWN,UNKNOWN). |
751 | | |
752 | | |
753 | | % remove redundant references (has to be done after analyse_eventb_machine has asserted all facts) |
754 | | filter_redundant_refs([],_,[]). |
755 | | filter_redundant_refs([ref(sees,Dest,_)|T],All,R) :- |
756 | | machine_type(Dest,context), |
757 | | member(ref(sees,Other,_),All), |
758 | | machine_references(Other,Refs), |
759 | | member(ref(extends,Dest,_),Refs), % Dest already seen by other seen context; |
760 | | % Note: Rodin export contains transitive sees relation; |
761 | | % if the user had included Dest in the sees clause we would have a warning "Redundant seen context" |
762 | | !, |
763 | | %format(user_output,'Redundant sees of ~w (~w)~n',[Dest,Other]), |
764 | | filter_redundant_refs(T,All,R). |
765 | | filter_redundant_refs([H|T],All,[H|R]) :- |
766 | | filter_redundant_refs(T,All,R). |
767 | | |
768 | | /************************************************************************/ |
769 | | /* The same for Event-B */ |
770 | | /************************************************************************/ |
771 | | |
772 | | analyse_eventb_hierarchy(Machines,Contextes) :- |
773 | | reset_hierarchy, |
774 | | get_eventb_name(Machines,Contextes,MainName), |
775 | | assertz(main_machine_name(MainName)), |
776 | ? | maplist(analyse_eventb_machine,Machines), |
777 | | analyse_eventb_refinement_types(Machines), |
778 | ? | maplist(analyse_eventb_context,Contextes),!. |
779 | | analyse_eventb_hierarchy(Machines,Contextes) :- |
780 | | add_internal_error('Analyzing Event-B Hierarchy Failed: ',analyse_eventb_hierarchy(Machines,Contextes)). |
781 | | |
782 | | get_eventb_name([MainMachine|_AbstractMachines],_Contextes,Name) :- |
783 | | event_b_model(MainMachine,Name,_),!. |
784 | | get_eventb_name(_Machines,[MainContext|_AbstractContextes],Name) :- |
785 | | event_b_context(MainContext,Name,_). |
786 | | |
787 | | event_b_model(event_b_model(_,Name,Sections),Name,Sections). |
788 | | event_b_context(event_b_context(_,Name,Sections),Name,Sections). |
789 | | |
790 | | analyse_eventb_machine(Machine) :- |
791 | | event_b_model(Machine,Name,Sections), |
792 | | % print(analyzing(Name)),nl, |
793 | | ( memberchk(refines(_,RName),Sections) -> Type=refinement, RRefs=[ref(refines,RName,'')] |
794 | | ; Type=abstract_model, RRefs=[], RName='$none'), |
795 | | get_identifiers([variables],Sections,Vars), |
796 | | get_sees_context_refs(Sections,SRefs), |
797 | | append(RRefs,SRefs,Refs), |
798 | | get_events(Name,Sections,Events,RName), |
799 | | store_eventb_hash(Name,Machine), |
800 | | assertz(machine_type(Name,Type)), |
801 | | assertz(machine_identifiers(Name,[],[],Vars,[],[],[])), |
802 | | assertz(machine_references(Name,Refs)), |
803 | | assertz(machine_operations(Name,Events)), |
804 | | assertz(machine_operation_calls(Name,[])), % Event-B events cannot call other events |
805 | ? | assert_if_has_theorems(Name,Sections). |
806 | | |
807 | | |
808 | | % analyze which kinds of refinments we have between events |
809 | | analyse_eventb_refinement_types([]). |
810 | | analyse_eventb_refinement_types([RefMachine,AbsMachine|_]) :- |
811 | | event_b_model(RefMachine,RefName,RefSections), |
812 | | memberchk(refines(_,AbsName),RefSections), |
813 | | event_b_model(AbsMachine,AbsName,AbsSections), |
814 | | get_opt_section(events,RefSections,RawEvents), |
815 | | get_opt_section(events,AbsSections,AbsRawEvents), |
816 | ? | member(RawEvent,RawEvents), |
817 | | bmachine_eventb:raw_event(RawEvent,_,RefEvName,_St1,Ref,_Prm1,RefGrd,_Thm1,RefAct,_Wit1,_Desc1), |
818 | | Ref = [AbsEvName], |
819 | ? | member(AbsRawEvent,AbsRawEvents), |
820 | | bmachine_eventb:raw_event(AbsRawEvent,_,AbsEvName,_St2,_,_Prm2,AbsGrd,_Thm2,AbsAct,_Wit2,_Desc2), |
821 | | check_raw_prefix(AbsGrd,RefGrd,SameGuard), |
822 | | check_raw_prefix(AbsAct,RefAct,SameAct), |
823 | | %% format('~nEvent refinement change ~w (~w) -> ~w (~w) guard: ~w, action: ~w~n',[RefEvName,RefName,AbsEvName,AbsName,SameGuard,SameAct]), |
824 | | %print(rawgrd(RefGrd,AbsGrd)), nl, print(rawact(RefAct,AbsAct)),nl, |
825 | | assertz(event_refinement_change(RefName,RefEvName,AbsName,AbsEvName,SameGuard,SameAct)), |
826 | | fail. |
827 | | analyse_eventb_refinement_types([_|T]) :- analyse_eventb_refinement_types(T). |
828 | | |
829 | | check_raw_prefix([],[],Res) :- !, Res=unchanged. |
830 | | check_raw_prefix([],[_|_],Res) :- !, Res=extends. % the refinement has some more actions/guards |
831 | | check_raw_prefix([Abs|AT],[Ref|RT],Result) :- same_raw_expression(Abs,Ref),!, |
832 | | check_raw_prefix(AT,RT,Result). |
833 | | check_raw_prefix(_,_,refines). |
834 | | |
835 | | % TO DO: check if we have a more complete version of this predicate; to do: handle @desc description/3 terms |
836 | | same_raw_expression(identifier(_,A),RHS) :- !, RHS=identifier(_,B), A=B. |
837 | | same_raw_expression(equal(_,A,B),RHS) :- !, RHS=equal(_,A2,B2), same_raw_expression(A,A2), same_raw_expression(B,B2). |
838 | | same_raw_expression(assign(_,A1,A2),RHS) :- !, RHS=assign(_,B1,B2), |
839 | | maplist(same_raw_expression,A1,B1), maplist(same_raw_expression,A2,B2). |
840 | | same_raw_expression(A,B) :- atomic(A),!, A=B. |
841 | | same_raw_expression(A,B) :- A =.. [F,_|AA], % print(match(F,AA)),nl, |
842 | | B=.. [F,_|BB], maplist(same_raw_expression,AA,BB). |
843 | | |
844 | | |
845 | | |
846 | | get_sees_context_refs(Sections,SRefs) :- |
847 | | get_opt_section(sees,Sections,Seen), |
848 | | findall(ref(sees,I,''),member(I,Seen),SRefs). |
849 | | |
850 | | :- use_module(bmachine_eventb,[raw_event/11]). |
851 | | get_events(Name,Sections,Events,AbsMachineName) :- |
852 | | get_opt_section(events,Sections,RawEvents), |
853 | | compute_event_refines(Name,RawEvents,AbsMachineName), |
854 | | findall( identifier(Pos,EvName), |
855 | | ( member(RawEvent,RawEvents), |
856 | | raw_event(RawEvent,Pos,EvName,_St,_Rf,_Prm,_Grd,_Thm,_Act,_Wit,_Desc) ), |
857 | | Events). |
858 | | |
859 | | % TO DO: detect when an event extends another one without changing it |
860 | | compute_event_refines(MachineName,RawEvents,AbsMachineName) :- |
861 | ? | member(RawEvent,RawEvents), |
862 | | bmachine_eventb:raw_event(RawEvent,_Pos,EvName,_St,Refines,_Prm,_Grd,_Thm,_Act,_Wit,_Desc), |
863 | | member(RefEvent,Refines), |
864 | | % print(refines(MachineName,EvName,AbsMachineName,RefEvent)),nl, |
865 | | assertz(refines_event(MachineName,EvName,AbsMachineName,RefEvent)), |
866 | | fail. |
867 | | compute_event_refines(_,_,_). |
868 | | |
869 | | |
870 | | % try and get the name of the machine we refine |
871 | | %machine_refines(Machine,AbsMachine) :- machine_references(Machine,Refs), member(ref(refines,AbsMachine,_),Refs). |
872 | | |
873 | | analyse_eventb_context(Context) :- |
874 | | event_b_context(Context,Name,Sections), |
875 | | get_identifiers([constants],Sections,ConcreteConstants), |
876 | | get_identifiers([abstract_constants],Sections,AbstractConstants), |
877 | | append([ConcreteConstants,AbstractConstants],AllConstants), |
878 | | get_sets(Sections,Sets), |
879 | | get_extends_refs(Sections,Refs), |
880 | | store_eventb_hash(Name,Context), |
881 | | assertz(machine_type(Name,context)), |
882 | | assertz(machine_identifiers(Name,[],Sets,[],[],[],AllConstants)), |
883 | | assertz(machine_references(Name,Refs)), |
884 | | assertz(machine_operations(Name,[])), |
885 | | assertz(machine_operation_calls(Name,[])), |
886 | | maplist(assert_raw_id_with_position(concrete_constant),ConcreteConstants), |
887 | | maplist(assert_raw_id_with_position(abstract_constant),AbstractConstants), |
888 | ? | assert_if_has_theorems(Name,Sections). |
889 | | |
890 | | get_extends_refs(Sections,Refs) :- |
891 | | get_opt_section(extends,Sections,Extended), |
892 | | findall(ref(extends,E,''),member(E,Extended),Refs). |
893 | | |
894 | | assert_if_has_theorems(Name,Sections) :- |
895 | | get_opt_section(theorems,Sections,[_|_]), |
896 | | assertz(machine_has_assertions(Name)). |
897 | | assert_if_has_theorems(_Name,_Sections). |
898 | | |
899 | | |
900 | | % compute a hash based on constants and properties |
901 | | properties_hash(MachineName,Hash) :- |
902 | | properties_hash_cached(MachineName,Hash1),!, |
903 | | Hash=Hash1. |
904 | | properties_hash(MachineName,Hash) :- |
905 | | compute_properties_hash(MachineName,Hash1), |
906 | | assertz(properties_hash_cached(MachineName,Hash1)), |
907 | | Hash=Hash1. |
908 | | compute_properties_hash(Name,Hash) :- |
909 | | raw_machine(Name,Machine), |
910 | | get_machine(Name,[Machine],_Type,_Header,_Refines,Body), |
911 | | extract_sorted_np_sets(Body,Sets), |
912 | | extract_sorted_np_constants(Body,Constants), |
913 | | extract_np_properties(Body,Properties), |
914 | | extract_used_np_definitions_from_properties(Body,Definitions), |
915 | | ToHash = [Sets,Constants,Properties,Definitions], |
916 | | raw_sha_hash(ToHash,Hash). |
917 | | % save_properties_hash(Name,ToHash,Hash). |
918 | | |
919 | | extract_sorted_np_sets(Body,Sets) :- |
920 | | get_sets(Body,PosSets), |
921 | | remove_raw_position_info(PosSets,UnsortedSets), |
922 | | sort(UnsortedSets,Sets). |
923 | | extract_sorted_np_constants(Body,Constants) :- |
924 | | get_opt_sections([constants,concrete_constants,abstract_constants],Body,PosConstants), |
925 | | remove_raw_position_info(PosConstants,UnsortedConstants), |
926 | | sort(UnsortedConstants,Constants). |
927 | | extract_np_properties(Body,Properties) :- |
928 | | get_opt_section(properties,Body,PosProperties), |
929 | | remove_raw_position_info(PosProperties,Properties). |
930 | | extract_used_np_definitions_from_properties(Body,Definitions) :- |
931 | | get_opt_section(properties,Body,Properties), |
932 | | extract_used_np_definitions(Properties,Body,Definitions,_). |
933 | | extract_used_np_definitions(RawSyntax,Body,Definitions,PosDefinitions) :- |
934 | | extract_raw_identifiers(RawSyntax,UsedIds), |
935 | | all_definition_ids(Body,AllDefs), |
936 | | ord_intersection(UsedIds,AllDefs,UsedDefNames), |
937 | | transitive_used_definitions(UsedDefNames,AllUsedDefs), |
938 | | findall( definition(none,Name,Args,DefBody), |
939 | | ( member(Name,AllUsedDefs),b_get_definition(Name,_DefType,Args,DefBody,_Deps)), |
940 | | PosDefinitions), |
941 | | maplist(remove_raw_position_info,PosDefinitions,Definitions). |
942 | | all_definition_ids(Body,Ids) :- |
943 | | get_opt_section(definitions,Body,Definitions), |
944 | | convlist(get_definition_name,Definitions,Ids1), |
945 | | sort(Ids1,Ids). |
946 | | transitive_used_definitions(Defs,TransDefs) :- |
947 | | findall( D, reachable_definition(Defs,D), TD1), |
948 | | sort(TD1,TransDefs). |
949 | | reachable_definition(Defs,D) :- |
950 | | member(N,Defs), |
951 | | ( D=N |
952 | | ; b_get_definition(N,_DefType,_Args,_Body,Deps), |
953 | | reachable_definition(Deps,D)). |
954 | | |
955 | | |
956 | | /* just for debugging: |
957 | | save_properties_hash(MachineName,ToHash,Hash) :- |
958 | | main_machine_name(Main), |
959 | | open('/home/plagge/hashes.pl',append,S), |
960 | | writeq(S,hash(Main,MachineName,ToHash,Hash)), |
961 | | write(S,'.\n'), |
962 | | close(S). |
963 | | */ |
964 | | |
965 | | % --------------------------- |
966 | | :- dynamic event_info/3. |
967 | | analyze_extends_relation :- |
968 | | retractall(event_info(_,_,_)), |
969 | | bmachine:b_get_machine_operation(_Name,_Results,_RealParameters,TBody,_OType,_OpPos), |
970 | | treat_event_body(TBody),fail. |
971 | | analyze_extends_relation. |
972 | | |
973 | | treat_event_body(TBody) :- |
974 | | rlevent_info(TBody,EventName,Machine,Status,AbstractEvents), |
975 | | % format('Event ~w:~w ~w~n',[Machine,EventName,Status]), |
976 | | assertz(event_info(Machine,EventName,Status)), |
977 | | maplist(treat_event_body,AbstractEvents). |
978 | | |
979 | | :- use_module(bsyntaxtree,[get_texpr_expr/2]). |
980 | | rlevent_info(TBody,EventName,Machine,FStatus,AbstractEvents) :- |
981 | | get_texpr_expr(TBody,Event), |
982 | | Event = rlevent(EventName,Machine,TStatus,_Params,_Guard,_Theorems,_Actions,_VWit,_PWit,_Unmod,AbstractEvents), |
983 | | bsyntaxtree:get_texpr_expr(TStatus,Status), %ordinary, convergent, anticipated |
984 | | functor(Status,FStatus,_). |
985 | | |
986 | | |
987 | | % write the event refinement hierarchy to a dot file |
988 | | % (currently) only makes sense for Event-B models |
989 | | |
990 | | :- use_module(preferences,[get_preference/2]). |
991 | | :- use_module(tools_strings,[ajoin/2, ajoin_with_sep/3]). |
992 | | |
993 | | :- public dot_refinement_node_new/4. |
994 | | % variation for: use_new_dot_attr_pred |
995 | | dot_refinement_node_new(event_refinement,M:Ev,M,[label/Desc,shape/Shape,tooltip/Tooltip|T1]) :- |
996 | | dot_event_node(M:Ev,M,Desc,Shape,Style,Color), |
997 | | (Style=none -> T1=T2 ; T1=[style/Style|T2]), |
998 | | (Color=none -> T2=[] ; T2=[color/Color]), |
999 | | (event_info(M,Ev,Status) -> true ; Status=unknown), |
1000 | | (event_refinement_change(M,Ev,AbsName,AbsEvName,SameGuard,SameAct) |
1001 | | -> ajoin(['Event ',Ev,' in ',M, |
1002 | | '\n status: ',Status, |
1003 | | '\n refines ',AbsEvName,' in ',AbsName, |
1004 | | '\n guard: ',SameGuard, |
1005 | | '\n action: ',SameAct], Tooltip) |
1006 | | ; ajoin(['Event ',Ev,' in ',M, |
1007 | | '\n status: ',Status], Tooltip)). |
1008 | | dot_refinement_node_new(variable_refinement(With),M:Var,M,[label/Desc,shape/Shape,color/Color,tooltip/Tooltip|T1]) :- |
1009 | | (With=with_constants -> machine_ids(M,Vars) ; machine_variables(M,Vars)), |
1010 | | member(Var,Vars), |
1011 | | Desc=Var, |
1012 | | (id_exists_in_abstraction(M,Anc,Var) |
1013 | | -> get_preference(dot_event_hierarchy_unchanged_event_colour,Color), T1=[style/filled], |
1014 | | Shape = rect, % we could use plain; makes kept events smaller |
1015 | | ajoin(['Variable kept from abstraction ',Anc],Tooltip) |
1016 | | ; machine_type(M,context), machine_sets(M,Sets), member(Var,Sets) -> |
1017 | | get_preference(dot_event_hierarchy_refines_colour,Color), T1=[style/'rounded,filled'], |
1018 | | Shape = rect, |
1019 | | ajoin(['New set in ',M],Tooltip) |
1020 | | ; machine_type(M,context) -> % it must be a constant |
1021 | | get_preference(dot_event_hierarchy_new_event_colour,Color), T1=[style/rounded], |
1022 | | Shape = rect, |
1023 | | ajoin(['New constant in ',M],Tooltip) |
1024 | | ; get_preference(dot_event_hierarchy_new_event_colour,Color), T1=[], |
1025 | | Shape = rect, |
1026 | | ajoin(['New variable in ',M],Tooltip) |
1027 | | ). |
1028 | | %dot_refinement_node_new(variable_refinement(_),C:Cst,M,[label/Desc,shape/rect,color/Color,tooltip/Desc|T1]) :- |
1029 | | % new_seen_context(M,C), machine_constants(C,Csts), member(Cst,Csts), |
1030 | | % Desc=Cst, T1=[style/rounded], get_preference(dot_event_hierarchy_extends_colour,Color). |
1031 | | |
1032 | | % the node ide is M:Ev as Ev can and usually does occur multiple times |
1033 | | dot_event_node(M:Ev,M,Desc,Shape,Style,Color) :- |
1034 | | findall(showev(M,Ev),event_to_show(M,Ev),List), sort(List,SList), |
1035 | | member(showev(M,Ev),SList), |
1036 | | (event_info(M,Ev,Status) -> true ; Status=unknown), |
1037 | | (event_refinement_change(M,Ev,_,_,SameGuard,SameAction) |
1038 | | -> true ; SameGuard=unknown, SameAction=unknown), |
1039 | | (SameGuard=SameAction, SameGuard \= unknown -> ajoin([Ev,'\\n(',SameAction,')'],Ev2) |
1040 | | ; SameGuard=unchanged ,SameAction=extends-> ajoin([Ev,'\\n(same grd, extends act)'],Ev2) |
1041 | | ; SameGuard=unchanged -> ajoin([Ev,'\\n(same grd)'],Ev2) |
1042 | | ; SameGuard=extends,SameAction=unchanged -> ajoin([Ev,'\\n(same act, extends grd)'],Ev2) |
1043 | | ; SameAction=unchanged -> ajoin([Ev,'\\n(same act)'],Ev2) |
1044 | | ; SameGuard=extends -> ajoin([Ev,'\\n(extends grd)'],Ev2) |
1045 | | ; SameAction=extends -> ajoin([Ev,'\\n(extends act)'],Ev2) |
1046 | | ; Ev2=Ev), |
1047 | | (Status = convergent -> ajoin([Ev2,' (<)'],Desc) |
1048 | | ; Status = anticipated -> ajoin([Ev2, ' (<=)'],Desc) |
1049 | | ; Desc=Ev2), |
1050 | | % format(user_output,'event ~w, status:~w, same guard:~w, same action:~w~n',[Ev,Status,SameGuard,SameAction]), |
1051 | | dot_get_color_style(M,Ev,Status,SameGuard,SameAction,Shape,Color,Style). |
1052 | | |
1053 | | event_to_show(M,Ev) :- machine_operations(M,Evs), |
1054 | | raw_identifier_member(Ev,Evs), Ev \= 'INITIALISATION'. |
1055 | | event_to_show(M,Ev) :- |
1056 | | event_refinement_change(M,Ev,_,_,_,_), % for events that disappear, i.e., are not refined until bottom level |
1057 | | Ev \= 'INITIALISATION'. |
1058 | | |
1059 | | |
1060 | | dot_get_color_style(M,Ev,_Status,_,_,box,Color,Style) :- \+ refines_event(M,Ev,_,_),!, |
1061 | | get_preference(dot_event_hierarchy_new_event_colour,Color), Style=none. |
1062 | | dot_get_color_style(M,Ev,_,SameGuard,SameAction,box,Color,Style) :- |
1063 | | refines_event(M,Ev,_,Ev2), dif(Ev2,Ev),!, % changes name |
1064 | | ((SameGuard,SameAction)=(unchanged,unchanged) |
1065 | | -> get_preference(dot_event_hierarchy_rename_unchanged_event_colour,Color) |
1066 | | ; get_preference(dot_event_hierarchy_rename_event_colour,Color)), Style=filled. |
1067 | | dot_get_color_style(_M,_Ev,_,unchanged,unchanged,plain,Color,Style) :- % keeps name and adds no guard or action |
1068 | | !, |
1069 | | get_preference(dot_event_hierarchy_unchanged_event_colour,Color), Style=filled. |
1070 | | dot_get_color_style(_M,_Ev,_,_,unchanged,box,Color,Style) :- % keeps name and adds no action, but modifies guard |
1071 | | !, |
1072 | | get_preference(dot_event_hierarchy_grd_strengthening_event_colour,Color), Style=filled. |
1073 | | dot_get_color_style(_M,_Ev,_,unchanged,_,box,Color,Style) :- % keeps name and adds action but keeps guard |
1074 | | !, |
1075 | | get_preference(dot_event_hierarchy_grd_keeping_event_colour,Color), Style=filled. |
1076 | | dot_get_color_style(_M,_Ev,_,_,_,box,Color,Style) :- % keeps name but adds or modifies |
1077 | | % TO DO: distinguish extends from refines |
1078 | | get_preference(dot_event_hierarchy_refines_colour,Color), Style=filled. |
1079 | | |
1080 | | :- public dot_refines_event/4. |
1081 | | % dot transition predicate for event and variable refinement hierarchy diagram |
1082 | | dot_refines_event(event_refinement,M2:Ev2,M1:Ev1,[label/Label,color/Color,style/Style]) :- |
1083 | | Label = '', % TO DO: detect refine, extends, identical |
1084 | | refines_event(M1,Ev1,M2,Ev2), |
1085 | | Ev1 \= 'INITIALISATION', |
1086 | | (event_refinement_change(M1,Ev1,_,_,SameGuard,SameAct) |
1087 | | -> arrow_style(SameGuard,SameAct,Style,ColPref), get_preference(ColPref,Color) |
1088 | | ; Style=solid, Color=red % should not happen |
1089 | | ). |
1090 | | dot_refines_event(variable_refinement(With),M1:Var,M2:Var2,[label/Label,color/Color,style/Style|T1]) :- |
1091 | | get_preference(dot_event_hierarchy_edge_colour,Color), |
1092 | | (With=with_constants -> machine_ids(M2,Vars2) ; machine_variables(M2,Vars2)), |
1093 | | if((member(Var,Vars2), |
1094 | | id_exists_in_abstraction(M2,M1,Var)), |
1095 | | (Label='',Var2=Var,Style=dashed, T1=[]), |
1096 | | (% no variable of M2 exists in M1 |
1097 | | refines_or_extends_machine(M2,M1), |
1098 | | machine_ids(M1,[Var|_]), % get first variable of M1 |
1099 | | Vars2=[Var2|_], Style=dotted, % add virtual edge to first variable of M2 if no variable is kept |
1100 | | get_dot_cluster_name(M1,M1C), get_dot_cluster_name(M2,M2C), |
1101 | | T1 = [ltail/M1C, lhead/M2C], |
1102 | | (machine_type(M2,context) -> Label='extends' ; Label='') |
1103 | | ) |
1104 | | ). |
1105 | | dot_refines_event(variable_refinement(with_constants),M1:Var1,M2:Cst2,[label/Label,color/Color,style/Style|T1]) :- Label='sees', |
1106 | | Color=gray80, Style=solid, |
1107 | | new_seen_context(M1,M2), |
1108 | | machine_ids(M1,[Var1|_]), |
1109 | | machine_ids(M2,[Cst2|_]), |
1110 | | get_dot_cluster_name(M1,M1C), get_dot_cluster_name(M2,M2C), |
1111 | | T1 = [ltail/M1C, lhead/M2C, dir/forward]. |
1112 | | |
1113 | | arrow_style(unchanged,unchanged,Style,ColPref) :- !, |
1114 | | Style=arrowhead(none,solid), ColPref=dot_event_hierarchy_extends_colour. |
1115 | | arrow_style(refines,_,Style,ColPref) :- !, Style=solid, ColPref=dot_event_hierarchy_edge_colour. |
1116 | | arrow_style(_,refines,Style,ColPref) :- !, Style=solid, ColPref=dot_event_hierarchy_edge_colour. |
1117 | | %arrow_style(_,_,Style) :- Style = arrowhead(vee,arrowtail(box,solid)). % we have extends |
1118 | | arrow_style(_,_,Style,dot_event_hierarchy_extends_colour) :- Style = arrowhead(vee,solid). % we have extends |
1119 | | |
1120 | | |
1121 | | %dot_same_rank(SameRankVals) :- machine_operations(M,Evs), |
1122 | | % findall(M:Ev,raw_identifier_member(Ev,Evs),SameRankVals). |
1123 | | |
1124 | | dot_subgraph(Kind,sub_graph_with_attributes(M,Attrs), filled,Colour) :- |
1125 | | get_preference(dot_event_hierarchy_machine_colour,MColour), |
1126 | | Attrs = [label/Label, tooltip/ToolTip], |
1127 | | machine_operations(M,Ops), |
1128 | | (Kind=event_refinement -> Ops=[_|_] ; true), |
1129 | | (machine_type(M,context) -> IDS = 'csts', Colour=gray90 |
1130 | | ; IDS = 'vars', Colour=MColour), |
1131 | | (machine_ids(M,Vars) |
1132 | | -> length(Vars,V), |
1133 | | findall(C,new_seen_context(M,C),NewC), |
1134 | | split_list(id_exists_in_abstraction(M),Vars,_Old,NewVars), |
1135 | | length(NewVars,NewNr), |
1136 | | findall(Del,(var_exists_in_abstraction(M,Del), nonmember(Del,Vars)),DelVars), |
1137 | | length(DelVars,DelNr), |
1138 | | (Kind=event_refinement, get_preference(dot_hierarchy_show_extra_detail,false) |
1139 | | -> Label=M |
1140 | | ; ajoin([M,'\\n#',IDS,'=',V, ' (+',NewNr,',-', DelNr,')'],Label) |
1141 | | ), |
1142 | | ajoin_with_sep(NewVars,',',NV), |
1143 | | ajoin_with_sep(DelVars,',',DV), |
1144 | | ajoin_with_sep(NewC,',',NC), |
1145 | | ajoin(['machine ',M,'\\n#',IDS,'=',V, ' (+',NewNr,',-', DelNr,')', |
1146 | | '\\nnew sees=',NC, |
1147 | | '\\nnew ',IDS,'=',NV, |
1148 | | '\\ndel ',IDS,'=',DV],ToolTip) |
1149 | | ; Label=M, ToolTip=M). |
1150 | | |
1151 | | machine_variables(M,Vars) :- machine_identifiers(M,_Params,_Sets,AVars,CVars,_AConsts,_CConsts), |
1152 | | append(CVars,AVars,RVars), % for Event-B: CVars=[] |
1153 | | maplist(get_raw_identifier,RVars,Vars). |
1154 | | %machine_constants(M,Consts) :- machine_identifiers(M,_Params,_Sets,_AVars,_CVars,AConsts,CConsts), |
1155 | | % append(CConsts,AConsts,Raw), |
1156 | | % maplist(get_raw_identifier,Raw,Consts). |
1157 | | machine_ids(M,Vars) :- machine_identifiers(M,_Params,Sets,AVars,CVars,AConsts,CConsts), |
1158 | | append([Sets,CConsts,CVars,AConsts,AVars],RVars), |
1159 | | maplist(get_raw_identifier,RVars,Vars). |
1160 | | machine_sets(M,Vars) :- machine_identifiers(M,_,Sets,_,_,_,_), |
1161 | | maplist(get_raw_identifier,Sets,Vars). |
1162 | | |
1163 | | var_exists_in_abstraction(M,Var) :- |
1164 | | var_exists_in_abstraction(M,_Anc,Var). |
1165 | | var_exists_in_abstraction(M,Anc,Var) :- |
1166 | | refines_machine(M,Anc), |
1167 | | machine_variables(Anc,AncVars), |
1168 | | member(Var,AncVars). |
1169 | | |
1170 | | refines_machine(M,Anc) :- |
1171 | | machine_references(M,Refs), |
1172 | | member(ref(refines,Anc,_),Refs). |
1173 | | |
1174 | | % variable or constant exists in abstraction |
1175 | | id_exists_in_abstraction(M,Var) :- |
1176 | | id_exists_in_abstraction(M,_Anc,Var). |
1177 | | id_exists_in_abstraction(M,Anc,Var) :- |
1178 | | refines_or_extends_machine(M,Anc), |
1179 | | machine_ids(Anc,AncVars), |
1180 | | member(Var,AncVars). |
1181 | | |
1182 | | refines_or_extends_machine(M,Anc) :- |
1183 | | machine_references(M,Refs), |
1184 | | (member(ref(refines,Anc,_),Refs) -> true ; member(ref(extends,Anc,_),Refs)). |
1185 | | |
1186 | | new_seen_context(M,Context) :- machine_references(M,Refs), |
1187 | | member(ref(sees,Context,_),Refs), |
1188 | | \+ (member(ref(refines,Anc,_),Refs), |
1189 | | sees_context(Anc,Context)). |
1190 | | |
1191 | | sees_context(M,Context) :- machine_references(M,Refs), member(ref(sees,Context,_),Refs). |
1192 | | |
1193 | | |
1194 | | write_dot_event_hierarchy_to_file(File) :- |
1195 | | write_dot_ref_hierarchy_to_file(event_refinement,File). |
1196 | | write_dot_variable_hierarchy_to_file(File) :- |
1197 | | (get_preference(dot_hierarchy_show_extra_detail,false) -> With=no_constants ; With=with_constants), |
1198 | | write_dot_ref_hierarchy_to_file(variable_refinement(With),File). |
1199 | | write_dot_ref_hierarchy_to_file(Kind,File) :- |
1200 | | analyze_extends_relation, |
1201 | | (get_preference(dot_event_hierarchy_horizontal,true) |
1202 | | -> PageOpts=[compound/true,rankdir/'LR',no_page_size] |
1203 | | ; PageOpts=[compound/true]), |
1204 | | gen_dot_graph(File,PageOpts, |
1205 | | use_new_dot_attr_pred(b_machine_hierarchy:dot_refinement_node_new(Kind)), |
1206 | | use_new_dot_attr_pred(b_machine_hierarchy:dot_refines_event(Kind)), |
1207 | | dot_no_same_rank,dot_subgraph(Kind)). |
1208 | | %gen_dot_graph(File,PageOpts,dot_event_node,dot_refines_event,dot_no_same_rank,dot_subgraph). |
1209 | | |
1210 | | % -------------------- |
1211 | | |
1212 | | reference_link(FromMachine,DestMachine) :- |
1213 | | machine_references(FromMachine,Refs), |
1214 | | filter_redundant_refs(Refs,Refs,UsefulRefs), |
1215 | | member(ref(_Type,DestMachine,_Prefix),UsefulRefs). |
1216 | | |
1217 | | :- use_module(library(ugraphs),[vertices_edges_to_ugraph/3, top_sort/2, transitive_closure/2, |
1218 | | min_paths/3, min_path/5, neighbours/3, del_vertices/3]). |
1219 | | |
1220 | | % b_machine_hierarchy:get_machine_topological_order(V) |
1221 | | % get machine in topological order of inclusion |
1222 | | get_machine_topological_order(SortedVertices) :- |
1223 | | get_machine_inclusion_graph(_Vertices,_Edges,Graph), |
1224 | | top_sort(Graph,SortedVertices). |
1225 | | |
1226 | | % get machine inclusion graph in format for ugraphs (unweighted graphs) library |
1227 | | get_machine_inclusion_graph(Vertices,Edges,Graph) :- |
1228 | | findall(Mach,machine_name(Mach),Vertices), |
1229 | | findall(From-To,reference_link(From,To),Edges), |
1230 | | vertices_edges_to_ugraph(Vertices, Edges, Graph). |
1231 | | |
1232 | | print_machine_topological_order :- |
1233 | | format('Topological sorting of B machine references~n',[]), |
1234 | | get_machine_inclusion_graph(Vertices,Edges,Graph), |
1235 | | length(Vertices,LenV),format(' * number of machines: ~w~n',[LenV]), |
1236 | | length(Edges,LenE), format(' * number of reference links: ~w~n',[LenE]), |
1237 | | top_sort(Graph,SortedVertices), |
1238 | | main_machine_name(Main), |
1239 | | %min_paths(Main,Graph,MinPaths), |
1240 | | transitive_closure(Graph,TGraph), |
1241 | | maplist(top_print_machine(Main,Graph,TGraph),SortedVertices), |
1242 | | greedy_cover(Main,Graph,TGraph,GreedyCover), length(GreedyCover,GLen), |
1243 | | format(' * sufficient includes (~w) to cover all machines: ~w~n',[GLen,GreedyCover]). |
1244 | | |
1245 | | top_print_machine(Main,Graph,TGraph,M) :- |
1246 | | (machine_references(M,Refs) -> length(Refs,Len) ; Len=0), |
1247 | | neighbours(M,TGraph,N), length(N,Len2), |
1248 | | format(' ~w has ~w direct references and ~w indirect ones~n',[M,Len,Len2]), |
1249 | | min_path(Main, M, Graph, Path, Length), |
1250 | | format(' inclusion length ~w: ~w~n',[Length,Path]). |
1251 | | |
1252 | | greedy_cover(Main,Graph,TGraph,GreedyCover) :- |
1253 | | neighbours(Main,Graph,TopLevelIncludes), |
1254 | | greedy_cover(TopLevelIncludes,TGraph,GreedyCover). |
1255 | | |
1256 | | % compute a list of included machines which cover all required inclusions |
1257 | | % we try to make this inclusion minimal, using a greedy algorithm |
1258 | | greedy_cover([],_TGraph,Cover) :- !, Cover=[]. |
1259 | | greedy_cover(RemainingIncludes,TGraph, GreedyCover) :- |
1260 | | findall(candidate(NrCovered,Machine), |
1261 | | (member(Machine,RemainingIncludes), |
1262 | | neighbours(Machine,TGraph,N), length(N,NrCovered)), Cands), |
1263 | | (max_member(candidate(MaxNrCov,NextChoice),Cands) % pick the machine covering the most other ones |
1264 | | -> |
1265 | | (MaxNrCov =< 0 |
1266 | | -> GreedyCover = [] % everything is included already |
1267 | | ; GreedyCover = [NextChoice|TGreedyCover], |
1268 | | select(NextChoice,RemainingIncludes,Rem2), % this include is no longer available |
1269 | | neighbours(NextChoice,TGraph,NowCovered), |
1270 | | del_vertices(TGraph, [NextChoice|NowCovered], TGraph2), % delete all machines included by NextChoice |
1271 | | greedy_cover(Rem2,TGraph2,TGreedyCover) |
1272 | | ) |
1273 | | ; GreedyCover = [] % everything is included already; no node appears in the graph anymore |
1274 | | ). |
1275 | | |