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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
type cell = int * int
type expr =
| Number of float
| Ref of cell
| Range of cell * cell
| Unary of char * expr
| Binop of char * expr * expr
| Call of string * expr list
type content = Formula of expr | Invalid of string | Value of float | Text of string | Blank
let name_of_cell ((col, row) : cell) =
let rec letters n acc =
let acc = String.make 1 (Char.chr (Char.code 'A' + (n mod 26))) ^ acc in
if n < 26 then acc else letters ((n / 26) - 1) acc
in
letters col "" ^ string_of_int (row + 1)
let cell_of_name (s : string) : cell option =
let n = String.length s in
let rec split i = if i < n && s.[i] >= 'A' && s.[i] <= 'Z' then split (i + 1) else i in
let i = split 0 in
if i = 0 || i = n then None
else
let col = ref 0 in
String.iter (fun c -> col := (!col * 26) + (Char.code c - Char.code 'A') + 1) (String.sub s 0 i);
match int_of_string_opt (String.sub s i (n - i)) with
| Some row when row >= 1 -> Some (!col - 1, row - 1)
| _ -> None
type token = TNum of float | TName of string | TOp of char | TOpen | TClose | TComma | TColon
exception Bad of string
let tokens (s : string) : token list =
let n = String.length s in
let rec go i acc =
if i >= n then List.rev acc
else
match s.[i] with
| ' ' | '\t' -> go (i + 1) acc
| '(' -> go (i + 1) (TOpen :: acc)
| ')' -> go (i + 1) (TClose :: acc)
| ',' -> go (i + 1) (TComma :: acc)
| ':' -> go (i + 1) (TColon :: acc)
| ('+' | '-' | '*' | '/') as c -> go (i + 1) (TOp c :: acc)
| c when (c >= '0' && c <= '9') || c = '.' ->
let j = ref i in
while !j < n && ((s.[!j] >= '0' && s.[!j] <= '9') || s.[!j] = '.') do
incr j
done;
let text = String.sub s i (!j - i) in
(match float_of_string_opt text with
| Some f -> go !j (TNum f :: acc)
| None -> raise (Bad (Printf.sprintf "%S is not a number" text)))
| c when (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ->
let j = ref i in
while
!j < n
&& ((s.[!j] >= 'A' && s.[!j] <= 'Z')
|| (s.[!j] >= 'a' && s.[!j] <= 'z')
|| (s.[!j] >= '0' && s.[!j] <= '9'))
do
incr j
done;
go !j (TName (String.uppercase_ascii (String.sub s i (!j - i))) :: acc)
| c -> raise (Bad (Printf.sprintf "%C has no meaning here" c))
in
go 0 []
let parse (s : string) : (expr, string) result =
let rest = ref [] in
let peek () = match !rest with [] -> None | t :: _ -> Some t in
let eat () = match !rest with [] -> raise (Bad "it stops too soon") | t :: r -> rest := r; t in
let expect t what = if eat () <> t then raise (Bad (Printf.sprintf "expected %s" what)) in
let name_as_cell name =
match cell_of_name name with
| Some c -> c
| None -> raise (Bad (Printf.sprintf "%s is not a cell" name))
in
let rec expr () =
let left = ref (term ()) in
let rec go () =
match peek () with
| Some (TOp (('+' | '-') as c)) ->
ignore (eat ());
left := Binop (c, !left, term ());
go ()
| _ -> ()
in
go ();
!left
and term () =
let left = ref (factor ()) in
let rec go () =
match peek () with
| Some (TOp (('*' | '/') as c)) ->
ignore (eat ());
left := Binop (c, !left, factor ());
go ()
| _ -> ()
in
go ();
!left
and factor () =
match peek () with
| Some (TOp '-') ->
ignore (eat ());
Unary ('-', factor ())
| Some (TOp '+') ->
ignore (eat ());
factor ()
| _ -> atom ()
and atom () =
match eat () with
| TNum f -> Number f
| TOpen ->
let e = expr () in
expect TClose ")";
e
| TName name -> (
match peek () with
| Some TOpen ->
ignore (eat ());
let args = ref [] in
(if peek () <> Some TClose then
let rec go () =
args := expr () :: !args;
if peek () = Some TComma then (
ignore (eat ());
go ())
in
go ());
expect TClose ")";
Call (name, List.rev !args)
| Some TColon ->
ignore (eat ());
let upto = match eat () with
| TName n -> name_as_cell n
| _ -> raise (Bad "a range ends at a cell")
in
Range (name_as_cell name, upto)
| _ -> Ref (name_as_cell name))
| TOp c -> raise (Bad (Printf.sprintf "%C where a value was expected" c))
| TClose -> raise (Bad "one ) too many")
| TComma | TColon -> raise (Bad "a stray , or :")
in
try
rest := tokens s;
if !rest = [] then Error "an empty formula"
else
let e = expr () in
if !rest <> [] then Error "there is something after the end of the formula" else Ok e
with Bad msg -> Error msg
let content_of (s : string) : content =
let trimmed = String.trim s in
if trimmed = "" then Blank
else if trimmed.[0] = '=' then
match parse (String.sub trimmed 1 (String.length trimmed - 1)) with
| Ok e -> Formula e
| Error msg -> Invalid msg
else match float_of_string_opt trimmed with Some f -> Value f | None -> Text trimmed
let to_string (e : expr) : string =
let prec = function Binop (('+' | '-'), _, _) -> 1 | Binop (('*' | '/'), _, _) -> 2 | _ -> 3 in
let rec go e =
match e with
| Number f -> if Float.is_integer f then Printf.sprintf "%.0f" f else Printf.sprintf "%g" f
| Ref c -> name_of_cell c
| Range (a, b) -> name_of_cell a ^ ":" ^ name_of_cell b
| Unary (c, e) -> Printf.sprintf "%c%s" c (wrap e 3)
| Binop (c, a, b) ->
let p = prec e in
Printf.sprintf "%s%c%s" (wrap a p) c (wrap b (p + 1))
| Call (name, args) -> Printf.sprintf "%s(%s)" name (String.concat "," (List.map go args))
and wrap e p = if prec e < p then "(" ^ go e ^ ")" else go e in
go e
let shift ((dc, dr) : int * int) (e : expr) : expr =
let move (c, r) = (max 0 (c + dc), max 0 (r + dr)) in
let rec go = function
| Number f -> Number f
| Ref c -> Ref (move c)
| Range (a, b) -> Range (move a, move b)
| Unary (c, e) -> Unary (c, go e)
| Binop (c, a, b) -> Binop (c, go a, go b)
| Call (name, args) -> Call (name, List.map go args)
in
go e
let refs (e : expr) : cell list =
let out = ref [] in
let rec go = function
| Number _ -> ()
| Ref c -> out := c :: !out
| Range ((c1, r1), (c2, r2)) ->
for col = min c1 c2 to max c1 c2 do
for row = min r1 r2 to max r1 r2 do
out := (col, row) :: !out
done
done
| Unary (_, e) -> go e
| Binop (_, a, b) -> go a; go b
| Call (_, args) -> List.iter go args
in
go e;
List.sort_uniq compare !out