Source file spellcheck.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
(** Helpers to provide hints to PPX users for typos or spellchecks. *)
open! Import
exception Cutoff_met
let levenshtein_distance s t cutoff =
let m = String.length s and n = String.length t in
if cutoff = 0 || abs (m - n) >= cutoff then None
else
let d = Array.make_matrix ~dimx:(m + 1) ~dimy:(n + 1) 0 in
for i = 0 to m do
d.(i).(0) <- i
done;
for j = 0 to n do
d.(0).(j) <- j
done;
let line_min = ref 0 in
let distance =
try
for j = 1 to n do
if !line_min >= cutoff - 1 && j >= cutoff - 1 then raise Cutoff_met;
line_min := max m n;
for i = 1 to m do
let value =
if Char.equal s.[i - 1] t.[j - 1] then d.(i - 1).(j - 1)
else
min
(d.(i - 1).(j) + 1)
(min
(d.(i).(j - 1) + 1)
(d.(i - 1).(j - 1) + 1) )
in
d.(i).(j) <- value;
line_min := min !line_min value
done
done;
if d.(m).(n) < cutoff then Some d.(m).(n) else None
with Cutoff_met -> None
in
distance
let spellcheck names name =
let cutoff =
match String.length name with
| 1 | 2 -> 0
| 3 | 4 -> 1
| 5 | 6 -> 2
| _ -> 3
in
let _, suggestions =
List.fold_left names ~init:(Int.max_int, [])
~f:(fun
((best_distance, names_at_best_distance) as acc) registered_name ->
match levenshtein_distance name registered_name cutoff with
| None -> acc
| Some dist ->
if dist < best_distance then (dist, [ registered_name ])
else if dist > best_distance then acc
else (dist, registered_name :: names_at_best_distance))
in
match List.rev suggestions |> List.filter ~f:(String.( <> ) name) with
| [] -> None
| last :: rev_rest ->
Some
(Printf.sprintf "Hint: Did you mean %s%s%s?"
(String.concat ~sep:", " (List.rev rev_rest))
(if List.is_empty rev_rest then "" else " or ")
last)