package ez_config

  1. Overview
  2. Docs

Source file simpleConfigOCaml.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
(**************************************************************************)
(*                                                                        *)
(*   Typerex Libraries                                                    *)
(*                                                                        *)
(*   Copyright 2011-2017 OCamlPro SAS                                     *)
(*                                                                        *)
(*   All rights reserved.  This file is distributed under the terms of    *)
(*   the GNU Lesser General Public License version 2.1, with the          *)
(*   special exception on linking described in the file LICENSE.          *)
(*                                                                        *)
(**************************************************************************)

open SimpleConfigTypes

open Genlex

let lexer = make_lexer ["="; "{"; "}"; "["; "]"; ";"; "("; ")"; ","; "."; "@"]

let once_values = Hashtbl.create 13

let parse_config_file str =

  let rec parse_top options =
    match Stream.peek str with
    | Some (Ident s | String s) ->
      begin
        Stream.junk str;
        match Stream.next str with
        | Kwd "=" ->
          let v = parse_option () in
          parse_top ( (s,v) :: options)
        | _ -> failwith "Operator '=' expected"
      end
    | tok -> List.rev options, tok

  and parse_option () =
    match Stream.next str with
    | Ident s | String s -> StringValue s
    | Int i -> IntValue i
    | Float f -> FloatValue f
    | Char c -> StringValue (String.make 1 c)
    | Kwd "[" -> parse_list "]" []
    | Kwd "(" -> parse_list ")" []
    | Kwd "{" ->
      begin
      let (options, tok) = parse_top [] in
      match tok with
      | Some (Kwd "}") ->
        Stream.junk str;
        Module options
      | _ -> failwith "Symbol '}' expected"
      end
    | Kwd "@" ->
      begin
        match Stream.next str with
        | Int i ->
          let v = parse_once_value i in
          OnceValue v
        | _ -> failwith "expected int"
      end
    | _ -> failwith "expected value"

  and parse_once_value i =
    match Stream.next str with
    | Kwd "@" ->
      begin
        try Hashtbl.find once_values i with Not_found ->
          Printf.kprintf failwith "once value @%d@ is unknown" i
      end
    | Kwd "=" ->
      let v = parse_option () in
      Hashtbl.add once_values i v;
      v
    | _ -> failwith "operators '=' or '@' expected"

  and parse_list end_kwd values =
    match Stream.peek str with
    | None ->
      Printf.kprintf failwith "reached end of file before %s" end_kwd
    | Some (Kwd ( ";" | "," | ".") ) ->
      Stream.junk str;
      parse_list end_kwd values
    | Some (Kwd kwd) when kwd = end_kwd ->
      Stream.junk str;
      List (List.rev values)
    | _ ->
      let v = parse_option () in
      parse_list end_kwd (v :: values)

  in
  let (options, tok) = parse_top [] in
  match tok with
  | Some _ -> failwith "ident or string expected"
  | None -> options

let parse filename ic =
      Hashtbl.clear once_values;
    let s = Stream.of_channel ic in
    let stream = lexer s in
    let list =
      try parse_config_file stream with
      | e ->
        raise (LoadError (filename,
                          ParseError (Stream.count s, Printexc.to_string e)))
    in
    Hashtbl.clear once_values;
    list



let exit_exn = Exit


let once_values_counter = ref 0
let once_values_rev = Hashtbl.create 13

let reset () =
  once_values_counter := 0;
  Hashtbl.clear once_values_rev

let safe_string s =
  if s = "" then "\"\""
  else
    try
      match s.[0] with
        'a'..'z' | 'A'..'Z' ->
          for i = 1 to String.length s - 1 do
            match s.[i] with
              'a'..'z' | 'A'..'Z' | '_' | '0'..'9' -> ()
            | _ -> raise exit_exn
          done;
          s
      | _ ->
          if Int64.to_string (Int64.of_string s) = s ||
             string_of_float (float_of_string s) = s
          then
            s
          else raise exit_exn
    with
      _ -> Printf.sprintf "\"%s\"" (String.escaped s)

let comment option_comment =
  let option_comment = Printf.sprintf "%s" option_comment in
  let lines = EzString.split option_comment '\n' in
  let max_length = ref 10 in
  List.iter (fun line ->
      let len = String.length line in
      if len > !max_length then max_length := len) lines;
  let max_length =
    if !max_length > 74 then 74 else !max_length in
  let spaces = String.make max_length ' ' in
  let lines = List.map (fun line ->
      let len = String.length line in
      if len < max_length then
        line ^ String.sub spaces 0 (max_length - len)
      else line) lines in
  Printf.sprintf "(* %s *)" (String.concat " *)\n(* " lines)

let compact_string oc f =
  let b = Buffer.create 100 in
  f b;
  let s = Buffer.contents b in
  let b = Buffer.create 100 in
  let rec iter b i len s =
    if i < len then
      let c = s.[i] in
      if c = ' ' || c = '\n' || c = '\t' then begin
          iter_space b (i+1) len s
        end
      else begin
          Buffer.add_char b c;
          iter b (i+1) len s
        end
  and  iter_space b i len s =
    if i < len then
      let c = s.[i] in
      if c = ' ' || c = '\n' || c = '\t' then begin
          iter_space b (i+1) len s
        end
      else begin
          Buffer.add_char b ' ';
          Buffer.add_char b c;
          iter b (i+1) len s
        end
  in
  iter b 0 (String.length s) s;
  let ss = Buffer.contents b in
  Buffer.add_string oc (if String.length ss < 80 then ss else s)

let rec save_module with_help indent oc list =
  let subm = ref [] in
  List.iter
    (fun (name, help, value) ->
       match name with
         [] -> assert false
       | [name] ->
           if with_help && help <> "" then
             Printf.bprintf oc "\n%s\n" (comment help);
           Printf.bprintf oc "%s%s = " indent (safe_string name);
           save_value indent oc value;
           Printf.bprintf oc "\n"
       | m :: tail ->
           let p =
             try List.assoc m !subm
             with
             | Not_found -> let p = ref [] in subm := (m, p) :: !subm; p
           in
           p := (tail, help, value) :: !p)
    list;
  List.iter
    (fun (m, p) ->
       Printf.bprintf oc "%s%s = {\n" indent (safe_string m);
       save_module with_help (indent ^ "  ") oc !p;
       Printf.bprintf oc "%s}\n" indent)
    !subm
and save_list indent oc list =
  match list with
    [] -> ()
  | [v] -> save_value indent oc v
  | v :: tail ->
      save_value indent oc v; Printf.bprintf oc ", "; save_list indent oc tail
and save_list_nl indent oc list =
  match list with
    [] -> ()
  | [v] -> Printf.bprintf oc "\n%s" indent; save_value indent oc v
  | v :: tail ->
      Printf.bprintf oc "\n%s" indent;
      save_value indent oc v;
      Printf.bprintf oc ";";
      save_list_nl indent oc tail
and save_value indent oc v =
  match v with
    StringValue s -> Printf.bprintf oc "%s" (safe_string s)
  | IntValue i -> Printf.bprintf oc "%d" i
  | FloatValue f -> Printf.bprintf oc "%F" f
  | List l ->
      compact_string oc (fun oc ->
          Printf.bprintf oc "[";
          save_list_nl (indent ^ "  ") oc l;
          Printf.bprintf oc "\n%s]" indent)
  | DelayedValue f -> f oc indent
  | SmallList l ->
      Printf.bprintf oc "(";
      save_list (indent ^ "  ") oc l;
      Printf.bprintf oc ")"
  | Module m ->
      compact_string oc (fun oc ->
          Printf.bprintf oc "{";
          save_module_fields (indent ^ "  ") oc m;
          Printf.bprintf oc "%s}" indent)
  | OnceValue v ->
      try
        let i = Hashtbl.find once_values_rev v in Printf.bprintf oc "@%Ld@" i
      with
        Not_found ->
          incr once_values_counter;
          let i = Int64.of_int !once_values_counter in
          Hashtbl.add once_values_rev v i;
          Printf.bprintf oc "@%Ld = " i;
          save_value indent oc v
and save_module_fields indent oc m =
  match m with
    [] -> ()
  | (name, v) :: tail ->
      Printf.bprintf oc "%s%s = " indent (safe_string name);
      save_value indent oc v;
      Printf.bprintf oc "\n";
      save_module_fields indent oc tail

let save_binding oc name value =
  Printf.bprintf oc "%s = " (safe_string name);
  save_value "  " oc value;
  Printf.bprintf oc "\n"

let save_module ~with_help ~indent buf list =
  save_module with_help indent buf list
let save_value ~indent buf v = save_value indent buf v
OCaml

Innovation. Community. Security.