open Ppxlib open Ast_builder.Default module E = Extension module A = Ast_pattern type quoted_attributes = { quoted_attributes : attributes; (* The attributes that appear quoted, e.g. [@foo] in [%expr [%e e] [@foo]] *) field_name : string; (* The field name where attributes are stored for the kind of AST the quoted attributes are placed on, e.g. pexp_attributes. *) } let coalesce_arity_expr (input : expression) super = match input with | { pexp_desc = Pexp_function _; pexp_loc = loc; _ } -> let ppxlib_coalesce_arity = Ldot ( Ldot (Ldot (Lident "Ppxlib", "Ast_builder"), "Default"), "coalesce_arity" ) in pexp_apply ~loc (pexp_ident ~loc { txt = ppxlib_coalesce_arity; loc }) [ (Nolabel, super input) ] | exp -> super exp (* Produce a pattern that matches on the maximum arity of a function *) let coalesce_arity_pat (input : expression) super = match input with | { pexp_desc = Pexp_function _; pexp_loc = _loc; _ } as exp -> let c = Ast_builder.Default.coalesce_arity exp in super c | e -> super e module Make (M : sig type result val annotate : result -> core_type -> result val cast : (* The instance of the [std_lifters] class being used. *) < attributes : attributes -> result ; typed : result -> string -> result ; .. > -> extension -> quoted_attributes option -> (* e.g. [expression]; the callee is responsible for calling [self#typed ast type_name] on the AST to add a type annotation that constrains its type. *) type_name:string -> result val location : location -> result val location_stack : (location -> result) option val attributes : (location -> result) option val coalesce : (expression -> (expression -> result) -> result) option class std_lifters : location -> [result] Ppxlib_traverse_builtins.std_lifters end) = struct let lift loc = object (self) inherit [M.result] Ast_traverse.lift as super inherit! M.std_lifters loc method typed ast name = let loc = { loc with loc_ghost = true } in M.annotate ast (ptyp_constr ~loc { loc; txt = Ldot (Ldot (Lident "Ppxlib_ast", "Ast"), name) } []) method! attribute x = Attribute.mark_as_handled_manually x; super#attribute x method! location _ = M.location loc method! attributes x = match M.attributes with | None -> super#attributes x | Some f -> assert_no_attributes x; f loc method! location_stack x = match M.location_stack with | None -> super#location_stack x | Some f -> f loc method! expression e = match (M.coalesce, e.pexp_desc) with | _, Pexp_extension (({ txt = "e"; _ }, _) as ext) -> let attributes = { quoted_attributes = e.pexp_attributes; field_name = "pexp_attributes"; } in M.cast self ext (Some attributes) ~type_name:"expression" | Some f, _ -> f e super#expression | None, _ -> super#expression e method! pattern p = match p.ppat_desc with | Ppat_extension (({ txt = "p"; _ }, _) as ext) -> let attributes = { quoted_attributes = p.ppat_attributes; field_name = "ppat_attributes"; } in M.cast self ext (Some attributes) ~type_name:"pattern" | _ -> super#pattern p method! core_type t = match t.ptyp_desc with | Ptyp_extension (({ txt = "t"; _ }, _) as ext) -> let attributes = { quoted_attributes = t.ptyp_attributes; field_name = "ptyp_attributes"; } in M.cast self ext (Some attributes) ~type_name:"core_type" | _ -> super#core_type t method! module_expr m = match m.pmod_desc with | Pmod_extension (({ txt = "m"; _ }, _) as ext) -> let attributes = { quoted_attributes = m.pmod_attributes; field_name = "pmod_attributes"; } in M.cast self ext (Some attributes) ~type_name:"module_expr" | _ -> super#module_expr m method! module_type m = match m.pmty_desc with | Pmty_extension (({ txt = "m"; _ }, _) as ext) -> let attributes = { quoted_attributes = m.pmty_attributes; field_name = "pmty_attributes"; } in M.cast self ext (Some attributes) ~type_name:"module_type" | _ -> super#module_type m method! structure_item i = match i.pstr_desc with | Pstr_extension ((({ txt = "i"; _ }, _) as ext), attrs) -> assert_no_attributes attrs; M.cast self ext None ~type_name:"structure_item" | _ -> super#structure_item i method! signature_item i = match i.psig_desc with | Psig_extension ((({ txt = "i"; _ }, _) as ext), attrs) -> assert_no_attributes attrs; M.cast self ext None ~type_name:"signature_item" | _ -> super#signature_item i end end module Expr = Make (struct type result = expression let location loc = evar ~loc:{ loc with loc_ghost = true } "loc" let location_stack = None let attributes = None let coalesce = Some coalesce_arity_expr class std_lifters = Ppxlib_metaquot_lifters.expression_lifters let annotate e core_type = pexp_constraint ~loc:core_type.ptyp_loc e core_type (* Append the quoted attributes to the attributes present on the antiquoted construct. Take this as example: [%expr [%e e] [@attr]] Suppose e has pexp_attributes = [attr1]. Then the resulting attributes are [ attr1; [@attr] ]. The decision to put outer attributes (here, [@attr]) at the end of the list is consistent with other parts of ppxlib that accumulate attributes. *) let add_quoted_attributes self e { quoted_attributes; field_name } ~type_name ~loc = match quoted_attributes with | [] -> self#typed e type_name | _ :: _ -> let loc = { loc with loc_ghost = true } in let var = gen_symbol ~prefix:"_ppx_metaquot_helper_var" () in let var_expr = pexp_ident ~loc (Located.mk ~loc (Lident var)) in let field_name = Located.mk ~loc (Lident field_name) in let reified_attrs = self#attributes quoted_attributes in (* append arg1 arg2 = [%expr Stdlib.List.append [%e arg1] [%e arg2]] *) let append arg1 arg2 = pexp_apply ~loc (pexp_ident ~loc (Located.mk ~loc (Ldot (Ldot (Lident "Stdlib", "List"), "append")))) [ (Nolabel, arg1); (Nolabel, arg2) ] in (* Morally, {[ let var = ([%expr e] : [%type: type_name]) in { var with pexp_attributes = var.pexp_attributes @ [%e reified_attrs ] } ]} *) pexp_let Nonrecursive ~loc [ value_binding ~loc ~pat:(ppat_var ~loc (Located.mk ~loc var)) ~expr:(self#typed e type_name); ] (pexp_record ~loc [ ( field_name, append (pexp_field ~loc var_expr field_name) reified_attrs ); ] (Some var_expr)) let cast self ext attrs ~type_name = match snd ext with | PStr [ { pstr_desc = Pstr_eval (e, inner_attrs); _ } ] -> ( assert_no_attributes inner_attrs; match attrs with | None -> self#typed e type_name | Some quoted_attrs -> add_quoted_attributes self e quoted_attrs ~type_name ~loc:(loc_of_extension ext)) | _ -> pexp_extension ~loc:(loc_of_extension ext) (Location.error_extensionf ~loc:(loc_of_extension ext) "expression expected") end) module Patt = Make (struct type result = pattern let location loc = ppat_any ~loc:{ loc with loc_ghost = true } let location_stack = Some (fun loc -> ppat_any ~loc:{ loc with loc_ghost = true }) let attributes = Some (fun loc -> ppat_any ~loc:{ loc with loc_ghost = true }) let coalesce = Some coalesce_arity_pat class std_lifters = Ppxlib_metaquot_lifters.pattern_lifters let annotate p core_type = ppat_constraint ~loc:core_type.ptyp_loc p core_type let cast self ext attrs ~type_name = match snd ext with | PPat (p, None) -> (match attrs with | None -> () | Some { quoted_attributes; field_name = _ } -> assert_no_attributes quoted_attributes); self#typed p type_name | PPat (_, Some e) -> Ast_builder.Default.( ppat_extension ~loc:e.pexp_loc (Location.error_extensionf ~loc:e.pexp_loc "guard not expected here")) | _ -> Ast_builder.Default.( ppat_extension ~loc:(loc_of_extension ext) (Location.error_extensionf ~loc:(loc_of_extension ext) "pattern expected")) end) let () = let extensions ctx lifter = [ E.declare "metaquot.expr" ctx A.(single_expr_payload __) (fun ~loc ~path:_ e -> let lift = lifter loc in lift#typed (lift#expression e) "expression"); E.declare "metaquot.pat" ctx A.(ppat __ none) (fun ~loc ~path:_ p -> let lift = lifter loc in lift#typed (lift#pattern p) "pattern"); E.declare "metaquot.str" ctx A.(pstr __) (fun ~loc ~path:_ s -> let lift = lifter loc in lift#typed (lift#structure s) "structure"); E.declare "metaquot.stri" ctx A.(pstr (__ ^:: nil)) (fun ~loc ~path:_ s -> let lift = lifter loc in lift#typed (lift#structure_item s) "structure_item"); E.declare "metaquot.sig" ctx A.(psig __) (fun ~loc ~path:_ s -> let lift = lifter loc in lift#typed (lift#signature s) "signature"); E.declare "metaquot.sigi" ctx A.(psig (__ ^:: nil)) (fun ~loc ~path:_ s -> let lift = lifter loc in lift#typed (lift#signature_item s) "signature_item"); E.declare "metaquot.type" ctx A.(ptyp __) (fun ~loc ~path:_ t -> let lift = lifter loc in lift#typed (lift#core_type t) "core_type"); ] in let extensions = extensions Expression Expr.lift @ extensions Pattern Patt.lift in Driver.register_transformation "metaquot" ~extensions