aboutsummaryrefslogtreecommitdiff
path: root/lib/syntax/write_only.ml
blob: e77ad88e6550a5c33eeff3815239c03f194f161d (plain)
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
(** Check all the write_only variables *)

open StdLabels

(** Identifier for the module *)
let identifier = "write_only"

(** Short description*)
let description = "Check variables never read"

(** Is the test active or not *)
let active = ref false

let is_global = true

module Key = struct
  type t = string

  let equal = String.equal
  let hash = Hashtbl.hash
  let compare = String.compare
end

module StringMap = Hashtbl.Make (Key)
module Set = Set.Make (Key)

type data = { write : bool; read : bool; position : S.pos list }
type context = (string * data) StringMap.t

let initialize () = StringMap.create 16

let keywords =
  [
    "BACKIMAGE";
    "$BACKIMAGE";
    "BCOLOR";
    "DEBUG";
    "DISABLESCROLL";
    "DISABLESUBEX";
    "FCOLOR";
    "$FNAME";
    "FSIZE";
    "GC";
    "LCOLOR";
    "NOSAVE";
  ]
  |> Set.of_list

let set_readed :
    ?update_only:bool -> S.pos -> string -> string -> context -> unit =
 fun ?(update_only = false) pos identifier filename map ->
  if not (Set.mem identifier keywords) then
    match (update_only, StringMap.find_opt map identifier) with
    | false, None ->
        StringMap.add map identifier
          (filename, { write = false; read = true; position = [] })
    | _, Some (filename, v) ->
        StringMap.replace map identifier
          (filename, { v with read = true; position = pos :: v.position })
    | true, None -> ()

let set_write : S.pos -> string -> string -> context -> unit =
 fun pos identifier filename map ->
  if not (Set.mem identifier keywords) then
    match StringMap.find_opt map identifier with
    | None ->
        StringMap.add map identifier
          (filename, { write = true; read = false; position = pos :: [] })
    | Some (filename, v) ->
        StringMap.replace map identifier
          (filename, { v with write = true; position = pos :: v.position })

module Expression = struct
  type t = string -> context -> unit

  let v : t -> t = Fun.id

  include Default.Expression (struct
    type nonrec t = t

    let default _ map = ignore map
  end)

  let ident : (S.pos, t) S.variable -> t =
   fun variable filename map ->
    (* Update the map and set the read flag *)
    set_readed variable.pos variable.name filename map

  let literal : S.pos -> t T.literal list -> t =
   fun pos l filename map ->
    List.iter l ~f:(function
      | T.Text t ->
          set_readed pos ~update_only:true (String.uppercase_ascii t) filename
            map
      | T.Expression exprs ->
          (* When the string contains an expression evaluate it *)
          exprs filename map)

  let function_ : S.pos -> T.function_ -> t list -> t =
   fun _ _ exprs filename map -> List.iter ~f:(fun v -> v filename map) exprs

  let uoperator : S.pos -> T.uoperator -> t -> t = fun _ _ t map -> t map

  let boperator : S.pos -> T.boperator -> t -> t -> t =
   fun _ _ t1 t2 filename map ->
    t1 filename map;
    t2 filename map
end

module Instruction = struct
  type t = Expression.t
  (** Internal type used in the evaluation *)

  type t' = t

  let v : t -> t' = Fun.id

  type expression = Expression.t

  let location : S.pos -> string -> t = fun _pos _ _ _ -> ()

  let call : S.pos -> T.keywords -> expression list -> t =
   fun _ op exprs filename map ->
    match op with
    | T.KillVar ->
        (* Killing a variable does not count as reading it *)
        ()
    | _ -> List.iter ~f:(fun v -> v filename map) exprs

  let comment : S.pos -> t = fun _ _ _ -> ()
  let expression : expression -> t = fun expression map -> expression map

  let fold_clause : (expression, t) S.clause -> t =
   fun clause filename map ->
    let _, expr, exprs = clause in
    let () = expr filename map in
    let () = List.iter ~f:(fun v -> v filename map) exprs in
    ()

  let if_ :
      S.pos ->
      (expression, t) S.clause ->
      elifs:(expression, t) S.clause list ->
      else_:(S.pos * t list) option ->
      t =
   fun pos clauses ~elifs ~else_ filename map ->
    ignore pos;
    let () = fold_clause clauses filename map in
    let () = List.iter ~f:(fun v -> fold_clause v filename map) elifs in
    Option.iter
      (fun (_, exprs) -> List.iter exprs ~f:(fun v -> v filename map))
      else_;
    ()

  let act : S.pos -> label:expression -> t list -> t =
   fun pos ~label exprs filename map ->
    ignore pos;
    ignore label;
    List.iter ~f:(fun v -> v filename map) exprs

  let assign :
      S.pos ->
      (S.pos, expression) S.variable ->
      T.assignation_operator ->
      expression ->
      t =
   fun pos variable op expr filename map ->
    ignore op;
    ignore expr;
    Option.iter (fun v -> v filename map) variable.index;
    expr filename map;
    set_write pos variable.name filename map
end

module Location = struct
  type t = unit
  type instruction = string -> context -> unit

  let v : t -> Report.t list = fun _ -> []

  let location : context -> S.pos -> instruction list -> t =
   fun context pos instructions ->
    let file_name = (fst pos).Lexing.pos_fname in
    ignore pos;
    ignore context;
    let () = List.iter ~f:(fun v -> v file_name context) instructions in
    ()
end

(** Extract the results from the whole parsing *)
let finalize : context -> (string * Report.t) list =
 fun map ->
  let () =
    StringMap.filter_map_inplace
      (fun _ (loc, value) ->
        match value.read && value.write with
        | true -> None
        | false -> Some (loc, value))
      map
  in

  let report =
    StringMap.fold
      (fun ident (loc, value) report ->
        match value.read with
        | false ->
            List.fold_left value.position ~init:report ~f:(fun report pos ->
                let msg =
                  Report.debug pos
                    (String.concat ~sep:" "
                       [ "The variable"; ident; "is never read" ])
                in
                (loc, msg) :: report)
        | true -> report)
      map []
  in
  report