(* This example shows how the AST mapping approach could be used instead of Camlp4 in order to give a nice syntax for js_of_ocaml (properties and method calls). The code below overloads regular syntax for field projection and assignment for Javascript properties, and (currified) method call for Javascript method calls. This is enabled by a fake local open on pseudo module JS, i.e. in a scope like "JS.(...)" or "let open JS in ...". *) open Asttypes open Ast_mapper open Location open Parsetree open Longident (* A few local helper functions to simplify the creation of AST nodes. *) let constr_ c l = T.constr (mknoloc (Longident.parse c)) l let apply_ f l = E.apply_nolabs (E.lid f) l let oobject l = T.object_ (List.map (fun (s, t) -> T.field s t) l @ [T.field_var ()]) let eident x = E.ident (mknoloc (Lident x)) let pvar x = P.var (mknoloc x) let annot e t = E.constraint_ e (Some t) None let rnd = Random.State.make [|0x513511d4|] let random_var () = Format.sprintf "a%08Lx" (Random.State.int64 rnd 0x100000000L : Int64.t) let fresh_type () = T.var (random_var ()) let unescape lab = assert (lab <> ""); let lab = if lab.[0] = '_' then String.sub lab 1 (String.length lab - 1) else lab in try let i = String.rindex lab '_' in if i = 0 then raise Not_found; String.sub lab 0 i with Not_found -> lab let method_literal meth = E.strconst (unescape meth) let access_object loc e m m_typ f = let open E in let x = random_var () in let obj_type = random_var () in let obj = annot e T.(constr_ "Js.t" [alias (oobject []) obj_type]) in let y = random_var () in let o = annot (eident y) (T.var obj_type) in let constr = function_ "" None [pvar y, annot (send o m) m_typ] in let e = let_ Nonrecursive [pvar x, obj; P.any (), constr] (f (eident x)) in (set_loc loc) # expr e let method_call loc obj meth args = let args = List.map (fun e -> (e, fresh_type ())) args in let ret_type = fresh_type () in let method_type = List.fold_right (fun (_, arg_ty) rem_ty -> T.arrow "" arg_ty rem_ty) args (constr_ "Js.meth" [ret_type]) in access_object loc obj meth method_type (fun x -> let args = List.map (fun (e, t) -> apply_ "Js.Unsafe.inject" [annot e t]) args in annot (apply_ "Js.Unsafe.meth_call" [x; method_literal meth; E.array args]) ret_type ) let mapper = object(this) inherit Ast_mapper.create as super val js = false method! expr e = let loc = e.pexp_loc in match e.pexp_desc with | Pexp_open ({txt = Lident "JVS"; loc = _}, e) -> {< js = true >} # expr e | Pexp_field (o, {txt = Lident meth; loc = _}) when js -> let o = this # expr o in let prop_type = fresh_type () in let meth_type = constr_ "Js.gen_prop" [oobject ["get", prop_type]] in access_object loc o meth meth_type (fun x -> annot (apply_ "Js.Unsafe.get" [x; method_literal meth]) prop_type) | Pexp_setfield (o, {txt = Lident meth; loc = _}, e) when js -> let o = this # expr o and e = this # expr e in let prop_type = fresh_type () in let meth_type = constr_ "Js.gen_prop" [oobject ["set", T.arrow "" prop_type (constr_ "unit" [])]] in access_object loc o meth meth_type (fun x -> apply_ "Js.Unsafe.set" [x; method_literal meth; annot e prop_type]) | Pexp_apply ({pexp_desc = Pexp_send (o, meth); pexp_loc = loc}, args) when js -> method_call loc o meth (List.map (this # expr) (List.map snd args)) | Pexp_send (o, meth) when js -> method_call loc o meth [] | _ -> super # expr e end let () = mapper # main