1 | % (c) 2009-2024 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(state_as_dot_graph,[ | |
6 | print_cstate_graph/2, print_cstate_graph/1]). | |
7 | ||
8 | :- use_module(probsrc(module_information)). | |
9 | :- module_info(group,dot). | |
10 | :- module_info(description,'Display canonical form of state graph using dot.'). | |
11 | ||
12 | :- use_module(extrasrc(state_graph_canon),[state_graph_for_dot/2]). | |
13 | ||
14 | ||
15 | /* ---------------------------- */ | |
16 | /* DOT generation */ | |
17 | /* ---------------------------- */ | |
18 | ||
19 | ||
20 | :- use_module(probsrc(error_manager)). | |
21 | :- use_module(probsrc(preferences)). | |
22 | :- use_module(probsrc(state_space),[current_expression/2]). | |
23 | :- use_module(probsrc(tools),[ajoin/2, string_escape/2]). | |
24 | ||
25 | print_cstate_graph(File) :- | |
26 | current_expression(_,State), | |
27 | print_cstate_graph(State,File). | |
28 | print_cstate_graph(State,File) :- | |
29 | state_graph_for_dot(State,GCE), | |
30 | open(File,write,Stream,[encoding(utf8)]), | |
31 | (graph2dot(Stream,GCE) -> close(Stream) | |
32 | ; close(Stream), | |
33 | add_error(graph_canon,'Dot conversion failed: ',graph2dot(File,GCE)) | |
34 | ). | |
35 | ||
36 | ||
37 | graph2dot(Stream,G) :- reset_dot, | |
38 | (get_preference(dot_horizontal_layout,true) -> RDir='LR'; RDir='TB'), | |
39 | PSize='', %(get_preference(dot_with_page_size,true) -> PSize='page="8.5, 11",ratio=fill,size="7.5,10",' ; PSize=''), | |
40 | format(Stream,'digraph state {\n graph [~wfontsize=12]\nrankdir=~w;~n',[PSize,RDir]), | |
41 | edges2dot(G,Stream), | |
42 | nodes2dot(Stream), | |
43 | findall(cluster(Ty,El),cluster(Ty,El),T), | |
44 | clusters2dot(T,Stream), | |
45 | format(Stream,'}~n',[]). | |
46 | ||
47 | :- use_module(probsrc(b_global_sets)). | |
48 | cluster(Type,Elements) :- b_global_set(Type), | |
49 | % compute all elements of global sets which are used in current state graph | |
50 | findall(Translation, (enum_global_type_limited(El,Type), node_col(El,Translation,_,_)),Elements). | |
51 | % maybe add boolean ?? | |
52 | edges2dot([],_). | |
53 | edges2dot([(A,B,C)|T],Stream) :- /* edge from A -> C with label B */ | |
54 | translate_node_value(A,AID), | |
55 | (B = abs_cn(BB,ColBB) | |
56 | -> translate_abs_cn_node(BB,ColBB,EdgeLabel) | |
57 | ; EdgeLabel = B), | |
58 | translate_node_value(C,CID), | |
59 | get_col_assoc(edge,EdgeLabel,EdgCol), | |
60 | format(Stream,'\"~w\" -> \"~w\" [label = \"~w\", color = \"~w\"];\n', | |
61 | [AID,CID,EdgeLabel,EdgCol]), | |
62 | edges2dot(T,Stream). | |
63 | ||
64 | reset_dot :- retractall(col_association(_,_,_)), | |
65 | retractall(node_col(_,_,_,_)), | |
66 | retractall(cur_col(_,_)), | |
67 | assertz(cur_col(node,0)), assertz(cur_col(edge,99)). | |
68 | ||
69 | :- use_module(probsrc(translate),[translate_bvalue_for_dot/2]). | |
70 | translate_node_value(abs_cn(AA,ColAA), Translation) :- !, | |
71 | translate_abs_cn_node(AA,ColAA,Translation). | |
72 | translate_node_value(sg_root,Translation) :- !, | |
73 | Translation = 'ROOT-NODE', add_node(sg_root,Translation). | |
74 | translate_node_value(Val,Translation) :- | |
75 | translate_bvalue_for_dot(Val,Translation), | |
76 | add_node(Val,Translation). | |
77 | ||
78 | nodes2dot(Stream) :- node_col(_,Translation,Col,Shape), | |
79 | (Shape=record(Label) | |
80 | -> format(Stream,'\"~w\" [shape=record, label=\"~w\", color = \"~w\", style = \"filled, solid\"]\n', | |
81 | [Translation,Label,Col]) | |
82 | ; format(Stream,'\"~w\" [color = \"~w\", style = \"filled, solid\", shape = \"~w\"]\n',[Translation,Col,Shape]) | |
83 | ), | |
84 | fail. | |
85 | nodes2dot(_). | |
86 | ||
87 | :- use_module(probsrc(kernel_reals),[is_real/2]). | |
88 | :- dynamic node_col/4. | |
89 | add_node(X,_) :- node_col(X,_,_,_),!. % already processed | |
90 | add_node(sg_root,Translation) :- | |
91 | assertz(node_col(sg_root,Translation,lightblue,diamond)). | |
92 | add_node(fd(X,Type),Translation) :- !, | |
93 | get_col_assoc(node,Type,Col), | |
94 | get_preference(dot_normal_node_shape,Shape), | |
95 | assertz(node_col(fd(X,Type),Translation,Col,Shape)). | |
96 | add_node(int(X),Translation) :- !, | |
97 | Col = 'wheat3', %get_col_assoc(node,integer,Col), | |
98 | get_preference(dot_normal_node_shape,Shape), | |
99 | assertz(node_col(int(X),Translation,Col,Shape)). | |
100 | add_node(string(X),Translation) :- !, | |
101 | Col = 'khaki1', %'lavender' 'burlywood', %get_col_assoc(node,integer,Col), | |
102 | get_preference(dot_normal_node_shape,Shape), | |
103 | assertz(node_col(string(X),Translation,Col,Shape)). | |
104 | add_node(pred_true,T) :- !, assertz(node_col(pred_true,T,'OliveDrab4',ellipse)). | |
105 | add_node(pred_false,T) :- !, assertz(node_col(pred_false,T,brown,ellipse)). | |
106 | add_node(RecordOrPair,Translation) :- get_fields(RecordOrPair,Fields),!, | |
107 | translate_fields(Fields,Atoms), | |
108 | ajoin(['|{ ' | Atoms], RecTranslation), | |
109 | assertz(node_col(RecordOrPair,Translation,burlywood,record(RecTranslation))). % TO DO: maybe use dot record | |
110 | add_node(Val,Translation) :- is_set(Val),!, | |
111 | Col = 'LightSteelBlue1', %get_col_assoc(node,integer,Col), | |
112 | get_preference(dot_normal_node_shape,Shape), | |
113 | assertz(node_col(Val,Translation,Col,Shape)). | |
114 | add_node(Term,Translation) :- is_real(Term,_),!, | |
115 | Col = 'wheat2', %get_col_assoc(node,integer,Col), | |
116 | get_preference(dot_normal_node_shape,Shape), | |
117 | assertz(node_col(Term,Translation,Col,Shape)). | |
118 | add_node(_Val,_Translation). | |
119 | ||
120 | % copied from state_graph_canon: | |
121 | list_set([]). | |
122 | list_set([_|_]). | |
123 | is_set(avl_set(_)). | |
124 | is_set(X) :- list_set(X). | |
125 | ||
126 | get_fields(rec(Fields),Fields). | |
127 | get_fields((A,B),Fields) :- get_pair_fields((A,B),Fields,[]). | |
128 | ||
129 | get_pair_fields((A,B)) --> !, get_pair_fields(A), get_pair_fields(B). | |
130 | get_pair_fields(A) --> [field(prj,A)]. | |
131 | ||
132 | translate_fields([],[' }|']). | |
133 | translate_fields([field(Name,Value)],Res) :- !, | |
134 | trans_field(Name,VS,Res,[' }|']), | |
135 | translate_bvalue_for_dot(Value,VS). | |
136 | translate_fields([field(Name,Value)|T],Res) :- | |
137 | trans_field(Name,VS,Res,['|' | Rest]), | |
138 | translate_bvalue_for_dot(Value,VS), | |
139 | translate_fields(T,Rest). | |
140 | ||
141 | trans_field(prj,VS, [' { ', VS, ' } ' | Rest], Rest) :- !. | |
142 | trans_field(Name,VS, [' { ',Name,' | ', VS, ' } ' | Rest], Rest). | |
143 | ||
144 | :- dynamic col_association/3. % store colours used for types, edge labels,... | |
145 | get_col_assoc(Domain,T,Col) :- col_association(Domain,T,Col),!. | |
146 | get_col_assoc(Domain,T,Col) :- get_next_col(Domain,Col), | |
147 | assertz(col_association(Domain,T,Col)). | |
148 | ||
149 | get_next_col(Type,Col) :- retract(cur_col(Type,N)),!, N1 is N+1, | |
150 | (default_colours(N1,Col) -> NX=N1 | |
151 | ; NX = 1, default_colours(1,Col)), | |
152 | assertz(cur_col(Type,NX)). | |
153 | get_next_col(_T,red). | |
154 | ||
155 | :- dynamic cur_col/2. | |
156 | cur_col(node,0). | |
157 | cur_col(edge,99). | |
158 | default_colours(1,'#efdf84'). | |
159 | default_colours(2,'#bdef6b'). | |
160 | default_colours(3,'#5863ee'). | |
161 | default_colours(4,'LightSteelBlue1'). | |
162 | default_colours(5,gray). | |
163 | ||
164 | default_colours(100,firebrick). | |
165 | default_colours(101,sienna). | |
166 | default_colours(102,'SlateBlue4'). | |
167 | default_colours(103,black). | |
168 | ||
169 | translate_abs_cn_node(AA,ColAA,Translation) :- | |
170 | tools:string_concatenate('_',AA,AID1), | |
171 | tools:string_concatenate(ColAA,AID1,AID2), | |
172 | tools:string_concatenate('abs',AID2,Translation). | |
173 | ||
174 | clusters2dot([],_). | |
175 | clusters2dot([cluster(Name,Elements)|T],Stream) :- | |
176 | string_escape(Name,EscName), | |
177 | format(Stream,'subgraph \"cluster_~w\" {node [style=filled,color=white]; label=\"~w\"; style=filled;color=lightgrey; ',[EscName,EscName]), | |
178 | cnodes2dot(Elements,Stream), | |
179 | clusters2dot(T,Stream). | |
180 | ||
181 | :- use_module(probsrc(tools),[string_escape/2]). | |
182 | cnodes2dot([],Stream) :- format(Stream,'}~n',[]). | |
183 | cnodes2dot([H|T],Stream) :- | |
184 | translate:translate_params_for_dot([H],HP), | |
185 | string_escape(HP,HPE), | |
186 | format(Stream,'~w; ',[HPE]), | |
187 | cnodes2dot(T,Stream). |