package sexp

  1. Overview
  2. Docs

Source file utils.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
open Core

let simple_query query sexp = Lazy_list.to_list (Semantics.query query sexp)

let get_fields sexp field =
  simple_query (Syntax.Pipe (Syntax.Smash, Syntax.Field field)) sexp
;;

module Non_unique_field = struct
  type t =
    { field : string
    ; sexp : Sexp.t
    ; matches : Sexp.t list
    }
  [@@deriving sexp]
end

let get_one_field sexp field =
  let results = get_fields sexp field in
  match results with
  | [] | _ :: _ :: _ ->
    Or_error.error
      "non-unique field"
      { Non_unique_field.field; sexp; matches = results }
      Non_unique_field.sexp_of_t
  | [ result ] -> Ok result
;;

let sexp_rewrite_aux sexp ~f:visit =
  let rec aux sexp =
    match visit sexp with
    | `Changed sexp' -> Some sexp'
    | `Removed -> None
    | `Unchanged ->
      (match sexp with
       | Sexp.Atom _ -> Some sexp
       | Sexp.List sexps ->
         let sexps' = List.filter_map ~f:aux sexps in
         if List.length sexps = List.length sexps'
            && List.for_all2_exn ~f:phys_equal sexps sexps'
         then Some sexp
         else Some (Sexp.List sexps'))
  in
  match aux sexp with
  | None -> Or_error.error "not a record" sexp Fn.id
  | Some sexp -> Ok sexp
;;

let sexp_rewrite sexp ~f = sexp_rewrite_aux sexp ~f |> Or_error.ok_exn

let immediate_fields = function
  | Sexp.List children ->
    List.fold ~init:(Ok []) children ~f:(fun acc child ->
      match acc with
      | Error _ -> acc
      | Ok by_field ->
        (match child with
         | Sexp.List [ Sexp.Atom field; value ] ->
           (match List.Assoc.find by_field ~equal:String.equal field with
            | None -> Ok (List.Assoc.add by_field ~equal:String.equal field value)
            | Some _ -> Or_error.error "multiple values for field" field String.sexp_of_t)
         | _ -> Or_error.error "not a field" child Fn.id))
    |> fun result ->
    (* Restore original order *)
    Or_error.map result ~f:List.rev
  | Sexp.Atom atom -> Or_error.error "not a record" atom String.sexp_of_t
;;

let to_record_sexp by_fields =
  Sexp.List
    (List.map by_fields ~f:(fun (field, value) -> Sexp.List [ Sexp.Atom field; value ]))
;;

let replace_immediate_field ~field ~value sexp =
  Or_error.map (immediate_fields sexp) ~f:(fun by_field ->
    List.Assoc.remove by_field ~equal:String.equal field
    |> (fun by_field -> List.Assoc.add by_field ~equal:String.equal field value)
    |> to_record_sexp)
;;

let replace_field_recursively ~field ~value sexp =
  sexp_rewrite_aux sexp ~f:(function
    | Sexp.List [ Sexp.Atom f; _ ] when String.equal field f ->
      `Changed (Sexp.List [ Sexp.Atom f; value ])
    | _ -> `Unchanged)
;;

let replace_field ~field ~value sexp immediate_or_recursive =
  match immediate_or_recursive with
  | `Immediate -> replace_immediate_field ~field ~value sexp
  | `Recursive ->
    let%bind.Or_error result = replace_field_recursively ~field ~value sexp in
    if Sexp.( = ) result sexp
    then Or_error.error "field not found" field String.sexp_of_t
    else Ok result
;;

let remove_immediate_field ~field sexp =
  Or_error.map (immediate_fields sexp) ~f:(fun by_field ->
    List.Assoc.remove by_field ~equal:String.equal field |> to_record_sexp)
;;

let remove_field_recursively ~field sexp =
  sexp_rewrite_aux sexp ~f:(function
    | Sexp.List [ Sexp.Atom f; _ ] when String.equal field f -> `Removed
    | _ -> `Unchanged)
;;

let remove_field ~field sexp immediate_or_recursive =
  match immediate_or_recursive with
  | `Immediate -> remove_immediate_field ~field sexp
  | `Recursive -> remove_field_recursively ~field sexp
;;

let%test_module "Utils" =
  (module struct
    let sexp =
      Sexp.of_string
        "((first (a b c)) (second 123) (third ()) (fourth ((foo a) (boo b))))"
    ;;

    let%test _ =
      [%compare.equal: Sexp.t Or_error.t]
        (get_one_field sexp "second")
        (Ok (Sexp.Atom "123"))
    ;;

    let%test _ = Result.is_error (get_one_field sexp "zoo")

    let%test _ =
      [%compare.equal: Sexp.t Or_error.t] (get_one_field sexp "boo") (Ok (Sexp.Atom "b"))
    ;;

    let%test _ = Result.is_error (immediate_fields (Sexp.of_string "zoo"))
    let%test _ = Result.is_error (immediate_fields (Sexp.of_string "(zoo)"))
    let%test _ = Result.is_error (immediate_fields (Sexp.of_string "(zoo boo)"))
    let%test _ = Result.is_error (immediate_fields (Sexp.of_string "((good true)(bad))"))

    let%test _ =
      [%equal: Sexp.t]
        (List.Assoc.find_exn
           (Or_error.ok_exn (immediate_fields sexp))
           ~equal:String.equal
           "second")
        (Atom "123")
    ;;

    let%test _ =
      [%equal: Sexp.t]
        (List.Assoc.find_exn
           (Or_error.ok_exn (immediate_fields sexp))
           ~equal:String.equal
           "third")
        (List [])
    ;;

    let%test _ =
      [%equal: Sexp.t]
        (List.Assoc.find_exn
           (Or_error.ok_exn (immediate_fields sexp))
           ~equal:String.equal
           "fourth")
        (Sexp.of_string "((foo a) (boo b))")
    ;;

    let%test _ =
      [%equal: Sexp.t] (to_record_sexp (Or_error.ok_exn (immediate_fields sexp))) sexp
    ;;

    let%test _ =
      let value = Sexp.Atom "my-new-value" in
      let sexp = Or_error.ok_exn (replace_field ~field:"second" ~value sexp `Immediate) in
      [%equal: Sexp.t]
        (List.Assoc.find_exn
           (Or_error.ok_exn (immediate_fields sexp))
           ~equal:String.equal
           "second")
        value
    ;;

    let to_alist_exn sexp = Or_error.ok_exn (immediate_fields sexp)

    let ( -@! ) record_sexp field_name =
      List.Assoc.find_exn (to_alist_exn record_sexp) ~equal:String.equal field_name
    ;;

    let ( -@? ) record_sexp field_name =
      List.Assoc.find (to_alist_exn record_sexp) ~equal:String.equal field_name
    ;;

    let%test _ =
      let value = Sexp.Atom "my-new-value" in
      let sexp = Or_error.ok_exn (replace_field ~field:"foo" ~value sexp `Recursive) in
      [%equal: Sexp.t] (sexp -@! "fourth" -@! "foo") value
    ;;

    let%test "remove_field immediate" =
      let sexp = Or_error.ok_exn (remove_field ~field:"second" sexp `Immediate) in
      [%equal: Sexp.t option] (sexp -@? "second") None
    ;;

    let%test "remove_field recursive" =
      let sexp = Or_error.ok_exn (remove_field ~field:"foo" sexp `Recursive) in
      [%equal: Sexp.t option] (sexp -@! "fourth" -@? "foo") None
    ;;

    let%test "remove_field error" =
      let sexp_or_error = remove_field ~field:"foo" (Sexp.of_string "(foo)") `Immediate in
      Or_error.is_error sexp_or_error
    ;;
  end)
;;
OCaml

Innovation. Community. Security.