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(state_custom_dot_graph,[tcltk_generate_state_custom_dot_graph/1,
7 state_custom_dot_graph_available/0,
8 tcltk_generate_state_custom_dot_graph_for_expr/2,
9 is_valid_custom_dot_graph_record/1]).
10
11 :- use_module(probsrc(module_information)).
12 :- module_info(group,dot).
13 :- module_info(description,'This module provides a way to generate custom state graphs (using info from DEFINITIONS).').
14
15 % The best way to use this feature is to describe nodes and edges by records
16 % Nodes should be records with a
17 % - value field, used to compute the internal id and used by the edges
18 % - label field: the text shown in dot for the node
19 % Here is an example:
20 % CUSTOM_GRAPH_NODES == {p•p∈Pairs|rec(`value`:p, label:```${p}\nu=${u(p)}```,
21 % style: IF prj2(p)=certain THEN "solid" ELSE "dashed" END,
22 % color: IF prj1(p)=fair THEN "green" ELSE "red" END, shape:"rect")};
23 % Edges should be records with
24 % - from and to fields which should match the value field of some node
25 % - label field: the text associated with the edge
26 % Here is an example:
27 % CUSTOM_GRAPH_EDGES == {a,b• a∈Pairs ∧ b∈Pairs ∧ u(a) > u(b) | rec(from:a,to:b,label:">")}
28 % You can also merge everything into a single CUSTOM_GRAPH definition
29 % CUSTOM_GRAPH == rec(layout:"circo", nodes:mynodes, edges:myedges)
30
31 :- use_module(dotsrc(dot_graph_generator)).
32
33 %:- use_module(self_check).
34 :- use_module(probsrc(error_manager)).
35
36 :- use_module(probsrc(state_space),[current_expression/2]).
37 :- use_module(probsrc(bsyntaxtree), [get_texpr_id/2]).
38 :- use_module(probsrc(specfile), [state_corresponds_to_fully_setup_b_machine/2]).
39 :- use_module(probsrc(debug), [debug_println/2, debug_format/3]).
40 :- use_module(probsrc(bmachine), [b_get_machine_custom_nodes_function/2,b_get_machine_custom_edges_function/2,
41 b_get_machine_custom_graph_function/2]).
42 :- use_module(probsrc(preferences),[valid_rgb_color/1, valid_dot_shape/1,
43 valid_dot_line_style/1, valid_dot_node_style/1]).
44 :- use_module(probsrc(tools),[start_ms_timer/1, stop_ms_timer_with_msg/2]).
45 :- use_module(library(lists),[exclude/3]).
46
47 :- set_prolog_flag(double_quotes, codes).
48
49
50 state_custom_dot_graph_available :-
51 b_get_machine_custom_graph_function(_,_) ;
52 % b_get_machine_custom_nodes_function(_,_) ;
53 b_get_machine_custom_edges_function(_,_).
54
55 reset_custom_defs :-
56 retractall(custom_nodes(_,_,_,_)),
57 retractall(custom_edges(_,_,_,_)),
58 retractall(custom_graph(_,_)),
59 retractall(custom_node_id(_,_)), retractall(nodectr(_)),
60 retractall(custom_trans_label(_,_)), retractall(trans_ctr(_)), assertz(trans_ctr(1)).
61
62 tcltk_generate_state_custom_dot_graph(File) :-
63 get_state_for_graph(BState),
64 (state_custom_dot_graph_available
65 -> true
66 ; add_error_and_fail(tcltk_generate_state_custom_dot_graph,
67 'No CUSTOM_GRAPH_EDGES or CUSTOM_GRAPH Function in DEFINITIONS')),
68 reset_custom_defs,
69 start_ms_timer(Timer),
70 eval_defs(BState),
71 gen_graph(File),
72 stop_ms_timer_with_msg(Timer,'custom graph: ').
73
74 :- use_module(probsrc(eval_let_store),[extend_state_with_probids_and_lets/2]).
75 :- use_module(probsrc(specfile), [get_current_state_for_b_formula/2]).
76 % provide a way to create a custom graph from an expression
77 % this expression should be a record with nodes, nedges fields, like a CUSTOM_GRAPH definition
78 tcltk_generate_state_custom_dot_graph_for_expr(GraphFunction,File) :-
79 get_current_state_for_b_formula(GraphFunction,BState),
80 extend_state_with_probids_and_lets(BState,BState1),
81 reset_custom_defs,
82 eval_custom_graph_function(GraphFunction,0,BState1),
83 gen_graph(File).
84
85 :- use_module(probsrc(preferences),[temporary_set_preference/3, reset_temporary_preference/2]).
86 gen_graph(File) :-
87 (custom_graph(_,GraphAttrs) -> true ; GraphAttrs=[]),
88 temporary_set_preference(dot_print_self_loops,true,Chng), % the user provides the edges function explicitly, show all edges
89 call_cleanup(gen_dot_graph(File,GraphAttrs,
90 use_new_dot_attr_pred(state_custom_dot_graph:cg_node_predicate),
91 use_new_dot_attr_pred(state_custom_dot_graph:cg_trans_predicate),
92 dot_no_same_rank,dot_no_subgraph),
93 reset_temporary_preference(dot_print_self_loops,Chng)).
94
95 get_state_for_graph(BState) :-
96 current_expression(_CurID,State),
97 state_corresponds_to_fully_setup_b_machine(State,BState),!.
98 get_state_for_graph(_) :- % we could look at type of definitions and use get_current_state_for_b_formula
99 add_error_and_fail(state_custom_dot_graph,'Only possible for initialised B machine.').
100
101 % check if something is a valid record for CUSTOM_GRAPH
102 % it must have a nodes and edges field
103 is_valid_custom_dot_graph_record(b(rec(Fields),_,_)) :-
104 member(field(NodesField,_),Fields), is_nodes_field(NodesField,_),!,
105 member(field(EdgesField,_),Fields), is_edges_field(EdgesField,_),!.
106
107 :- use_module(library(lists),[select/3]).
108 eval_defs(BState) :-
109 b_get_machine_custom_nodes_function(NodesFunction,Nr),
110 eval_definition_fun(NodesFunction, Nr, nodes, BState, Infos, NodesRes),
111 debug_println(19,custom_nodes(Nr,NodesRes)),
112 (select(label/EFID,Infos,RestInfos)
113 -> assertz(custom_nodes(EFID,Nr,RestInfos,NodesRes))
114 ; get_texpr_id(NodesFunction,EFID)
115 -> assertz(custom_nodes(EFID,Nr,Infos,NodesRes))
116 ; % we cannot derive name, then generate one
117 number_codes(Nr,NC), append("custom",NC,CC), atom_codes(Custom,CC),
118 assertz(custom_nodes(Custom,Nr,Infos,NodesRes))),
119 fail.
120 eval_defs(BState) :-
121 b_get_machine_custom_edges_function(EdgesFunction,Nr),
122 eval_definition_fun(EdgesFunction, Nr, edges, BState, Infos, EdgesRes),
123 debug_println(19,custom_edges(Nr,EdgesRes)),
124 (select(label/Label,Infos,RestInfos) -> true
125 ; get_texpr_id(EdgesFunction,Label) -> RestInfos=Infos
126 ; b_get_machine_custom_edges_function(_,Nr2), Nr2 \= Nr % other relation exists; makes sense to use label
127 -> number_codes(Nr,NC), append("custom",NC,CC),
128 atom_codes(Label,CC),RestInfos=Infos
129 ; Label='',RestInfos=Infos
130 ),
131 assertz(custom_edges(Label,Nr,RestInfos,EdgesRes)),
132 % print(edges(Label,Nr,RestInfos)),nl, translate:print_bvalue(EdgesRes),nl,nl,
133 fail.
134 eval_defs(BState) :-
135 b_get_machine_custom_graph_function(GraphFunction,Nr),
136 format('Detected single CUSTOM_GRAPH DEFINITION ~w~n',[Nr]),
137 eval_custom_graph_function(GraphFunction,Nr,BState),
138 fail.
139 eval_defs(_).
140
141 % Process a single CUSTOM_GRAPH record
142 % Example:
143 % rec(layout:"fdp",directed:TRUE,
144 % edges:rec(colorscheme:"purples9",
145 % edges:{x,y•x:2..50 & y:2..(x-1) & x mod y =0|
146 % rec(edge:y|->x, label:"div", color:1 + x mod 9)} ),
147 % nodes:rec(colorscheme:"set312", style:"filled",
148 % nodes:{x•x:2..50|rec(label:x,
149 % fillcolor: 1 + x mod 11) } ) );
150 eval_custom_graph_function(GraphFunction,Nr,BState) :-
151 eval_definition_fun(GraphFunction, Nr, graph, BState, GraphAttrs, rec(OtherFields)),
152 exclude(process_nodes_field([],GraphFunction),OtherFields,Infos1),
153 exclude(process_edges_field([],GraphFunction),Infos1,Infos2),
154 % instead of passing [] above, we could auto-detect GraphAttrs which are node or edge attributes
155 (Infos2 = [] ->
156 (Infos1=[]
157 -> add_message(state_custom_dot_graph,'No edges detected in CUSTOM_GRAPH: ',OtherFields,GraphFunction)
158 ; true)
159 ; add_warning(state_custom_dot_graph,'Unrecognized CUSTOM_GRAPH fields: ',Infos2,GraphFunction)),
160 (custom_graph(_,_)
161 -> add_warning(state_custom_dot_graph,'Duplicate CUSTOM_GRAPH definition: ',Nr,GraphFunction)
162 ; assertz(custom_graph(Nr,GraphAttrs))
163 ).
164
165 % process a single nodes:Nodes field and assert custom_nodes facts
166 process_nodes_field(DefaultAttrs,Span,field(NodeField,Nodes)) :-
167 is_nodes_field(NodeField,Nr),
168 assert_custom_nodes2(Nodes,DefaultAttrs,Span,Nr).
169
170 assert_custom_nodes2(Nodes,DefaultAttrs,Span,_Nr) :- Nodes = rec(_),
171 extract_record_fields(Nodes,_,DotAttributes,OtherFields),!,
172 % we have a nested record with default Attributes for the nodes
173 add_default_attrs(DefaultAttrs,DotAttributes,NewDefaultAttrs),
174 exclude(process_nodes_field(NewDefaultAttrs,Span),OtherFields,Infos1),
175 (Infos1 = [] -> true
176 ; add_warning(state_custom_dot_graph,'Unrecognized CUSTOM_GRAPH nodes fields: ',Infos1,Span)).
177 assert_custom_nodes2(Nodes,DefaultAttrs,_,Nr) :-
178 try_expand_custom_set_with_catch(Nodes, NodesRes,extract_dot_function),
179 assertz(custom_nodes('custom',Nr,DefaultAttrs,NodesRes)). %translate:print_bvalue(NodesRes),nl
180
181 is_nodes_field(nodes,0).
182 is_nodes_field(nodes0,0).
183 is_nodes_field(nodes1,1).
184 is_nodes_field(nodes2,2).
185 is_nodes_field(nodes3,3).
186 is_nodes_field(nodes4,4).
187 is_nodes_field(nodes5,5).
188 is_nodes_field(nodes6,6).
189 is_nodes_field(nodes7,7).
190 is_nodes_field(nodes8,8).
191 is_nodes_field(nodes9,9).
192
193 % process a single edges:Nodes field and assert custom_edges facts
194 process_edges_field(DefaultAttrs,Span,field(EdgesField,Edges)) :-
195 is_edges_field(EdgesField,Nr),
196 assert_custom_edges2(Edges,DefaultAttrs,Span,Nr).
197
198 assert_custom_edges2(Edges,DefaultAttrs,Span,_Nr) :- Edges = rec(_),
199 extract_record_fields(Edges,_,DotAttributes,OtherFields),!,
200 % we have a nested record with default Attributes for the edges
201 add_default_attrs(DefaultAttrs,DotAttributes,NewDefaultAttrs),
202 exclude(process_edges_field(NewDefaultAttrs,Span),OtherFields,Infos1),
203 (Infos1 = [] -> true
204 ; add_warning(state_custom_dot_graph,'Unrecognized CUSTOM_GRAPH edges fields: ',Infos1,Span)).
205 assert_custom_edges2(Edges,DefaultAttrs,_,Nr) :-
206 try_expand_custom_set_with_catch(Edges, EdgesRes,extract_dot_function),
207 (select(label/Label,DefaultAttrs,DA) -> true
208 ; Label='edge', DA=DefaultAttrs),
209 assertz(custom_edges(Label,Nr,DA,EdgesRes)).
210
211
212 is_edges_field(edges,0).
213 is_edges_field(edges0,0).
214 is_edges_field(edges1,1).
215 is_edges_field(edges2,2).
216 is_edges_field(edges3,3).
217 is_edges_field(edges4,4).
218 is_edges_field(edges5,5).
219 is_edges_field(edges6,6).
220 is_edges_field(edges7,7).
221 is_edges_field(edges8,8).
222 is_edges_field(edges9,9).
223
224 :- dynamic custom_nodes/4, custom_edges/4, custom_graph/2.
225
226 :- public cg_node_predicate/3.
227 :- use_module(probsrc(tools),[string_escape/2]).
228 % Custom Graph node predicate for dot_graph_generator.pl
229 cg_node_predicate(NodeID,SubGraph,Attributes) :- SubGraph=none,
230 custom_nodes(CustomID,_Nr,DefaultAttrs,Nodes),
231 debug_format(19,'Processing CUSTOM_GRAPH_NODES ~w~n',[CustomID]),
232 (var(Nodes) -> add_error(cg_node_predicate,'Variable custom_nodes: ',Nodes),fail ; true),
233 get_node_attributes(Nodes,DefaultAttrs,NodeVal,Attributes),
234 %tools:print_message(node(NodeVal,ColVal,Color,NodeID)),
235 gen_id(NodeVal,NodeID). %tools:print_message(nodeid(NodeID)).
236
237 get_node_attributes(CustomNodes,DefaultAttrs,NodeVal,[label/NodeDesc|Attrs]) :-
238 %(member(style/DefStyle,DefaultAttrs) -> true ; DefStyle=filled), % to do: extract multiple styles
239 %(member(shape/DefShape,DefaultAttrs) -> true ; DefShape=box),
240 member(Node,CustomNodes),
241 deconstruct_node(Node,DefaultAttrs,NodeVal,Attrs,Label),
242 (Label\='$default_no_label' -> NodeDesc=Label
243 ; translate_value_and_escape(NodeVal,NodeDesc) -> true
244 ; NodeDesc = '???').
245
246
247 deconstruct_node(((NodeVal,string(Shape)),ColVal),DefaultAttrs,ResNodeVal,Attrs,'$default_no_label') :-
248 valid_dot_shape(Shape),
249 !, % we have a triple (Node,"box","colour") = ((Node,"box"),"colour")
250 ResNodeVal=NodeVal,
251 translate_bvalue_to_colour(ColVal,ResColVal),
252 add_default_attrs(DefaultAttrs,[color/ResColVal,shape/Shape],Attrs).
253 deconstruct_node((NodeVal,string(ColVal)),DefaultAttrs,ResNodeVal,Attrs,'$default_no_label') :-
254 valid_rgb_color(ColVal),
255 !, % we have a pair (Node,"colour")
256 ResNodeVal=NodeVal,
257 add_default_attrs(DefaultAttrs,[color/ColVal],Attrs).
258 deconstruct_node((NodeVal,string(Shape)),DefaultAttrs,ResNodeVal, Attrs,'$default_no_label') :-
259 valid_dot_shape(Shape),
260 !, % we have a pair (Node,"shape")
261 ResNodeVal=NodeVal,
262 add_default_attrs(DefaultAttrs,[shape/Shape],Attrs).
263 deconstruct_node(Record,DefaultAttrs,NodeVal,
264 Attrs,Label) :-
265 % New record style: this should probably be the default now
266 extract_record_fields(Record,AllFields,DotAttributes,RemainingVals),
267 DotAttributes = [_|_], % at least one field recognised
268 ( RemainingVals = [field(_,NodeVal)] -> true
269 ; member(field(value,NodeVal),RemainingVals) -> true
270 ; member(field(id,NodeVal),RemainingVals) -> true % like in VisB / SVG; note is also a valid dot_attribute_field
271 ; RemainingVals=[], member(label/_,DotAttributes)
272 -> member(field(label,NodeVal),AllFields) % get original value before translation to string
273 % but using label as node value may not be ideal for linking edges, better use separate value/id field
274 ; RemainingVals \= [] -> add_warning(state_custom_dot_graph,'Node has no label or value field:',Record,Record),
275 NodeVal = rec(RemainingVals)
276 ),
277 !,
278 % add default values if not specified:
279 ( select(label/LB,DotAttributes,Attrs0) -> Label=LB
280 ; select(description/LB,DotAttributes,Attrs0) -> Label=LB
281 ; Label = '$default_no_label', Attrs0=DotAttributes),
282 add_default_attrs(DefaultAttrs,Attrs0,Attrs).
283 deconstruct_node(NodeVal,DefaultAttrs,NodeVal,Attrs,'$default_no_label') :-
284 (DefaultAttrs=[]
285 -> format('CUSTOM_GRAPH_NODES value not recognised:~w~nUse rec(label:L,shape:"rect",...)~n',[NodeVal])
286 ; true % user has provided attributes in outer rec(...) construct
287 ),
288 add_default_attrs(DefaultAttrs,[],Attrs). % will remove none attributes
289 % shapes: triangle,ellipse,box,diamond,hexagon,octagon,house,invtriangle,invhouse,invtrapez,doubleoctagon,egg,parallelogram,pentagon,trapezium...
290
291 add_default_attrs([],Attrs,Attrs).
292 add_default_attrs([Attr/Val|T],Attrs,ResAttrs) :-
293 (member(Attr/_,Attrs) -> add_default_attrs(T,Attrs,ResAttrs)
294 ; Val=none, \+ none_valid(Attr) -> add_default_attrs(T,Attrs,ResAttrs) % Should we completely remove this line?
295 ; ResAttrs = [Attr/Val|RT],
296 add_default_attrs(T,Attrs,RT)).
297
298 % none is valid for arrowhead, arrowtail, ... and is different from default:
299 none_valid(arrowhead).
300 none_valid(arrowtail).
301
302 :- dynamic custom_node_id/2, nodectr/1.
303 nodectr(0).
304
305 get_ctr(Res) :- retract(nodectr(C)),!, C1 is C+1, assertz(nodectr(C1)), Res=C.
306 get_ctr(0) :- assertz(nodectr(1)).
307
308 % generate or lookup ID for node
309 gen_id(NodeVal,ID) :-
310 (custom_node_id(NodeVal,ID) -> true
311 ; get_ctr(C),
312 assertz(custom_node_id(NodeVal,C)),ID=C).
313
314 % TODO: hash NodeVal to improve performance for large graphs
315
316 % lookup ID and generate message if it does not exist
317 lookup_id(NodeVal,Kind,ID) :-
318 (custom_node_id(NodeVal,ID)
319 -> true
320 ; get_as_string(NodeVal,VS),
321 format(user_error,'The ~w node does not exist in CUSTOM_GRAPH_NODES: ~w~n',[Kind,VS]),
322 %format('Internal value: ~w~n',[NodeVal]), portray_nodes,
323 string_escape(VS,VSC),
324 assertz(custom_node_id(NodeVal,VSC)),
325 ID=VSC
326 ).
327
328 :- public portray_nodes/0.
329 portray_nodes :- format(' ~w : ~w (~w)~n',['ID','Value','internal value']),
330 custom_node_id(NodeVal,ID),
331 translate_bvalue(NodeVal,NS),
332 format(' ~w : ~w (~w)~n',[ID,NS,NodeVal]),fail.
333 portray_nodes.
334
335 :- dynamic custom_trans_label/2, trans_ctr/1.
336 trans_ctr(1).
337 gen_trans_color(Label,Col,_,IsColor) :- custom_trans_label(Label,C),!,Col=C,IsColor=false.
338 gen_trans_color(_Label,Col,Infos,IsColor) :- member(color/C,Infos),!,Col=C,IsColor=false.
339 gen_trans_color(Label,Col,_,IsColor) :- try_translate_bvalue_to_colour(Label,C),!,Col=C,IsColor=true.
340 gen_trans_color(Label,Col,_,IsColor) :- retract(trans_ctr(Ctr)), C1 is Ctr+1, assertz(trans_ctr(C1)),
341 translate_bvalue_to_colour(int(Ctr),C),
342 assertz(custom_trans_label(Label,C)), Col=C,IsColor=false.
343
344 :- public cg_trans_predicate/3.
345 % Custom graph transition predicate for dot_graph_generator.pl
346 cg_trans_predicate(NodeID,SuccID,DotAttributes) :-
347 custom_edges(DefaultLabel,_,Infos,Edges),
348 (member(color/DefaultCol,Infos) -> true ; DefaultCol=blue),
349 (var(Edges) -> add_error(trans_predicate,'Variable custom_edges: ',Edges),fail ; true),
350 member(Edge,Edges),
351 cg_trans_aux(Edge,(DefaultLabel,DefaultCol),Infos,FromNode,ToNode,Attrs),
352 add_default_attrs(Infos,Attrs,DotAttributes),
353 lookup_id(FromNode,source,NodeID),
354 lookup_id(ToNode,target,SuccID).
355
356 cg_trans_aux(Pair,DefaultLabelCol,Infos, FromNode,ToNode,[label/Label, color/Colour]) :-
357 get_pair(Pair,From1,To2),!,
358 trans_pred_aux(From1,DefaultLabelCol,Infos,To2,FromNode,ToNode,Label,Colour).
359 cg_trans_aux(Record,(DefaultLabel,DefaultCol),_Infos, FromNode,ToNode,Attrs) :-
360 extract_record_fields(Record,_,DotAttrs,Vals),
361 get_from_to(Vals,FromNode,ToNode),
362 add_default_attrs([color/DefaultCol,label/DefaultLabel],DotAttrs,Attrs).
363
364 :- use_module(probsrc(specfile), [animation_minor_mode/1]).
365 :- use_module(probsrc(custom_explicit_sets),[singleton_set/2]).
366 get_pair((From,To),From,To).
367 get_pair(Set,From,To) :- % process optional values, e.g., in Alloy
368 singleton_set(Set,El), !, El = (From,To).
369 get_pair(avl_set(A),From,To) :- % process TLA+ tuples <<from,to>> translated to -> {(1,From),(2,To)}
370 animation_minor_mode(tla),
371 try_expand_custom_set_with_catch(avl_set(A), FunctionRes,get_pair),
372 FunctionRes = [(int(1),From),(int(2),To)].
373
374
375 get_from_to(Vals,FromNode,ToNode) :- % either two field from:Fromnode, to:ToNode
376 member(field(from,FromNode),Vals),!,
377 member(field(to,ToNode),Vals).
378 get_from_to(Vals,FromNode,ToNode) :- % or a single edge field which is a pair
379 member(field(edge,(FromNode,ToNode)),Vals),!.
380
381
382 % we have a transition where the colour is specified (e.g., CUSTOM_GRAPH_EDGES == {n1,col,n2 | ... }
383 % we assume trans_predicate has been called first
384 trans_pred_aux((From,To),(Label,_Col),_Infos,string(Colour),From,To,Label,Colour) :-
385 To \= string(_), % we are not in one of the cases below where To is a label
386 valid_rgb_color(Colour),!.
387 % stems from something like CUSTOM_GRAPH_EDGES1 == graph*{"red"};
388 trans_pred_aux(FromValue,_Defaults,Infos,To,From,To,ELabel,Color) :-
389 % format(user_output,'from: ~w~nto: ~w~n~n',[FromValue,To]),
390 % ((From,LabelCol) |-> To) case
391 get_trans_label_and_color(FromValue,Infos,From,Color,ELabel), % the From Value contains color and label
392 !.
393 trans_pred_aux(From,_Defaults,Infos,(LabelVal,ToVal),From,To,ELabel,Color) :-
394 % (From |-> (LabelCol,To)) case
395 get_trans_label_and_color((ToVal,LabelVal),Infos,To,Color,ELabel), % the To Value contains color and label
396 !.
397 trans_pred_aux(From,(Label,Col),_Infos,To,From,To,Label,Col).
398 % no label or color specified in transition pair
399
400
401 % try and decompose a from value, detecting color and label string ((From,"label"),"color")
402 get_trans_label_and_color((FromVal,LabelVal),Infos,From,Color,ELabel) :-
403 gen_trans_color(LabelVal,Color,Infos,IsColor),
404 (IsColor
405 -> get_trans_label(FromVal,From,ELabel)
406 ; translate_value_and_escape(LabelVal,ELabel),
407 From=FromVal
408 ).
409
410 get_trans_label((From,string(LabelVal)),From,ELabel) :- string_escape(LabelVal,ELabel).
411 get_trans_label(From,From,''). % or should we use DefaultLabel
412
413 translate_value_and_escape(string(S),ELabel) :- !, string_escape(S,ELabel).
414 translate_value_and_escape(LabelVal,ELabel) :-
415 translate:translate_bvalue(LabelVal,Label),string_escape(Label,ELabel).
416
417 :- use_module(probsrc(b_interpreter),[b_compute_explicit_epression_no_wf/6]).
418 :- use_module(probsrc(custom_explicit_sets),[try_expand_custom_set_with_catch/3]).
419 :- use_module(probsrc(bsyntaxtree),[get_texpr_info/2]).
420
421 % Kind = edges, nodes, graph
422 eval_definition_fun(AnimFunction, Nr, Kind, BState, SInfos, FunctionRes) :-
423 b_compute_explicit_epression_no_wf(AnimFunction,[],BState,FunctionResCl,'custom state graph',Nr),
424 %nl, print('FunctionResult'(FunctionResCl)),nl,
425 get_texpr_info(AnimFunction,Pos),
426 extract_dot_function(FunctionResCl, Kind, Infos, FunctionRes,Pos),
427 sort(Infos,SInfos).
428
429 extract_dot_function(Value,graph,Attributes,FunctionRes,Pos) :- !, % for CUSTOM_GRAPH
430 extract_dot_graph_function(Value,Attributes,OtherFields,Pos),
431 FunctionRes=rec(OtherFields). % list of fields
432 extract_dot_function((Label,Value),Kind,Info,FunctionRes,Pos) :- % treat e.g. == ("red","F",F)
433 extract_dot_info(Label,Info,Info2),!,
434 extract_dot_function(Value,Kind,Info2,FunctionRes,Pos).
435 extract_dot_function((Value,Label),Kind,Info,FunctionRes,Pos) :-
436 extract_dot_info(Label,Info,Info2),!,
437 extract_dot_function(Value,Kind,Info2,FunctionRes,Pos).
438 extract_dot_function(Record,Kind,DefaultDotAttributes,FunctionRes,Pos) :-
439 % treat e.g. CUSTOM_GRAPH_NODES == rec(color:"blue", shape:"rect", nodes:e);
440 % or CUSTOM_GRAPH_EDGES == rec(color:"red", style:"dotted", edges:e);
441 % or CUSTOM_GRAPH_NODES == rec(color:"blue", label:"root", shape:"")
442 extract_record_fields(Record,_,DotAttributes,Vals),
443 !,
444 ( select(field(ValAttr,Value),Vals,RestVals),
445 member(ValAttr,[value, Kind]) % there is a value, edge, nodes field
446 % this a record setting attributes for a set of nodes/edges
447 -> DefaultDotAttributes = DotAttributes,
448 try_expand_custom_set_with_catch(Value, FunctionRes,extract_dot_function),
449 (RestVals=[] -> true ; add_message(state_custom_dot_graph,'Unrecognised DOT attributes: ',RestVals,Pos))
450 ; member(label/_,DotAttributes) % in case we have a label this represents a single record
451 -> FunctionRes = [Record], DefaultDotAttributes=[]
452 ; add_message(state_custom_graph,'CUSTOM_GRAPH record not ok (use fields color,shape,style,description and either label, edges or nodes): ',Vals,Pos),
453 fail
454 ).
455 extract_dot_function(Value,_Kind,[],FunctionRes,_) :-
456 % we try to see if it is a set of nodes (ideally as records) or edges
457 try_expand_custom_set_with_catch(Value,FunctionRes,extract_dot_function),!.
458 extract_dot_function(Value,_Kind,_,_,Pos) :-
459 add_warning(state_custom_dot_graph,'Illegal CUSTOM_GRAPH value (use rec(label:L,shape:,...) record or set of records): ',Value,Pos),
460 fail.
461
462 % treat single CUSTOM_GRAPH record:
463 extract_dot_graph_function(Record,Attributes,OtherFields,_) :-
464 extract_record_fields(Record,_,Attributes,OtherFields),!.
465 extract_dot_graph_function(Value,_,_,Pos) :-
466 add_warning(state_custom_dot_graph,'Illegal CUSTOM_GRAPH value, use record',Value,Pos),
467 fail.
468
469 :- use_module(probsrc(translate), [translate_bvalue/2]).
470 % extract dot information from a B value, if it looks like a color, shape, style or label
471 extract_dot_info((A,B),I0,I2) :- !, extract_dot_info(A,I0,I1), extract_dot_info(B,I1,I2).
472 extract_dot_info(string(Str),[Type/Str|T],T) :-
473 (infer_string_type(Str,Type) -> true ; Type=label).
474 extract_dot_info(Value,[label/Str|T],T) :- simple_label_value(Value),
475 translate_bvalue(Value,Str).
476 simple_label_value(fd(_,_)).
477
478 infer_string_type(Str,color) :- valid_rgb_color(Str).
479 infer_string_type(Str,shape) :- valid_dot_shape(Str).
480 infer_string_type(Str,style) :- valid_dot_line_style(Str).
481 infer_string_type(Str,style) :- valid_dot_node_style(Str).
482
483 % get record fields from record or partial function STRING +-> STRING
484 % and select dot attributes
485
486 extract_record_fields(Record,Fields,DotAttributes,OtherFields) :-
487 flexible_get_record_fields(Record,Fields),
488 extract_info_from_fields(Fields,DotAttributes,OtherFields).
489
490 :- use_module(library(lists),[maplist/3]).
491 :- use_module(probsrc(custom_explicit_sets),[try_expand_custom_set_with_catch/3, is_set_value/2]).
492 % similar to get_VISB_record_fields
493 flexible_get_record_fields(rec(Fields),Res) :- !, Res=Fields.
494 flexible_get_record_fields(StringFunction,Fields) :-
495 is_set_value(StringFunction,flexible_get_record_fields),
496 try_expand_custom_set_with_catch(StringFunction,Expanded,get_visb_DEFINITION_svg_object),
497 % TODO: check we have no duplicates
498 maplist(convert_to_field,Expanded,Fields).
499 convert_to_field((string(FieldName),Value),field(FieldName,Value)).
500
501 extract_info_from_fields([],[],[]).
502 extract_info_from_fields([field(FName,FVAL)|TF],[Type/Str|TI],Val) :-
503 dot_attribute_field(FName,Type),!,
504 ( \+ checked_attribute(Type) -> get_as_string_for_attr(Type,FVAL,Str)
505 ; get_label_value(Type,FVAL,Str) -> true
506 ; add_message(state_custom_dot_graph,'Unexpected value for dot attribute: ',Type/FVAL),
507 get_as_string(FVAL,Str)
508 ),
509 extract_info_from_fields(TF,TI,Val).
510 extract_info_from_fields([field(FName,FVAL)|TF],[FName/EStr|TI],Val) :-
511 \+ definitely_not_dot_attribute(FName),
512 get_as_string(FVAL,Str),
513 !,
514 add_message(state_custom_dot_graph,'Assuming this is a dot attribute: ',FName/Str),
515 string_escape(Str,EStr),
516 extract_info_from_fields(TF,TI,Val).
517 extract_info_from_fields([F|TF],Info,[F|TVal]) :-
518 extract_info_from_fields(TF,Info,TVal).
519
520 get_label_value(label,Val,Str) :- !, get_as_string(Val,Str).
521 get_label_value(description,string(Str),Str).
522 get_label_value(Type,string(Str),Str) :- infer_string_type(Str,Type).
523 get_label_value(color,Val,Str) :- get_color(Val,Str).
524
525 get_color(string(Str),Val) :- !, Val=Str.
526 get_color(int(Nr),Val) :- !, Val=Nr. % numbers can be valid DOT colors when colorscheme provided
527 get_color(Val,Col) :-
528 try_translate_bvalue_to_colour(Val,Col). % from dot_graph_generator
529
530 checked_attribute(description).
531 checked_attribute(color).
532 checked_attribute(shape).
533 checked_attribute(style).
534
535 definitely_not_dot_attribute(edge).
536 definitely_not_dot_attribute(from).
537 definitely_not_dot_attribute(nodes).
538 definitely_not_dot_attribute(to).
539 definitely_not_dot_attribute(value).
540 definitely_not_dot_attribute(E) :- is_edges_field(E,_).
541 definitely_not_dot_attribute(N) :- is_nodes_field(N,_).
542
543 :- use_module(probsrc(kernel_strings),[to_b_string/2]).
544 get_as_string(BValue,Str) :- to_b_string(BValue,string(Str)).
545
546 % cf b_value_to_id_string in VisB; allow to use pairs to concatenate strings
547 get_as_id_string(string(SValue),Res) :- !, Res=SValue.
548 get_as_id_string((A,B),Res) :- !,
549 get_as_id_string(A,VA), get_as_id_string(B,VB), atom_concat(VA,VB,Res).
550 % TODO: maybe convert sequence of values using conc
551 get_as_id_string(FValue,Res) :- get_as_string(FValue,Res).
552
553 get_as_string_for_attr(id,Val,Str) :- !, get_as_id_string(Val,Str). % like SVG ids in VisB
554 get_as_string_for_attr(_,Val,Str) :- !, get_as_string(Val,Str).
555
556 :- use_module(probsrc(tools_matching),[is_dot_attribute/1]).
557 dot_attribute_field(colour,color).
558 dot_attribute_field(fontcolour,fontcolor).
559 dot_attribute_field(description,description). % virtual attribute
560 dot_attribute_field(stroke,style). % used in SVG
561 dot_attribute_field(Name,Name) :- is_dot_attribute(Name).
562
563