1 | :- module(welldef, [ensure_wd/2, | |
2 | po_on_lhs/1, | |
3 | po_on_lhs/2, | |
4 | strip_pos/2, | |
5 | strip_pos_keep_toplevel_conj/2, | |
6 | wd_pos/2, | |
7 | filter_typing_pos/1, | |
8 | preprocess_pos_for_cdclt/1]). | |
9 | ||
10 | :- use_module(library(lists), [maplist/3, maplist/4, append/2]). | |
11 | ||
12 | :- use_module(wdsrc(well_def_hyps), [empty_hyps/1, push_hyp/4, negate_hyp/2]). | |
13 | :- use_module(wdsrc(well_def_analyser), [transform_wd/4, compute_wd/4, | |
14 | toplevel_wd_pos/3]). | |
15 | :- use_module(probsrc(bsyntaxtree), [repair_used_ids/3, add_texpr_infos/3, get_texpr_info/2]). | |
16 | :- use_module(probsrc(b_ast_cleanup), [clean_up_pred/3]). | |
17 | :- use_module(probsrc(preferences), [get_preference/2]). | |
18 | :- use_module(cdclt_solver('cdclt_preprocessing')). | |
19 | ||
20 | :- use_module(fuzztypes). | |
21 | ||
22 | :- dynamic filter_typing_pos/0, preprocess_pos_for_cdclt/0. | |
23 | ||
24 | % Removes some POs that just define the type. | |
25 | filter_typing_pos(true) :- | |
26 | filter_typing_pos, !. | |
27 | filter_typing_pos(true) :- | |
28 | \+ filter_typing_pos, | |
29 | !, | |
30 | asserta(filter_typing_pos). | |
31 | filter_typing_pos(false) :- | |
32 | retractall(filter_typing_pos). | |
33 | ||
34 | % Some preprocessing for CDCL(T) such as rewriting WD POs to IDL. | |
35 | preprocess_pos_for_cdclt(true) :- | |
36 | preprocess_pos_for_cdclt, !. | |
37 | preprocess_pos_for_cdclt(true) :- | |
38 | \+ preprocess_pos_for_cdclt, | |
39 | !, | |
40 | asserta(preprocess_pos_for_cdclt). | |
41 | preprocess_pos_for_cdclt(false) :- | |
42 | retractall(preprocess_pos_for_cdclt). | |
43 | ||
44 | %% wd_pos(Predicate, ProofObligations). | |
45 | % | |
46 | % Returns a list of proof obligations for the given predicate. | |
47 | wd_pos(Pred, WDPOs) :- | |
48 | empty_hyps(Hyps), | |
49 | findall(WDPO, compute_wd(Pred, Hyps, [discharge_po], WDPO), WDPOs). | |
50 | ||
51 | ||
52 | %% ensure_wd(+Predicate, -PredicateWithProofObligations). | |
53 | % | |
54 | % Ensures that the given predicate is well-defined by adding open | |
55 | % proof obligations for all well-definedness conditions as additional conjuncts. | |
56 | ensure_wd(Pred, WDPred) :- | |
57 | empty_hyps(Hyps), | |
58 | % push_hyp(Hyps, Pred, [create_full_po], FullHyps), | |
59 | % transform_bexpr(b_ast_cleanup:compute_wd_info, Pred, InterPred), | |
60 | transform_wd(Pred, Hyps, [discharge_po], AnnotatedPred), | |
61 | ensure_wd_node(AnnotatedPred, Hyps, _, PrelimWD), | |
62 | repair_used_ids('Welldef Enforcer', PrelimWD, WDPred). | |
63 | ||
64 | %% ensure_wd_node(+Ast, +Hyps, -RemainingGoals, -WdAst). | |
65 | % | |
66 | % Implements any WD conditions into the closest predicate nodes. | |
67 | % When not possible the remaining conditions are returned via `RemainingGoals`. | |
68 | ensure_wd_node([N | Ns], H, RemainingPOs, [Wd|Wds]) :- | |
69 | !, % Special case: List. | |
70 | ensure_wd_node_list([N | Ns], H, NestedRemainingPOs, [Wd|Wds]), | |
71 | append(NestedRemainingPOs, RemainingPOs). | |
72 | ensure_wd_node(field(Name,Expr), H, RemainingPOs, NewAst) :- | |
73 | % special case for records b(rec([field(Name,Expr),...]),_,_) | |
74 | !, | |
75 | ensure_wd_node(Expr, H, RemainingPOs, WdExpr), | |
76 | NewAst = field(Name,WdExpr). | |
77 | ensure_wd_node(b(record_field(Id,Atom),Type,Info), _, [], NewAst) :- | |
78 | !, | |
79 | NewAst = b(record_field(Id,Atom),Type,Info). | |
80 | ensure_wd_node(b(Node, Type, Info), H, RemainingPOs, NewAst) :- | |
81 | % Special cases: POs to LHS (middle child) | |
82 | Cases = [general_sum, general_product, quantified_intersection, quantified_union, lambda, forall], | |
83 | node_split(Node, Name, [Ids, Pred, Expr]), | |
84 | member(Name, Cases), | |
85 | !, | |
86 | wd_to_lhs(Pred, Expr, H, WdPred, WdExpr), | |
87 | node_split(WdNode, Name, [Ids, WdPred, WdExpr]), | |
88 | WdAst = b(WdNode, Type, Info), | |
89 | toplevel_wd_pos(WdAst, H, RootPOs), | |
90 | conjunct_or_propagate_pos(WdAst, RootPOs, NewAst, RemainingPOs). | |
91 | ensure_wd_node(b(Node, Type, Info), H, RemainingPOs, NewAst) :- | |
92 | % Special cases: POs to RHS (last child) | |
93 | Cases = [event_b_comprehension_set], | |
94 | node_split(Node, Name, [Ids, Expr, Pred]), | |
95 | member(Name, Cases), | |
96 | !, | |
97 | wd_to_rhs(Expr, Pred, H, WdExpr, WdPred), | |
98 | node_split(WdNode, Name, [Ids, WdExpr, WdPred]), | |
99 | WdAst = b(WdNode, Type, Info), | |
100 | toplevel_wd_pos(WdAst, H, RootPOs), | |
101 | conjunct_or_propagate_pos(WdAst, RootPOs, NewAst, RemainingPOs). | |
102 | ensure_wd_node(b(assertion_expression(Test, ErrText, Value), Type, Info), H, POs, Result) :- | |
103 | Result = b(assertion_expression(Test, ErrText, WdValue), Type, Info), | |
104 | ensure_wd_node(Value, H, POs, WdValue). | |
105 | ensure_wd_node(b(if_then_else(If, Then, Else), Type, Info), H, POs, WdIfThenElse) :- | |
106 | Type \= pred, !, % Special case for If-Expressions. | |
107 | ensure_wd_node(If, H, [], WdIf), | |
108 | push_hyp(H, If, [create_full_po], ThenH), | |
109 | transform_wd(Then, H, [discharge_po], TThen), | |
110 | ensure_wd_node(TThen, ThenH, ThenPOs, WdThen), | |
111 | negate_hyp(If, NotIf), | |
112 | push_hyp(H, NotIf, [create_full_po], ElseH), | |
113 | ensure_wd_node(Else, ElseH, ElsePOs, WdElse), | |
114 | if_then_else_po(WdIf, ThenPOs, IfThen), | |
115 | ensure_wd_node(NotIf, H, [], WdNotIf), | |
116 | if_then_else_po(WdNotIf, ElsePOs, NotIfElse), | |
117 | WdIfThenElse = b(if_then_else(WdIf, WdThen, WdElse), Type, Info), | |
118 | append(IfThen, NotIfElse, POs). | |
119 | ensure_wd_node(b(Node, Type, Info), H, RemainingPOs, NewAst) :- | |
120 | % Special cases: WD constraints from LHS chain into RHS | |
121 | Cases = [conjunct,implication], | |
122 | node_split(Node, Name, [Lhs, Rhs]), | |
123 | member(Name, Cases), | |
124 | !, | |
125 | ensure_wd_node(Lhs, H, [], WdLhs), | |
126 | push_hyp(H, WdLhs, [create_full_po], RhsH), | |
127 | ensure_wd_node(Rhs, RhsH, [], WdRhs), | |
128 | node_split(WdNode, Name, [WdLhs, WdRhs]), | |
129 | WdAst = b(WdNode, Type, Info), | |
130 | toplevel_wd_pos(WdAst, H, RootPOs), | |
131 | conjunct_or_propagate_pos(WdAst, RootPOs, NewAst, RemainingPOs). | |
132 | ensure_wd_node(Node, H, RemainderPOs, WdPred) :- | |
133 | wd_children_and_pos(Node, H, POs, InterNode), | |
134 | conjunct_or_propagate_pos(InterNode, POs, WdPred, RemainderPOs). | |
135 | ||
136 | ||
137 | ensure_wd_node_list([], _, [], []) :- !. | |
138 | ensure_wd_node_list([N | Ns], H, [R | Rs], [Wd | Wds]) :- | |
139 | ensure_wd_node(N, H, R, Wd), | |
140 | ensure_wd_node_list(Ns, H, Rs, Wds). | |
141 | ||
142 | ||
143 | ||
144 | % Helper for repetitive code. | |
145 | wd_children_and_pos(b(Node, Type, Info), H, POs, UpdatedNode) :- | |
146 | node_split(Node, Name, Children), | |
147 | ensure_wd_node_list(Children, H, Remainders, WdChildren), | |
148 | append(Remainders, ChildPOs), | |
149 | node_split(NewNode, Name, WdChildren), | |
150 | UpdatedNode = b(NewNode, Type, Info), | |
151 | toplevel_wd_pos(UpdatedNode, H, RootPOs), | |
152 | append(ChildPOs, RootPOs, POs). | |
153 | ||
154 | ||
155 | % Helps dispatching whether the POs need to be included into a predicate or | |
156 | % an expression needs to propagate them upwards. | |
157 | conjunct_or_propagate_pos(b(Node, pred, Info), POs, WdPred, []) :- | |
158 | !, % predicates conjunct the POs into themselves. | |
159 | filter_discharged_pos(POs, UndischargedPOs), | |
160 | ( filter_typing_pos | |
161 | -> filter_typing_pos(UndischargedPOs, FilteredPOs) | |
162 | ; FilteredPOs = UndischargedPOs | |
163 | ), | |
164 | ( preprocess_pos_for_cdclt | |
165 | -> clean_up_pos_for_cdclt(FilteredPOs, CleanPOs) | |
166 | ; CleanPOs = FilteredPOs | |
167 | ), | |
168 | include_pos(CleanPOs, b(Node, pred, Info), WdPred). | |
169 | conjunct_or_propagate_pos(Node, POs, Node, POs). % Expressions remain unchanged. | |
170 | ||
171 | ||
172 | ||
173 | ||
174 | ||
175 | % Assumes Lhs is predicate and Rhs is expression. | |
176 | wd_to_lhs(Lhs, Rhs, H, WdLhs, Rhs2) :- | |
177 | ensure_wd_node(Lhs, H, [], Lhs2), | |
178 | push_hyp(H, Lhs2, [create_full_po], NewH), | |
179 | wd_children_and_pos(Rhs, NewH, POs, Rhs2), | |
180 | filter_discharged_pos(POs, UndischargedPOs), | |
181 | ( filter_typing_pos | |
182 | -> filter_typing_pos(UndischargedPOs, FilteredPOs) | |
183 | ; FilteredPOs = UndischargedPOs | |
184 | ), | |
185 | ( preprocess_pos_for_cdclt | |
186 | -> clean_up_pos_for_cdclt(FilteredPOs, CleanPOs) | |
187 | ; CleanPOs = FilteredPOs | |
188 | ), | |
189 | include_pos(CleanPOs, Lhs2, WdLhs). | |
190 | % Assumes Lhs is expression and Rhs is predicate. | |
191 | wd_to_rhs(Lhs, Rhs, H, WdLhs, WdRhs) :- | |
192 | wd_to_lhs(Rhs, Lhs, H, WdRhs, WdLhs). | |
193 | ||
194 | ||
195 | if_then_else_po(Condition, BranchPOs, Res) :- | |
196 | filter_discharged_pos(BranchPOs, UndischargedPOs), | |
197 | ( filter_typing_pos | |
198 | -> filter_typing_pos(UndischargedPOs, FilteredPOs) | |
199 | ; FilteredPOs = UndischargedPOs | |
200 | ), | |
201 | ( preprocess_pos_for_cdclt | |
202 | -> clean_up_pos_for_cdclt(FilteredPOs, CleanPOs) | |
203 | ; CleanPOs = FilteredPOs | |
204 | ), | |
205 | if_then_else_implication(CleanPOs, Condition, Res). | |
206 | ||
207 | if_then_else_implication([], _, []) :- !. | |
208 | if_then_else_implication(POs, Condition, [Res]) :- | |
209 | po_conjunct(POs, BranchConjunct), | |
210 | Res = b(implication(Condition, BranchConjunct), pred, [contains_wd_condition]). | |
211 | ||
212 | ||
213 | ||
214 | ||
215 | %% include_pos(ProofObligations, Predicate, PredicateWithPOs). | |
216 | % | |
217 | % Returns a predicate with the given proof obligations included via `conjunct` nodes. | |
218 | include_pos([], Pred, Pred) :- !. | |
219 | include_pos(POs, Pred, b(conjunct(POsConj, Pred), pred, [contains_wd_condition,fuzzed_with_wd_po_included])) :- | |
220 | po_conjunct(POs, POsConj). | |
221 | ||
222 | filter_typing_pos([], []). | |
223 | filter_typing_pos([PO|POs], [PO|Rest]) :- | |
224 | \+ is_typing_pred_without_wd(PO), | |
225 | !, | |
226 | filter_typing_pos(POs, Rest). | |
227 | filter_typing_pos([_|POs], Filtered) :- | |
228 | filter_typing_pos(POs, Filtered). | |
229 | ||
230 | filter_discharged_pos([], []). | |
231 | filter_discharged_pos([PO | POs], [PO | NonDischarged]) :- | |
232 | PO = b(Node, _, Info), | |
233 | Node \= unknown_truth_value(_), % e.g., from external predicate calls such as RPOW | |
234 | \+ member(discharged(true, _), Info), !, | |
235 | filter_discharged_pos(POs, NonDischarged). | |
236 | filter_discharged_pos([_|POs], NonDischarged) :- | |
237 | filter_discharged_pos(POs, NonDischarged). | |
238 | ||
239 | po_conjunct([PO], PO) :- !. | |
240 | po_conjunct([PO | POs], b(conjunct(PO, POsConj), pred, [contains_wd_condition])) :- | |
241 | po_conjunct(POs, POsConj). | |
242 | ||
243 | %% po_on_lhs(+AST). | |
244 | % | |
245 | % Succeeds if the left side of the AST is marked as POs. | |
246 | % This will be the case, if the b/3 structure contains the | |
247 | % `fuzzed_with_wd_po_included` flag. | |
248 | % | |
249 | % AST: The AST to check. | |
250 | po_on_lhs(b(conjunct(_, _), _, Info)) :- | |
251 | member(fuzzed_with_wd_po_included, Info). | |
252 | ||
253 | %% po_on_lhs(+AST, -RHS). | |
254 | % | |
255 | % Succeeds if the left side of the AST is marked as POs. | |
256 | % This will be the case, if the b/3 structure contains the | |
257 | % `fuzzed_with_wd_po_included` flag. | |
258 | % | |
259 | % AST: The AST to check. | |
260 | % RHS: The non-PO part of the AST. | |
261 | po_on_lhs(b(conjunct(_, RHS), _, Info), RHS) :- | |
262 | member(fuzzed_with_wd_po_included, Info). | |
263 | ||
264 | ||
265 | %% strip_pos(+Ast, -AstWithoutPOs). | |
266 | % | |
267 | % Removes any subtrees which are generated by `ensure_wd` from the predicate. | |
268 | strip_pos([A|As], Raws) :- | |
269 | !, | |
270 | maplist(strip_pos, [A|As], Raws). | |
271 | strip_pos(Ast, RawAst) :- | |
272 | po_on_lhs(Ast, InterAst), | |
273 | !, | |
274 | strip_pos(InterAst, RawAst). | |
275 | strip_pos(b(assertion_expression(_, _, SecuredPred), _, _), Result) :- | |
276 | !, % assertion expressions are discarded. | |
277 | strip_pos(SecuredPred, Result). | |
278 | strip_pos(field(Name,Expr), field(Name,SExpr)) :- | |
279 | % special case for records b(rec([field(Name,Expr),...]),_,_) | |
280 | !, | |
281 | strip_pos(Expr, SExpr). | |
282 | strip_pos(b(Node, Type, Info), b(NewNode, Type, Info)) :- | |
283 | node_split(Node, Name, Children), | |
284 | maplist(strip_pos, Children, NewChildren), | |
285 | node_split(NewNode, Name, NewChildren). | |
286 | ||
287 | strip_pos_keep_toplevel_conj(b(Node,Type,Info), Raw) :- | |
288 | strip_pos_keep_toplevel_conj_e(Node, Type, Info, RawNode), | |
289 | Raw = b(RawNode,Type,Info). | |
290 | ||
291 | strip_pos_keep_toplevel_conj_e(conjunct(Arg1,Arg2), _, _, RawNode) :- | |
292 | !, | |
293 | strip_pos_keep_toplevel_conj(Arg1, RawArg1), | |
294 | strip_pos_keep_toplevel_conj(Arg2, RawArg2), | |
295 | ( RawArg1 == truth | |
296 | -> RawNode = RawArg2 | |
297 | ; RawArg2 == truth | |
298 | -> RawNode = RawArg1 | |
299 | ; RawNode = conjunct(RawArg1,RawArg2) | |
300 | ). | |
301 | strip_pos_keep_toplevel_conj_e(disjunct(_,_), _, Info, RawNode) :- | |
302 | % introduced from IDL solver in CDCL(T) | |
303 | member(fuzzed_with_wd_po_included, Info), | |
304 | !, | |
305 | RawNode = truth. | |
306 | strip_pos_keep_toplevel_conj_e(Node, Type, Info, RawNode) :- | |
307 | strip_pos(b(Node,Type,Info), RawAst), | |
308 | RawAst = b(RawNode,_,_). | |
309 | ||
310 | node_split(Node, Name, [b(Child1, T1, I1) | OtherChildren]) :- | |
311 | Node =.. [Name, b(Child1, T1, I1) | OtherChildren], !. | |
312 | node_split(Node, Name, [[A|As] | OtherChildren]) :- | |
313 | Node =.. [Name, [A|As] | OtherChildren], !. | |
314 | node_split(Node, Node, []). % Catches things like integer(42) or integer_set('NAT'). | |
315 | ||
316 | % Some adjustments to be used in the CDCL(T) solver | |
317 | % just some hard coded cases that occurred often | |
318 | is_typing_pred_without_wd(b(member(Id,B),pred,_)) :- | |
319 | B \= b(domain(_),_,_), | |
320 | B \= b(empty_set,_,_), | |
321 | B \= b(value([]),_,_), | |
322 | B \= b(seq(_),_,_), % seq typing has a wd condition | |
323 | B \= b(seq1(_),_,_), | |
324 | Id = b(identifier(_),_,_). | |
325 | is_typing_pred_without_wd(b(subset(Id,B),pred,_)) :- | |
326 | B \= b(domain(_),_,_), | |
327 | B \= b(empty_set,_,_), | |
328 | B \= b(value([]),_,_), | |
329 | B \= b(seq(_),_,_), | |
330 | B \= b(seq1(_),_,_), | |
331 | Id = b(identifier(_),_,_). | |
332 | is_typing_pred_without_wd(b(subset_strict(Id,B),pred,_)) :- | |
333 | B \= b(domain(_),_,_), | |
334 | B \= b(empty_set,_,_), | |
335 | B \= b(value([]),_,_), | |
336 | B \= b(seq(_),_,_), | |
337 | B \= b(seq1(_),_,_), | |
338 | Id = b(identifier(_),_,_). | |
339 | % from rewriting let-expressions | |
340 | is_typing_pred_without_wd(b(member(b(comprehension_set([b(identifier('_zzzz_unary'),boolean,[]),b(identifier('_zzzz_binary'),integer,[])],_),_,_),_),pred,_)). | |
341 | is_typing_pred_without_wd(b(exists([b(identifier('_zzzz_binary'),integer,_)],_),pred,_)). | |
342 | ||
343 | clean_up_pos_for_cdclt([], []). | |
344 | clean_up_pos_for_cdclt([PO|POs], [Clean|Rest]) :- | |
345 | clean_up_and_preprocess_pos_for_cdclt(PO, Clean), | |
346 | !, | |
347 | clean_up_pos_for_cdclt(POs, Rest). | |
348 | ||
349 | clean_up_and_preprocess_pos_for_cdclt(Pred, Clean) :- | |
350 | clean_up_pred(Pred, _, TClean), | |
351 | get_preference(cdclt_use_idl_theory_solver, RewriteToIdl), % possibly rewrite WD condition to IDL | |
352 | preprocess_predicate(false, RewriteToIdl, TClean, TLiftedPred, _, _), | |
353 | !, | |
354 | add_texpr_infos(TLiftedPred, [fuzzed_with_wd_po_included], LiftedPred), | |
355 | rewrite_emptyset(LiftedPred, Clean). | |
356 | ||
357 | rewrite_emptyset(b(negation(b(equal(A,b(empty_set,Type,Info)),pred,Info2)),pred,Info3), Out) :- | |
358 | % important if we use b_compile on the full predicate in cdclt_solver.pl since empty_set is compiled to value([]) | |
359 | !, | |
360 | Out = b(negation(b(equal(A,b(value([]),Type,Info)),pred,Info2)),pred,Info3). | |
361 | rewrite_emptyset(Out, Out). |