1 | :- module(plspec,[spec_pre/2,spec_post/3,spec_invariant/2, | |
2 | ||
3 | defspec/2, defspec_pred/2, defspec_pred_recursive/4, | |
4 | defspec_connective/4, | |
5 | ||
6 | setup_uber_check/4, check_posts/4, which_posts/8, | |
7 | plspec_some/3, error_not_matching_any_pre/3, | |
8 | enable_spec_check/1, enable_all_spec_checks/0, | |
9 | set_error_handler/1, | |
10 | asserted_spec_pre/5, asserted_spec_invariant/3, | |
11 | asserted_spec_invariant/4, asserted_spec_post/7, | |
12 | check_predicate/1 % called by term expander | |
13 | ]). | |
14 | ||
15 | :- use_module(validator). | |
16 | :- use_module(prettyprinter). | |
17 | :- use_module(plspec_logger). | |
18 | ||
19 | :- use_module(library(lists)). | |
20 | :- use_module(library(terms), [variant/2]). | |
21 | ||
22 | :- dynamic asserted_spec_pre/5, asserted_spec_invariant/3, | |
23 | asserted_spec_invariant/4, asserted_spec_post/7. | |
24 | ||
25 | %% set up facts | |
26 | ||
27 | named_spec(Name:Spec, Name, Spec). | |
28 | ||
29 | asserted_spec_invariant(Pred, Spec) :- | |
30 | asserted_spec_invariant(Pred, _, Spec). | |
31 | ||
32 | ||
33 | check_ground(_Pred, Spec, _SpecType) :- | |
34 | ground(Spec). | |
35 | check_ground(Pred, Spec, SpecType) :- | |
36 | \+ ground(Spec), | |
37 | log(info,'~w is not ground; got ~w in ~w. It is handled as a specific, but unknown spec.', [SpecType, Spec, Pred]). | |
38 | ||
39 | check_arity(_Pred, Spec, _SpecType, Arity) :- | |
40 | length(Spec, Arity). | |
41 | check_arity(Pred, Spec, SpecType, Arity) :- | |
42 | \+ length(Spec, Arity), | |
43 | log(error,'~w of ~w does not match in length!',[SpecType, Pred]), | |
44 | fail. | |
45 | ||
46 | spec_pre(Pred,PreSpec) :- | |
47 | ? | check_ground(Pred, PreSpec, 'pre specs'), |
48 | Pred = Module:Functor/Arity, | |
49 | ? | check_arity(Pred, PreSpec, 'A pre spec', Arity), |
50 | (ground(PreSpec) -> | |
51 | assertz(asserted_spec_pre(Functor,Arity,Module,PreSpec,def)) | |
52 | ; | |
53 | assertz(asserted_spec_pre(Functor,Arity,Module,PreSpec,any))), | |
54 | log(debug,'Asserted spec pre for ~w.',[Pred]). | |
55 | ||
56 | spec_invariant(Pred, InvariantSpec) :- | |
57 | ? | check_ground(Pred, InvariantSpec, 'invariant specs'), |
58 | Pred = _:_/Arity, | |
59 | ? | check_arity(Pred, InvariantSpec, 'An invariant spec', Arity), |
60 | (ground(InvariantSpec) -> | |
61 | (maplist(plspec:named_spec, InvariantSpec, Names, Specs) -> | |
62 | assertz(asserted_spec_invariant(Pred, Names, Specs, def)) | |
63 | ; | |
64 | assertz(asserted_spec_invariant(Pred, InvariantSpec, def))) | |
65 | ; | |
66 | (maplist(plspec:named_spec, InvariantSpec, Names, Specs) -> | |
67 | assertz(asserted_spec_invariant(Pred, Names, Specs, any)) | |
68 | ; | |
69 | assertz(asserted_spec_invariant(Pred, InvariantSpec, any)))), | |
70 | log(debug,'Assertedc spec invariant for ~w.',[Pred]). | |
71 | ||
72 | spec_post(Pred,PreSpec,PostSpec) :- | |
73 | ? | check_ground(Pred, PreSpec, 'post-specs'), |
74 | ? | check_ground(Pred, PostSpec, 'post-specs'), |
75 | Pred = Module:F/Arity, | |
76 | ? | check_arity(Pred, PreSpec, 'A post spec (precondition)', Arity), |
77 | ? | check_arity(Pred, PostSpec, 'A post spec (postcondition)', Arity), |
78 | (ground(PreSpec) -> | |
79 | (ground(PostSpec) -> | |
80 | assertz(asserted_spec_post(F,Arity,Module,PreSpec,PostSpec,def,def)) | |
81 | ; | |
82 | assertz(asserted_spec_post(F,Arity,Module,PreSpec,PostSpec,def,any))) | |
83 | ; | |
84 | (ground(PostSpec) -> | |
85 | assertz(asserted_spec_post(F,Arity,Module,PreSpec,PostSpec,any,def)) | |
86 | ; | |
87 | assertz(asserted_spec_post(F,Arity,Module,PreSpec,PostSpec,any,any)))), | |
88 | log(debug,'Asserted spec post for ~w.',[Pred]). | |
89 | ||
90 | log_spec_already_exists(SpecId, ExistingDefinition, NewDefinition) :- | |
91 | (variant(spec(SpecId,ExistingDefinition),spec(SpecId,NewDefinition)) -> | |
92 | log(info,'spec is overwritten with itself, proceeding~n', [SpecId]) | |
93 | ; | |
94 | log(warning,'spec ~w already exists, will not be redefined~n', [SpecId])). | |
95 | ||
96 | :- meta_predicate defspec(+, 1). | |
97 | defspec(SpecId, OtherSpec) :- | |
98 | (spec_exists(SpecId, Existing) -> | |
99 | %% we use variant in order to determine whether it is actually the same spec; | |
100 | % for example, consider defspec(foo(X,Y), bar(X,Y)), defspec(foo(X,Y), bar(Y,X)). | |
101 | % we do not want to unify X = Y but also notice these are not the same specs. | |
102 | log_spec_already_exists(SpecId, Existing, indirection(OtherSpec)) | |
103 | ; | |
104 | assertz(spec_indirection(SpecId, OtherSpec)), | |
105 | log(info,'Spec ~w defined.',[SpecId])). | |
106 | ||
107 | :- meta_predicate defspec_pred(+, 1). | |
108 | defspec_pred(SpecId, Predicate) :- | |
109 | (spec_exists(SpecId, Existing) -> | |
110 | log_spec_already_exists(SpecId, Existing, predicate(Predicate)) | |
111 | ; | |
112 | assertz(spec_predicate(SpecId, Predicate))). | |
113 | ||
114 | :- meta_predicate defspec_pred_recursive(+, 3,3,4). | |
115 | defspec_pred_recursive(SpecId, Predicate, MergePred, MergePredInvariant) :- | |
116 | (spec_exists(SpecId, Existing) -> | |
117 | log_spec_already_exists( | |
118 | SpecId, | |
119 | Existing, | |
120 | predicate_recursive(Predicate, MergePred, MergePredInvariant) | |
121 | ) | |
122 | ; | |
123 | assertz( | |
124 | spec_predicate_recursive( | |
125 | SpecId, | |
126 | Predicate, | |
127 | MergePred, | |
128 | MergePredInvariant) | |
129 | ), | |
130 | log(info, 'Recursive spec ~w defined.',[SpecId]) | |
131 | ). | |
132 | ||
133 | :- meta_predicate defspec_connective(+, 3,3,4). | |
134 | defspec_connective(SpecId, Predicate, MergePred, MergePredInvariant) :- | |
135 | (spec_exists(SpecId, Existing) -> | |
136 | log_spec_already_exists( | |
137 | SpecId, | |
138 | Existing, | |
139 | connective(Predicate, MergePred, MergePredInvariant) | |
140 | ) | |
141 | ; | |
142 | assertz( | |
143 | spec_connective( | |
144 | SpecId, | |
145 | Predicate, | |
146 | MergePred, | |
147 | MergePredInvariant | |
148 | ) | |
149 | ), | |
150 | log(info,'Connective spec ~w defined.') | |
151 | ). | |
152 | ||
153 | ||
154 | :- dynamic check_predicate/1. | |
155 | enable_spec_check([H|T]) :- !, | |
156 | maplist(enable_spec_check, [H|T]). | |
157 | enable_spec_check(F/N) :- | |
158 | log(info, 'Spec check enabled for ~w/~w.',[F/N]), | |
159 | functor(X,F,N), | |
160 | assertz(check_predicate(X)). | |
161 | enable_spec_check(X) :- | |
162 | log(error, 'Spec check cannot be enabled for ~w use Functor/Arity syntax!',[X]). | |
163 | % Note: SICStus flag disable_plspec_runtime_checks can turn off all runtime checks | |
164 | ||
165 | enable_all_spec_checks :- | |
166 | retractall(check_predicate(_)), | |
167 | assertz(check_predicate(_)). | |
168 | ||
169 | ||
170 | :- dynamic error_handler/1. | |
171 | error_handler(plspec_default_error_handler). | |
172 | ||
173 | :- public plspec_default_error_handler/1. | |
174 | plspec_default_error_handler(X) :- | |
175 | pretty_print_error(X), !, | |
176 | throw(plspec_error). | |
177 | ||
178 | :- meta_predicate set_error_handler(1). | |
179 | set_error_handler(Pred) :- | |
180 | retractall(error_handler(_)), | |
181 | assertz(error_handler(Pred)). | |
182 | ||
183 | %% check coroutine magic | |
184 | setup_uber_check(Location,any,Spec,Val) :- | |
185 | log(debug,'setup_uber_check'), | |
186 | setup_check(Location,Res,Spec,Val,any), | |
187 | freeze(Res, ((Res == true) -> true ; error_handler(X), call(X, Res))). | |
188 | setup_uber_check(Location,def,Spec,Val) :- | |
189 | log(debug,'setup_uber_check'), | |
190 | setup_check(Location,Res,Spec,Val,def), | |
191 | freeze(Res, ((Res == true) -> true ; error_handler(X), call(X, Res))). | |
192 | setup_check(Location,Res,Spec,Val,Type) :- | |
193 | setup_check_aux(Spec,Location,Val,Res,Type). | |
194 | ||
195 | setup_check_aux(Spec, Location, Val, Res, def) :- | |
196 | spec_basic(Spec, Pred), !, | |
197 | freeze(Val, (call(Pred, Val) -> true ; reason(Spec, Location, Val, Res))). | |
198 | setup_check_aux(Spec, Location, Val, Res, _) :- | |
199 | spec_predicate(Spec, Pred), !, | |
200 | freeze(Val, (call(Pred, Val) -> true ; reason(Spec, Location, Val, Res))). | |
201 | setup_check_aux(Spec, Location, Val, Res, Type) :- | |
202 | spec_indirection(Spec, OtherSpec), !, | |
203 | setup_check_aux(OtherSpec, Location, Val, Res, Type). | |
204 | setup_check_aux(Spec, Location, Val, Res, _Type) :- | |
205 | spec_predicate_recursive(Spec, Pred, _MergePred, MergePredInvariant), | |
206 | !, | |
207 | freeze( | |
208 | Val, | |
209 | (call(Pred, Val, NewSpecs, NewVals) -> | |
210 | call(MergePredInvariant, NewSpecs, NewVals, Location, Res) | |
211 | ; | |
212 | reason(Spec, Location, Val, Res) | |
213 | ) | |
214 | ). | |
215 | setup_check_aux(Spec, Location, Val, Res, _Type) :- | |
216 | spec_connective(Spec, Pred, _MergePred, MergePredInvariant), !, | |
217 | freeze( | |
218 | Val, | |
219 | (call(Pred, Val, NewSpecs, NewVals) -> | |
220 | call(MergePredInvariant, NewSpecs, NewVals, Location, Res) | |
221 | ; | |
222 | reason(Spec, Location, Val, Res) | |
223 | ) | |
224 | ). | |
225 | setup_check_aux( | |
226 | Spec, | |
227 | Location, | |
228 | _, | |
229 | fail(spec_not_found(spec(Spec), location(Location))),_). | |
230 | ||
231 | reason(T, Location, V, Reason) :- | |
232 | copy_term(Location, LocationWithoutAttributes, _Goals), | |
233 | Reason = | |
234 | fail( | |
235 | spec_violated( | |
236 | spec(T), | |
237 | value(V), | |
238 | location(LocationWithoutAttributes) | |
239 | ) | |
240 | ). | |
241 | ||
242 | which_posts([],[],[],[],_,[],[],[]) :- !. | |
243 | which_posts([Pre|PresBefore], [PreType|PreTypes], | |
244 | [Post|Posts], [PostType|PostTypes], Args, | |
245 | [Pre|PresAfter], [Post|PostAfter], [PostType|PostTypesAfter]) :- | |
246 | maplist(valid(PreType),Pre,Args),!, | |
247 | which_posts( | |
248 | PresBefore, | |
249 | PreTypes, | |
250 | Posts, | |
251 | PostTypes, | |
252 | Args, | |
253 | PresAfter, | |
254 | PostAfter, | |
255 | PostTypesAfter). | |
256 | which_posts([_|Pres],[_|PreTypes],[_|Posts],[_|PostTypes],Args,PreT,T,Z) :- | |
257 | which_posts(Pres,PreTypes,Posts,PostTypes,Args,PreT,T,Z). | |
258 | ||
259 | ||
260 | ||
261 | check_posts([], [], [], _). | |
262 | check_posts([Arg|ArgT], [Pre|PreT], [Post|PostT], PostType) :- | |
263 | evaluate_spec_match(Post, PostType, Arg, Res),!, | |
264 | (Res == true -> | |
265 | check_posts(ArgT, PreT, PostT, PostType) | |
266 | ; | |
267 | error_handler(X), | |
268 | call( | |
269 | X, | |
270 | fail( | |
271 | postcondition_violated( | |
272 | matched_pre(Pre), | |
273 | violated_post(Post), | |
274 | value(Arg) | |
275 | ) | |
276 | ) | |
277 | ) | |
278 | ). | |
279 | ||
280 | %% term expansion | |
281 | :- meta_predicate plspec_some(1, ?, ?). | |
282 | plspec_some(Goal, List, List2) :- | |
283 | plspec_some1(List, List2, Goal). | |
284 | plspec_some1([],[], _) :- fail. | |
285 | plspec_some1([H|_], [G|_], Goal) :- | |
286 | call(Goal,H,G), | |
287 | log(debug, 'Pre-Spec ~w matched!',[H]), | |
288 | !. | |
289 | plspec_some1([_|T], [_|S], Goal) :- | |
290 | plspec_some1(T, S, Goal). | |
291 | ||
292 | :- public spec_matches/4. %THIS SEEMS NOT USED - TO DO: investigate | |
293 | spec_matches([], true, [], _). | |
294 | spec_matches([Arg|ArgsT], Res, [Spec|SpecT], Type) :- | |
295 | evaluate_spec_match(Spec, Type, Arg, R), | |
296 | (R == true -> | |
297 | spec_matches(ArgsT, Res, SpecT, Type) | |
298 | ; | |
299 | Res = spec_not_matched(Spec, Arg, in(R))). | |
300 | ||
301 | error_not_matching_any_pre(Functor, Args, PreSpecs) :- | |
302 | error_handler(X), | |
303 | call( | |
304 | X, | |
305 | fail( | |
306 | prespec_violated(specs(PreSpecs), values(Args), location(Functor)) | |
307 | ) | |
308 | ). | |
309 | ||
310 | and_invariant([], [], _, true, _). | |
311 | and_invariant([HSpec|TSpec], [HVal|TVal], Location, R, Type) :- | |
312 | setup_check(Location, ResElement,HSpec, HVal, Type), | |
313 | freeze(TVal, and_invariant(TSpec, TVal, Location, ResTail, Type)), % TODO: do we need this freeze? | |
314 | both_eventually_true(ResElement, ResTail, R). | |
315 | ||
316 | :- public and_invariant/4. | |
317 | and_invariant(Specs, Vals, Location, R) :- | |
318 | (ground(Specs) -> | |
319 | and_invariant(Specs, Vals, Location, R, def) | |
320 | ; | |
321 | and_invariant(Specs, Vals, Location, R, any) | |
322 | ). | |
323 | ||
324 | ||
325 | or_invariant([], [], Acc, OrigVals, OrigPattern, Location, UberVar) :- | |
326 | freeze( | |
327 | Acc, | |
328 | (Acc == fail -> | |
329 | (reason(OrigPattern, Location, OrigVals, Reason), UberVar = Reason) | |
330 | ; | |
331 | true | |
332 | ) | |
333 | ). | |
334 | or_invariant([H|T], [V|VT], Prior, OrigVals, OrigPattern, Location, UberVar) :- | |
335 | setup_check(Location, ResOption, H, V), | |
336 | freeze( | |
337 | ResOption, | |
338 | (ResOption == true -> | |
339 | (UberVar = true, Current = true) | |
340 | ; | |
341 | freeze( | |
342 | Prior, | |
343 | (Prior == true -> true; Current = fail) | |
344 | ) | |
345 | ) | |
346 | ), | |
347 | or_invariant(T, VT, Current, OrigVals, OrigPattern, Location, UberVar). | |
348 | ||
349 | :- public or_invariant/4. | |
350 | or_invariant(NewSpecs, NewVals, Location, FutureRes) :- | |
351 | or_invariant(NewSpecs, NewVals, [], NewVals, or(NewSpecs), Location, FutureRes). | |
352 | ||
353 | %% merge recursive specs | |
354 | both_eventually_true(V1, V2, Res) :- | |
355 | when((nonvar(V1); nonvar(V2)), | |
356 | (V1 == true | |
357 | -> freeze(V2, Res = V2) %% look at the other co-routined variable | |
358 | ; nonvar(V1) | |
359 | -> Res = V1 %% since it is not true | |
360 | ; V2 == true | |
361 | -> freeze(V1, Res = V1) | |
362 | ; nonvar(V2) -> Res = V2)). |