Goji
An OCaml bindings generator for JavaScript libraries
Install / Use
/learn @klakplok/GojiREADME
Goji: OCaml-JavaScript bindings generator
Goji is a multi-tool for OCaml-JavaScript interoperability.
For now, its is able to:
- Generate bindings from high level descriptions
- Grab JavaScript dependencies and include them in you application code
Its main features are:
- An OCaml-centric approach, including the possibility to map complex data types and to use OCaml's optional and labeled arguments
- Concise ans high level descriptions based on lenses
- Meta-generation of binding descriptions (built as an embedded DSL)
- Customizable generation scheme, in particular to handle events either by hand or with your concurrency library of choice
- Some static checking and optional insertion of dynamic cheks for debugging purposes
- Automated handling of JavaScript dependencies
Some sample bindings are available at: https://github.com/klakplok/goji-bindings
HOWTO start writing your first library binding
- Build and install Goji. This installs a
gojicommand and two OCamlFind packages:goji_libandgoji_runtime. Among others, Goji depends on the pprint package. The pprint package is available in opam (http://opam.ocaml.org/). However, Goji does not build with the latest version (see https://github.com/klakplok/goji/issues/1). As a workaround, you can pin pprint to the version 20130324 like that opam pin pprint 20130324. - Bindings descriptions must be defined in OCaml source files, using
binding description pimitives provided by the
goji_libpackage. In other words, you use predefined OCaml functions and types to build an intermediate representation of your binding. Goji then takes this intermediate representation to generate your binding as new OCaml source files (along with METAs, Makefiles, etc.). This is similar to CamlIDL, and different from ocaml-ctypes. So open up a new OCaml source file in which to write your description, for instance with the same name as the library you want to bind. - The first step is to declare a package for your library using
Goji.register_package, providing its version and a short documentation. Once done, you can start building the architecture of your library by adding top level modules usingGoji.register_component. If you have a lot of toplevel modules, you can define them in separate OCaml source files for convenience, but you'll have to be careful abour their order when feeding them togoji. You are free to architecture your binding as you wish, in particular, you do not have to respect the structure of the underlying library. - For each component, you have to define meta-information. Some are
optional, such as your name or OCamlFind dependencies. Some are
required, such as the license. This is required because most of the
time, you will include some parts of the original library, in
particular in documentation strings, and in such case, you have to
respect the original license.
Goji.Licensecontains predefined licenses, and you can define your own. You can also explain how to obtain the sources of the JavaScript library by usingGoji.Grab. This is explained in a dedicated section below. - Describe the internal structure as a list of toplevel binding
elements (type
Goji.AST.binding). You can define sub-modules, types and values as described in a following section. - When you are ready and that your descriptions successfully compile
against the
goji_libpackage, you can feed them to thegoji generatecommand. By default, this will produce a folder for each of the packages you registered, with the generated sources, METAs and Makefiles inside. You can callgoji generate --helpfor more options.
Writing binding descriptions using the AST or the DSL
You have two options to build binding descriptions.
- Directly write values from Goji's intermediate representation (AST
nodes), by calling the constructors defined by the various types of
the
Goji.ASTmodule. The AST is made public, documented and should be fairly stable. Even if you don't use it directly, it is a good idea to browse its definition for understanding how descriptions are structured. - The AST has been designed for being actually writable by hand, but
since it encode features only needed by complex bindings, writing
simple descriptions can be a little verbose or confusing. For this,
the
Goji.DSLmodule defines functions that correspond to AST constructors but with some parameters made optional. For instance, documentation can be passed with~doc:"..."or omitted (but should not).
Apart from providing basic constructor functions, the DSL also defines more high level functions which generate complex AST parts for common non trivial bindings constructs (such as binding an enum to a sum type). The idea is to use OCaml as the meta language to encode custom binding constructs as OCaml functions that produce AST parts or combine other DSL constructs.
If you write new DSL constructs which seem useful outside of your specific binding, don't hesitate to ask for their integration.
Top level binding description
The Goji.register_component takes a list of Goji.AST.binding
elements. This list contains descriptions of the top level elements of
the generated OCaml module.
- Type definitions explain how an OCaml data type is mapped to a JavaScript data structure, as explained in the following section. It produces an OCaml type definition and two conversion functions and two converters for internal use (these functions appear in the signature for cross-library interoperability, but are omitted for the OCamlDoc).
- Function definitions explain how to map an OCaml function to some JavaScript primitive (not mandatorily a JavaScript function call). A later section is dedicated to this topic.
- Some object oriented features such as inheritance between types and method definitions are also available. By default, these do not produce object oriented code. Inheritance produces explicit corecion functions and methods produce functions to whom the object is passed as parameter. An object oriented back-end is planned in which an abstract type, associated methods and inheritance declarations are actually mapped to an OCaml class. It is thus a good idea not to forget to these primitives.
- Local exceptions can be declared, so they can be raised either when a JavaScript exception is thrown in an external code or when some value cannot be converted (see the section on guards).
- The structure of the OCaml code can be defined by using the
StructureAST node, which produces a submodule. TheSectionnode simply adds a documentation header to a series of bindings. TheGroupconstructor is on the other hand completely transparent, its content is flattened into its parent. This is useful for writing macros that have to produce several AST nodes but must only return one.
Describing data mappings
In order for the library user to see only OCaml values, values have to
be converted back and forth between their OCaml and JavaScript
representations. For simple types, Goji has predefined construct
defined by the type Goji.AST.mapping. When converting values of
complex, structured types, the binding has to explain the mappings
between the elements of the OCaml structure and those of the
JavaScript one, though the type Goji.AST.value. This can often be
done inline (for instance when describing the return value of a
JavaScript function), or in two steps by using Goji's type definition
construct and then by to refering this definition by name. This second
option is also the only possibility when mapping records or variants.
The goal of a type definition is twofold:
- Produce an OCaml type definition / abbreviation which helps having a clean and documented interface.
- Explain how a value of this type is converted to a JavaScript value.
The second task is done by attaching two convertion functions to the type:
- The injector takes an OCaml value and converts it to JavaScript. In the case of type definitions, the result is a single JavaScript value, but in other contexts, various effects can be performed in the JavaScript world (for instance assigning function parameters or globals), hence the name. We say that the function injects an OCaml value in the JavaScript context.
- The extractor performs the opposite conversion: it extracts an OCaml value from the JavaScript context. In the case of a type definition, this context is actually a single JavaScript value.
Conversion lenses
The conversion functions are automatically generated from a single declarative description of the relations between the OCaml type definition and the JavaScript structure. These definitions are OCaml oriented, consistently with the rest of Goji, and are naturally read as extractions. However, they are actually reversible and one definition is enough to generate both converters (and can be seen as a dedicated kind of lenses).
A lens is described using the following three AST node types.
Goji.AST.valueis the top level part of the description. It describes the structure of the OCaml type, for instanceTuple, orVariant. The leaves are described using theValuecase, which associates amappingto astorageto describe the conversion of a single JavaScript value.- The
Goji.AST.storagegives the location of the JavaScript value. It is basically a path inside the JavaScript context, and in the case of a type definition, a path from the root of the JavaScript value. For insta
Related Skills
node-connect
345.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
104.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
