| 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 |