mirror of
https://github.com/c-cube/linol.git
synced 2025-12-10 05:04:00 -05:00
git-subtree-dir: thirdparty/lsp git-subtree-split: aae6986391a8519de3da6a7a341f2bd3376e0d2f
681 lines
22 KiB
OCaml
681 lines
22 KiB
OCaml
open! Import
|
|
open! Ts_types
|
|
|
|
(* TypeScript to OCaml conversion pipeline. The goal of this pipeline is to do
|
|
the conversion in logical stages. Unfortunately, this doesn't quite work *)
|
|
|
|
(* These declarations are all excluded because we don't support them or their
|
|
definitions are hand written *)
|
|
let skipped_ts_decls =
|
|
[ "InitializedParams"
|
|
; "NotificationMessage"
|
|
; "RequestMessage"
|
|
; "ResponseError"
|
|
; "DocumentUri"
|
|
; "ResponseMessage"
|
|
; "Message"
|
|
; "ErrorCodes"
|
|
; "MarkedString"
|
|
; "ProgressToken"
|
|
; "ProgressParams"
|
|
; "TextDocumentFilter"
|
|
; "PrepareRenameResult"
|
|
; "LSPAny"
|
|
; "LSPObject"
|
|
; "LSPArray"
|
|
; "LSPErrorCodes"
|
|
; "NotebookDocumentSyncOptions"
|
|
; "NotebookDocumentFilter"
|
|
; "NotebookDocumentSyncRegistrationOptions"
|
|
; "URI"
|
|
]
|
|
;;
|
|
|
|
(* XXX this is temporary until we support the [supportsCustomValues] field *)
|
|
let with_custom_values =
|
|
[ "FoldingRangeKind"; "CodeActionKind"; "PositionEncodingKind"; "WatchKind" ]
|
|
;;
|
|
|
|
module Expanded = struct
|
|
(** The expanded form is still working with typescript types. However, all
|
|
"anonymous" records and sums have been hoisted to the toplevel. So there
|
|
is a 1-1 correspondence to the OCaml typse we are going to generate *)
|
|
|
|
[@@@ocaml.warning "-37"]
|
|
|
|
type binding =
|
|
| Record of Resolved.field list
|
|
| Interface of Resolved.interface
|
|
| Poly_enum of Resolved.typ list
|
|
| Alias of Resolved.typ
|
|
|
|
type t = binding Ml.Module.t
|
|
|
|
(** Every anonymous record *)
|
|
let new_binding_of_typ (x : Resolved.typ) : binding option =
|
|
let record = function
|
|
| [ { Named.name = _; data = Resolved.Pattern _ } ] -> None
|
|
| f -> Some (Record f)
|
|
in
|
|
match x with
|
|
| List (Record d) | Record d -> record d
|
|
| Sum [ _; Record d ] -> record d
|
|
| _ -> None
|
|
;;
|
|
|
|
class discovered_types =
|
|
object
|
|
inherit [binding Named.t list] Resolved.fold as super
|
|
|
|
(** Every record valued field introduces a new type
|
|
|
|
TODO handle the case where two fields share a type *)
|
|
method! field f ~init =
|
|
let init =
|
|
match f.data with
|
|
| Pattern _ -> init
|
|
| Single { optional = _; typ } ->
|
|
(match new_binding_of_typ typ with
|
|
| None -> init
|
|
| Some data ->
|
|
let new_record = { f with data } in
|
|
if List.mem ~equal:Poly.equal init new_record
|
|
then init
|
|
else new_record :: init)
|
|
in
|
|
super#field f ~init
|
|
end
|
|
|
|
let bindings (r : Resolved.t) =
|
|
let t : binding Named.t =
|
|
let data =
|
|
match r.data with
|
|
| Enum_anon _ -> assert false
|
|
| Interface i -> Interface i
|
|
| Type typ ->
|
|
(match new_binding_of_typ typ with
|
|
| Some data -> data
|
|
| None -> Alias typ)
|
|
in
|
|
{ data; name = "t" }
|
|
in
|
|
let init = [ t ] in
|
|
match r.data with
|
|
| Enum_anon _ -> assert false
|
|
| Type typ -> (new discovered_types)#typ typ ~init
|
|
| Interface intf -> (new discovered_types)#typ (Record intf.fields) ~init
|
|
;;
|
|
|
|
let of_ts (r : Resolved.t) : t =
|
|
let name = Ml.Module.Name.of_string (String.capitalize_ascii r.name) in
|
|
{ Ml.Module.name; bindings = bindings r }
|
|
;;
|
|
end
|
|
|
|
module Json = Json_gen
|
|
|
|
module Module : sig
|
|
open Ml
|
|
|
|
type t = (Module.sig_ Module.t, Module.impl Module.t) Kind.pair
|
|
|
|
val add_private_values : t -> Expr.toplevel Named.t list -> t
|
|
val type_decls : Module.Name.t -> Type.decl Named.t list Kind.Map.t -> t
|
|
|
|
(** Use Json.Nullable_option or Json.Assoc.t where appropriate *)
|
|
val use_json_conv_types : t -> t
|
|
|
|
(** Rename fields that are also OCaml keywords *)
|
|
val rename_invalid_fields : Ml.Kind.t -> Type.decl -> Type.decl
|
|
|
|
val pp : t -> unit Pp.t Kind.Map.t
|
|
end = struct
|
|
module Module = Ml.Module
|
|
|
|
type t = (Module.sig_ Module.t, Module.impl Module.t) Ml.Kind.pair
|
|
|
|
let type_decls name (type_decls : Ml.Type.decl Named.t list Ml.Kind.Map.t) : t =
|
|
let module_ bindings = { Ml.Module.name; bindings } in
|
|
let intf : Module.sig_ Module.t =
|
|
List.map type_decls.intf ~f:(fun (td : Ml.Type.decl Named.t) ->
|
|
{ td with Named.data = (Ml.Module.Type_decl td.data : Ml.Module.sig_) })
|
|
|> module_
|
|
in
|
|
let impl =
|
|
List.map type_decls.impl ~f:(fun (td : Ml.Type.decl Named.t) ->
|
|
{ td with Named.data = Ml.Module.Type_decl td.data })
|
|
|> module_
|
|
in
|
|
{ Ml.Kind.intf; impl }
|
|
;;
|
|
|
|
let add_private_values (t : t) bindings : t =
|
|
let bindings =
|
|
List.map bindings ~f:(fun (v : _ Named.t) ->
|
|
{ v with Named.data = Ml.Module.Value v.data })
|
|
in
|
|
let impl = { t.impl with bindings = t.impl.bindings @ bindings } in
|
|
{ t with impl }
|
|
;;
|
|
|
|
let json_assoc_t = Ml.Path.Dot (Dot (Ident "Json", "Assoc"), "t")
|
|
|
|
let rename_invalid_fields =
|
|
let map (kind : Ml.Kind.t) =
|
|
let open Ml.Type in
|
|
object (self)
|
|
inherit [unit, unit] Ml.Type.mapreduce as super
|
|
method empty = ()
|
|
method plus () () = ()
|
|
|
|
method! field x f =
|
|
let f =
|
|
if Ml.is_kw f.name
|
|
then (
|
|
let attrs =
|
|
match kind with
|
|
| Impl -> ("key", [ sprintf "%S" f.name ]) :: f.attrs
|
|
| Intf -> f.attrs
|
|
in
|
|
{ f with name = f.name ^ "_"; attrs })
|
|
else f
|
|
in
|
|
super#field x f
|
|
|
|
method! assoc x k v = self#t x (App (Path json_assoc_t, [ k; v ]))
|
|
end
|
|
in
|
|
fun kind t -> (map kind)#decl () t |> fst
|
|
;;
|
|
|
|
let use_json_conv_types =
|
|
let map =
|
|
let open Ml.Type in
|
|
object (self)
|
|
inherit [unit, unit] Ml.Type.mapreduce as super
|
|
method empty = ()
|
|
method plus () () = ()
|
|
|
|
method! optional x t =
|
|
if t = Json_gen.json_t
|
|
then super#optional x t
|
|
else (
|
|
let opt = Ml.Path.Dot (Dot (Ident "Json", "Nullable_option"), "t") in
|
|
self#t x (App (Path opt, [ t ])))
|
|
|
|
method! field x f =
|
|
let f =
|
|
match f.typ with
|
|
| Optional t ->
|
|
if t = Json_gen.json_t
|
|
then { f with attrs = ("yojson.option", []) :: f.attrs }
|
|
else
|
|
{ f with
|
|
attrs =
|
|
("default", [ "None" ])
|
|
:: ("yojson_drop_default", [ "( = )" ])
|
|
:: f.attrs
|
|
}
|
|
| _ -> f
|
|
in
|
|
super#field x f
|
|
|
|
method! assoc x k v = self#t x (App (Path json_assoc_t, [ k; v ]))
|
|
end
|
|
in
|
|
fun (t : t) ->
|
|
let impl =
|
|
let bindings =
|
|
List.map t.impl.bindings ~f:(fun (x : _ Named.t) ->
|
|
let data =
|
|
match (x.data : Module.impl) with
|
|
| Type_decl decl -> Ml.Module.Type_decl (map#decl () decl |> fst)
|
|
| x -> x
|
|
in
|
|
{ x with data })
|
|
in
|
|
{ t.impl with bindings }
|
|
in
|
|
{ t with impl }
|
|
;;
|
|
|
|
let pp (t : t) ~kind =
|
|
match (kind : Ml.Kind.t) with
|
|
| Intf -> Ml.Module.pp_sig t.intf
|
|
| Impl -> Ml.Module.pp_impl t.impl
|
|
;;
|
|
|
|
let pp t = { Ml.Kind.intf = pp t ~kind:Intf; impl = pp t ~kind:Impl }
|
|
end
|
|
|
|
let enum_module ~allow_other ({ Named.name; data = constrs } as t) =
|
|
let json_bindings = Json_gen.Enum.conv ~allow_other ~poly:false { t with name = "t" } in
|
|
let t =
|
|
let data =
|
|
let constrs = List.map constrs ~f:(fun (name, _) -> Ml.Type.constr ~name []) in
|
|
let constrs =
|
|
if allow_other
|
|
then
|
|
(* [String] is a hack. It could be a differnt type, but it isn't in
|
|
practice *)
|
|
constrs @ [ Ml.Type.constr ~name:"Other" [ Ml.Type.Prim String ] ]
|
|
else constrs
|
|
in
|
|
Ml.Type.Variant constrs
|
|
in
|
|
{ Named.name = "t"; data }
|
|
in
|
|
let type_decls = Ml.Kind.Map.make_both [ t ] in
|
|
let module_ = Module.type_decls (Ml.Module.Name.of_string name) type_decls in
|
|
Module.add_private_values module_ json_bindings
|
|
;;
|
|
|
|
module Entities = struct
|
|
type t = (Ident.t * Resolved.t) list
|
|
|
|
let find db e : _ Named.t =
|
|
match List.assoc db e with
|
|
| Some s -> s
|
|
| None -> Code_error.raise "Entities.find: unable to find" [ "e", Ident.to_dyn e ]
|
|
;;
|
|
|
|
let of_map map ts =
|
|
List.map ts ~f:(fun (r : Resolved.t) -> String.Map.find_exn map r.name, r)
|
|
;;
|
|
|
|
let rev_find (db : t) (resolved : Resolved.t) : Ident.t =
|
|
match
|
|
List.filter_map db ~f:(fun (id, r) ->
|
|
if r.name = resolved.name then Some id else None)
|
|
with
|
|
| [] -> Code_error.raise "rev_find: resolved not found" []
|
|
| [ x ] -> x
|
|
| _ :: _ -> Code_error.raise "re_vind: duplicate entries" []
|
|
;;
|
|
end
|
|
|
|
module Mapper : sig
|
|
(* Convert typescript types to OCaml types *)
|
|
|
|
val make_typ : Entities.t -> Resolved.typ Named.t -> Ml.Type.t
|
|
|
|
type literal_field =
|
|
{ field_name : string
|
|
; literal_value : string
|
|
}
|
|
|
|
(** Map a TS record into an OCaml record. Literal valued fields such as kind:
|
|
'foo' are extracted into a separate list *)
|
|
val record_
|
|
: Entities.t
|
|
-> Resolved.field list Named.t
|
|
-> Ml.Type.decl Named.t * literal_field list
|
|
|
|
(** Extract all untagged unions in field position. These will be turned into
|
|
polymorphic variants using a naming scheme for the tags. *)
|
|
val extract_poly_vars : Ml.Type.decl -> Ml.Type.decl * Ml.Type.constr list Named.t list
|
|
end = struct
|
|
type literal_field =
|
|
{ field_name : string
|
|
; literal_value : string
|
|
}
|
|
|
|
module Type = Ml.Type
|
|
|
|
let is_same_as_json =
|
|
let constrs =
|
|
[ Prim.Null; String; Bool; Number; Object; List ]
|
|
|> List.map ~f:(fun s -> Resolved.Ident s)
|
|
in
|
|
fun set -> List.for_all constrs ~f:(fun e -> List.mem set e ~equal:Poly.equal)
|
|
;;
|
|
|
|
let id = Type.name "Jsonrpc.Id.t"
|
|
|
|
let is_same_as_id =
|
|
let sort = List.sort ~compare:Poly.compare in
|
|
let constrs =
|
|
[ Prim.String; Number ] |> List.map ~f:(fun s -> Resolved.Ident s) |> sort
|
|
in
|
|
fun cs -> List.equal ( = ) constrs (sort cs)
|
|
;;
|
|
|
|
(* Any type that includes null needs to be extracted to be converted to an
|
|
option *)
|
|
let remove_null cs =
|
|
let is_null x =
|
|
match x with
|
|
| Resolved.Ident Prim.Null -> Either.Left x
|
|
| _ -> Right x
|
|
in
|
|
let nulls, non_nulls = List.partition_map ~f:is_null cs in
|
|
match nulls with
|
|
| [] -> `No_null_present
|
|
| _ :: _ :: _ -> assert false
|
|
| [ _ ] -> `Null_removed non_nulls
|
|
;;
|
|
|
|
let make_typ db { Named.name; data = t } =
|
|
let rec type_ topmost_field_name (t : Resolved.typ) =
|
|
match t with
|
|
| Ident Uinteger -> Type.int (* XXX shall we use a dedicated uinteger eventually? *)
|
|
| Ident Number -> Type.int
|
|
| Ident String -> Type.string
|
|
| Ident Bool -> Type.bool
|
|
| Ident Object -> Type.json_object
|
|
| Ident Self -> Type.t (* XXX wrong *)
|
|
| Ident Any -> Type.json
|
|
| Ident Null -> assert false
|
|
| Ident List -> Type.list Type.json
|
|
| Ident Uri | Ident (Resolved { id = _; name = "URI" }) ->
|
|
Type.module_t "DocumentUri"
|
|
| Ident (Resolved { id = _; name = "LSPAny" }) -> Type.json
|
|
| Ident (Resolved { id = _; name = "LSPObject" }) -> Type.json_object
|
|
| Ident (Resolved r) ->
|
|
let entity = Entities.find db r in
|
|
Type.module_t entity.name
|
|
| List (Ident (Uinteger | Number)) when topmost_field_name = Some "data" ->
|
|
Type.array Type.int
|
|
| List t -> Type.list (type_ topmost_field_name t)
|
|
| Tuple ts -> Type.Tuple (List.map ~f:(type_ topmost_field_name) ts)
|
|
| Sum s -> sum topmost_field_name s
|
|
| App _ | Literal _ -> Type.void
|
|
| Record r -> record r
|
|
and sum topmost_field_name s =
|
|
if is_same_as_json s
|
|
then Type.json
|
|
else (
|
|
match remove_null s with
|
|
| `No_null_present -> if is_same_as_id s then id else poly topmost_field_name s
|
|
| `Null_removed [ s ] -> Type.Optional (type_ topmost_field_name s)
|
|
| `Null_removed [] -> assert false
|
|
| `Null_removed cs -> Type.Optional (sum topmost_field_name cs))
|
|
and simplify_record (fields : Resolved.field list) =
|
|
(* A record with only a pattern field is simplified to an association
|
|
list *)
|
|
match fields with
|
|
| [ { Named.name; data = Pattern { pat; typ } } ] ->
|
|
let topmost_field_name = Some name in
|
|
let key = type_ topmost_field_name pat in
|
|
let data = type_ topmost_field_name typ in
|
|
Some (Type.assoc_list ~key ~data)
|
|
| [] -> Some Type.json_object
|
|
| _ -> None
|
|
and record fields =
|
|
match simplify_record fields with
|
|
| None -> Type.name name
|
|
| Some a -> a
|
|
and poly topmost_field_name s : Ml.Type.t =
|
|
let type_ = type_ topmost_field_name in
|
|
try
|
|
Poly_variant
|
|
(List.map s ~f:(fun t ->
|
|
let name, constrs =
|
|
match (t : Resolved.typ) with
|
|
| Ident Self | Ident Null -> assert false
|
|
| Ident String -> "String", [ type_ t ]
|
|
| Ident Number -> "Int", [ type_ t ]
|
|
| Ident Object -> "Assoc", [ type_ t ]
|
|
| Ident Bool -> "Bool", [ type_ t ]
|
|
| List _ | Ident List -> "List", [ type_ t ]
|
|
| Ident (Resolved r) -> (Entities.find db r).name, [ type_ t ]
|
|
| Tuple [ Ident Uinteger; Ident Uinteger ] -> "Offset", [ type_ t ]
|
|
| Literal (String x) -> x, []
|
|
| Record _ ->
|
|
let topmost_field_name = Option.value_exn topmost_field_name in
|
|
topmost_field_name, [ type_ t ]
|
|
| _ -> raise Exit
|
|
in
|
|
Type.constr ~name constrs))
|
|
with
|
|
| Exit -> Type.unit
|
|
in
|
|
type_ (Some name) t
|
|
;;
|
|
|
|
let make_field db (field : Resolved.field) =
|
|
match field.data with
|
|
| Pattern { pat; typ } ->
|
|
let key = make_typ db { Named.name = field.name; data = pat } in
|
|
let data = make_typ db { Named.name = field.name; data = typ } in
|
|
let typ = Type.assoc_list ~key ~data in
|
|
Either.Left (Ml.Type.field typ ~name:field.name)
|
|
| Single { typ = Literal s; optional = false } ->
|
|
let literal_value =
|
|
match s with
|
|
| String s -> s
|
|
| _ -> assert false
|
|
in
|
|
Right { literal_value; field_name = field.name }
|
|
| Single { typ; optional } ->
|
|
let typ = make_typ db { Named.name = field.name; data = typ } in
|
|
let typ = if optional then Type.Optional typ else typ in
|
|
Left (Ml.Type.field typ ~name:field.name)
|
|
;;
|
|
|
|
let record_ db { Named.name; data = (fields : Resolved.field list) } =
|
|
let data, literals =
|
|
match fields with
|
|
| [ { Named.name; data = Pattern { pat; typ } } ] ->
|
|
let key = make_typ db { Named.name; data = pat } in
|
|
let data = make_typ db { Named.name; data = typ } in
|
|
Type.Alias (Type.assoc_list ~key ~data), []
|
|
| [] -> Type.Alias Type.json_object, []
|
|
| _ ->
|
|
let fields, literals = List.partition_map fields ~f:(make_field db) in
|
|
Type.Record fields, literals
|
|
in
|
|
{ Named.name; data }, literals
|
|
;;
|
|
|
|
let extract_poly_vars s =
|
|
let extract =
|
|
object (self)
|
|
inherit
|
|
[string option, Ml.Type.constr list Named.t list] Ml.Type.mapreduce as super
|
|
|
|
method empty = []
|
|
|
|
(* TODO grossly slow *)
|
|
method plus x y = x @ y
|
|
|
|
method! field _ (f : Ml.Type.field) =
|
|
let env = Some f.name in
|
|
super#field env f
|
|
|
|
method! poly_variant env constrs =
|
|
match env with
|
|
| None -> super#poly_variant env constrs
|
|
| Some name ->
|
|
(* This hack is needed to avoid collision with user visible types
|
|
that we might introduce *)
|
|
let name = name ^ "_pvar" in
|
|
let replacement = Ml.Type.name name in
|
|
let constrs, m = self#fold_left_map ~f:(self#constr env) constrs in
|
|
replacement, self#plus m [ { Named.name; data = constrs } ]
|
|
end
|
|
in
|
|
extract#decl None s
|
|
;;
|
|
end
|
|
|
|
module Gen : sig
|
|
val module_ : Entities.t -> Expanded.binding Ml.Module.t -> Module.t
|
|
end = struct
|
|
module Type = Ml.Type
|
|
|
|
let type_ db ({ Named.name; data = _ } as t) =
|
|
let main_type =
|
|
let typ = Mapper.make_typ db t in
|
|
{ Named.name; data = Type.Alias typ }
|
|
in
|
|
[ main_type ]
|
|
;;
|
|
|
|
let record db ({ Named.name = _; data = _ } as t) =
|
|
let main_type, literals = Mapper.record_ db t in
|
|
Some (main_type, literals)
|
|
;;
|
|
|
|
let poly_enum { Named.name; data = _ } : Type.decl Named.t list =
|
|
[ { Named.name; data = Type.Alias Type.unit } ]
|
|
;;
|
|
|
|
let poly_enum_conv (t : _ Named.t) =
|
|
if List.for_all t.data ~f:(fun (c : Ml.Type.constr) -> List.is_empty c.args)
|
|
then
|
|
(* This is equivalent to an enum *)
|
|
List.map t.data ~f:(fun (c : Ml.Type.constr) -> c.name, Literal.String c.name)
|
|
|> Named.set_data t
|
|
|> Json_gen.Enum.conv ~allow_other:false ~poly:true
|
|
else [ Json_gen.Poly_variant.of_json t; Json_gen.Poly_variant.to_json t ]
|
|
;;
|
|
|
|
(* This is the more complex case *)
|
|
|
|
let module_ db { Ml.Module.name; bindings } : Module.t =
|
|
let type_decls =
|
|
let add_record = function
|
|
| None -> []
|
|
| Some (decl, literals) -> [ `Record (decl, literals) ]
|
|
in
|
|
let add_else = List.map ~f:(fun x -> `Type x) in
|
|
List.concat_map bindings ~f:(fun (r : Expanded.binding Named.t) ->
|
|
match r.data with
|
|
| Record data -> record db { r with data } |> add_record
|
|
| Interface data -> record db { r with data = data.fields } |> add_record
|
|
| Poly_enum data -> poly_enum { r with data } |> add_else
|
|
| Alias data -> type_ db { r with data } |> add_else)
|
|
in
|
|
let intf : Ml.Module.sig_ Named.t list =
|
|
List.map type_decls ~f:(function
|
|
| `Record (t, _) -> t
|
|
| `Type t -> t)
|
|
|> List.concat_map ~f:(fun (td : Ml.Type.decl Named.t) ->
|
|
let td = { td with data = Module.rename_invalid_fields Intf td.data } in
|
|
[ { td with Named.data = (Ml.Module.Type_decl td.data : Ml.Module.sig_) } ]
|
|
@ Ml_create.intf_of_type td)
|
|
in
|
|
let impl : Ml.Module.impl Named.t list =
|
|
(* TODO we should make sure to handle duplicate variants extracted *)
|
|
List.concat_map type_decls ~f:(fun d ->
|
|
let d, literal_wrapper =
|
|
match d with
|
|
| `Record (l, [ lw ]) -> l, Some lw
|
|
| `Record (l, []) -> l, None
|
|
| `Record (_, _ :: _) ->
|
|
assert false
|
|
(* we don't support multiple literals in a single record for
|
|
now *)
|
|
| `Type l -> l, None
|
|
in
|
|
let typ_, poly_vars = Mapper.extract_poly_vars (Named.data d) in
|
|
let poly_vars_and_convs =
|
|
List.concat_map poly_vars ~f:(fun pv ->
|
|
let decl =
|
|
Named.map pv ~f:(fun decl ->
|
|
Ml.Module.Type_decl (Alias (Poly_variant decl)))
|
|
in
|
|
let json_conv =
|
|
poly_enum_conv pv |> List.map ~f:(Named.map ~f:(fun v -> Ml.Module.Value v))
|
|
in
|
|
decl :: json_conv)
|
|
in
|
|
let typ_ = { d with data = typ_ } in
|
|
let literal_wrapper =
|
|
match literal_wrapper with
|
|
| None -> []
|
|
| Some { field_name; literal_value } ->
|
|
Json_gen.make_literal_wrapper_conv
|
|
~field_name
|
|
~literal_value
|
|
~type_name:typ_.name
|
|
in
|
|
let typ_ = { typ_ with data = Module.rename_invalid_fields Impl typ_.data } in
|
|
let json_convs_for_t =
|
|
match d.data with
|
|
| Alias (Poly_variant data) ->
|
|
poly_enum_conv { d with Named.data }
|
|
|> List.map ~f:(Named.map ~f:(fun v -> Ml.Module.Value v))
|
|
| _ -> []
|
|
in
|
|
poly_vars_and_convs
|
|
@ [ { typ_ with data = Ml.Module.Type_decl typ_.data } ]
|
|
@ json_convs_for_t
|
|
@ Ml_create.impl_of_type typ_
|
|
@ literal_wrapper)
|
|
in
|
|
let module_ bindings = { Ml.Module.name; bindings } in
|
|
{ Ml.Kind.intf = module_ intf; impl = module_ impl }
|
|
;;
|
|
end
|
|
|
|
(* extract all resovled identifiers *)
|
|
class name_idents =
|
|
object
|
|
inherit [Ident.t list] Resolved.fold
|
|
|
|
method! ident i ~init =
|
|
match i with
|
|
| Resolved r -> r :: init
|
|
| _ -> init
|
|
end
|
|
|
|
let resolve_typescript (ts : Unresolved.t list) =
|
|
let ts, db = Typescript.resolve_all ts in
|
|
let db = Entities.of_map db ts in
|
|
match
|
|
let idents = new name_idents in
|
|
Ident.Top_closure.top_closure
|
|
ts
|
|
~key:(fun x -> Entities.rev_find db x)
|
|
~deps:(fun x -> idents#t x ~init:[] |> List.map ~f:(Entities.find db))
|
|
with
|
|
| Error cycle ->
|
|
let cycle = List.map cycle ~f:(fun (x : Resolved.t) -> x.name) in
|
|
Code_error.raise "Unexpected cycle" [ "cycle", Dyn.(list string) cycle ]
|
|
| Ok ts -> db, ts
|
|
;;
|
|
|
|
let of_resolved_typescript db (ts : Resolved.t list) =
|
|
let simple_enums, everything_else =
|
|
List.filter_partition_map ts ~f:(fun (t : Resolved.t) ->
|
|
if List.mem skipped_ts_decls t.name ~equal:String.equal
|
|
then Skip
|
|
else (
|
|
match t.data with
|
|
| Enum_anon data -> Left { t with data }
|
|
| Interface _ | Type _ -> Right t))
|
|
in
|
|
let simple_enums =
|
|
List.map simple_enums ~f:(fun (t : _ Named.t) ->
|
|
(* "open" enums need an `Other constructor *)
|
|
let allow_other = List.mem ~equal:String.equal with_custom_values t.name in
|
|
let data =
|
|
List.filter_map t.data ~f:(fun (constr, v) ->
|
|
match (v : Ts_types.Enum.case) with
|
|
| Literal l -> Some (constr, l)
|
|
| Alias _ ->
|
|
(* TODO we don't handle these for now *)
|
|
None)
|
|
in
|
|
enum_module ~allow_other { t with data })
|
|
in
|
|
let everything_else =
|
|
List.map everything_else ~f:(fun (t : _ Named.t) ->
|
|
let mod_ = Expanded.of_ts t in
|
|
Gen.module_ db mod_)
|
|
in
|
|
simple_enums @ everything_else
|
|
|> List.map ~f:(fun (decl : _ Ml.Kind.pair) ->
|
|
let decl =
|
|
let intf = Json_gen.add_json_conv_for_t decl.intf in
|
|
{ decl with intf }
|
|
in
|
|
Module.use_json_conv_types decl)
|
|
;;
|
|
|
|
let of_typescript ts =
|
|
let db, ts = resolve_typescript ts in
|
|
of_resolved_typescript db ts
|
|
;;
|