|
27 | 27 | 21)
|
28 | 28 |
|
29 | 29 | (def ^:dynamic *coll-check-limit*
|
30 |
| - "The number of items validated in a collection spec'ed with 'coll'" |
31 |
| - 100) |
| 30 | + "The number of elements validated in a collection spec'ed with 'every'" |
| 31 | + 101) |
| 32 | + |
| 33 | +(def ^:dynamic *coll-error-limit* |
| 34 | + "The number of errors reported by explain in a collection spec'ed with 'every'" |
| 35 | + 20) |
32 | 36 |
|
33 | 37 | (def ^:private ^:dynamic *instrument-enabled*
|
34 | 38 | "if false, instrumented fns call straight through"
|
|
179 | 183 | ;;(prn {:ed ed})
|
180 | 184 | (doseq [[path {:keys [pred val reason via in] :as prob}] (::problems ed)]
|
181 | 185 | (when-not (empty? in)
|
182 |
| - (print "In:" in "")) |
| 186 | + (print "In:" (pr-str in) "")) |
183 | 187 | (print "val: ")
|
184 | 188 | (pr val)
|
185 | 189 | (print " fails")
|
186 | 190 | (when-not (empty? via)
|
187 |
| - (print " spec:" (last via))) |
| 191 | + (print " spec:" (pr-str (last via)))) |
188 | 192 | (when-not (empty? path)
|
189 |
| - (print " at:" path)) |
| 193 | + (print " at:" (pr-str path))) |
190 | 194 | (print " predicate: ")
|
191 | 195 | (pr pred)
|
192 | 196 | (when reason (print ", " reason))
|
193 | 197 | (doseq [[k v] prob]
|
194 | 198 | (when-not (#{:pred :val :reason :via :in} k)
|
195 |
| - (print "\n\t" k " ") |
| 199 | + (print "\n\t" (pr-str k) " ") |
196 | 200 | (pr v)))
|
197 | 201 | (newline))
|
198 | 202 | (doseq [[k v] ed]
|
199 | 203 | (when-not (#{::problems} k)
|
200 |
| - (print k " ") |
| 204 | + (print (pr-str k) " ") |
201 | 205 | (pr v)
|
202 | 206 | (newline))))
|
203 | 207 | (println "Success!")))
|
|
432 | 436 | [& pred-forms]
|
433 | 437 | `(and-spec-impl '~(mapv res pred-forms) ~(vec pred-forms) nil))
|
434 | 438 |
|
| 439 | +(defmacro every |
| 440 | + "takes a pred and validates collection elements against that pred. |
| 441 | +
|
| 442 | + Note that 'every' does not do exhaustive checking, rather it samples |
| 443 | + *coll-check-limit* elements. Nor (as a result) does it do any |
| 444 | + conforming of elements. 'explain' will report at most *coll-error-limit* |
| 445 | + problems. Thus 'every' should be suitable for potentially large |
| 446 | + collections. |
| 447 | +
|
| 448 | + Takes several kwargs options that further constrain the collection: |
| 449 | +
|
| 450 | + :count - specifies coll has exactly this count (default nil) |
| 451 | + :min-count, :max-count - coll has count (<= min count max) (default nil) |
| 452 | + :distinct - all the elements are distinct (default nil) |
| 453 | +
|
| 454 | + And additional args that control gen |
| 455 | +
|
| 456 | + :gen-max - the maximum coll size to generate (default 20) |
| 457 | + :gen-into - the default colection to generate into (will be emptied) (default []) |
| 458 | + |
| 459 | + Optionally takes :gen generator-fn, which must be a fn of no args that |
| 460 | + returns a test.check generator |
| 461 | +" |
| 462 | + [pred & {:keys [count max-count min-count distinct gen-max gen-into gen] :as opts}] |
| 463 | + `(every-impl '~pred ~pred ~(dissoc opts :gen) ~gen)) |
| 464 | + |
| 465 | +(defmacro every-kv |
| 466 | + "like 'every' but takes separate key and val preds and works on associative collections. |
| 467 | +
|
| 468 | + Same options as 'every'" |
| 469 | + |
| 470 | + [kpred vpred & opts] |
| 471 | + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (key v#)) :gen-into {} ~@opts)) |
| 472 | + |
435 | 473 | (defmacro *
|
436 | 474 | "Returns a regex op that matches zero or more values matching
|
437 | 475 | pred. Produces a vector of matches iff there is at least one match"
|
@@ -1034,6 +1072,94 @@ by ns-syms. Idempotent."
|
1034 | 1072 | (with-gen* [_ gfn] (and-spec-impl forms preds gfn))
|
1035 | 1073 | (describe* [_] `(and ~@forms))))
|
1036 | 1074 |
|
| 1075 | +(defn ^:skip-wiki every-impl |
| 1076 | + "Do not call this directly, use 'every'" |
| 1077 | + ([form pred opts] (every-impl form pred opts nil)) |
| 1078 | + ([form pred {:keys [count max-count min-count distinct gen-max gen-into ::kfn] |
| 1079 | + :or {gen-max 20, gen-into []} |
| 1080 | + :as opts} |
| 1081 | + gfn] |
| 1082 | + (let [check? #(valid? pred %) |
| 1083 | + kfn (c/or kfn (fn [i v] i))] |
| 1084 | + (reify |
| 1085 | + Spec |
| 1086 | + (conform* [_ x] |
| 1087 | + (cond |
| 1088 | + (c/or (not (seqable? x)) |
| 1089 | + (c/and distinct (not (empty? x)) (not (apply distinct? x))) |
| 1090 | + (c/and count (not= count (bounded-count (inc count) x))) |
| 1091 | + (c/and (c/or min-count max-count) |
| 1092 | + (not (<= (c/or min-count 0) |
| 1093 | + (bounded-count (if max-count (inc max-count) min-count) x) |
| 1094 | + (c/or max-count Integer/MAX_VALUE))))) |
| 1095 | + :invalid |
| 1096 | + |
| 1097 | + :else |
| 1098 | + (if (indexed? x) |
| 1099 | + (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] |
| 1100 | + (loop [i 0] |
| 1101 | + (if (>= i (c/count x)) |
| 1102 | + x |
| 1103 | + (if (check? (nth x i)) |
| 1104 | + (recur (c/+ i step)) |
| 1105 | + ::invalid)))) |
| 1106 | + (c/or (c/and (every? check? (take *coll-check-limit* x)) x) |
| 1107 | + ::invalid)))) |
| 1108 | + (unform* [_ x] x) |
| 1109 | + (explain* [_ path via in x] |
| 1110 | + (cond |
| 1111 | + (not (seqable? x)) |
| 1112 | + {path {:pred 'seqable? :val x :via via :in in}} |
| 1113 | + |
| 1114 | + (c/and distinct (not (empty? x)) (not (apply distinct? x))) |
| 1115 | + {path {:pred 'distinct? :val x :via via :in in}} |
| 1116 | + |
| 1117 | + (c/and count (not= count (bounded-count count x))) |
| 1118 | + {path {:pred `(= ~count (c/count %)) :val x :via via :in in}} |
| 1119 | + |
| 1120 | + (c/and (c/or min-count max-count) |
| 1121 | + (not (<= (c/or min-count 0) |
| 1122 | + (bounded-count (if max-count (inc max-count) min-count) x) |
| 1123 | + (c/or max-count Integer/MAX_VALUE)))) |
| 1124 | + {path {:pred `(<= ~(c/or min-count 0) (c/count %) ~(c/or max-count 'Integer/MAX_VALUE)) :val x :via via :in in}} |
| 1125 | + |
| 1126 | + :else |
| 1127 | + (apply merge |
| 1128 | + (take *coll-error-limit* |
| 1129 | + (keep identity |
| 1130 | + (map (fn [i v] |
| 1131 | + (let [k (kfn i v)] |
| 1132 | + (when-not (check? v) |
| 1133 | + (let [prob (explain-1 form pred (conj path k) via (conj in k) v)] |
| 1134 | + prob)))) |
| 1135 | + (range) x)))))) |
| 1136 | + (gen* [_ overrides path rmap] |
| 1137 | + (if gfn |
| 1138 | + (gfn) |
| 1139 | + (let [init (empty gen-into) |
| 1140 | + pgen (gensub pred overrides path rmap form)] |
| 1141 | + (gen/fmap |
| 1142 | + #(if (vector? init) % (into init %)) |
| 1143 | + (cond |
| 1144 | + distinct |
| 1145 | + (if count |
| 1146 | + (gen/vector-distinct pgen {:num-elements count :max-tries 100}) |
| 1147 | + (gen/vector-distinct pgen {:min-elements (c/or min-count 0) |
| 1148 | + :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) |
| 1149 | + :max-tries 100})) |
| 1150 | + |
| 1151 | + count |
| 1152 | + (gen/vector pgen count) |
| 1153 | + |
| 1154 | + (c/or min-count max-count) |
| 1155 | + (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) |
| 1156 | + |
| 1157 | + :else |
| 1158 | + (gen/vector pgen 0 gen-max)))))) |
| 1159 | + |
| 1160 | + (with-gen* [_ gfn] (every-impl form pred opts gfn)) |
| 1161 | + (describe* [_] `(every ~form ~@(mapcat identity opts))))))) |
| 1162 | + |
1037 | 1163 | ;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;;
|
1038 | 1164 | ;;See:
|
1039 | 1165 | ;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/
|
|
0 commit comments