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/3, asserted_spec_invariant/3,
11 asserted_spec_invariant/4, asserted_spec_post/5,
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/3, asserted_spec_invariant/3,
23 asserted_spec_invariant/4, asserted_spec_post/5.
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 = _:_/Arity),
49 ? check_arity(Pred, PreSpec, 'A pre spec', Arity),
50 (ground(PreSpec) ->
51 assert(asserted_spec_pre(Pred,PreSpec,def))
52 ;
53 assert(asserted_spec_pre(Pred,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(named_spec, InvariantSpec, Names, Specs) ->
62 assert(asserted_spec_invariant(Pred, Names, Specs, def))
63 ;
64 assert(asserted_spec_invariant(Pred, InvariantSpec, def)))
65 ;
66 (maplist(named_spec, InvariantSpec, Names, Specs) ->
67 assert(asserted_spec_invariant(Pred, Names, Specs, any))
68 ;
69 assert(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 = _:_/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 assert(asserted_spec_post(Pred,PreSpec,PostSpec,def,def))
81 ;
82 assert(asserted_spec_post(Pred,PreSpec,PostSpec,def,any)))
83 ;
84 (ground(PostSpec) ->
85 assert(asserted_spec_post(Pred,PreSpec,PostSpec,any,def))
86 ;
87 assert(asserted_spec_post(Pred,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_pred(+, 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 assert(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 assert(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 assert(
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 assert(
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(X) :-
158 log(info, 'Spec check enabled for ~w.',[X]),
159 assert(check_predicate(X)).
160
161 enable_all_spec_checks :-
162 assert(check_predicate(_)).
163
164
165 :- dynamic error_handler/1.
166 error_handler(plspec_default_error_handler).
167
168 :- public plspec_default_error_handler/1.
169 plspec_default_error_handler(X) :-
170 pretty_print_error(X), !,
171 throw(plspec_error).
172
173 :- meta_predicate set_error_handler(1).
174 set_error_handler(Pred) :-
175 retractall(error_handler(_)),
176 assert(error_handler(Pred)).
177
178 %% check coroutine magic
179 setup_uber_check(Location,any,Spec,Val) :-
180 log(debug,'setup_uber_check'),
181 setup_check(Location,Res,Spec,Val,any),
182 freeze(Res, ((Res == true) -> true ; error_handler(X), call(X, Res))).
183 setup_uber_check(Location,def,Spec,Val) :-
184 log(debug,'setup_uber_check'),
185 setup_check(Location,Res,Spec,Val,def),
186 freeze(Res, ((Res == true) -> true ; error_handler(X), call(X, Res))).
187 setup_check(Location,Res,Spec,Val,Type) :-
188 setup_check_aux(Spec,Location,Val,Res,Type).
189
190 setup_check_aux(Spec, Location, Val, Res, def) :-
191 spec_basic(Spec, Pred), !,
192 freeze(Val, (call(Pred, Val) -> true ; reason(Spec, Location, Val, Res))).
193 setup_check_aux(Spec, Location, Val, Res, _) :-
194 spec_predicate(Spec, Pred), !,
195 freeze(Val, (call(Pred, Val) -> true ; reason(Spec, Location, Val, Res))).
196 setup_check_aux(Spec, Location, Val, Res, Type) :-
197 spec_indirection(Spec, OtherSpec), !,
198 setup_check_aux(OtherSpec, Location, Val, Res, Type).
199 setup_check_aux(Spec, Location, Val, Res, _Type) :-
200 spec_predicate_recursive(Spec, Pred, _MergePred, MergePredInvariant),
201 !,
202 freeze(
203 Val,
204 (call(Pred, Val, NewSpecs, NewVals) ->
205 call(MergePredInvariant, NewSpecs, NewVals, Location, Res)
206 ;
207 reason(Spec, Location, Val, Res)
208 )
209 ).
210 setup_check_aux(Spec, Location, Val, Res, _Type) :-
211 spec_connective(Spec, Pred, _MergePred, MergePredInvariant), !,
212 freeze(
213 Val,
214 (call(Pred, Val, NewSpecs, NewVals) ->
215 call(MergePredInvariant, NewSpecs, NewVals, Location, Res)
216 ;
217 reason(Spec, Location, Val, Res)
218 )
219 ).
220 setup_check_aux(
221 Spec,
222 Location,
223 _,
224 fail(spec_not_found(spec(Spec), location(Location))),_).
225
226 reason(T, Location, V, Reason) :-
227 copy_term(Location, LocationWithoutAttributes, _Goals),
228 Reason =
229 fail(
230 spec_violated(
231 spec(T),
232 value(V),
233 location(LocationWithoutAttributes)
234 )
235 ).
236
237 which_posts([],[],[],[],_,[],[],[]) :- !.
238 which_posts([Pre|PresBefore], [PreType|PreTypes],
239 [Post|Posts], [PostType|PostTypes], Args,
240 [Pre|PresAfter], [Post|PostAfter], [PostType|PostTypesAfter]) :-
241 maplist(valid(PreType),Pre,Args),!,
242 which_posts(
243 PresBefore,
244 PreTypes,
245 Posts,
246 PostTypes,
247 Args,
248 PresAfter,
249 PostAfter,
250 PostTypesAfter).
251 which_posts([_|Pres],[_|PreTypes],[_|Posts],[_|PostTypes],Args,PreT,T,Z) :-
252 which_posts(Pres,PreTypes,Posts,PostTypes,Args,PreT,T,Z).
253
254
255
256 check_posts([], [], [], _).
257 check_posts([Arg|ArgT], [Pre|PreT], [Post|PostT], PostType) :-
258 evaluate_spec_match(Post, PostType, Arg, Res),!,
259 (Res == true ->
260 check_posts(ArgT, PreT, PostT, PostType)
261 ;
262 error_handler(X),
263 call(
264 X,
265 fail(
266 postcondition_violated(
267 matched_pre(Pre),
268 violated_post(Post),
269 value(Arg)
270 )
271 )
272 )
273 ).
274
275 %% term expansion
276 :- meta_predicate plspec_some(1, +).
277 plspec_some(Goal, List, List2) :-
278 plspec_some1(List, List2, Goal).
279 plspec_some1([],[], _) :- fail.
280 plspec_some1([H|_], [G|_], Goal) :-
281 call(Goal,H,G),
282 log(debug, 'Pre-Spec ~w matched!',[H]),
283 !.
284 plspec_some1([_|T], [_|S], Goal) :-
285 plspec_some1(T, S, Goal).
286
287 :- public spec_matches/4. %THIS SEEMS NOT USED - TO DO: investigate
288 spec_matches([], true, [], _).
289 spec_matches([Arg|ArgsT], Res, [Spec|SpecT], Type) :-
290 evaluate_spec_match(Spec, Type, Arg, R),
291 (R == true ->
292 spec_matches(ArgsT, Res, SpecT, Type)
293 ;
294 Res = spec_not_matched(Spec, Arg, in(R))).
295
296 error_not_matching_any_pre(Functor, Args, PreSpecs) :-
297 error_handler(X),
298 call(
299 X,
300 fail(
301 prespec_violated(specs(PreSpecs), values(Args), location(Functor))
302 )
303 ).
304
305 and_invariant([], [], _, true, _).
306 and_invariant([HSpec|TSpec], [HVal|TVal], Location, R, Type) :-
307 setup_check(Location, ResElement,HSpec, HVal, Type),
308 freeze(TVal, and_invariant(TSpec, TVal, Location, ResTail, Type)), % TODO: do we need this freeze?
309 both_eventually_true(ResElement, ResTail, R).
310
311 :- public and_invariant/4.
312 and_invariant(Specs, Vals, Location, R) :-
313 (ground(Specs) ->
314 and_invariant(Specs, Vals, Location, R, def)
315 ;
316 and_invariant(Specs, Vals, Location, R, any)
317 ).
318
319
320 or_invariant([], [], Acc, OrigVals, OrigPattern, Location, UberVar) :-
321 freeze(
322 Acc,
323 (Acc == fail ->
324 (reason(OrigPattern, Location, OrigVals, Reason), UberVar = Reason)
325 ;
326 true
327 )
328 ).
329 or_invariant([H|T], [V|VT], Prior, OrigVals, OrigPattern, Location, UberVar) :-
330 setup_check(Location, ResOption, H, V),
331 freeze(
332 ResOption,
333 (ResOption == true ->
334 (UberVar = true, Current = true)
335 ;
336 freeze(
337 Prior,
338 (Prior == true -> true; Current = fail)
339 )
340 )
341 ),
342 or_invariant(T, VT, Current, OrigVals, OrigPattern, Location, UberVar).
343
344 :- public or_invariant/4.
345 or_invariant(NewSpecs, NewVals, Location, FutureRes) :-
346 or_invariant(NewSpecs, NewVals, [], NewVals, or(NewSpecs), Location, FutureRes).
347
348 %% merge recursive specs
349 both_eventually_true(V1, V2, Res) :-
350 when((nonvar(V1); nonvar(V2)),
351 (V1 == true
352 -> freeze(V2, Res = V2) %% look at the other co-routined variable
353 ; nonvar(V1)
354 -> Res = V1 %% since it is not true
355 ; V2 == true
356 -> freeze(V1, Res = V1)
357 ; nonvar(V2) -> Res = V2)).