Traverse record declarations and generate typed field accessors
This commit is contained in:
5 files changed
+73
-1
No files matched your search
@@ -1,7 +1,10 @@
|
|||||||
OCAMLC ?= ocamlc
|
OCAMLC ?= ocamlc
|
||||||
|
|
||||||
.PHONY: all test clean
|
.PHONY: all test clean
|
||||||
all: fieldglass.cma
|
all: fieldglass.cma rewrite
|
||||||
|
|
||||||
|
rewrite: rewrite.ml
|
||||||
|
$(OCAMLC) -I +compiler-libs ocamlcommon.cma $< -o $@
|
||||||
|
|
||||||
fieldglass.cmi: fieldglass.mli
|
fieldglass.cmi: fieldglass.mli
|
||||||
$(OCAMLC) -c $<
|
$(OCAMLC) -c $<
|
||||||
@@ -15,6 +18,8 @@ fieldglass.cma: fieldglass.cmo
|
|||||||
test: all
|
test: all
|
||||||
$(OCAMLC) fieldglass.cma runtime.ml -o runtime.byte
|
$(OCAMLC) fieldglass.cma runtime.ml -o runtime.byte
|
||||||
./runtime.byte
|
./runtime.byte
|
||||||
|
$(OCAMLC) -ppx ./rewrite fieldglass.cma record.ml -o record.byte
|
||||||
|
./record.byte
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.cmi *.cmo *.cma *.byte rewrite
|
rm -f *.cmi *.cmo *.cma *.byte rewrite
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
Lenses for reading and updating immutable data in OCaml.
|
Lenses for reading and updating immutable data in OCaml.
|
||||||
|
|
||||||
|
Annotate a record with `[@@fieldglass generate]` to generate field accessors.
|
||||||
|
The `[@@lens generate]` spelling is also accepted.
|
||||||
|
|
||||||
Build with `make`; run the checks with `make test`.
|
Build with `make`; run the checks with `make test`.
|
||||||
|
|
||||||
The `_hd` and `_tl` lenses raise `Invalid_argument` on empty lists.
|
The `_hd` and `_tl` lenses raise `Invalid_argument` on empty lists.
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
bin: ["rewrite" {"fieldglass"}]
|
||||||
lib: ["fieldglass.cmi" "fieldglass.cma" "fieldglass.mli" "META"]
|
lib: ["fieldglass.cmi" "fieldglass.cma" "fieldglass.mli" "META"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
module Colour = struct
|
||||||
|
type t = { red : int; green : int; blue : int } [@@fieldglass generate]
|
||||||
|
end
|
||||||
|
|
||||||
|
module Legacy = struct
|
||||||
|
type t = { value : string } [@@lens generate]
|
||||||
|
end
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let colour = Colour.{ red = 3; green = 4; blue = 5 } in
|
||||||
|
assert (Colour.red colour = 3);
|
||||||
|
assert (Colour.green colour = 4);
|
||||||
|
assert (Legacy.value Legacy.{ value = "grey" } = "grey")
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
open Asttypes
|
||||||
|
open Parsetree
|
||||||
|
open Ast_helper
|
||||||
|
|
||||||
|
let ident ~loc name = Location.mkloc (Longident.Lident name) loc
|
||||||
|
let value ~loc name = Exp.ident ~loc (ident ~loc name)
|
||||||
|
let variable ~loc name = Pat.var ~loc (Location.mkloc name loc)
|
||||||
|
let lambda ~loc label pattern body = Exp.fun_ ~loc label None pattern body
|
||||||
|
|
||||||
|
let is_marker (name, _) = name.txt = "fieldglass" || name.txt = "lens"
|
||||||
|
|
||||||
|
let configured declaration =
|
||||||
|
match List.filter is_marker declaration.ptype_attributes with
|
||||||
|
| [] -> false
|
||||||
|
| [(_, PStr [{ pstr_desc = Pstr_eval
|
||||||
|
({ pexp_desc = Pexp_ident { txt = Longident.Lident "generate"; _ }; _ }, _); _ }])] -> true
|
||||||
|
| _ -> Location.raise_errorf ~loc:declaration.ptype_loc
|
||||||
|
"fieldglass: expected [@@fieldglass generate]"
|
||||||
|
|
||||||
|
let generate declaration =
|
||||||
|
let loc = declaration.ptype_loc in
|
||||||
|
let fields = match declaration.ptype_kind with
|
||||||
|
| Ptype_record fields -> fields
|
||||||
|
| _ -> Location.raise_errorf ~loc "fieldglass: expected a record type"
|
||||||
|
in
|
||||||
|
let source = value ~loc "__fg_source" in
|
||||||
|
let source_pattern = Pat.constraint_ ~loc (variable ~loc "__fg_source")
|
||||||
|
(Typ.constr ~loc (ident ~loc declaration.ptype_name.txt)
|
||||||
|
(List.map (fun _ -> Typ.any ~loc ()) declaration.ptype_params)) in
|
||||||
|
List.map (fun field ->
|
||||||
|
let name = field.pld_name.txt in
|
||||||
|
let getter = lambda ~loc Nolabel source_pattern
|
||||||
|
(Exp.field ~loc source (ident ~loc name)) in
|
||||||
|
Str.value ~loc Nonrecursive [Vb.mk ~loc (variable ~loc name) getter]) fields
|
||||||
|
|
||||||
|
let structure mapper items =
|
||||||
|
List.concat (List.map (fun item ->
|
||||||
|
let item = Ast_mapper.default_mapper.structure_item mapper item in
|
||||||
|
match item.pstr_desc with
|
||||||
|
| Pstr_type (recursive, declarations) ->
|
||||||
|
let generated = List.concat (List.map (fun declaration ->
|
||||||
|
if configured declaration then generate declaration else []) declarations) in
|
||||||
|
let declarations = List.map (fun declaration ->
|
||||||
|
{ declaration with ptype_attributes =
|
||||||
|
List.filter (fun attribute -> not (is_marker attribute)) declaration.ptype_attributes }) declarations in
|
||||||
|
{ item with pstr_desc = Pstr_type (recursive, declarations) } :: generated
|
||||||
|
| _ -> [item]) items)
|
||||||
|
|
||||||
|
let () = Ast_mapper.register "fieldglass"
|
||||||
|
(fun _ -> { Ast_mapper.default_mapper with structure })
|
||||||
Reference in new issue
Block a user