package brisk-reconciler
A lightweight library for modeling tree-shaped state with stateful functions
Install
Dune Dependency
Authors
Maintainers
Sources
v1.0.0-alpha1.tar.gz
sha512=3c133d9254b0aa122930fc3145cdef2502c825eaae4d71995919fb4bace19e8d168b8601bd198cd8bf32a0197337dd9ca6f5bafa597ee594bb8cff3a5e057aed
doc/src/brisk_ppx/brisk_ppx.ml.html
Source file brisk_ppx.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
module P = Ppxlib.Ast module ATH = Ppxlib.Ast_helper module Ast_builder = Ppxlib.Ast_builder.Default let component_ident ~loc = Ast_builder.(pexp_ident ~loc (Located.lident ~loc "brisk-component")) let component_ident_pattern ~loc = Ast_builder.(ppat_var ~loc (Located.mk ~loc "brisk-component")) let hooks_ident ~loc = Ast_builder.(pexp_ident ~loc (Located.lident ~loc "brisk-hooks")) let hooks_ident_pattern ~loc = Ast_builder.(ppat_var ~loc (Located.mk ~loc "brisk-hooks")) module JSX_ppx = struct let rec props_filter_children ~acc = function | [] -> List.rev acc | (P.Labelled "children", P.([%expr []])) :: tail -> props_filter_children ~acc tail | (P.Labelled "children", P.([%expr [%e? h] :: [%e? t]] as exp)) :: tail -> let loc = exp.P.pexp_loc in let prop = ( P.Labelled "children", P.([%expr Brisk_reconciler.Expert.jsx_list ([%e h] :: [%e t])]) ) in props_filter_children ~acc:(prop :: acc) tail | prop :: tail -> props_filter_children ~acc:(prop :: acc) tail let props_filter_children props = props_filter_children ~acc:[] props let rewrite_apply ~loc ~attributes:attrs props = let args = props_filter_children props in ATH.Exp.apply ~loc ~attrs (component_ident ~loc) args let is_jsx = let open Ppxlib.Ast_pattern in let jsx_attr = attribute ~name:(string "JSX") ~payload:__ in fun attr -> parse jsx_attr Ppxlib.Location.none ~on_error:(fun _ -> false) attr (fun _ -> true) let filter_jsx = List.filter is_jsx let exists_jsx = List.exists is_jsx let rec transform_createElement = let open Longident in function | Ldot (head, "createElement") -> Ldot (head, "make") | Lapply (left, right) -> Lapply (left, transform_createElement right) | Lident _ as ident -> ident | Ldot _ as ldot -> ldot let expr expr = match expr.P.pexp_desc with | P.Pexp_apply (fn, args) when exists_jsx expr.pexp_attributes -> let attributes = filter_jsx expr.pexp_attributes in let args = List.map (fun (label, arg) -> (label, arg)) args in let loc = expr.P.pexp_loc in let fn = match fn.P.pexp_desc with | P.Pexp_ident { txt; loc } -> let txt = transform_createElement txt in { fn with pexp_desc = Pexp_ident { txt; loc } } | _ -> fn in P.( [%expr let [%p component_ident_pattern ~loc] = [%e fn] in [%e rewrite_apply ~attributes ~loc:expr.P.pexp_loc args]]) | _ -> expr end module Declaration_ppx = struct let func_pattern = Ppxlib.Ast_pattern.( alt ( pexp_fun __ __ __ __ |> map ~f:(fun f lbl opt_arg pat expr -> f (`Fun (lbl, opt_arg, pat, expr))) ) ( pexp_newtype __' __ |> map ~f:(fun f ident expr -> f (`Newtype (ident, expr))) )) let match_ pattern ?on_error loc ast_node ~with_ = Ppxlib.Ast_pattern.parse pattern ?on_error loc ast_node with_ let attribute_name = function | `Component -> "component" | `Native -> "nativeComponent" let transform_component_expr ~useDynamicKey ~attribute ~component_name expr = let rec map_component_expression ({ P.pexp_loc = loc } as expr) = match_ func_pattern loc expr ~with_:(function | `Fun (lbl, opt_arg, pat, child_expression) -> ( let make_fun_with_expr ~expr = Ast_builder.pexp_fun ~loc lbl opt_arg pat expr in let loc = pat.Ppxlib.ppat_loc in match (lbl, pat) with | (Ppxlib.Labelled _ | Optional _), _ -> make_fun_with_expr ~expr:(map_component_expression child_expression) | Ppxlib.Nolabel, [%pat? ()] -> let loc = child_expression.pexp_loc in make_fun_with_expr ~expr: [%expr [%e component_ident ~loc] ~key [%e child_expression]] | _ -> Location.raise_errorf ~loc "A labelled argument or () was expected" ) | `Newtype (ident, child_expression) -> Ast_builder.pexp_newtype ~loc ident (map_component_expression child_expression)) in let open P in let loc = expr.P.pexp_loc in let create_component_expr = match attribute with | `Native -> [%expr Brisk_reconciler.Expert.nativeComponent] | `Component -> [%expr Brisk_reconciler.Expert.component] in [%expr let [%p component_ident_pattern ~loc] = [%e create_component_expr] ~useDynamicKey:[%e Ast_builder.(ebool ~loc useDynamicKey)] [%e component_name] in fun ?(key = Brisk_reconciler.Key.none) -> [%e map_component_expression expr]] let declare_attribute ctx typ = let open Ppxlib.Attribute in declare (attribute_name typ) ctx Ppxlib.Ast_pattern.( alt_option (single_expr_payload (pexp_ident (lident __'))) (pstr nil)) (function | Some { txt = "useDynamicKey" } -> true | Some { loc } -> Location.raise_errorf ~loc "A labelled argument or () was expected" | None -> false) let expr_attribute_component = declare_attribute Ppxlib.Attribute.Context.expression `Component let expr_attribute_nativeComponent = declare_attribute Ppxlib.Attribute.Context.expression `Native let expr_attribute = function | `Component -> expr_attribute_component | `Native -> expr_attribute_nativeComponent let expr unmatched_expr = let consume_attr attr = Ppxlib.Attribute.consume (expr_attribute attr) unmatched_expr in let transform ~useDynamicKey attribute expr = let loc = expr.P.pexp_loc in transform_component_expr ~useDynamicKey ~attribute ~component_name:[%expr __LOC__] expr in match consume_attr `Component with | Some (expr, useDynamicKey) -> transform ~useDynamicKey `Component expr | None -> ( match consume_attr `Native with | Some (expr, useDynamicKey) -> transform ~useDynamicKey `Native expr | None -> unmatched_expr ) let value_binding_attribute_component = declare_attribute Ppxlib.Attribute.Context.value_binding `Component let value_binding_attribute_nativeComponent = declare_attribute Ppxlib.Attribute.Context.value_binding `Native let value_binding_attribute = function | `Component -> value_binding_attribute_component | `Native -> value_binding_attribute_nativeComponent let value_binding unmatched_value_binding = let consume_attr attr = Ppxlib.Attribute.consume (value_binding_attribute attr) unmatched_value_binding in let transform ~useDynamicKey attribute value_binding = let value_binding_loc = value_binding.P.pvb_loc in Ppxlib.Ast_pattern.(parse (value_binding ~pat:(ppat_var __) ~expr:__)) value_binding_loc value_binding (fun var_pat expr -> let component_name = ATH.Exp.constant ~loc:expr.P.pexp_loc (ATH.Const.string var_pat) in let component_pat = value_binding.pvb_pat in let transformed_expr = transform_component_expr ~useDynamicKey ~attribute ~component_name expr in Ast_builder.( value_binding ~pat:component_pat ~loc:value_binding_loc ~expr:transformed_expr)) in match consume_attr `Component with | Some (value_binding, useDynamicKey) -> transform ~useDynamicKey `Component value_binding | None -> ( match consume_attr `Native with | Some (value_binding, useDynamicKey) -> transform ~useDynamicKey `Native value_binding | None -> unmatched_value_binding ) let register attribute = let open Ppxlib in Extension.declare (attribute_name attribute) Extension.Context.structure_item Ast_pattern.( pstr ( pstr_value __ (value_binding ~pat:(ppat_var __) ~expr:__ ^:: nil) ^:: nil )) (fun ~loc ~path recursive pat expr -> let component_name = ATH.Exp.constant ~loc (ATH.Const.string (path ^ "." ^ pat)) in let transformed_expression = transform_component_expr ~useDynamicKey:false ~attribute ~component_name expr in let pat = ATH.Pat.var ~loc (Ast_builder.Default.Located.mk ~loc pat) in match recursive with | Recursive -> [%stri let rec [%p pat] = [%e transformed_expression]] | Nonrecursive -> [%stri let [%p pat] = [%e transformed_expression]]) end module Hooks_ppx = struct open Ppxlib (* Grab a list of all the output expressions *) let lint_hook_usage = object inherit [bool] Ast_traverse.fold as super method! expression expr _ = let open Extension.Context in match get_extension expression expr with | Some (({ txt = "hook" }, _), _) -> true | Some _ | None -> super#expression expr false end let contains_hook_expression expr = lint_hook_usage#expression expr false let expand ~loc expr = let expansion = match expr.pexp_desc with | Pexp_let (Nonrecursive, [ binding ], next_expression) -> let wrapped_next_expression = if contains_hook_expression expr then [%expr [%e next_expression] [%e hooks_ident ~loc]] else [%expr [%e next_expression], [%e hooks_ident ~loc]] in [%expr fun [%p hooks_ident_pattern ~loc] -> let [%p binding.pvb_pat], [%p hooks_ident_pattern ~loc] = [%e binding.pvb_expr] [%e hooks_ident ~loc] in [%e wrapped_next_expression]] | Pexp_let (Recursive, _, _) -> Location.raise_errorf ~loc "'let%%hook' may not be recursive" | _ -> Location.raise_errorf ~loc "'hook' can only be used with 'let'" in { expansion with pexp_attributes = expr.pexp_attributes @ expansion.pexp_attributes; } let extension = Extension.declare "hook" Extension.Context.expression Ast_pattern.(single_expr_payload __) (fun ~loc ~path:_ expr -> expand ~loc expr) end let declaration_mapper = object inherit Ppxlib.Ast_traverse.map as super method! expression e = let e = super#expression e in Declaration_ppx.expr e method! value_binding binding = let binding = super#value_binding binding in Declaration_ppx.value_binding binding end let jsx_mapper = object inherit Ppxlib.Ast_traverse.map as super method! expression e = let e = super#expression e in JSX_ppx.expr e end let () = Ppxlib.Driver.register_transformation "component" ~impl:declaration_mapper#structure ~extensions: [ Declaration_ppx.register `Component; Declaration_ppx.register `Native; Hooks_ppx.extension; ]; Ppxlib.Driver.register_transformation "JSX" ~impl:jsx_mapper#structure
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>