File: expr.ml

package info (click to toggle)
ocaml-obuild 0.1.11-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 796 kB
  • sloc: ml: 6,570; sh: 171; ansic: 34; makefile: 11
file content (271 lines) | stat: -rw-r--r-- 8,385 bytes parent folder | download | duplicates (2)
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
open Ext.Fugue

exception UnknownSymbol of (string * string)
exception UnknownExpression of string
exception ExpressionEmpty
exception UnbalancedParenthesis
exception MalformedExpression
exception InvalidDependencyName of string
exception CannotParseConstraints of (string * string)

type version = string

module Token = struct
  type t =
    | VER of string (* version *)
    | ID of string (* ident *)
    | LPAREN
    | RPAREN
    | AND
    | OR
    | NOT
    | EQ
    | NE
    | GT
    | LT
    | GE
    | LE

  let to_string = function
    | VER v  -> v
    | ID s   -> s
    | LPAREN -> "("
    | RPAREN -> ")"
    | AND    -> "&"
    | OR     -> "|"
    | NOT    -> "!"
    | EQ     -> "=="
    | NE     -> "!="
    | GT     -> ">"
    | LT     -> "<"
    | GE     -> ">="
    | LE     -> "<="

  let of_string symbol s = match symbol with
    | "&&" | "&"  -> AND
    | "||" | "|"  -> OR
    | ">"         -> GT
    | "<"         -> LT
    | ">="        -> GE
    | "<="        -> LE
    | "==" | "="  -> EQ
    | "!=" | "/=" -> NE
    | "!"         -> NOT
    | _           -> raise (UnknownSymbol (symbol,s))

  let process_one_char c next =
    match (c,next) with
    | '(', _ -> LPAREN
    | ')', _ -> RPAREN
    | '!', Some '=' -> raise Not_found (* should be parsed as a string != *)
    | '!', _ -> NOT
    | _ -> raise Not_found

  (* valid char per types *)
  let is_symbol_char c = try let _ = String.index "&/|!+=><()" c in true with _ -> false
  let is_ident_char c = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
                        (c >= '0' && c <= '9') || c == '_' || c == '.' || c == '-'
  let is_version_char c = (c >= '0' && c <= '9') || c = '.' || c = '*'

  let lexer s =
    let len = String.length s in
    let while_pred pred o =
      let i = ref o in
      while !i < len && pred s.[!i] do i := !i + 1 done;
      (String.sub s o (!i-o), !i)
    in
    (* Per type lexer *)
    let eat_symbol o =
      let (tok,no) =
        let next = if o+1 < len then Some (s.[o+1]) else None in
        try let tok = process_one_char s.[o] next in (tok,o+1)
        with Not_found ->
          let (p, no) = while_pred is_symbol_char o in
          let tok = of_string p s in
          (tok,no)
      in (tok,no)
    in
    let eat_version o = while_pred is_version_char o in
    let eat_ident o = while_pred is_ident_char o in
    (* main lexing loop *)
    let rec loop o =
      if o >= len then []
      else begin
        (* TODO skip chunk of space in one go *)
        if s.[o] == ' ' || s.[o] == '\t' then (
          loop (o+1)
        ) else if is_symbol_char s.[o] then (
          let (sym, no) = eat_symbol o in sym :: loop no
        ) else if (s.[o] >= 'a' && s.[o] <= 'z') ||
                  (s.[o] >= 'A' && s.[o] <= 'Z') then (
          let (id, no) = eat_ident o in ID id :: loop no
        ) else if is_version_char s.[o] then (
          let (ver, no) = eat_version o in VER ver :: loop no
        ) else
          failwith (Printf.sprintf "unknown character in expression '%c'" s.[o])
      end
    in
    loop 0
end

type t =
  | And of t * t
  | Or of t * t
  | Not of t
  | Paren of t
  | Eq of version
  | Le of version
  | Lt of version
  | Ge of version
  | Gt of version
  | Ne of version

let compare_version v1 v2 =
  let skip i p s e =
    let rec loop i = if i = e then i else if (p s.[i]) then loop (i + 1) else i
    in loop i
  in
  let split_version v =
    let (p1,rest) = match (string_split ':' v ~limit:2) with
        [ _ ] -> ("", v)
      | [ p1; rest] -> (p1, rest) in
    let (p1, p2, p3) = match (string_split '-' rest ~limit:2) with
        [ _ ] -> (p1, rest, "")
      | [ p2 ; p3 ] -> (p1, p2, p3) in
    (p1, p2, p3)
  in
  let compare_part p1 p2 =
    let l1 = String.length p1 in
    let l2 = String.length p2 in
    let is_digit = function | '0'..'9' -> true | _ -> false in
    let rec loop i1 i2 =
      let compare_numbers i1 i2 =
        let rec loop_numbers n1 n2 last =
          if n2 = last then loop n1 n2
          else
            let comp = Char.compare p1.[n1] p2.[n2] in
            if comp = 0 then loop_numbers (n1 + 1) (n2 + 1) last else comp
        in
        let end1 = skip i1 is_digit p1 l1 in
        let end2 = skip i2 is_digit p2 l2 in
        let comp = compare (end1 - i1) (end2 - i2) in
        if comp = 0 then loop_numbers i1 i2 end1 else comp
      in
      match (i1 = l1, i2 = l2) with
      | true,true -> 0
      | true,false -> let end2 = skip i2 (fun c -> c = '0') p2 l2 in
        if end2 = l2 then 0 else -1
      | false,true -> let end1 = skip i1 (fun c -> c = '0') p1 l1 in
        if end1 = l1 then 0 else 1
      | false,false -> match (is_digit p1.[i1], is_digit p2.[i2]) with
        | true,true ->
          compare_numbers (skip i1 (fun c -> c = '0') p1 l1) (skip i2 (fun c -> c = '0') p2 l2)
        | true,false -> -1
        | false,true -> 1
        | false,false -> let comp = Char.compare p1.[i1] p2.[i2] in
          if comp = 0 then loop (i1 + 1) (i2 + 1) else comp
    in
    loop 0 0
  in
  if v1 = v2 then 0
  else
    let (v1_1, v1_2, v1_3) = split_version v1 in
    let (v2_1, v2_2, v2_3) = split_version v2 in
    let c1 = compare_part v1_1 v2_1 in
    if c1 <> 0 then c1 else
      let c2 = compare_part v1_2 v2_2 in
      if c2 <> 0 then c2 else
        compare_part v1_3 v2_3

let rec eval version constr =
  match constr with
  | And (e1,e2) -> (eval version e1) && (eval version e2)
  | Or (e1,e2) -> (eval version e1) || (eval version e2)
  | Not e -> not (eval version e)
  | Paren e -> eval version e
  | Eq v -> compare_version version v = 0
  | Le v -> compare_version version v <= 0
  | Lt v -> compare_version version v < 0
  | Ge v -> compare_version version v >= 0
  | Gt v -> compare_version version v > 0
  | Ne v -> compare_version version v <> 0

let rec to_string = function
  | And (e1,e2) -> (to_string e1) ^ " && " ^ (to_string e2)
  | Or (e1,e2) -> (to_string e1) ^ " || " ^ (to_string e2)
  | Not e -> "! " ^ (to_string e)
  | Paren e -> "(" ^ (to_string e) ^ ")"
  | Eq v -> "=" ^ v
  | Le v -> "<=" ^ v
  | Lt v -> "<" ^ v
  | Ge v -> ">=" ^ v
  | Gt v -> ">" ^ v
  | Ne v -> "!=" ^ v

let showList sep f l = String.concat sep (List.map f l)

let parse_expr l =
  let rec parse_sub_expr l =
    match l with
    | [] -> raise MalformedExpression
    | Token.NOT :: r ->
      let (e, r) = parse_sub_expr r in ((Not e), r)
    | Token.LPAREN :: r ->
      let (e, r) = parse_sub_expr r in
      let rec loop e r =
        (match r with
         | Token.RPAREN :: r -> (Paren e, r)
         | Token.OR :: _ | Token.AND :: _ ->
           let (e, r) = parse_bin_expr e r in
           loop e r
         | _ -> raise UnbalancedParenthesis;
        )
      in
      loop e r
    | Token.GT :: Token.VER v :: r -> (Gt v, r)
    | Token.GE :: Token.VER v :: r -> (Ge v, r)
    | Token.EQ :: Token.VER v :: r -> (Eq v, r)
    | Token.LT :: Token.VER v :: r -> (Lt v, r)
    | Token.LE :: Token.VER v :: r -> (Le v, r)
    | Token.NE :: Token.VER v :: r -> (Ne v, r)
    | z              -> raise (UnknownExpression (showList "," Token.to_string z))
  and parse_bin_expr expr l =
    match l with
    | Token.OR :: r -> let (e, r) = parse_sub_expr r in ((Or (expr,e)), r)
    | Token.AND :: r -> let (e, r) = parse_sub_expr r in ((And (expr,e)), r)
    | _ -> raise MalformedExpression
  in
  let (e, r) = parse_sub_expr l in
  let rec loop e r =
    if(List.length r) = 0 then e
    else let (e,r) = parse_bin_expr e r in
      loop e r
  in
  loop e r

let parse_constraints name cs =
  try
    match cs with
    | []   -> None
    | expr -> let e = parse_expr expr in
      Some e
  with e ->
    let err =
      match e with
      | UnknownExpression z -> "unknown constraints expression \"" ^ z ^ "\""
      | UnbalancedParenthesis -> "unbalanced parenthesis"
      | MalformedExpression -> "malformed expression"
      | _                   -> Printexc.to_string e
    in
    raise (CannotParseConstraints (name,err))

let parse name s =
  match Token.lexer s with
  | [] -> raise ExpressionEmpty
  | constraints -> parse_constraints name constraints

let parse_builddep s =
  match Token.lexer s with
  | []                    -> raise ExpressionEmpty
  | Token.ID name :: constraints -> (name, (parse_constraints name constraints))
  | x :: _           -> raise (InvalidDependencyName (Token.to_string x))