package atd

  1. Overview
  2. Docs

Source file annot.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
(*
   Utilities for interpreting annotations of type Ast.annot.
*)

open Import

type t = Ast.annot

let error_at loc s =
  failwith (sprintf "%s:\n%s" (Ast.string_of_loc loc) s)

let fields ~section ~field l =
  List.filter_map (fun (s, (_, fs)) ->
    if s = section then Some fs else None) l
    |> List.map (fun fs ->
      List.filter_map (fun (f, (l, s)) ->
        if f = field then Some (l, s) else None)
      fs)
    |> List.flatten

let field ~section ~field l =
  match fields ~section ~field l with
  | [fieldmatch] -> Some fieldmatch
  | (loc, _) :: others -> error_at loc
    (sprintf "Duplicate annotation %s.%s (also in:\n  %s\n)" section field
    (List.map (fun (loc, _) -> (Ast.string_of_loc loc)) others
     |> String.concat ",\n  "))
  | _ -> None

let has_section k l =
  Option.is_some (List.assoc k l)

let has_field ~sections:k ~field:k2 l =
  List.exists (fun k1 ->
    field ~section:k1 ~field:k2 l
    |> Option.is_some
  ) k

let get_flag ~sections:k ~field:k2 l =
  k
  |> List.find_map (fun k1 ->
    field ~section:k1 ~field:k2 l
    |> Option.map (fun (loc, o) ->
      match o with
      | None | Some "true" -> true
      | Some "false" -> false
      | Some s ->
          error_at loc
            (sprintf "Invalid value %S for flag %s.%s" s k1 k2)))
  |> Option.value ~default:false

let get_field ~parse ~default ~sections:k ~field:k2 l =
  k
  |> List.find_map (fun k1 ->
    let open Option.O in
    field l ~section:k1 ~field:k2 >>= fun (loc, o) ->
    match o with
    | Some s ->
        (match parse s with
           Some _ as y -> y
         | None ->
             error_at loc
               (sprintf "Invalid annotation <%s %s=%S>" k1 k2 s))
    | None ->
        error_at loc
          (sprintf "Missing value for annotation %s.%s" k1 k2))
  |> Option.value ~default

let get_fields ~parse ~sections ~field l =
  List.find_map (fun section ->
    Some (
      fields l ~section ~field
      |> List.map (fun (loc, o) ->
        match o with
        | None ->
            error_at loc
              (sprintf "Missing value for annotation %s.%s" section field)
        | Some s ->
            (match parse s with
             | None ->
                 error_at loc
                   (sprintf "Invalid annotation <%s %s=%S>" section field s)
             | Some v -> v))
    )) sections
  |> Option.value ~default:[]

let get_opt_field ~parse ~sections ~field l =
  let parse s =
    match parse s with
    | None -> None (* indicates parse error *)
    | Some v -> Some (Some v)
  in
  get_field ~parse ~default:None ~sections ~field l

let set_field ~loc ~section:k ~field:k2 v l : Ast.annot =
  match List.assoc k l with
  | None -> (k, (loc, [ k2, (loc, v) ])) :: l
  | Some (section_loc, section) ->
      let section_loc, section = List.assoc_exn k l in
      let section =
        match List.assoc k2 section with
        | None -> (k2, (loc, v)) :: section
        | Some _ -> List.assoc_update k2 (loc, v) section
      in
      List.assoc_update k (section_loc, section) l

let get_loc ~sections:k ~field:k2 l =
  k
  |> List.find_map (fun k1 ->
    let open Option.O in
    field l ~section:k1 ~field:k2 >>= fun (loc, _o) -> Some loc)

let get_loc_exn ~sections ~field l =
  get_loc ~sections ~field l |> Option.value_exn

let collapse merge l =
  let tbl = Hashtbl.create 10 in
  let n = ref 0 in

  List.iter (
    fun (s1, f1) ->
      incr n;
      try
        let _, f2 = Hashtbl.find tbl s1 in
        Hashtbl.replace tbl s1 (!n, merge f1 f2)
      with Not_found ->
        Hashtbl.add tbl s1 (!n, f1)
  ) (List.rev l);

  let l = Hashtbl.fold (fun s (i, f) l -> (i, (s, f)) :: l) tbl [] in
  let l = List.sort (fun (i, _) (j, _) -> compare j i) l in
  List.map snd l

let override_values x1 _ = x1

let override_fields (loc1, l1) (_, l2) =
  (loc1, collapse override_values (l1 @ l2))

let merge l =
  collapse override_fields l

let create_id =
  let n = ref (-1) in
  fun () ->
    incr n;
    if !n < 0 then
      failwith "Annot.create_id: counter overflow"
    else
      string_of_int !n

type node_kind =
  | Module_head
  | Type_def
  | Type_expr
  | Variant
  | Cell
  | Field

type schema_field = node_kind * string

type schema_section = {
  section: string;
  fields: schema_field list;
}

type schema = schema_section list

let validate_section sec root =
  (* split fields by location where they may occur *)
  let in_module_head = ref [] in
  let in_type_def = ref [] in
  let in_type_expr = ref [] in
  let in_variant = ref [] in
  let in_cell = ref [] in
  let in_field = ref [] in
  sec.fields
  |> List.iter (fun (kind, field_name) ->
    let acc =
      match kind with
      | Module_head -> in_module_head
      | Type_def -> in_type_def
      | Type_expr -> in_type_expr
      | Variant -> in_variant
      | Cell -> in_cell
      | Field -> in_field
    in
    acc := field_name :: ! acc
  );
  let check acc =
    let allowed_fields = List.rev !acc in
    fun _node (an : Ast.annot) () ->
      an
      |> List.iter (fun ((sec_name, (loc, fields)) : Ast.annot_section) ->
        if sec_name = sec.section then
          fields
          |> List.iter (fun (field_name, (loc2, _opt_val)) ->
            if not (List.mem field_name allowed_fields) then
              Ast.error_at loc2
                (sprintf "Invalid or misplaced annotation <%s ... %s... >"
                   sec_name field_name)
          )
      )
  in
  Ast.fold_annot
    ~module_head:(check in_module_head)
    ~type_def:(check in_type_def)
    ~type_expr:(check in_type_expr)
    ~variant:(check in_variant)
    ~cell:(check in_cell)
    ~field:(check in_field)
    root ()

let validate schema root =
  List.iter (fun sec ->
    validate_section sec root
  ) schema
OCaml

Innovation. Community. Security.