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