LexiFi / gen_js_api

Easy OCaml bindings for Javascript libraries
MIT License
177 stars 31 forks source link

[@@js.invoke] attribute to call the global object as a function #152

Closed cannorin closed 3 years ago

cannorin commented 3 years ago

We would want to bind to a global object with methods as a OCaml module with js.scope. Sometimes, however, the global object is a "callable" one, which can also be called as a function.

A good example is the Number class/global object in ES5. Number is a global object which can be used as a constructor and can also be used as a function. Today we would have to bind to it as:

(* number.mli *)

type t = private Ojs.t

val toString: t -> ?radix:int -> unit -> float [@@js.call]
val toFixed: t -> ?fractionDigits:int -> unit -> float [@@js.call]
val toExponential: t -> ?fractionDigits:int -> unit -> float [@@js.call]
val toPrecision: t -> ?precision:int -> unit -> float [@@js.call]
val valueOf: t -> float [@@js.call]

module [@js.scope "Number"] Static : sig
  val create: 'any -> t [@@js.create]

  val min_value: float [@@js.global "MIN_VALUE"]
  val max_value: float [@@js.global "MAX_VALUE"]
  val nan: float [@@js.global "NaN"]
  val negative_infinity: float [@@js.global "NEGATIVE_INFINITY"]
  val positive_infinity: float [@@js.global "POSITIVE_INFINITY"]
end

val number: 'any -> float [@@js.global "Number"]

The problem is we can't js.scope the entire file because we lack the function counterpart of js.create, which calls the global object as a constructor.

This PR introduces a new attribute js.invoke to fill the gap:

as a function as a constructor
call the first argument [@@js.apply] [@@js.apply_newable]
call the global object [@@js.invoke] (NEW) [@@js.create]
call a member of the first argument [@@js.call "name"] N/A
call a member of the global object [@@js.global "name"] [@@js.new "name"]

With this, we would be able to rewrite the above binding as:

[@@@js.scope "Number"]

type t = private Ojs.t

val toString: t -> ?radix:int -> unit -> float [@@js.call]
val toFixed: t -> ?fractionDigits:int -> unit -> float [@@js.call]
val toExponential: t -> ?fractionDigits:int -> unit -> float [@@js.call]
val toPrecision: t -> ?precision:int -> unit -> float [@@js.call]
val valueOf: t -> float [@@js.call]

val create: 'any -> t [@@js.create]
val number: 'any -> float [@@js.invoke]

val min_value: float [@@js.global "MIN_VALUE"]
val max_value: float [@@js.global "MAX_VALUE"]
val nan: float [@@js.global "NaN"]
val negative_infinity: float [@@js.global "NEGATIVE_INFINITY"]
val positive_infinity: float [@@js.global "POSITIVE_INFINITY"]
nojb commented 3 years ago

cc @mlasson

mlasson commented 3 years ago

Thanks for the PR. It's great.