This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,393 @@
(* Copyright (C) 2019--2025 Petter A. Urkedal <paurkedal@gmail.com>
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version, with the LGPL-3.0 Linking Exception.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* and the LGPL-3.0 Linking Exception along with this library. If not, see
* <http://www.gnu.org/licenses/> and <https://spdx.org>, respectively.
*)
open Shims
module Private = struct
type t =
| L of string
| V : 'a Field_type.t * 'a -> t
| Q of string
| P of int
| E of string
| S of t list
end
open Private
type t = Private.t
let empty = S []
let lit frag = L frag
let quote str = Q str
let param i = P i
let var v = E v
let cat q1 q2 =
(match q1, q2 with
| S [], qs | qs, S [] -> qs
| S qs1, S qs2 -> S (List.append qs1 qs2)
| S qs1, q2 -> S (List.append qs1 [q2])
| q1, S qs2 -> S (q1 :: qs2)
| q1, q2 -> S [q1; q2])
let concat =
let rec loop pfx acc = function
| [] -> acc
| q :: qs -> loop pfx (pfx :: q :: acc) qs
in
fun ?sep qs ->
(match sep, qs with
| _, [] -> S []
| None, _ -> S qs
| Some sep, q :: qs -> S (q :: loop (L sep) [] (List.rev qs)))
let parens q = concat [lit "("; q; lit ")"]
let bool x = V (Field_type.Bool, x)
let int x = V (Field_type.Int, x)
let int16 x = V (Field_type.Int16, x)
let int32 x = V (Field_type.Int32, x)
let int64 x = V (Field_type.Int64, x)
let float x = V (Field_type.Float, x)
let string x = V (Field_type.String, x)
let octets x = V (Field_type.Octets, x)
let pdate x = V (Field_type.Pdate, x)
let ptime x = V (Field_type.Ptime, x)
let ptime_span x = V (Field_type.Ptime_span, x)
let const t v = V (t, v)
let rec const_fields_opt : type a. a Row_type.t -> a option -> t list =
(function
| Field ft ->
(function None -> [L "NULL"] | Some x -> [V (ft, x)])
| Option t ->
let of_t = const_fields_opt t in
(function None -> of_t None | Some x -> of_t x)
| Product (_, pt) ->
const_fields_opt_of_product pt
| Annot (_, t) ->
const_fields_opt t)
and const_fields_opt_of_product
: type i a. (i, a) Row_type.product -> a option -> t list =
(function
| Proj_end -> fun _ -> []
| Proj (t, p, pt) ->
let of_t = const_fields_opt t in
let of_pt = const_fields_opt_of_product pt in
fun x -> of_t (Option.map p x) @ of_pt x)
let const_fields (t : _ Row_type.t) =
let f = const_fields_opt t in
fun x -> f (Some x)
let rec equal_list f xs ys = (* stdlib 4.12.0 *)
(match xs, ys with
| [], [] -> true
| x :: xs', y :: ys' -> f x y && equal_list f xs' ys'
| [], _ :: _ | _ :: _, [] -> false)
let normal =
let rec collect acc = function
| [] -> List.rev acc
| ((L"" | S[]) :: qs) -> collect acc qs
| ((P _ | V _ | Q _ | E _ as q) :: qs) -> collect (q :: acc) qs
| (S (q' :: qs') :: qs) -> collect acc (q' :: S qs' :: qs)
| (L s :: qs) -> collectL acc [s] qs
and collectL acc accL = function
| ((L"" | S[]) :: qs) -> collectL acc accL qs
| (L s :: qs) -> collectL acc (s :: accL) qs
| (S (q' :: qs') :: qs) -> collectL acc accL (q' :: S qs' :: qs)
| [] | ((P _ | V _ | Q _ | E _) :: _) as qs ->
collect (L (String.concat "" (List.rev accL)) :: acc) qs
in
fun q ->
(match collect [] [q] with
| [] -> S[]
| [q] -> q
| qs -> S qs)
let rec equal t1 t2 =
(match t1, t2 with
| L s1, L s2 -> String.equal s1 s2
| V (t1, v1), V (t2, v2) ->
(match Field_type.unify t1 t2 with
| None -> false
| Some Type.Equal -> Field_type.equal_value t1 v1 v2)
| Q s1, Q s2 -> String.equal s1 s2
| P i1, P i2 -> Int.equal i1 i2
| E n1, E n2 -> String.equal n1 n2
| S ts1, S ts2 -> equal_list equal ts1 ts2
| V _, _ -> false
| L _, _ -> false
| Q _, _ -> false
| P _, _ -> false
| E _, _ -> false
| S _, _ -> false)
let hash = Hashtbl.hash
let rec pp ppf = function
| L s -> Format.pp_print_string ppf s
| V (t, v) -> Field_type.pp_value ppf (t, v)
| Q s ->
(* Using non-SQL quoting, to avoid issues with newlines and other control
* characters when printing to log files. *)
Format.pp_print_string ppf "E'";
for i = 0 to String.length s - 1 do
(match s.[i] with
| '\\' -> Format.pp_print_string ppf {|\\|}
| '\'' -> Format.pp_print_string ppf {|\'|}
| '\t' -> Format.pp_print_string ppf {|\t|}
| '\n' -> Format.pp_print_string ppf {|\n|}
| '\r' -> Format.pp_print_string ppf {|\r|}
| '\x00'..'\x1f' as c -> Format.fprintf ppf {|\x%02x|} (Char.code c)
| _ -> Format.pp_print_char ppf s.[i])
done;
Format.pp_print_char ppf '\''
| P n -> Format.pp_print_char ppf '$'; Format.pp_print_int ppf (n + 1)
| E n -> Format.fprintf ppf "$(%s)" n
| S qs -> List.iter (pp ppf) qs
let show q =
let buf = Buffer.create 512 in
let ppf = Format.formatter_of_buffer buf in
pp ppf q; Format.pp_print_flush ppf ();
Buffer.contents buf
module Expand_error = struct
type nonrec t = {
query: t;
var: string;
reason: [`Undefined | `Invalid of t];
}
let pp ppf {query; var; reason} =
let open Format in
(match reason with
| `Undefined ->
fprintf ppf "Undefined variable %s in query %a" var pp query
| `Invalid expansion ->
fprintf ppf
"While expanding %a, lookup of %s gives %a, which is invalid \
because it contains an environment or parameter reference."
pp query var pp expansion)
end
exception Expand_error of Expand_error.t
type subst = string -> t
let expand ?(final = false) f query =
let rec is_valid = function
| L _ | V _ | Q _ -> true
| P _ | E _ -> false
| S qs -> List.for_all is_valid qs
in
let rec recurse = function
| L _ | V _ | Q _ | P _ as q -> q
| E var as q ->
let not_found () =
if not final then q else
raise (Expand_error {query; var; reason = `Undefined})
in
(match f var with
| q' ->
if is_valid q' then q' else
raise (Expand_error {query; var; reason = `Invalid q'})
| exception Not_found ->
let l = String.length var in
if l > 0 && var.[l - 1] = '.' then
(match f (String.sub var 0 (l - 1)) with
| frag ->
(match normal frag with
| S[] as q' -> q'
| q' -> S[q'; L"."])
| exception Not_found -> not_found ())
else
not_found ())
| S qs -> S (List.map recurse qs)
in
recurse query
module Angstrom_parsers = struct
open Angstrom
let failf = Printf.ksprintf fail
let ign p = p >>| fun _ -> ()
let is_digit = function '0'..'9' -> true | _ -> false
let is_digit_nz = function '1'..'9' -> true | _ -> false
let is_idrfst = function 'a'..'z'|'A'..'Z' | '_' -> true | _ -> false
let is_idrcnt = function 'a'..'z'|'A'..'Z' | '_' | '0'..'9' -> true | _ -> false
let is_space = function ' ' | '\t' | '\n' | '\r' -> true | _ -> false
let single_quoted = skip_many (ign (not_char '\'') <|> ign (string "''"))
let double_quoted = skip_many (ign (not_char '"') <|> ign (string "\"\""))
let tagged_quote_cont =
consumed (skip is_idrfst *> skip_while is_idrcnt) <* char '$' >>= fun tag ->
many_till any_char (char '$' *> string tag <* char '$') >>| (fun _ -> ())
let verbatim =
let fragment = any_char >>= function
| '\'' -> single_quoted <* char '\''
| '"' -> double_quoted <* char '"'
| '`' -> skip_many (not_char '`') <* char '`'
| '-' ->
(peek_char >>= function
| Some '-' -> skip_while ((<>) '\n') <* char '\n'
| _ -> return ())
| '$' -> tagged_quote_cont
| '?' | ';' as c -> failf "%C is not valid here" c
| _ -> return ()
in
consumed (many1 fragment) >>| (fun s -> (L s))
let skip_idr = skip is_idrfst *> skip_while is_idrcnt
let identifier_dot = consumed (skip_idr *> char '.')
let identifier_dotopt = consumed (option () skip_idr *> option ' ' (char '.'))
let parameter_number = consumed (skip is_digit_nz *> skip_while is_digit)
let lookup =
choice ~failure_msg:"invalid environment lookup" [
string "$(" *> identifier_dotopt <* char ')' >>| (fun v -> E v);
string "$." >>| (fun _ -> E ".");
char '$' *> identifier_dot >>| (fun v -> E v);
]
let untagged_quote =
let nonlookup =
consumed (many1 (satisfy (function '$' -> false | _ -> true)))
>>| (fun s -> (L s))
in
string "$$" *> many_till (lookup <|> nonlookup) (string "$$") >>| fun qs ->
normal (S ([L "$$"] @ qs @ [L "$$"]))
let atom =
peek_char_fail >>= function
| '$' ->
choice ~failure_msg:"invalid dollar sequence" [
char '$' *> parameter_number >>| (fun iP -> P (int_of_string iP - 1));
lookup;
untagged_quote;
verbatim;
]
| '?' ->
let valid_lookahead = peek_char >>= function
| Some ':' ->
(peek_string 2 >>= function
| "::" -> return ()
| _ -> fail "':' is not allowed after parameter reference '?'")
| Some ('A'..'Z' | 'a'..'z' | '0'..'9' | '_'
| '!' | '"' | '#' | '$' | '%' | '&' | '\'' | '.'
| '<' | '=' | '>' | '?' | '@' | '^' | '`' | '|' | '~' as c) ->
failf "%C is not allowed after parameter reference '?'" c
| None | Some _ ->
return ()
in
char '?' >>| (fun _ -> P (-1)) <* valid_lookahead
| _ ->
verbatim
let atom_or_semi = (char ';' >>| fun _ -> L";") <|> atom
let reindex atoms =
if List.for_all (function P (-1) -> false | _ -> true) atoms then
return atoms
else
let rec loop iP acc = function
| [] -> return (List.rev acc)
| P (-1) :: frags -> loop (iP + 1) (P iP :: acc) frags
| P _ :: _ -> fail "Inconsistent parameter style."
| frag :: frags -> loop iP (frag :: acc) frags
in
loop 0 [] atoms
let expression =
let stop =
peek_char >>= function
| None | Some ';' -> return ()
| _ -> fail "unterminated"
in
fix (fun p -> (stop *> return []) <|> (List.cons <$> atom <*> p))
>>= reindex >>| (function [q] -> q | qs -> S qs)
let expression_with_semi =
let stop =
peek_char >>= function
| None -> return ()
| _ -> fail "unterminated"
in
fix (fun p -> (stop *> return []) <|> (List.cons <$> atom_or_semi <*> p))
>>= reindex >>| (function [q] -> q | qs -> S qs)
let expression_list =
let white =
many (take_while1 is_space <|> (string "--" *> take_till ((=) '\n')))
<* commit
in
white *> many (expression <* char ';' <* white)
end
let angstrom_parser = Angstrom_parsers.expression
let angstrom_parser_with_semicolon = Angstrom_parsers.expression_with_semi
let angstrom_list_parser = Angstrom_parsers.expression_list
module Parse_error = struct
type t = {
position: int;
message: string;
}
let create position message = {position; message}
let position err = err.position
let message err = err.message
let pp ppf err =
Format.fprintf ppf "Parse error at byte %d: %s" err.position err.message
end
exception Parse_error of Parse_error.t
let parse_result s =
let open Angstrom.Unbuffered in
(match parse angstrom_parser_with_semicolon with
| Partial {committed = 0; continue} ->
let len = String.length s in
let bs = Bigstringaf.of_string ~off:0 ~len s in
(match continue bs ~off:0 ~len Complete with
| Done (committed, q) when committed = len -> Ok q
| Done (committed, _) | Partial {committed; _} ->
let msg = "Expression cannot contain semicolon." in
Error (Parse_error.create committed msg)
| Fail (committed, _, msg) ->
Error (Parse_error.create committed msg))
| Partial _ | Done _ | Fail _ ->
assert false)
let parse s =
(match parse_result s with
| Ok q -> q
| Error err -> raise (Parse_error err))
module Infix = struct
let (@++) = cat
let (^++) pfx q = cat (lit pfx) q
let (++^) q sfx = cat q (lit sfx)
end