123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195moduleLoader=LoadermodulePalette=PalettemoduleUtils=UtilsopenPpxlibmoduleAst=Ast_builder.Default(* ppxlib 0.33+ changed pexp_function to take function_param list instead of
case list. Use pexp_fun + pexp_match for cross-version compatibility. *)letfunction_of_cases~loccases=Ast.pexp_fun~locNolabelNone(Ast.ppat_var~loc{txt="__x";loc})(Ast.pexp_match~loc(Ast.pexp_ident~loc{txt=Lident"__x";loc})cases)(*
Example palette module:
module Basic : Palette.M = struct
type t =
| BrightWhite
let of_string = function
| "bright-white" -> BrightWhite
| name -> raise @@ InvalidColorName name
let to_code = function
| BrightWhite -> 97
let to_color = function
| BrightWhite -> Color.of_rgb 255 255 255
let color_list = [
Color.of_rgb 255 255 255;
]
end
*)letvariant_of_defs~locdefs=letconstructorname=(* one member of the variant *)Ast.constructor_declaration~loc~name:{txt=name;loc}~args:(Pcstr_tuple[])~res:NoneinAst.pstr_type~locRecursive[Ast.type_declaration~loc~name:{txt="t";loc}~params:[]~cstrs:[]~kind:(Ptype_variant(List.map(fun(_,(def:Loader.t))->constructordef.name)defs))~private_:Public~manifest:None;](* build AST for the generated of_string method *)letof_string_f_of_defs~locdefs=letdef_to_case(def:Loader.t)=letname=Utils.camel_to_kebabdef.nameinAst.case~lhs:(Ast.ppat_constant~loc(Pconst_string(name,loc,None)))~guard:None~rhs:(Ast.pexp_construct~loc{txt=Lidentdef.name;loc}None)inletdefault_case=Ast.case~lhs:[%pat?_]~guard:None~rhs:[%exprraise@@Palette.InvalidColorName__x]inletcases=List.map(fun(_,def)->def_to_casedef)defsin(* Match on String.lowercase_ascii of the input for case-insensitive lookup,
but use the original input (__x) in the error message *)Ast.pexp_fun~locNolabelNone(Ast.ppat_var~loc{txt="__x";loc})(Ast.pexp_match~loc[%exprString.lowercase_ascii__x](cases@[default_case]))letconst_integer_of_inti=Pconst_integer(Int.to_stringi,None)(* build AST for the generated to_code method *)letto_code_f_of_defs~locdefs=letdef_to_case(def:Loader.t)=Ast.case~lhs:(Ast.ppat_construct~loc{txt=Lidentdef.name;loc}None)~guard:None~rhs:(Ast.pexp_constant~loc(const_integer_of_intdef.code))inletcases=List.map(fun(_,def)->def_to_casedef)defsinfunction_of_cases~loccasesletapply_color_of_def~loc(def:Loader.t)=Ast.pexp_apply~loc(Ast.pexp_ident~loc{txt=Ldot(Ldot(Lident"Color","Rgb"),"to_gg");loc})[(Nolabel,Ast.pexp_apply~loc(Ast.pexp_ident~loc{txt=Ldot(Ldot(Lident"Color","Rgb"),"v");loc})(List.map(func->(Nolabel,Ast.pexp_constant~loc(const_integer_of_intc)))[def.r;def.g;def.b]))](* build AST for the generated to_color method *)letto_color_f_of_defs~locdefs=letdef_to_case(def:Loader.t)=Ast.case~lhs:(Ast.ppat_construct~loc{txt=Lidentdef.name;loc}None)~guard:None~rhs:(apply_color_of_def~locdef)inletcases=List.map(fun(_,def)->def_to_casedef)defsinfunction_of_cases~loccases(* build AST for the generated color_list *)letcolor_list_of_defs~locdefs=letcolors_list_expr=(* AST for a List is recursive nested Head::Tail pairs *)List.fold_right(fun(_,def)accumulated->Ast.pexp_construct~loc{txt=Lident"::";loc}(Some(Ast.pexp_tuple~loc[apply_color_of_def~locdef;accumulated])))defs(Ast.pexp_construct~loc{txt=Lident"[]";loc}None)inAst.pstr_value~locNonrecursive[Ast.value_binding~loc~pat:(Ast.ppat_var~loc{txt="color_list";loc})~expr:colors_list_expr;]letrecfind_in_ancestors~startrelpath=letcandidate=Filename.concatstartrelpathinifSys.file_existscandidatethenSomecandidateelseletparent=Filename.dirnamestartinifString.equalparentstartthenNoneelsefind_in_ancestors~start:parentrelpathletresolve_palette_filepathfilepath=ifnot(Filename.is_relativefilepath)thenfilepathelseifSys.file_existsfilepaththenfilepathelsematchSys.getenv_opt"DUNE_SOURCEROOT"with|Someroot->letcandidate=Filename.concatrootfilepathinifSys.file_existscandidatethencandidateelse(matchfind_in_ancestors~start:(Sys.getcwd())filepathwith|Somep->p|None->filepath)|None->(matchfind_in_ancestors~start:(Sys.getcwd())filepathwith|Somep->p|None->filepath)(*
Generate a Palette module from the given json config
module MyPalette : Palette.M = [%palette "mycolors.json"]
*)letexpand~ctxtfilepath=letloc=Expansion_context.Extension.extension_point_locctxtinletdefs=Loader.load_assoc(resolve_palette_filepathfilepath)inletmod_struct=[variant_of_defs~locdefs;[%striletof_string=[%eof_string_f_of_defs~locdefs]];[%striletto_code=[%eto_code_f_of_defs~locdefs]];[%striletto_color=[%eto_color_f_of_defs~locdefs]];color_list_of_defs~locdefs;[%striletnearest=Palette.nearest_of_listcolor_list];]inAst.pmod_structure~locmod_structletpalette_extension=Extension.V3.declare"palette"Extension.Context.module_expr(* where it's valid *)Ast_pattern.(single_expr_payload(estring__))(* arg def: expect a string *)expandletrule=Ppxlib.Context_free.Rule.extensionpalette_extensionlet()=Driver.register_transformation~rules:[rule]"palette"