1 % (c) 2014-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(table_tools, [expand_and_translate_to_table/3,
6 expand_and_translate_to_table_for_expr/5,
7 print_table/4, print_table/2,
8 print_value_as_table/4, print_value_as_table/2
9 ]).
10
11 :- use_module(probsrc(module_information),[module_info/2]).
12 :- module_info(group,pltables).
13 :- module_info(description,'This module provides utilities to print tables.').
14
15 :- use_module(probsrc(translate)).
16 :- use_module(probsrc(tools_strings),[ajoin/2]).
17 :- use_module(library(lists)).
18 :- use_module(probsrc(self_check)).
19 :- use_module(probsrc(error_manager)).
20
21 print_value_as_table(Expr,Value) :- print_value_as_table(user_output,Expr,Value,[]).
22
23 print_value_as_table(Stream,Expr,Value,Opts) :-
24 expand_and_translate_to_table(Value,Header,Table,Opts),
25 print_value_as_table2(Stream,Expr,Header,Table,Opts).
26
27 print_value_as_table2(Stream,Expr,Header,Table,Opts) :-
28 get_header_from_expression(Expr,ExprHeader,Opts),
29 % check if ExprHeader is valid:
30 same_length(Header,ExprHeader),
31 !,
32 print_table(Stream,ExprHeader,Table,Opts).
33 print_value_as_table2(Stream,_Expr,Header,Table,Opts) :-
34 print_table(Stream,Header,Table,Opts).
35
36 :- use_module(probsrc(tools),[latex_escape_atom/2]).
37 get_header_from_expression(TExpr,ExprHeader,Opts) :-
38 get_texpr_header_ids(TExpr,toplevel,Hs),
39 ? (member(latex,Opts) -> maplist(latex_escape_atom,Hs,Header) ; Header=Hs),
40 (member('no-row-numbers',Opts) -> ExprHeader = Header ; ExprHeader = ['Nr'|Header]).
41
42 get_texpr_header_ids(b(E,Type,_),Top,IDs) :- get_expr_header_ids(E,Type,Top,IDs).
43 get_expr_header_ids(comprehension_set([_TID1],_),set(record(FieldTypes)),toplevel,IDs) :- !,
44 % special case for set of records
45 findall(Field,member(field(Field,_),FieldTypes),IDs).
46 get_expr_header_ids(comprehension_set(TIDs,_),_,toplevel,IDs) :- !,get_texpr_original_ids(TIDs,IDs).
47 get_expr_header_ids(identifier(OID),Type,Infos,[ID]) :-
48 get_texpr_original_id2(OID,Infos,ID),
49 single_col_type(Type). % otherwise the column will be split e.g. using prj1/2,... into multiple ones
50 get_expr_header_ids(couple(A,B),_,_,IDs) :-
51 get_texpr_header_ids(A,inner,I1), get_texpr_header_ids(B,inner,I2),
52 % note: the same id/field-name can occur in left and right; in principle we should add path info
53 append(I1,I2,IDs).
54 get_expr_header_ids(rec(FieldTypes),_,_,IDs) :-
55 findall(Header,(member(field(Field,Val),FieldTypes),
56 (get_texpr_header_ids(Val,inner,InnerIds)
57 -> member(II,InnerIds),ajoin([Field,'.',II],Header)
58 ; Header=Field)), IDs).
59
60
61 get_texpr_original_ids(TIDs,IDs) :-
62 % variation of get_texpr_original_ids, to rewrite _lambda_result_ into original form before detect_lambdas
63 % relevant for test 1840
64 maplist(get_texpr_original_id,TIDs,IDs).
65 get_texpr_original_id(b(identifier(OID),_,I),ID) :- get_texpr_original_id2(OID,I,ID).
66 get_texpr_original_id2(OID,Infos,ID) :-
67 ? (member(lambda_result_id_was(ID),Infos) -> true ; ID=OID).
68
69 single_col_type(integer).
70 single_col_type(boolean).
71 single_col_type(string).
72 single_col_type(global(_)).
73 single_col_type(set(X)) :- single_col_type(X). % set is ok if outer ID is not the only one in a couple; TO DO: merge header and value extraction in a single predicate to avoid such issues
74
75 % expand a SetValue into a Header List and a Table List
76
77 expand_and_translate_to_table_for_expr(TypedExpr,Value,Header,ResTable,Opts) :-
78 (get_header_from_expression(TypedExpr,Header,Opts) -> true ; true), % try and get simpler header from typed expression ids and field names
79 (expand_and_translate_to_table(Value,Header,Table,Opts)
80 -> ResTable=Table
81 ; add_internal_error('Failed:',expand_and_translate_to_table_for_expr(TypedExpr,Value,Header,ResTable,Opts)),
82 ResTable=[]
83 ).
84
85 :- assert_must_succeed(( expand_and_translate_to_table([rec([field(a,int(1)),field(b,int(2))])],H,T),
86 H == ['Nr',a,b], T==[list([1,'1','2'])] )).
87 :- assert_must_succeed(( expand_and_translate_to_table([(int(1),int(2)),(int(33),int(44))],H,T),
88 H == ['Nr',prj1,prj2], T==[list([1,'1','2']),list([2,'33','44'])] )).
89 :- assert_must_succeed(( expand_and_translate_to_table([(int(1),rec([field(f,int(2)),field(g,int(3))]))],H,T),
90 H==['Nr',prj1,'prj2\'f','prj2\'g'], T==[list([1,'1','2','3'])] )).
91 expand_and_translate_to_table(Value,Header,Table) :-
92 expand_and_translate_to_table(Value,Header,Table,[]).
93 expand_and_translate_to_table(Value,Header,Table,Opts) :-
94 expand(Value, ExpVal),
95 translate_to_table(ExpVal,1,Table,Header,Opts). %, print(table(Res)),nl.
96
97 :- use_module(probsrc(custom_explicit_sets),[is_custom_explicit_set/1,try_expand_custom_set_with_catch/3]).
98 expand(Var,Res) :- var(Var),!,Res=[Var].
99 expand((A,B),[(A,B)]) :- !. % wrap into a singleton set
100 expand(rec(Fields),[rec(Fields)]) :- !. % wrap into a singleton set
101 expand(Value,ExpVal) :- is_custom_explicit_set(Value),!,
102 try_expand_custom_set_with_catch(Value, ExpVal,table_tools).
103 expand([H|T],V) :- !, V=[H|T].
104 expand([],V) :- !, V=[].
105 expand(Val,[Val]).
106
107
108 set_header([Header|T],[El|TE]) :- !,
109 (set_header_el(Header,El) -> true ; add_warning(table_tools,'set_header failed: ',Header:El)),
110 set_header(T,TE).
111 set_header([],TE) :- !, (TE=[] -> true ; add_warning(table_tools,'set_header extra elements: ',TE)).
112 set_header(Hs,[]) :- !, add_warning(table_tools,'set_header extra headers: ',Hs).
113 set_header_el(H,_) :- atomic(H),!. % header already set by caller
114 set_header_el(Header,El) :- Header=El.
115
116 translate_to_table(A,RowNr,[list(L)],Header,Opts) :- var(A),!,
117 (member('no-row-numbers',Opts) -> set_header(Header,['Value']),L=AT
118 ; set_header(Header,['Nr','Value']),L=[RowNr|AT]),
119 translate:translate:translate_bvalue(A,AT).
120 translate_to_table([],_RowNr,[],Header,Opts) :- !,
121 (ground(Header) -> true % we already have a header
122 ; member('no-row-numbers',Opts) -> set_header(Header,['Elements']) ; set_header(Header,['Nr','Elements'])).
123 translate_to_table([A|T],RowNr,[list(LA)|TT],Header,Opts) :- member('no-row-numbers',Opts),
124 !,
125 get_table_entry(A,LA,Header,Opts),
126 R1 is RowNr+1,
127 translate_to_table(T,R1,TT,_,Opts).
128 translate_to_table([A|T],RowNr,[list([RowNr|LA])|TT],['Nr'|Header],Opts) :- !,
129 get_table_entry(A,LA,Header,Opts),
130 R1 is RowNr+1,
131 translate_to_table(T,R1,TT,_,Opts).
132 translate_to_table(A,_RowNr,[list(AT)],['Value'],Opts) :- member('no-row-numbers',Opts),!,
133 translate:translate:translate_bvalue(A,AT).
134 translate_to_table(A,RowNr,[list([RowNr|AT])],Header,_) :- !,
135 set_header(Header,['Nr','Value']),
136 translate:translate:translate_bvalue(A,AT).
137 translate_to_table(ExpVal,Row,Table,Header,Opts) :-
138 add_internal_error('Failed:',translate_to_table(ExpVal,Row,Table,Header,Opts)),fail.
139
140 :- use_module(library(lists),[maplist/3]).
141 get_table_entry(Var,LA,Header,_) :- var(Var),!, LA = [VT], set_header(Header,['prj1']),
142 translate:translate:translate_bvalue(Var,VT).
143 get_table_entry(rec(Fields),Vals,FieldNames,Opts) :-
144 maplist(get_record_field_val_name(Opts),Fields,Vals,FieldNames),!.
145 get_table_entry(A,[AT],Header,_) :- A \= (_,_),!,
146 set_header(Header,['Elements']),
147 translate:translate:translate_bvalue(A,AT).
148 get_table_entry(A,AT,HA,Opts) :- %A=(A1,A2), (A1 = (_,_) ; A2=(_,_)),
149 !,
150 get_table_entry2(A,[],AT,HA,Opts).
151 %get_table_entry((A,B),[AT,BT],['prj1','prj2']) :- !,
152 % translate:translate:translate_bvalue(A,AT),
153 % translate:translate:translate_bvalue(B,BT).
154
155 get_record_field_val_name(Opts,field(Name,Value),ValS,ColName) :-
156 (member(latex,Opts) -> latex_escape_atom(Name,ColName) ; ColName=Name),
157 translate:translate:translate_bvalue(Value,ValS).
158
159 get_table_entry2(A,Path,[AT],Header,_Opts) :- var(A),!,
160 reverse(Path,RP), ajoin(['prj'|RP],PRJRP), % to do: do all this more efficiently
161 set_header(Header,[PRJRP]),
162 translate:translate:translate_bvalue(A,AT).
163 get_table_entry2((A,B),Path,ABT,Header,Opts) :- !,
164 get_table_entry2(A,[1|Path],AT,HA,Opts),
165 get_table_entry2(B,[2|Path],BT,HB,Opts),
166 append(AT,BT,ABT), append(HA,HB,HAB),
167 % TO DO: fix issue when Header given and not same length as HAB, :table {lhs,rhs| lhs |-> rhs : delta} for PDA_to_CFG.mch
168 set_header(Header,HAB).
169 get_table_entry2(rec([field(Name,Val)|Rest]),Path,Values,Header,Opts) :- !,
170 (member(latex,Opts) -> latex_escape_atom(Name,ColName) ; ColName=Name),
171 get_table_entry2(Val,[ColName,'\''|Path],AT,HA,Opts),
172 (Rest=[] -> Values=AT, HAB=HA
173 ; get_table_entry2(rec(Rest),Path,BT,HB,Opts),
174 append(AT,BT,Values), append(HA,HB,HAB)
175 ), set_header(Header,HAB).
176 get_table_entry2(A,Path,[AT],Header,_Opts) :- !,
177 reverse(Path,RP), ajoin(['prj'|RP],PRJRP), % to do: do all this more efficiently
178 set_header(Header,[PRJRP]),
179 translate:translate:translate_bvalue(A,AT).
180
181
182 valid_option(latex).
183 valid_option('no-tabular').
184 valid_option('no-hline').
185 valid_option('no-headings').
186 valid_option('no-row-numbers').
187 valid_option(argument_value('max-table-size',_)).
188 check_option(O) :- (valid_option(O) -> true ; add_error(table_tools,'Illegal option:',O)).
189
190
191 print_table(Header,Table) :- print_table(user_output,Header,Table,[]).
192
193 print_table(Stream,Header,Table,Opts) :-
194 maplist(check_option,Opts),
195 (member(argument_value('max-table-size',Sze),Opts)
196 -> split_list(Table,Sze,Tables), % split into individual tables, each with own header; useful for Latex
197 maplist(print_table_aux_nl(Stream,Header,Opts),Tables)
198 ; print_table_aux(Stream,Header,Opts,Table)
199 ).
200 print_table_aux_nl(Stream,Header,Opts,Table) :- print_table_aux(Stream,Header,Opts,Table),nl(Stream).
201
202 print_table_aux(Stream,Header,Opts,Table) :-
203 print_table_header(Stream,Header,Opts),
204 ? (member('no-headings',Opts) -> true ;
205 print_row(Stream,Opts,list(Header)),
206 print_header_terminator(Stream,Opts)
207 ),
208 maplist(print_row(Stream,Opts),Table),
209 print_table_footer(Stream,Opts).
210
211
212 print_row(Stream,Opts,list(Row)) :-
213 print_row_aux(Row,Stream,Opts),
214 print_row_terminator(Stream,Opts).
215
216 print_row_aux(V,Stream,_Opts) :- var(V),!, format(Stream,'~w ... ',[V]).
217 print_row_aux([],_,_) :- !.
218 print_row_aux([H|T],Stream,Opts) :- !, print_entry(Stream,H,T,Opts),
219 print_row_aux(T,Stream,Opts).
220 print_row_aux(list(L),Stream,Opts) :- !, % alternative format also for Tcl/Tk
221 print_row_aux(L,Stream,Opts).
222 print_row_aux(Other,Stream,_Opts) :- format(Stream,'~w ... ',[Other]).
223
224 get_latex_col(_,108). % 99 is lower-case l
225 %get_latex_col(_,99). % 99 is lower-case c
226
227 print_table_header(Stream,list(Header),Opts) :- !,
228 print_table_header(Stream,Header,Opts).
229 ?print_table_header(Stream,Header,Opts) :- member(latex,Opts),
230 nonmember('no-tabular',Opts),
231 !,
232 maplist(get_latex_col,Header,LatexColSpec),
233 format(Stream,'\\begin{tabular}{~s}~n',[LatexColSpec]),
234 hline(Stream,Opts).
235 print_table_header(_,_,_).
236
237 ?print_row_terminator(Stream,Opts) :- member(latex,Opts),!, format(Stream,'\\\\~n',[]).
238 print_row_terminator(Stream,_) :- nl(Stream).
239
240 ?print_header_terminator(Stream,Opts) :- member(latex,Opts),!,
241 format('\\\\~n',[]),
242 hline(Stream,Opts).
243 print_header_terminator(_,_).
244
245 ?print_table_footer(Stream,Opts) :- member(latex,Opts),
246 nonmember('no-tabular',Opts),
247 !,
248 hline(Stream,Opts),
249 format(Stream,'\\end{tabular}~n',[]).
250 print_table_footer(_,_).
251
252 hline(_,Opts) :- member('no-hline',Opts),!.
253 hline(Stream,_) :- format(Stream,'\\hline ~n',[]).
254
255 % TO DO: determine column length dynamically
256 ?print_entry(Stream,E,T,Opts) :- member(latex,Opts),!,
257 print(Stream,' $'),print(Stream,E), (T==[] -> print(Stream,'$ ') ; print(Stream,'$ & ')).
258 print_entry(Stream,E,_,_) :- print(Stream,E), my_atom_length(E,Len), WS is 10-Len, print_ws(Stream,WS),!.
259 print_entry(Stream,E,_,_) :- add_internal_error('Failed: ',print_entry(Stream,E,_,_)),
260 print(Stream,print_entry_failed(E)),nl(Stream).
261
262 print_ws(Stream,X) :- X>1, !, print(Stream,' '), X1 is X-1, print_ws(Stream,X1).
263 print_ws(Stream,_) :- print(Stream,' ').
264
265 my_atom_length(AtomOrNumber,Len) :- atom(AtomOrNumber),!, atom_length(AtomOrNumber,Len).
266 my_atom_length(Var,Len) :- var(Var),!, Len=6. % no idea how long a variable is
267 my_atom_length(C,Len) :- compound(C),!, Len=6. % no idea how long a compound is
268 my_atom_length(A,Len) :- number(A),!,number_codes(A,List), length(List,Len).
269 my_atom_length(A,Len) :- atom_codes(A,List), length(List,Len).
270
271 % split lists into sub-lists of at most length Maxlen
272 split_list([],_,[]).
273 split_list([H|T],Maxlen,[ [H|T1] |Rest]) :- get_list(T,Maxlen,T1,RT), split_list(RT,Maxlen,Rest).
274
275 get_list([],_,[],[]).
276 get_list([H|T],Max,[H|T1],Rest) :- Max>1, !, M1 is Max-1, get_list(T,M1,T1,Rest).
277 get_list(L,_,[],L).