Git Product home page Git Product logo

gen_js_api's Introduction

gen_js_api: easy OCaml bindings for JavaScript libraries

Build Status

Overview

gen_js_api aims at simplifying the creation of OCaml bindings for JavaScript libraries. It must currently be used with the js_of_ocaml compiler, although other ways to run OCaml code "against" JavaScript might be supported later with the same binding definitions (for instance, Bucklescript, or direct embedding of a JS engine in a native OCaml application).

gen_js_api is based on the following ideas:

  • Authors of bindings write OCaml signatures for JavaScript libraries and the tool generates the actual binding code with a combination of implicit conventions and explicit annotations.

  • The generated binding code takes care of translating values between OCaml and JavaScript and of dealing with JavaScript calling conventions.

  • All syntactic processing is done by authors of bindings: the client code is normal OCaml code and does not depend on custom syntax nor on JS-specific types.

gen_js_api can be used in two complementary ways:

Examples

The repository contains some examples of OCaml bindings to JavaScript libraries created with gen_js_api:

Documentation

Related projects

  • js_of_ocaml: The compiler and runtime system on which gen_js_api relies. (Note: gen_js_api doesn't depend on js_of_ocaml's OCaml library, nor on its language extension.)

  • goji: A DSL to describe OCaml bindings for JavaScript libraries.

  • DefinitelyMaybeTyped: A project to parse DefinitelyTyped interfaces and produce OCaml interfaces.

  • ReScript: Another compiler from OCaml to JavaScript, featuring the genType ppx for generating TS / Flow types and runtime converters.

About

gen_js_api has been created by LexiFi for porting a web application from JavaScript to OCaml. The tool has been used in production since 2015.

This gen_js_api package is licensed by LexiFi under the terms of the MIT license.

See see Changelog

Contact: [email protected]

Contributors:

  • Alain Frisch
  • Sebastien Briais

gen_js_api's People

Contributors

alainfrisch avatar armish avatar bikallem avatar cannorin avatar clembu avatar damiendoligez avatar hhugo avatar jchavarri avatar joelburget avatar mlasson avatar nojb avatar o-marshmallow avatar rgrinberg avatar ryyppy avatar sbriais avatar smorimoto avatar tmcgilchrist avatar vouillon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gen_js_api's Issues

Support type variables on interface files

I find myself doing this quite often in mli files that are processed with gen_js_api:

val use_state: (unit -> 'state) -> ('state * ('state -> unit))
[@@js.custom
  val use_state_internal : (unit -> Ojs.t) -> (Ojs.t * (Ojs.t -> unit)) [@@js.global "react.useState"]
  let use_state = Obj.magic use_state_internal
]

Binding to functions with type variables is very common, and regular external statements support it.

The manual process shown above is verbose and error prone, and I was wondering if it'd be possible for gen_js_api to interpret 'a as the black box that it is, that doesn't require any conversions (or rather because it can be any type, the only function applicable is identity), and basically automate the "magic" that is needed for those kind of signatures to exist.

[@js.scope ...] ignored by non-functional values

Here's my use-case:
Electron has an app object/module/thingy with properties, methods, and events.

I want to map those to values in an OCaml module Electron.App because electron.app is "static": you get it once, and work with it, you never get several of them. So no use for an OCaml-side app type or functions that take them.
I'd rather call App.on_ready f than any object call or a weird Electron.App.on app "ready" f

So here's how I planned things:

module App : sig
  val is_packaged : bool [@@js.global "isPackaged"]
  val app_menu : Menu.t option [@@js.global "applicationMenu"]
  (* ... more stuff ... *)
  val quit : unit -> unit [@@js.global]
  val exit : ?exit_code:int -> unit -> unit [@@js.global]
end [@js.scope "electron.app"]

but the generated ml looks like

module App =
  struct
    let (is_packaged : bool) =
      Ojs.bool_of_js (Ojs.get Ojs.global "isPackaged")
    let (app_menu : Menu.t option) =
      Ojs.option_of_js Menu.t_of_js (Ojs.get Ojs.global "applicationMenu")
    (* ... more stuff ... *)
    let (quit : unit -> unit) =
      fun () ->
        ignore
          (Ojs.call (Ojs.get (Ojs.get Ojs.global "electron") "app") "quit"
             [||])
    let (exit : ?exit_code:int -> unit -> unit) =
      fun ?exit_code:x4 ->
        fun () ->
          ignore
            (let x7 = Ojs.get (Ojs.get Ojs.global "electron") "app" in
             Ojs.call (Ojs.get x7 "exit") "apply"
               [|x7;((let x5 = Ojs.new_obj (Ojs.get Ojs.global "Array") [||] in
                      (match x4 with
                       | Some x6 ->
                           ignore (Ojs.call x5 "push" [|(Ojs.int_to_js x6)|])
                       | None -> ());
                      x5))|])
  end

The "properties" do not scope to electron.app, they stay in global.
Any reason why?

I mean I can still scope them manually and put electron.app in the [@@js.global ...] attribute but it'd be nice to not have to do that, if I had a lot of properties to map like that

PS: I have that Menu module defined with just a type t, and electron is in global scope with an unsafe call to process.mainModule.require 🤷‍♂️

Enum mapping giving 'Sum types without js.* attribute not supported in this context'

I've suddenly run into issues with enums -

Given a file with one line containing type t = [`foo | `bar [@js 42] | `Baz] [@@js.enum], when I run ocamlfind gen_js_api/gen_js_api test.mli I get Error: Sum types without js.* attribute not supported in this context.

I'm pretty sure this was working in the not too distant past - I'm running with ocaml 4.07.0, opam2 and gen_js_api 1.0.5 - gen_js_api is compiling without error on everything else I have.

Polymorphic variant type declaration not supported

Hi,

I'm currently writing a binding using gen_js_api and it seems that this code doesn't compile :

type t = [`foo | `bar [@js 42] | `Baz] [@@js.enum]
(* Throws this exception : Sum types without js.* attribute not supported in this context. *)

(* While this compile *)
val foo : t -> ([ `a |  `b] [@js.enum]) -> ...

After looking at gen_js_api.ml, the exception is raised by get_variant_kind because attrs doesn't contain the attribute "js.enum" attached to t. In fact the attribute js.enum is added to global_attrs in gen_funs and get_variant_kind doesn't look inside.

The workaround I use for now is to match against the return value of parse_typ to see if it is a Variant, then in this case I manually add the attribute to it, but it's a bit of a hack.

| Some ty, Ptype_abstract ->                                                                                      
      let ty = match parse_typ ~global_attrs ty with                                                                
         | Variant { location; global_attrs; attributes; constrs } ->                                                
             Variant { location; global_attrs; attributes = p.ptype_attributes @ attributes; constrs }               
         | ty -> ty                                                                                                  
       in                                                                                                            
       lazy (js2ml_fun ty), lazy (ml2js_fun ty)

I hope I clearly described this issue,

Thank you!

Codegen change for functions named "get" and "set"; was this intentional?

I wrote an MLI that looked like this:

(** https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map *)
type ('a, 'b) t

val create : unit -> ('a, 'b) t [@@js.new "Map"]
val set : ('a, 'b) t -> 'a -> 'b -> unit
val get : ('a, 'b) t -> 'a -> 'b option
val delete : ('a, 'b) t -> 'a -> unit 

Where the functions get, set, and delete called the corresponding functions on the Map object.

Recently, after updating my version of gen_js_api, the code generation changed so that "get" and "set" were doing property accesses instead of function calls.

I found this PR #132, but there is no mention of a breaking change, nor can I find any documentation about how to get back to the original behavior.

Conversion functions not included in module signature

Input (.ml file):

include [%js:
module M: sig
  type t
end
]

Generates (simplified):

include (
  struct
    module M = struct
      type t = Ojs.t

      let rec (t_of_js : Ojs.t -> t) = fun x2 -> x2

      and (t_to_js : t -> Ojs.t) = fun x1 -> x1
    end
  end :
    sig
      module M : sig
        type t
      end
    end )

One has to include the conversion functions t_of_js and t_to_js manually on the signature, which is quite tedious. Maybe gen_js_api could include them as well there?

js.global with payload doesn't work with js.scope

The following input:

include [%js:
module T: sig
  val log: 'a -> unit [@@js.global "jsLog"]
end [@js.scope "jsT"]
]

errors out with Spurious js.* attribute.

I would expect it to behave the same as:

include [%js:
module T: sig
  val log: 'a -> unit [@@js.global] (* Note the absence of payload here *)
end [@js.scope "jsT"]
]

And generate the following code (for the original input):

module T = struct
  let (log : 'a -> unit) =
   fun x1 -> ignore (Ojs.call (Ojs.get Ojs.global "jsT") "jsLog" [|Obj.magic x1|])
end

Documentation integration

Having seen Seb's recent JS's Date bindings, they look great.

However something I feel is missing is a way of generating links to documentation so that ocamldoc'd mlis have direct links to the documentation of the bound function (see e.g. the documentation of functions in tgls, automatically generated, or tsdl, manually done with a lot of query-replace-regexp). These are small touches but they have a huge productivity impact when you are working with the binding.

Most of the time it is sufficient to be able to define an URI pattern where a variable is substituted by the bound method name (and the ability to override in case of exceptions). Now one of the problem is that you don't generate the mlis. If your eventual goal is to generate mli's from other descriptions then this could actually be done at that time, otherwise you might consider generating mli's aswell from the annotated mlis and provide support for automatically generating doc strings.

The other problem is to find a good and reliable source of documentation for the browser APIs, directly linking on the standards may be a little bit dry. I tend to use mozilla's developer documentation which has links to the standards and information about browser support. See e.g. the Date documentation.

Allow "Open" statement

Currently is not allowed to use the "open" statement in a signature parsed by gen_js_api.
I do not think there is a strong reason not to allow it, but I may be wrong.

Note that, currently, there's a work around:

[@@@js.stop]
open M
[@@@js.start]
[@@@js.implem open M]

[question] How to define mutually recursive types including both JS-able types and non-JS-able types (with custom `of_js` and `to_js`)?

Assume we want to define two types t and u with the following definitions:

[@@@js.stop]
type t = (* alias to some not JS-able type *)
val t_to_js: t -> Ojs.t
val t_of_js: Ojs.t -> t
[@@@js.start]
[@@@js.implem
  type t = (* alias to some not JS-able type *)
  let t_to_js x = (* some code *)
  let t_of_js x = (* some code *)
]
type u =
  | Unknown of Ojs.t [@js.default]
  | T of t [@js "t"]
  [@@js.union on_field "type"]

If t is an alias to some type that involves u, t and u becomes mutually recursive. But if the said type is not supported by gen_js_api (i.e. using poly variants without js.* attribute), things become difficult to make it compile.

Now neither of the followings work:

type 'a dummy

type u =
  | Unknown of Ojs.t [@js.default]
  | T of t [@js "t"]
  [@@js.union on_field "type"]

and t = [`U of u] dummy
(* Error: Sum types without js.* attribute not supported in this context *)
type 'a dummy

type u =
| Unknown of Ojs.t [@js.default]
| T of t [@js "t"]
[@@js.union on_field "type"]

[@@@js.stop]
and t = [`U of u] dummy (* Error: Syntax error *)
[@@@js.start]
[@@@js.implem
and t = [`U of u] dummy (* Error: Syntax error *)
]

The best possible workaround I was able to invent is:

type 'a dummy

[@@@js.stop]
type 'u tmp = [ `U of 'u ] dummy
val tmp_to_js: ('u -> Ojs.t) -> 'u tmp -> Ojs.t
val tmp_of_js: (Ojs.t -> 'u) -> Ojs.t -> 'u tmp
[@@@js.start]
[@@@js.implem
  type 'u tmp = [ `U of 'u ] dummy
  let tmp_to_js _ x = Obj.magic x
  let tmp_of_js _ x = Obj.magic x
]

type u =
| Unknown of Ojs.t [@js.default]
| T of t [@js "t"]
[@@js.union on_field "type"]

and t = u tmp

But this requires a bit too much of coding, loses the information of u when writing custom to_js/of_js functions, and introduces an unwanted type tmp to the scope.

Is there some way to solve this problem nicely? I think it would be great if an attribute (say js.ignore) exists to let the type definition accepted without being processed by gen_js_api so that the following compiles:

type 'a dummy

type u =
  | Unknown of Ojs.t [@js.default]
  | T of t [@js "t"]
  [@@js.union on_field "type"]

and t = [`U of u] dummy [@@js.ignore]

[@@@js.stop]
val t_to_js: t -> Ojs.t
val t_of_js: Ojs.t -> t
[@@@js.start]
[@@@js.implem
  let t_to_js x = (* some code *)
  let t_of_js x = (* some code *)
]

Calling a binding in a different package throws an Ojs_exn.Error(_)

Hi

I have one package containing a library of a small set of jquery bindings and a test directory. I have a second package containing a library in which there is a dependency on the first library.

The test in the first package can quite happily call Jquery.selector string in the library. If I make the same call from the second library in the second package then a Ojs_exn.Error(_) is thrown.

Both packages compile without error using a dune environment and gen_js_api 1.0.5. I can call non-binding functions in the first library from the second library without error - so the first library is visible to the second library.

There is also another binding in the second library. The test directory in the second package can also call the bindings in the second library without error, but, again, none of the bindings in the first library.

I've spent just over a day on this now and I've run out of ideas on how to fix this - can anyone think of any possible suggestions?

Thanks
Nick

Bindings with requires

Considering a javascript library containing a module A with only one static
function called foo taking a string and returning an int. This library
has to be required before usage, but I did not found anything about require in
the documentation.

The first implementation I made was:

module A : sig
  type t
  val require_a : unit -> t
    [@@js.custom let require_a () = Helpers.require "A" ]
  val foo : t -> string -> int
end

To call the function foo in ocaml code, I have to write:

let module_a = A.require_a () in
let i = A.foo module_a "bar" in
...

But I'd like to get rid of the require line. This require would be done lazily into the
module, allowing the user to write directly let i = A.foo "bar" to call the
function.

My first thought was to do something like:

module A : sig
  [@@@js.implem
    let mod_a = ref None
    let get_a () =
      match !mod_a with
      | Some m -> m
      | None ->
        let m = Helpers.require "A" in
        mode_a := Some m;
        m
  ]
  val foo : string -> int
  [@@js.custom
    let foo str =
      let m = get_a () in
      let r = Ojs.call m "foo" [|(Ojs.string_to_js str)|] in
      Ojs.int_of_js r ]
end

But it becomes very annoying to write this when the javascript library has many
methods. What would be the best way to do this kind of things ? Does it match
the philosophy of javascript bindings ?

Parametrized types not compiling

Just a quick question - I'm trying to do bindings for Promise and I'm having trouble in setting up parametrisation - starting out with the notes in TYPES.md:

type 'a t
val t_to_js: ('a -> Ojs.t) -> 'a t -> Ojs.t
val t_of_js: (Ojs.t -> 'a) -> Ojs.t -> 'a t

I then compile with:

ocamlfind gen_js_api/gen_js_api js_promise.mli

and get:

File "js_promise.mli", line 3, characters 23-25:
Error: Cannot parse type

It seems that it doesn't like the 'a. Is there a work-around for this?

Module alias

Hi !

Sorry if I'm missing something but, is there a way to do module Foo = Lib_foo in .mli files used to bind a javascript lib ?
Actually gen_js_api throws an error, maybe it's a wanted behavior ?

Thanks

Build fails with ocaml 4.05-rc1

When I build gen_js_api with ocaml 4.05 rc1, I get this error:

ocamlc -w +A-4-41-45 -warn-error +8 -I +compiler-libs -o gen_js_api ocamlcommon.cma gen_js_api.mli gen_js_api.ml
File "gen_js_api.ml", line 536, characters 49-60:
Error: This expression has type string Asttypes.loc = string Location.loc
       but an expression was expected of type string
make[1]: *** [Makefile:14: all] Error 2
$ ocaml --version
The OCaml toplevel, version 4.05.0+rc1

Error: Unresolved internal primitive: %caml_js_opt_meth_call

I tried to implement a minimal "hello world" example using gen_js_api: https://github.com/Armael/gen_js_api_helloworld . However, make fails at the last step (the js_of_ocaml invocation) with:

js_of_ocaml --pretty -o test.js +gen_js_api/ojs_runtime.js test.byte
js_of_ocaml: Error: Unresolved internal primitive: %caml_js_opt_meth_call
make: *** [Makefile:7: all] Error 1

Am I doing something wrong? If it's a js_of_ocaml bug, it's a bit strange that people seem to manage usin gen_js_api on non-trivial examples, but I can't make it work on a trivial one...

[@@js.new] can infer empty class names

If there's no suffix to the function name (so it's simply called new_) and there's no explicit name given to [@@js.new], the class name seems to be infered as "".

For instace this:

 val new_ : ArrayBuffer.t -> t [@@js.new]

Generates:

    function new$0(x9)
     {var _c0_=caml_call1(ArrayBuffer[2],x9);return new (Ojs[16].)(_c0_)}

Which produces an invalid syntax error when run with node. It would be nice if the compilation failed with an error instead.

this

Trying to define this and can't quite work it out. With a javascript callback -

myFunction.on("mouseover", function (d) {
  myObject.select(this);
})

In my gen_js_api environment I have all my functions nicely typed and so to do a select(this) I have to perform a _to_js on myObject and then an Ojs.call on "select". Is there a more elegant way of doing this? It would be great to do something like val select: my_object_type this_type ....

Perhaps [@@js.call "select"] implicitly references this?

Request: Create an ocaml string from a javascript array

This is either a feature request or a documentation request...

js_of_ocaml's typed_array.ml contains the following:

module String = struct
  external of_uint8Array : uint8Array Js.t -> string = "caml_string_of_array"
  let of_arrayBuffer ab =
    let uint8 = new%js uint8Array_fromBuffer ab in
    of_uint8Array uint8
end

which is calling caml_string_of_array from jsoo's runtime/mlString.js to create an ocaml string backed by a javascript array.

Is there any way to do the same thing via gen_js_api?

Release 4.06-compatible version to OPAM

The current OPAM version of gen_js_api requires ocaml-version < "4.06.0". However, the repository itself has been patched to support OCaml 4.06. An update to the OPAM repository would save 4.06 users from having to pin this repository.

Recursive type definitions generate invalid `let-rec` definitions

Consider the following code:

module Issue142 = [%js:
  type t = [`Foo [@js 42]] [@js.enum]
  and u = t
]

This would generate the following implementation:

module Issue142 : sig type t = [ `Foo ]
                      and u = t end =
  ((struct
      [@@@js.dummy "!! This code has been generated by gen_js_api !!"]
      [@@@ocaml.warning "-7-32-39"]
      type t = [ `Foo ]
      and u = t
      let rec t_of_js : Ojs.t -> t =
        fun (x40 : Ojs.t) ->
          let x41 = x40 in
          match Ojs.int_of_js x41 with | 42 -> `Foo | _ -> assert false
      and t_to_js : t -> Ojs.t =
        fun (x39 : [ `Foo ]) -> match x39 with | `Foo -> Ojs.int_to_js 42
      and u_of_js : Ojs.t -> u = t_of_js
      and u_to_js : u -> Ojs.t = t_to_js
    end)[@merlin.hide ]) 

but this fails with an error Error: This kind of expression is not allowed as right-hand side of `let rec' because the right hand side of u_of_js is t_of_js, which is bound in the same group of let rec definitions.

The external function `caml_ojs_new_arr' is not available

Hello,

I get an error The external function caml_ojs_new_arr' is not available` with this .mli:

class _lat_lng : Ojs.t ->
  object
    inherit Ojs.obj
  end
class lat_lng: lat:float -> lng:float -> ?no_wrap:bool -> unit -> _lat_lng
% ocamlfind gen_js_api/gen_js_api googleMaps.mli
% sh build_examples.sh
+ ocamlfind ocamlc -c -safe-string -w A -w -E -package js_of_ocaml.ppx
-package js_of_ocaml -package gen_js_api -package lwt.ppx -I examples
-o examples/main.cmo examples/main.ml
File "examples/main.ml", line 6, characters 6-13:
Warning 26: unused variable element.
+ ocamlfind ocamlc -linkpkg -package js_of_ocaml.ppx -package js_of_ocaml
-package gen_js_api -package lwt.ppx googleMaps.cmo examples/main.cmo
-o examples/main.byte
File "_none_", line 1:
Error: Error while linking googleMaps.cmo:
The external function `caml_ojs_new_arr' is not available
Command exited with code 2.

If i replace the last line of the .mli file with this one, it's ok:

class lat_lng: float -> float -> bool option -> _lat_lng
% ocamlfind gen_js_api/gen_js_api googleMaps.mli
% sh build_examples.sh
+ ocamlfind ocamlc -c -safe-string -w A -w -E -package js_of_ocaml.ppx
-package js_of_ocaml -package gen_js_api -package lwt.ppx -I examples
-o examples/main.cmo examples/main.ml
File "examples/main.ml", line 6, characters 6-13:
Warning 26: unused variable element.
Finished, 7 targets (0 cached) in 00:00:00.
There are some missing primitives
Dummy implementations (raising 'Failure' exception) will be used if they are not
available at runtime. You can prevent the generation of dummy implementations
with the commandline option '--disable genprim'
Missing primitives:
  caml_hexstring_of_float

compiling with gen_js_api gives warning 58

When building bindings, we get these for each compiled file.

make build
dune build @install
File "_none_", line 1:
Warning 58: no cmx file was found in path for module Ojs, and its interface was not compiled with -opaque
File "_none_", line 1:
Warning 58: no cmx file was found in path for module Ojs, and its interface was not compiled with -opaque

== version info ==

<><> gen_js_api: information on all versions ><><><><><><><><><><><><><><><><><>
name                   gen_js_api
all-installed-versions 1.0.5
all-versions           1.0  1.0.1  1.0.2  1.0.3  1.0.4  1.0.5

<><> Version-specific details <><><><><><><><><><><><><><><><><><><><><><><><><>
version      1.0.5
repository   default
pin          git+ssh://[email protected]/LexiFi/gen_js_api
source-hash  7bd36e7d

How to use js_of_ocaml types?

For instance, when the first parameter of a JS constructor is a DOM element, what is the way to use the jsoo type Dom_html.element for this parameter?

Trying something like:

class foo : div:Dom_html.element -> _foo [@@js.new "..."]

gives

Error: Unbound value Dom_html.element_to_js

Is there any example available for this kind of binding? Thanks.

Planning to change the semantic of MlBytes.toString in js_of_ocaml

See ocsigen/js_of_ocaml#997.

In practice, it means that caml_js_get, caml_js_set, caml_js_delete will behave differently if called with non ascii strings.

Here are possible moves

  • Don't do anything, and accept the change in behavior
  • Implement Ojs.get, Ojs.set, Ojs.delete with a wrapper
let get o s = get o (string_to_js s)
  • Js_of_ocaml provides new stubs & optimization (caml_js_get_string_key, caml_js_set_string_key, caml_js_delete_string_key)

Array and list mapping question

When mapping a JS array to OCaml array with gen_js_api, a new object is instantiated:

  let n = int_of_js (get objs "length") in
  Array.init (n - start) (fun i -> f (array_get objs (start + i)))

A new object is also created when mapping OCaml array to JS:

let array_make n = new_obj (get global "Array") [|int_to_js n|]

let array_to_js f arr =
  let n = Array.length arr in
  let a = array_make n in
  for i = 0 to n - 1 do
    array_set a i (f arr.(i))
  done;
  a

I want to map a JS object (which has fields of type 'array') to OCaml:

module Dataset : sig
   (* JS object *)
    type t
   
    val data : t -> float array
    val set_data : t -> float array -> unit

   (* Some other bindings here *)

    val make : ?data:float array ->
               (* Some other bindings here *)
               unit ->
               t [@@js.builder]

Javascript object t is used by an external JS library to render data on a chart. When the data array is mutated, the chart is re-rendered.
The problem here is that when I do such a mapping, a new data array is created, and all changes are made with a new object, not the original one. Original array stay untouched. Mutating this new array causes no re-render of a chart because it is not a part of t object. To force re-render, I should not only mutate the new array, but also set this new array to t object via set data function. This seems like a great overhead.
What is the best way to handle this problem?
Is there any way to make "direct" mapping between JS and OCaml arrays without new allocations?

Problem using sum types

Hi !

I have been using gen_js_api for days and it works like a charm ! However, I am encountering a problem with sum types. Indeed, I created a file try.mli containing :
type t = | Foo [@js "foo"] | Bar [@js 42] | Baz [@@js.enum]
(It's the example given in the documentation)

When I compile it (with all the commands given), the command
ocamlfind ocamlc -c -package gen_js_api.ppx try.ml returns the error :
File "try.ml", line 4, characters 10-12: Error: Spurious js.* attribute

In fact, the .ml file still contains the annotations [@js "foo"], [@js 42] and [@@js.enum]. If I remove then from the .ml file, then it compiles and works well. So is it normal to still have the annotations in the .ml file ?

Thanks

Parametrized types in mli files

I am trying to use mli files with gen_js_api to write a binding to a JS function that has type

val foo: 'a array -> unit

With raw externals, one can use parametrized types as input, but with gen_js_api there's the error Error: Cannot parse type.

I tried to use Ojs as well, but because in this case there's no "creation function", I don't think it's possible, unless one writes converters for every potential type that will fill 'a, which is not convenient.

Is there a way to achieve this using an interface file?

Related to #77.

[@@js.enum] vs [@js.enum] with polymorphic variant.

I'd like to write the following ( [@@js.enum] )

module String_or_number' : sig
  type t =
    [ `string of string [@js.default]
    | `Int of int    [@js.default]
    ]
  [@@js.enum]

  val t_to_js : t -> Ojs.t
  val t_of_js : Ojs.t -> t
end =
  [%js]

It fails with
Error: Sum types without js.* attribute not supported in this context

The way to do it is currently ([@js.enum])

module String_or_number' : sig
  type t =
    [ `string of string [@js.default]
    | `Int of int    [@js.default]
    ]
  [@js.enum]

  val t_to_js : t -> Ojs.t
  val t_of_js : Ojs.t -> t
end =
  [%js]

It would be nice to support the first version.

Apostrophe breaks function name derivation.

Consider the following signature:

module Date : sig get_day' : t -> int end

Using the function after deriving an implementation with gen_js_api results in an ugly runtime error. I suspect that the apostrophe finds a way into the javascript world.

Change Ojs_exn to Ojs.Exn

It's recommended that every library has a single toplevel module, and (wrapped false) is only there to help people transition to dune. Would it be ok to break compatibility and to switch to Ojs.Exn? If not, then perhaps we could add something to dune to help the transition from (wrapped false) to (wrapped true). We already have one such mechanism (wrapped (transition "message")) but it doesn't quite work in some cases (like this one)

4.08 support

Currently fails with:

$ make
make -C src all
make[1]: Entering directory '/home/fabian/ocaml/not-mine/gen_js_api/src'
ocamlc -w +A-4-41-45 -warn-error +8 -c ojs.mli ojs.ml
ocamlc -w +A-4-41-45 -warn-error +8 -c ojs_exn.mli ojs_exn.ml
ocamlc -w +A-4-41-45 -warn-error +8 -a -o gen_js_api.cma ojs.cmo ojs_exn.cmo
ocamlc -w +A-4-41-45 -warn-error +8 -I +compiler-libs -o gen_js_api ocamlcommon.cma gen_js_api.mli gen_js_api.ml
File "gen_js_api.ml", line 156, characters 76-79:
156 |       | Error (loc, err) -> Some (Location.error_of_printer loc print_error err)
                                                                                  ^^^
Error: The function applied to this argument has type
         ?loc:Location.t -> ?sub:Location.msg list -> Location.report
This argument cannot be applied without label

calling new on a dynamic value

Is it possible to call new on a constructor that is passed as an argument? Sometimes libraries have nested constructors that are only reachable after creating another object, and libraries loaded with "require" would not expose a global path to the constructor.

PPX Syntax in MLI

Hello,

I meet a problem when generating an .ml file with my .mli
When implementing a function containing ppx syntax token with [@@js.custom], I get an error compiling the .ml file. Here an example.
In my mli file, I have:

[@@js.custom
  let foo str =
    (Js.Unsafe.global##.myfield := str)
]

The generated ml file interprets the ##. as an infix operator, so it writes in the ml file :

(##. Js.Unsafe.global myfield) := str

Then, I get a syntax error when compiling.
Is there a way to use other packages ppx syntax within the mli file?

Bindings to JavaScript functions that expect value undefined

There are JavaScript functions that expect the value undefined to be passed to them, not null. gen_js_api doesn't provide (as far as I know) a way to do this, as option types get converted to null.

I added this to the bindings I am working on:

[@@@js.stop]
type 'a option_undefined = 'a option
[@@@js.start]
[@@@js.implem
type 'a option_undefined = 'a option
external equals: Ojs.t -> Ojs.t -> bool = "caml_js_equals"
external pure_js_expr: string -> Ojs.t = "caml_pure_js_expr"
let undefined = pure_js_expr "undefined"
let option_undefined_of_js f x =
  if equals x undefined then None
  else Some (f x)

let option_undefined_to_js f = function
  | Some x -> f x
  | None -> undefined
]

But I wonder if this type option_undefined could be something built in as part of gen_js_api? Or maybe, as an annotation to type option, instead of a full new built-in type.

js.* attributes on val declarations fail to compile in 1.0.7

For vscode-ocaml-platform, we used [@@js.*] attributes on val declarations in implementation files that are described in PPX.md.

After trying to update to 1.0.7, the val declarations stopped compiling. The example in PPX.md has the following error:

1 | val alert_bool : bool -> unit [@@js.global "alert"]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: Value declarations are only allowed in signatures

In 1.0.6, this compiled without errors. As a workaround, each block of val declarations can be wrapped in include [%js: ... ], but that makes the code much messier when mixed with normal OCaml functions.

Would recursive modules be difficult (if possible) to implement?

I have something like this…

module rec Contact : sig
   type t
   val get_contact_groups: t -> ContactGroup.t list [@@js.call]
end

and ContactGroup : sig
   type t
   val get_contacts: t -> Contact.t list [@@js.call]
end

I solved the problem by getting type Contact.t out of Contact module.
But I do not know if this solution will always be enough?

Best regards

4.09 & 4.10 support

Currently:

% make
make -C src all
make[1]: Entering directory '/tmp/gen_js_api.1.0.5/src'
ocamlc -w +A-4-41-45 -warn-error +8 -c ojs.mli ojs.ml
ocamlc -w +A-4-41-45 -warn-error +8 -c ojs_exn.mli ojs_exn.ml
ocamlc -w +A-4-41-45 -warn-error +8 -a -o gen_js_api.cma ojs.cmo ojs_exn.cmo
ocamlc -w +A-4-41-45 -warn-error +8 -I +compiler-libs -o gen_js_api ocamlcommon.cma gen_js_api.mli gen_js_api.ml
File "gen_js_api.ml", line 156, characters 76-79:
156 |       | Error (loc, err) -> Some (Location.error_of_printer loc print_error err)
                                                                                  ^^^
Error: The function applied to this argument has type
         ?loc:Location.t -> ?sub:Location.msg list -> Location.report
This argument cannot be applied without label

including modules

I'm having trouble in trying to include modules:

type event_phase_type = Capturing_phase [@js 1] | At_target [@js 2] | Bubbling_phase [@js 3] [@@js.enum]

module Event : sig
  type t = private Ojs.t
  val bubbles: t -> bool
  val cancelable: t -> bool
  val current_target: t -> Ojs.t
  val event_phase: t -> event_phase_type
  val prevent_default: t -> unit
  val stop_immediate_propagation: t -> unit
  val stop_propagation: t -> unit
  val target: t -> Ojs.t
  val time_stamp: t -> float
  val type_: t -> string
end

module MouseEvent : sig
  include Event
  type t = Event.t
  val alt_key: t -> bool
  val button: t -> int
  val client_x: t -> int
  val client_y: t -> int
  val ctrl_key: t -> bool
  val detail: t -> int
  val meta_key: t -> bool
  val related_target: t -> Ojs.t
  val screen_x: t -> int
  val screen_y: t -> int
  val shift_key: t -> bool
  val which: t -> int
end

I get a Error: Cannot parse signature item at 'include Event'. I've played around with [%js] without a glimmer of success. Is there a solution for this?

Allow to run ppxlib deriving and gen_js_api ppx together

One currently cannot use ppxlib deriving together with gen_js_api.
In particular, running the code bellow will fail with Error: Cannot parse signature item

module M : sig 
  type t = { a : int; b : string } [@@deriving sexp]
  val t_of_js : Ojs.t -> t
  val t_to_js : t -> Ojs.t
end = [%js]

The reason is that ppxlib will expand deriving first and then run the gen_js_api mapper, at which point [@@deriving sexp] has generated item that gen_js_api doesn't understand (e.g. include).

One could imagine the following steps instead:

  1. expand gen_js_api first
module M : sig 
  type t = { a : int; b : string } [@@deriving sexp]
  val t_of_js : Ojs.t -> t
  val t_to_js : t -> Ojs.t
end = struct
  type t = { a : int; b : string } [@@deriving sexp]
  let t_of_js = ....
  let t_to_js = ....
end
  1. then apply deriving
module M : sig 
  type t = { a : int; b : string }
  include sig
    [@@@ocaml.warning "-333"]
     val t_of_sexp : sexp -> t
     val sexp_of_t : t -> sexp
  end 
  val t_of_js : Ojs.t -> t
  val t_to_js : t -> Ojs.t
end = struct
  type t = { a : int; b : string }
  let t_of_sexp = .....
  let sexp_of_t = .....
  let t_of_js = ....
  let t_to_js = ....
end

Talking with @jeremiedimino, there are two approaches to explore:

  • making gen_js_api register a Context_free.Rule.t instead of whole AST mapper. That might require changing the syntax a little
  • allow to specify some kind of ordering between AST mappers

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.