Source file Highlight_c.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
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
open Highlight_code
let control_keywords = [ "if"; "else"; "while"; "for"; "do"; "switch"; "case"; "default"; "return"; "break"; "continue"; "goto" ]
let type_keywords = [ "void"; "char"; "short"; "int"; "long"; "float"; "double"; "signed"; "unsigned"; "_Bool"; "__signed__" ]
let is_banner (t : Token_c.t) : bool =
t.kind = Comment
&& String.length t.text >= 7
&& (String.sub t.text 0 7 = "/******" || String.sub t.text 0 7 = "//*****")
let of_kind (t : Token_c.t) : category =
match t.kind with
| Comment -> Comment
| Keyword ->
if List.mem t.text control_keywords then Keyword_control
else if List.mem t.text type_keywords then Type
else Keyword
| Ident -> if Token_c.is_constant t.text then Constructor else Normal
| Int | Float -> Number
| Char | String -> String
| Operator -> Operator
| Punctuation -> Punctuation
| Directive -> Keyword_module
| Error -> Error
open Ast_c
type env = (string * (category * int)) list
let resolve (file : file) :
(int, category) Hashtbl.t * (int, int) Hashtbl.t * ((int * Highlight_code.space * int) list * (int * Highlight_code.space) list) =
let out = Hashtbl.create 1024 in
let binds = Hashtbl.create 256 in
let mark (n : name) (c : category) = if n.tok >= 0 then Hashtbl.replace out n.tok c in
let bound (n : name) (c : category) (env : env) : env =
if n.tok >= 0 then Hashtbl.replace binds n.tok n.tok;
(n.text, (c, n.tok)) :: env
in
let use (n : name) (b : int) = if n.tok >= 0 && b >= 0 then Hashtbl.replace binds n.tok b in
let values = Hashtbl.create 64 and types = Hashtbl.create 16 and tags = Hashtbl.create 16 in
let space_of tbl : Highlight_code.space = if tbl == types then Type else if tbl == tags then Tag else Value in
let declared = ref [] in
let defs = ref [] and refs = ref [] in
let declare tbl (n : name) (rank : int) =
if n.tok >= 0 then begin
declared := (tbl, n) :: !declared;
defs := (n.tok, space_of tbl, rank) :: !defs;
match Hashtbl.find_opt tbl n.text with Some (r, _) when r >= rank -> () | _ -> Hashtbl.replace tbl n.text (rank, n.tok)
end
in
let declare_base (t : ty) =
match t with
| Tstruct (Some tag, Some _) -> declare tags tag 3
| Tenum (tag, Some cs) -> Option.iter (fun n -> declare tags n 3) tag; List.iter (fun (n, _) -> declare values n 3) cs
| _ -> ()
in
List.iter (fun d -> declare values d.mname 1) file.defines;
let bodies = Hashtbl.create 16 in
List.iter
(function
| Ifunc (sp, _, _, _) | Idecl (sp, _) -> ( match sp.base with Tstruct (Some tag, Some _) -> Hashtbl.replace bodies tag.text tag | _ -> ())
| Imacro _ -> ())
file.items;
List.iter
(function
| Ifunc (sp, d, _, _) -> declare_base sp.base; Option.iter (fun n -> declare values n 3) d.dname
| Idecl (sp, ds) ->
declare_base sp.base;
List.iter
(fun d ->
Option.iter
(fun (n : name) ->
match (sp.typedef, sp.base) with
| true, Tstruct (Some tag, None) when tag.text = n.text && Hashtbl.mem bodies n.text ->
let body = Hashtbl.find bodies n.text in
declare types body 3;
use n body.tok
| true, _ -> declare types n 3
| false, _ -> declare values n (match d.dty with Tfunc _ -> 2 | _ -> 3))
d.dname)
ds
| Imacro _ -> ())
file.items;
List.iter (fun (tbl, (n : name)) -> match Hashtbl.find_opt tbl n.text with Some (_, b) -> Hashtbl.replace binds n.tok b | None -> ()) !declared;
let refer tbl (n : name) =
if n.tok >= 0 then
match Hashtbl.find_opt tbl n.text with
| Some (_, b) -> Hashtbl.replace binds n.tok b
| None -> refs := (n.tok, space_of tbl) :: !refs
in
let constants = Hashtbl.create 64 in
List.iter (fun d -> if d.mparams = None then Hashtbl.replace constants d.mname.text ()) file.defines;
let rec ty (env : env) (t : ty) =
match t with
| Tbase -> ()
| Tname n -> mark n Type; refer types n
| Tstruct (tag, fields) ->
Option.iter (fun n -> mark n (if fields = None then Type else Def_type)) tag;
if fields = None then Option.iter (refer tags) tag;
Option.iter (List.iter (decl env Field)) fields
| Tenum (tag, cs) ->
Option.iter (fun n -> mark n (if cs = None then Type else Def_type)) tag;
Option.iter
(List.iter (fun (n, v) ->
mark n Constructor;
Hashtbl.replace constants n.text ();
Option.iter (expr env) v))
cs
| Tptr t -> ty env t
| Tarray (t, e) -> ty env t; Option.iter (expr env) e
| Tfunc (t, ps) -> ty env t; List.iter (decl env Parameter) ps
| Ttypeof e -> expr env e
and decl (env : env) (c : category) (d : decl) =
Option.iter (fun n -> mark n c) d.dname;
ty env d.dty;
Option.iter (init env) d.dinit
and init env (i : init) =
match i with
| Iexpr e -> expr env e
| Ilist items ->
List.iter
(fun (ds, i) ->
List.iter (function Dfield n -> mark n Field | Dindex e -> expr env e) ds;
init env i)
items
and expr (env : env) (e : expr) =
match e with
| Econst -> ()
| Eident n -> (
match List.assoc_opt n.text env with
| Some (c, b) ->
mark n c;
use n b
| None ->
mark n (if Hashtbl.mem constants n.text || Token_c.is_constant n.text then Constructor else Normal);
refer values n)
| Efield (e, n) -> expr env e; mark n Field
| Ecall (f, args) -> expr env f; List.iter (expr env) args
| Ecast (t, e) -> ty env t; expr env e
| Etype t -> ty env t
| Ecompound (t, i) -> ty env t; init env i
| Estmt ss -> stmts env ss
| Emisc es -> List.iter (expr env) es
and stmts (env : env) (ss : stmt list) = ignore (List.fold_left stmt env ss)
and stmt (env : env) (s : stmt) : env =
match s with
| Sexpr e -> expr env e; env
| Sdecl (sp, ds) ->
ty env sp.base;
List.fold_left
(fun env d ->
let c = if sp.typedef then Def_type else Local in
let env = match d.dname with Some n when not sp.typedef -> bound n c env | _ -> env in
decl env c d;
env)
env ds
| Sblock ss -> stmts env ss; env
| Sfor (first, es, body) ->
let inner = match first with Some s -> stmt env s | None -> env in
List.iter (expr inner) es;
ignore (stmt inner body);
env
| Snest (es, ss) ->
List.iter (expr env) es;
List.iter (fun s -> ignore (stmt env s)) ss;
env
| Slabel (n, s) -> mark n Label; stmt env s
| Sgoto n -> mark n Label; env
in
let params_of (t : ty) : decl list = match t with Tfunc (_, ps) -> ps | _ -> [] in
let names (ds : decl list) : env = List.fold_right (fun d env -> match d.dname with Some n -> bound n Parameter env | None -> env) ds [] in
let item (it : item) =
match it with
| Ifunc (sp, d, kr, body) ->
ty [] sp.base;
decl [] Def_function d;
List.iter (decl [] Parameter) kr;
stmts (names (params_of d.dty) @ names kr) body
| Idecl (sp, ds) ->
ty [] sp.base;
List.iter
(fun d ->
let c = if sp.typedef then Def_type else match d.dty with Tfunc _ -> Def_function | _ -> Def_value in
decl [] c d)
ds
| Imacro e -> expr [] e
in
List.iter
(fun d ->
mark d.mname (if d.mparams = None then Def_value else Def_function);
let ps = Option.value d.mparams ~default:[] in
let env = List.fold_left (fun env p -> mark p Parameter; bound p Parameter env) [] ps in
List.iter
(fun (n : name) ->
match List.assoc_opt n.text env with
| Some (_, b) ->
mark n Parameter;
use n b
| None -> ())
d.mbody)
file.defines;
List.iter item file.items;
(out, binds, (List.rev !defs, List.rev !refs))
let categorize_bound (toks : Token_c.t list) =
let tree, binds, others = resolve (Parse_c.parse toks) in
let all = Array.of_list toks in
let n = Array.length all in
( Array.to_list
(Array.mapi
(fun i (t : Token_c.t) ->
let c =
match Hashtbl.find_opt tree i with
| Some c when t.kind = Ident -> c
| _ ->
if is_banner t || (t.kind = Comment && i > 0 && is_banner all.(i - 1) && i + 1 < n && is_banner all.(i + 1)) then Comment_section
else of_kind t
in
(t, c))
all),
binds,
others )
let categorize (toks : Token_c.t list) : (Token_c.t * category) list =
let cats, _, _ = categorize_bound toks in
cats
let includes (all : Token_c.t array) : string list =
let out = ref [] in
Array.iteri
(fun i (t : Token_c.t) ->
if t.kind = Directive && String.trim (String.sub t.text 1 (String.length t.text - 1)) = "include" && i + 1 < Array.length all then
let s = all.(i + 1).text in
if all.(i + 1).kind = String && String.length s >= 2 && s.[0] = '"' then out := String.sub s 1 (String.length s - 2) :: !out)
all;
List.rev !out
let analyze (src : string) : analysis =
let toks = Lexer_c.tokens src in
let cats, binds, (defs, refs) = categorize_bound toks in
let all = Array.of_list toks in
let places = Array.map (fun (t : Token_c.t) -> (t.line, t.col, t.text)) all in
{
spans = Highlight_code.lines src (List.rev (List.rev_map (fun ((t : Token_c.t), c) -> (t.line, t.col, t.text, c)) cats));
occurrences = Highlight_code.occurrences places binds;
definitions = List.rev (List.rev_map (fun (tok, space, rank) -> Highlight_code.definition places tok space rank) defs);
references = List.rev (List.rev_map (fun (tok, space) -> Highlight_code.reference places tok [] space) refs);
opens = [];
includes = includes all;
}
let lines (src : string) : span list array = (analyze src).spans