|
| 1 | + |
| 2 | +open Ast_helper |
| 3 | +module AC = Ast_convenience |
| 4 | + |
| 5 | +let mustache_from_file file = |
| 6 | + let chan = open_in file in |
| 7 | + let lex = Lexing.from_channel chan in |
| 8 | + Location.init lex file ; |
| 9 | + let t = Mustache.parse_lx lex in |
| 10 | + close_in chan ; |
| 11 | + t |
| 12 | + |
| 13 | +let mustache_from_string ~loc string = |
| 14 | + let lex = Lexing.from_string string in |
| 15 | + lex.Lexing.lex_curr_p <- loc ; |
| 16 | + Mustache.parse_lx lex |
| 17 | + |
| 18 | +let antiquot_pcdata ~loc ~lang var = |
| 19 | + let pcdata = Ppx_common.make ~loc lang "pcdata" in |
| 20 | + AC.list [ |
| 21 | + Exp.apply ~loc pcdata |
| 22 | + [Ppx_common.Label.nolabel, AC.evar var] |
| 23 | + ] |
| 24 | + |
| 25 | +module Var = struct |
| 26 | + |
| 27 | + module Env = Map.Make(String) |
| 28 | + |
| 29 | + type kind = |
| 30 | + | Var |
| 31 | + | Expr |
| 32 | + | Section of kind Env.t |
| 33 | + |
| 34 | + let pp fmt = function |
| 35 | + | Var -> Format.pp_print_string fmt "variable" |
| 36 | + | Expr -> Format.pp_print_string fmt "unescaped variable" |
| 37 | + | Section _ -> Format.pp_print_string fmt "section" |
| 38 | + |
| 39 | + let rec equal k k' = match k, k' with |
| 40 | + | Var, Var | Expr, Expr -> true |
| 41 | + | Section env, Section env' -> |
| 42 | + Env.equal equal env env' |
| 43 | + | _, _ -> false |
| 44 | + |
| 45 | + let error s k k' = |
| 46 | + Location.error @@ Format.asprintf |
| 47 | + "Variable %s is used both as a %a and a %a. This is not allowed." |
| 48 | + s pp k' pp k |
| 49 | + |
| 50 | + let add env s k = |
| 51 | + if Env.mem s env then |
| 52 | + let k' = Env.find s env in |
| 53 | + if equal k k then env |
| 54 | + else raise @@ Location.Error (error s k k') |
| 55 | + else |
| 56 | + Env.add s k env |
| 57 | + |
| 58 | + let union = |
| 59 | + let f s parentkind kind = match parentkind, kind with |
| 60 | + | Some k, Some k' -> raise @@ Location.Error (error s k k') |
| 61 | + | Some k, None | None, Some k -> Some k |
| 62 | + | None, None -> None |
| 63 | + in Env.merge f |
| 64 | + |
| 65 | + let section env s secenv = |
| 66 | + let k = Section secenv in |
| 67 | + add env s k |
| 68 | + |
| 69 | +end |
| 70 | + |
| 71 | +module Template = struct |
| 72 | + |
| 73 | + type t = desc list |
| 74 | + and desc = |
| 75 | + | Markup of string |
| 76 | + | Pcdata of string |
| 77 | + | Expr of string |
| 78 | + | Section of section |
| 79 | + and section = { |
| 80 | + inverted : bool; |
| 81 | + name: string; |
| 82 | + contents: t; |
| 83 | + } |
| 84 | + |
| 85 | + let rec of_mustache resolve = |
| 86 | + Mustache.fold |
| 87 | + ~string:(fun x -> [Markup x]) |
| 88 | + ~section: |
| 89 | + (fun ~inverted name contents -> [Section { inverted ; name ; contents }]) |
| 90 | + ~escaped:(fun x -> [Pcdata x]) |
| 91 | + ~unescaped:(fun x -> [Expr x]) |
| 92 | + ~partial: |
| 93 | + (fun s -> of_mustache resolve @@ mustache_from_file @@ resolve s) |
| 94 | + ~comment:(fun _ -> []) |
| 95 | + ~concat:List.concat |
| 96 | + |
| 97 | + let bindings ~env ~sec_env ~id = |
| 98 | + let f s b b' = match b, b' with |
| 99 | + | Some k, Some k' -> |
| 100 | + if Var.equal k k' then None |
| 101 | + else raise @@ Location.Error (Var.error s k k') |
| 102 | + | None, Some k' -> Some k' |
| 103 | + | _, None -> None |
| 104 | + in |
| 105 | + let env = Var.Env.merge f env sec_env in |
| 106 | + let make_binding k _ l = |
| 107 | + Vb.mk (AC.pvar k) (Exp.send id k) :: l |
| 108 | + in |
| 109 | + Exp.let_ Asttypes.Nonrecursive @@ Var.Env.fold make_binding env [] |
| 110 | + |
| 111 | + let rec desc_to_expr ~loc ~lang env t = |
| 112 | + Ast_helper.default_loc := loc ; |
| 113 | + match (t : desc) with |
| 114 | + | Markup s -> env, AC.str s |
| 115 | + | Pcdata s -> |
| 116 | + Var.add env s Var, antiquot_pcdata ~loc ~lang s |
| 117 | + | Expr s -> |
| 118 | + Var.add env s Expr, AC.evar s |
| 119 | + | Section { inverted ; name ; contents } -> |
| 120 | + let sec_env, e = |
| 121 | + to_expr ~simplify:false ~loc ~lang Var.Env.empty contents |
| 122 | + in |
| 123 | + let env = Var.section env name sec_env in |
| 124 | + let id = AC.evar name in |
| 125 | + let pid = AC.pvar name in |
| 126 | + if inverted then |
| 127 | + env, [%expr if [%e id] = [] then [] else [%e e]] |
| 128 | + else |
| 129 | + let e = bindings ~env ~sec_env ~id e in |
| 130 | + env, [%expr List.concat (List.map (fun [%p pid] -> [%e e]) [%e id])] |
| 131 | + |
| 132 | + and to_expr ~simplify ~loc ~lang env l = |
| 133 | + let f (env, acc) t = |
| 134 | + let env, expr = desc_to_expr ~loc ~lang env t in |
| 135 | + env, expr::acc |
| 136 | + in |
| 137 | + let env, l = List.fold_left f (env, []) l in |
| 138 | + env, Ppx_tyxml.markup_to_expr ~simplify lang loc @@ List.rev l |
| 139 | + |
| 140 | + let make_function env e = |
| 141 | + let f s _k e = |
| 142 | + Exp.fun_ (AC.Label.labelled s) None (AC.pvar s) e |
| 143 | + in |
| 144 | + Var.Env.fold f env e |
| 145 | + |
| 146 | +end |
| 147 | + |
| 148 | +let list_as_app = function |
| 149 | + | [] -> AC.unit () |
| 150 | + | h :: t -> Exp.apply h (List.map (fun x -> AC.Label.nolabel, x) t) |
| 151 | + |
| 152 | +let expr_of_mustache ~loc ~lang t = |
| 153 | + let env, e = |
| 154 | + Template.to_expr ~simplify:true ~loc ~lang Var.Env.empty @@ |
| 155 | + Template.of_mustache (fun _s -> assert false) @@ |
| 156 | + t |
| 157 | + in |
| 158 | + Template.make_function env e |
| 159 | + |
| 160 | +let expr_of_string ~loc ~lang s = |
| 161 | + expr_of_mustache ~loc ~lang @@ |
| 162 | + mustache_from_string ~loc:loc.loc_start s |
| 163 | + |
| 164 | + |
| 165 | +(** Mappers *) |
| 166 | + |
| 167 | +open Parsetree |
| 168 | + |
| 169 | +let error loc = |
| 170 | + Ppx_common.error loc "Invalid payload for [%%template]." |
| 171 | + |
| 172 | +let extract_str loc str = match AC.get_str str with |
| 173 | + | None -> error loc |
| 174 | + | Some s -> s |
| 175 | + |
| 176 | +let expr mapper e = |
| 177 | + let loc = e.pexp_loc in |
| 178 | + match e.pexp_desc with |
| 179 | + | Pexp_extension ({ txt = ("template" | "tyxml.template")}, payload) -> |
| 180 | + begin match payload with |
| 181 | + | PStr [[%stri let [%p? var] = [%e? str] in [%e? e]]] -> |
| 182 | + let s = extract_str loc str in |
| 183 | + Exp.let_ Asttypes.Nonrecursive |
| 184 | + [Vb.mk var @@ expr_of_string ~loc:str.pexp_loc ~lang:Html s] |
| 185 | + e |
| 186 | + |
| 187 | + | PStr [{pstr_desc = Pstr_eval (str, _)}] -> |
| 188 | + let s = extract_str loc str in |
| 189 | + expr_of_string ~loc:str.pexp_loc ~lang:Html s |
| 190 | + |
| 191 | + | _ -> error loc |
| 192 | + end |
| 193 | + | _ -> Ast_mapper.default_mapper.expr mapper e |
| 194 | + |
| 195 | +let structure_item mapper stri = |
| 196 | + let loc = stri.pstr_loc in |
| 197 | + match stri.pstr_desc with |
| 198 | + | Pstr_extension (({ txt = ("template" | "tyxml.template")}, payload), _) -> |
| 199 | + begin match payload with |
| 200 | + | PStr [[%stri let [%p? var] = [%e? str]]] -> |
| 201 | + let s = extract_str loc str in |
| 202 | + Str.value Asttypes.Nonrecursive |
| 203 | + [Vb.mk var @@ expr_of_string ~loc:str.pexp_loc ~lang:Html s] |
| 204 | + | _ -> error loc |
| 205 | + end |
| 206 | + | _ -> Ast_mapper.default_mapper.structure_item mapper stri |
| 207 | + |
| 208 | +let mapper _ = |
| 209 | + { Ast_mapper. default_mapper with expr ; structure_item } |
| 210 | + |
| 211 | +let () = Ast_mapper.register "tyxml.template" mapper |
0 commit comments