package ocsigen-ppx-rpc
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
This PPX adds a syntax for RPCs for Eliom and Ocsigen Start
Install
dune-project
Dependency
Authors
Maintainers
Sources
1.1.tar.gz
md5=314c601099e3371b776001a278209fdc
sha512=035ebb4e8b29a9b64fc04e5f060af2fe4c572f4b5d3389db11950a5dbe31c8ece709db374e2acdcb5a4192b5f363674db8494678be8d410cfd7b5d350a728d13
doc/src/ppx_rpc/ppx_rpc.ml.html
Source file ppx_rpc.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 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363module Parsetree = Ppxlib.Parsetree module Asttypes = Ppxlib.Asttypes module Longident = Ppxlib.Longident module Location = Ppxlib.Location open Ppxlib.Ast open Ppxlib.Ast_helper let mkloc txt loc = {txt; loc} let mkloc_opt ?(loc = !default_loc) x = mkloc x loc let str ?loc ?attrs s = Exp.constant ?loc ?attrs (Const.string s) let pvar ?loc name = Pat.var ?loc (mkloc_opt ?loc name) let ident x = Exp.ident (mkloc_opt (Longident.Lident x)) let unit ?loc ?attrs () = Exp.construct ?loc ?attrs (mkloc_opt ?loc (Longident.Lident "()")) None let tunit ?loc () = Typ.constr (mkloc_opt ?loc (Longident.Lident "unit")) [] type error = | No_parameter | Missing_parameter_type | Missing_parameter_name | Reserved_parameter of string | Duplicated_parameter of string | No_return_type let print_error ~loc (e : error) = let error_str = match e with | No_parameter -> "The function must have at least one parameter" | Missing_parameter_type -> "Missing parameter type anotation" | Missing_parameter_name -> "The parameter should be a variable" | Reserved_parameter nm -> Printf.sprintf "Parameter '%s' has a reserved name" nm | Duplicated_parameter nm -> Printf.sprintf "Two parameters have name '%s'" nm | No_return_type -> "An Lwt.t return type is mandatory" in Location.raise_errorf ~loc "%s" error_str let rpc_name fun_name = let filename = Filename.(!Ocaml_common.Location.input_name |> chop_extension |> basename) in Format.sprintf "%s.%s" filename fun_name let expr_tuple l = match l with | [] -> unit () | [(_, x, _)] -> ident x | _ -> Exp.tuple (List.map (fun (_, x, _) -> ident x) l) let pat_tuple l = match l with | [] -> Pat.any () | [(_, x, _)] -> pvar x | _ -> Pat.tuple (List.map (fun (_, x, _) -> pvar x) l) let typ_tuple l = match l with | [] -> tunit () | [(_, _, ty)] -> ty | _ -> Typ.tuple (List.map (fun (_, _, ty) -> ty) l) (** Extract T in the type expr [T Lwt.t], return None otherwise. *) let extract_lwt_t = function [%type: [%t? ty] Lwt.t] -> Some ty | _ -> None (** Extract T in the expression [(.. : T Lwt.t)], return None otherwise. *) let extract_lwt_t_expr = function | [%expr ([%e? _] : [%t? t])] -> extract_lwt_t t | _ -> None (** Name of an argument. Raise an error if there is no label and [pattern] is not [Ppat_var]. *) let arg_name label var_pattern = match label, var_pattern with | (Labelled n | Optional n), _ -> n | Nolabel, {ppat_desc = Ppat_var var; _} -> var.txt | Nolabel, _ -> print_error ~loc:var_pattern.ppat_loc Missing_parameter_name let process_param label def pat = let param var_pat ty = let name = arg_name label var_pat in let ty = match label, def with | Optional _, Some _ -> let loc = ty.ptyp_loc in [%type: [%t ty] option] | _ -> ty in `Param (label, name, ty) in match pat with (* [(var_pat : ty)] or [~(label : ty)]. *) | [%pat? ([%p? var_pat] : [%t? ty])] -> param var_pat ty | _ -> ( match def with (* [?(label = (def : ty))]. *) | Some [%expr ([%e? _] : [%t? ty])] -> param pat ty | _ -> print_error ~loc:pat.ppat_loc Missing_parameter_type) let rec collect_params l expr = match expr.pexp_desc with | Pexp_function (params, constraint_, Pfunction_body expr') -> let rec loop l params = match params with | [] -> let (l, has_unit), typ = collect_params l expr' in let typ = match typ, constraint_ with | Some _, _ -> typ | None, Some (Pconstraint cstr) -> extract_lwt_t cstr | None, _ -> None in (l, has_unit), typ | param :: rest -> ( match param.pparam_desc with | Pparam_val (label, def, pat) -> ( match pat.ppat_desc with | Ppat_construct ({txt = Lident "()"; _}, None) when label = Nolabel && def = None -> let (l, _), typ = collect_params l expr' in (l, true), typ | _ -> ( match process_param label def pat with | `Param (label, name, ty) -> loop ((label, name, ty) :: l) rest)) | Pparam_newtype _ -> loop l rest) in loop l params | Pexp_constraint (e, _) -> let (l, has_unit), typ = collect_params l e in let typ = if typ = None then extract_lwt_t_expr expr else typ in (l, has_unit), typ | _ -> (List.rev l, false), extract_lwt_t_expr expr let mk_function_param ?(loc = Location.none) ?(label = Nolabel) ?defexpr pat = {pparam_loc = loc; pparam_desc = Pparam_val (label, defexpr, pat)} let make_fun loc (params, has_unit_arg) body = let params = List.fold_right (fun (label, ident, _) acc -> mk_function_param ~label (pvar ident) :: acc) params (if has_unit_arg then [mk_function_param [%pat? ()]] else []) in Exp.mk ~loc (Pexp_function (params, None, Pfunction_body body)) let build_params loc (params, has_unit) = List.map (fun (label, x, _) -> label, ident x) params @ if has_unit then [Nolabel, [%expr ()]] else [] let apply args expr = Exp.apply expr args let server_function ~loc ~kind ~fun_var expr' = let expr = match kind with | `Connected -> [%expr fun (myid : Os_types.User.id) -> [%e expr']] | `Any -> [%expr fun (myid_o : Os_types.User.id option) -> [%e expr']] | `None -> expr' in [%stri let%server [%p fun_var] = [%e expr]] let server_cacher ~loc ~kind ~cache ~fun_name ~fun_var ~params = match cache with | None -> [%stri let%server _ = ()] | Some return_typ -> let id_param = match kind with | `Connected -> [Nolabel, [%expr myid]] | `Any -> [Nolabel, [%expr myid_o]] | `None -> [] in let cache expr = [%expr let%lwt x = [%e expr] in Bs_proxy.cache [%derive.caching: [%t return_typ]] x] in let parametrize_id expr = match kind with | `Connected -> [%expr fun myid -> [%e expr]] | `Any -> [%expr fun myid_o -> [%e expr]] | `None -> expr in let expr = fun_name |> ident |> apply (id_param @ build_params loc params) |> cache |> make_fun loc params |> parametrize_id in [%stri let%server [%p fun_var] = [%e expr] [@@ocaml.warning "-16"]] let server_wrapper ~loc ~kind ~raw ~cache ~fun_name ~fun_var ~params = if raw then [%stri let%server _ = ()] else let id_param = match kind with | `Connected -> [Nolabel, [%expr Os_current_user.get_current_userid ()]] | `Any -> [Nolabel, [%expr Os_current_user.Opt.get_current_userid ()]] | `None -> [] in let uncache expr = if cache <> None then [%expr Bs_proxy.extract [%e expr]] else expr in let expr = fun_name |> ident |> apply (id_param @ build_params loc params) |> uncache |> make_fun loc params in [%stri let%server [%p fun_var] = [%e expr] [@@ocaml.warning "-16-32"]] let client_wrapper ~loc ~kind ~raw ~cache ~fun_name ~fun_var ~params = let id_param = match kind with | `Connected -> [Nolabel, [%expr myid]] | `Any -> [Nolabel, [%expr myid_o]] | `None -> [] in let uncache expr = if cache <> None then [%expr Bs_proxy.extract [%e expr]] else expr in let parametrize' expr = [%expr fun [%p pat_tuple (fst params)] -> [%e expr]] in let parametrize_id expr = match kind with | `Connected -> [%expr fun myid -> [%e expr]] | `Any -> [%expr fun myid_o -> [%e expr]] | `None -> expr in let wrap expr = if raw then expr else match kind with | `Connected -> [%expr Os_session.connected_rpc [%e expr]] | `Any -> [%expr Os_session.Opt.connected_rpc [%e expr]] | `None -> [%expr Os_session.connected_wrapper [%e expr]] in let expr = fun_name |> ident |> apply (id_param @ build_params loc params) |> uncache |> parametrize' |> parametrize_id |> wrap in let expr = [%expr ~%(Eliom_client.server_function ~name:[%e str (rpc_name fun_name)] [%json: [%t typ_tuple (fst params)]] [%e expr]) [%e expr_tuple (fst params)]] in [%stri let%client [%p fun_var] = [%e make_fun loc params expr] [@@ocaml.warning "-16"]] let raw = ref false let cache = ref false let is_special_argument = function | [%pat? myid] -> Some `Connected | [%pat? myid_o] -> Some `Any | _ -> None let rec check_myid expr = match expr with | { pexp_desc = Pexp_function ( {pparam_desc = Pparam_val (Nolabel, None, pat); _} :: rest , constraint_ , (Pfunction_body body_expr as body) ) } -> ( match is_special_argument pat, rest with | Some sp, [] -> sp, body_expr | Some sp, rest -> sp, {expr with pexp_desc = Pexp_function (rest, constraint_, body)} | None, _ -> `None, expr) | [%expr ([%e? e] : [%t? t])] -> let kind, new_e = check_myid e in if kind <> `None then kind, {expr with pexp_desc = Pexp_constraint (new_e, t)} else `None, expr | _ -> `None, expr let extension_impl ~legacy ~loc ~path:_ ~return_typ_hint fun_name expr = let raw = !raw && not !cache in let cache = (not legacy) && !cache in let fun_var = pvar ~loc:fun_name.loc fun_name.txt in let fun_name = fun_name.txt in let kind, expr' = if raw then `None, expr else check_myid expr in let params, return_typ = collect_params [] expr' in let return_typ = match return_typ with Some _ -> return_typ | None -> return_typ_hint in (match params with | [], false -> print_error ~loc No_parameter | l, _ -> ignore (List.fold_left (fun acc (_, nm, _) -> if List.mem nm acc then print_error ~loc (Duplicated_parameter nm); if nm = "myid" || nm = "myid_o" then print_error ~loc (Reserved_parameter nm); nm :: acc) [] l)); if cache && return_typ = None then print_error ~loc No_return_type; let cache = if cache then return_typ else None in Str.include_ ~loc (Incl.mk ~loc (Mod.structure ~loc [ server_function ~loc ~kind ~fun_var expr' ; server_cacher ~loc ~kind ~cache ~fun_name ~fun_var ~params ; client_wrapper ~loc ~kind ~raw ~cache ~fun_name ~fun_var ~params ; server_wrapper ~loc ~kind ~raw ~cache ~fun_name ~fun_var ~params ])) let rec return_type_of_arrow ty = match ty.ptyp_desc with | Ptyp_arrow (_, _, ret) -> return_type_of_arrow ret | _ -> ty let lwt_return_type constraint_opt = match constraint_opt with | Some (Pvc_constraint {typ; _}) -> extract_lwt_t (return_type_of_arrow typ) | _ -> None let extension ~legacy ~loc ~path fun_name expr constraint_opt = extension_impl ~legacy ~loc ~path ~return_typ_hint:(lwt_return_type constraint_opt) fun_name expr let vb_pattern = let open Ppxlib.Ast_pattern in value_binding ~pat:(ppat_var __') ~expr:__ ~constraint_:__ let pattern = let open Ppxlib.Ast_pattern in pstr (pstr_value nonrecursive (vb_pattern ^:: nil) ^:: nil) let extensions = let open Ppxlib in List.concat @@ List.map (fun (legacy, exts) -> List.map (fun ext -> Extension.declare ext Extension.Context.structure_item pattern (extension ~legacy)) exts) [true, ["cw_rpc"; "crpc"; "crpc_opt"]; false, ["rpc"]] let driver_args = [ ( "--rpc-raw" , Arg.Unit (fun () -> raw := true) , " Do not insert any ocsigen-start session wrapper." ) ; ( "--rpc-cache" , Arg.Unit (fun () -> cache := true) , " Insert caching directives (for internal use at Be Sport)." ) ] let () = List.iter (fun (key, spec, doc) -> Ppxlib.Driver.add_arg key spec ~doc) driver_args let rules = List.map Ppxlib.Context_free.Rule.extension extensions let () = Ppxlib.Driver.register_transformation ~rules "rpc"
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>