aboutsummaryrefslogtreecommitdiff
path: root/lib/checks/locations.ml
blob: 8ee6ffae6c02d41bccf0d46d274d05c8bc0031ae (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
open StdLabels
module S = Qsp_syntax.S
module T = Qsp_syntax.T
module Report = Qsp_syntax.Report

module IgnoreCaseString = struct
  type t = string

  let compare t1 t2 =
    String.compare (String.lowercase_ascii t1) (String.lowercase_ascii t2)

  let equal t1 t2 =
    String.equal (String.lowercase_ascii t1) (String.lowercase_ascii t2)
end

module LocationSet = Set.Make (IgnoreCaseString)
module LocationCalls = Map.Make (IgnoreCaseString)

let identifier = "locations"
let description = "Ensure every call points to an existing location"
let is_global = true
let active = ref true

type t = {
  locations : LocationSet.t;
  calls : (string * S.pos) list LocationCalls.t;
}

type context = t ref

let initialize () =
  ref { locations = LocationSet.empty; calls = LocationCalls.empty }

let finalize : context -> (string * Report.t) list =
 fun context ->
  LocationCalls.fold
    (fun location positions acc ->
      let message = Printf.sprintf "The location %s does not exists" location in

      List.fold_left ~init:acc (List.rev positions)
        ~f:(fun acc (loc, position) ->
          let report = Report.error position message in
          (loc, report) :: acc))
    !context.calls []

(** Register a new call to a defined location. *)
let registerCall : S.pos -> string -> t -> t =
 fun pos location t ->
  let file_name = (fst pos).Lexing.pos_fname in
  match
    IgnoreCaseString.equal location file_name
    || LocationSet.mem location t.locations
  with
  | true -> t
  | false ->
      (* The location is not yet defined, register the call for later *)
      let calls =
        LocationCalls.update location
          (function
            | None -> Some [ (file_name, pos) ]
            | Some poss ->
                Some
                  (let new_pos = (file_name, pos) in
                   new_pos :: poss))
          t.calls
      in
      { t with calls }

(** Add a new location in the list of all the collected elements *)
let registerLocation : string -> t -> t =
 fun location t ->
  let calls = LocationCalls.remove location t.calls
  and locations = LocationSet.add location t.locations in
  { calls; locations }

(** The module Expression is pretty simple, we are only interrested by the
    strings ( because only the first argument of [gt …] is read ). 

    If the string is too much complex, we just ignore it. *)
module Expression = struct
  type t = string option

  include Default.Expression (struct
    type nonrec t = t

    let default = None
  end)

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

  (* Extract the litteral if this is a simple text *)
  let literal : S.pos -> t' T.literal list -> t' =
   fun _ ll -> match ll with Text lit :: [] -> Some lit | _ -> None
end

module Instruction = struct
  type nonrec t = t -> t
  type t' = t

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

  (** Keep a track of every gt or gs instruction *)
  let call : S.pos -> T.keywords -> Expression.t' list -> t =
   fun pos fn args t ->
    match (fn, args) with
    | T.Goto, Some dest :: _ -> registerCall pos dest t
    | T.Gosub, Some dest :: _ -> registerCall pos dest t
    | _ -> t

  let location : S.pos -> string -> t = fun _ _ -> Fun.id
  let comment : S.pos -> t = fun _ -> Fun.id
  let expression : Expression.t' -> t = fun _ -> Fun.id

  let if_ :
      S.pos ->
      (Expression.t', t) S.clause ->
      elifs:(Expression.t', t) S.clause list ->
      else_:(S.pos * t list) option ->
      t =
   fun _ clause ~elifs ~else_ t ->
    let traverse_clause t clause =
      let _, _, block = clause in
      List.fold_left block ~init:t ~f:(fun t instruction -> instruction t)
    in

    let t = traverse_clause t clause in
    let t = List.fold_left ~init:t ~f:traverse_clause elifs in
    match else_ with
    | None -> t
    | Some (_, instructions) ->
        List.fold_left instructions ~init:t ~f:(fun t instruction ->
            instruction t)

  let act : S.pos -> label:Expression.t' -> t list -> t =
   fun _ ~label instructions t ->
    ignore label;
    List.fold_left instructions ~init:t ~f:(fun t instruction -> instruction t)

  let assign :
      S.pos ->
      (S.pos, Expression.t') S.variable ->
      T.assignation_operator ->
      Expression.t' ->
      t =
   fun _ _ _ _ -> Fun.id
end

module Location = struct
  type t = unit

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

  let location : context -> S.pos -> Instruction.t list -> t =
   fun context pos instructions ->
    (* Register the location *)
    let file_name = (fst pos).Lexing.pos_fname in
    let c = registerLocation file_name !context in
    (* Then update the list of all the calls to the differents locations *)
    context :=
      List.fold_left instructions ~init:c ~f:(fun t instruction ->
          instruction t)
end