catala icon indicating copy to clipboard operation
catala copied to clipboard

Use ppxlib instead of printers to generate ocaml code

Open adelaett opened this issue 2 years ago • 1 comments
trafficstars

Currently we use custom printers when doing code generation. This is quite error-prone since it makes it possible to generate nonsense code. For example, ocaml code is printed in two different places: to_ocaml and web_api

We could use ppxlib to build simpler code generator for ocaml. Indeed, ppxlib gives an interface for dealing with code generation.

Here is an exemple for the to_ocaml.ml file

Before:

let tlit (fmt : Format.formatter) (l : typ_lit) : unit =
  base_type fmt
    (match l with
    | TUnit -> "unit"
    | TBool -> "bool"
    | TInt -> "integer"
    | TRat -> "decimal"
    | TMoney -> "money"
    | TDuration -> "duration"
    | TDate -> "date")

let rec format_typ (fmt : Format.formatter) (typ : typ) : unit =
  let format_typ_with_parens (fmt : Format.formatter) (t : typ) =
    if typ_needs_parens t then Format.fprintf fmt "(%a)" format_typ t
    else Format.fprintf fmt "%a" format_typ t
  in
  match Marked.unmark typ with
  | TLit l -> Format.fprintf fmt "%a" Print.tlit l
  | TTuple ts ->
    Format.fprintf fmt "@[<hov 2>(%a)@]"
      (Format.pp_print_list
         ~pp_sep:(fun fmt () -> Format.fprintf fmt "@ *@ ")
         format_typ_with_parens)
      ts
  | TStruct s -> Format.fprintf fmt "%a.t" format_to_module_name (`Sname s)
  | TOption t ->
    Format.fprintf fmt "@[<hov 2>(%a)@] %a" format_typ_with_parens t
      format_enum_name Ast.option_enum
  | TEnum e -> Format.fprintf fmt "%a.t" format_to_module_name (`Ename e)
  | TArrow (t1, t2) ->
    Format.fprintf fmt "@[<hov 2>%a@]"
      (Format.pp_print_list
         ~pp_sep:(fun fmt () -> Format.fprintf fmt " ->@ ")
         format_typ_with_parens)
      (t1 @ [t2])
  | TArray t1 -> Format.fprintf fmt "@[%a@ array@]" format_typ_with_parens t1
  | TAny -> Format.fprintf fmt "_"

After

let rec typ_lit (loc : Location.t) (ty : typ_lit) : Parsetree.core_type =
  match ty with
  | TBool -> [%type: bool]
  | TUnit -> [%type: unit]
  | TInt -> [%type: integer]
  | TRat -> [%type: rational]
  | TMoney -> [%type: money]
  | TDate -> [%type: date]
  | TDuration -> [%type: duration]

let rec typ (loc : Location.t) (ty : typ) : Parsetree.core_type =
  match Marked.unmark ty with
  | TLit l -> typ_lit loc l
  | TTuple ts -> ptyp_tuple ~loc (List.map (typ loc) ts)
  | TStruct n -> ptyp_constr ~loc (struct_name loc n) []
  | TEnum n -> ptyp_constr ~loc (enum_name loc n) []
  | TOption t -> ptyp_constr ~loc (enum_name loc Ast.option_enum) [typ loc t]
  | TArrow (ts, t) ->
    List.fold_right (ptyp_arrow ~loc Nolabel)
      (List.map (typ loc) ts)
      (typ loc t)
  | TArray t -> [%type: [%t typ loc t] array]
  | TAny -> [%type: _]

adelaett avatar Feb 21 '23 16:02 adelaett

That's indeed incredibly nicer !

My only conern is how much of the OCaml compiler internals will end up having to statically link ; but that might not be that much of an issue. (And heh, if we actually need to link it all we could take the opportunity to add options to run evaluation through an OCaml toplevel straight away, or even spit out native code… :thinking: )

Note that what you need is actualy metaquot, but nowadays that is a subpart of ppxlib I think.

AltGr avatar Feb 22 '23 11:02 AltGr