Name: goji
Owner: Ocsigen
Description: An OCaml bindings generator for JavaScript libraries
Forked from: klakplok/goji
Created: 2017-12-22 16:34:06.0
Updated: 2017-12-22 16:34:07.0
Pushed: 2014-07-26 11:07:04.0
Homepage: null
Size: 430
Language: OCaml
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
Goji is a multi-tool for OCaml-JavaScript interoperability.
For now, its is able to:
Its main features are:
Some sample bindings are available at: https://github.com/klakplok/goji-bindings
goji
command and two
OCamlFind packages: goji_lib
and goji_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.goji_lib
package.
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.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 using
Goji.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
to goji
. You are free to architecture your binding as you wish,
in particular, you do not have to respect the structure of the
underlying library.Goji.License
contains predefined
licenses, and you can define your own. You can also explain how to
obtain the sources of the JavaScript library by using
Goji.Grab
. This is explained in a dedicated section below.Goji.AST.binding
). You can define sub-modules,
types and values as described in a following section.goji_lib
package, you can feed them to the goji
generate
command. By default, this will produce a folder for each
of the packages you registered, with the generated sources, METAs
and Makefiles inside. You can call goji generate --help
for more
options.You have two options to build binding descriptions.
Goji.AST
module. 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.Goji.DSL
module 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.
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.
Structure
AST node, which produces a submodule. The Section
node simply adds a documentation header to a series of
bindings. The Group
constructor 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.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:
The second task is done by attaching two convertion functions to the type:
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.value
is the top level part of the description. It
describes the structure of the OCaml type, for instance Tuple
, or
Variant
. The leaves are described using the Value
case, which
associates a mapping
to a storage
to describe the conversion of
a single JavaScript value.Goji.AST.storage
gives 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 instance, Field ("x", Field ("b", Var
"root"))
. is equivalent to JavaScript root.b.x
. The elements of
the JavaScript context are accessed through the Var
construct. In
a type definition context, only the "root"
Goji variable is
defined. The DSL provides the primitives field
, var
and root
so the previous example could br written field root "x"
or
field (var "root") "x"
.Goji.AST.mapping
explains how to convert the JavaScript
value. Predefined cases (such as Int
, int
in the DSL) are given
for basic types, are mapped to their native equivalents in both
languages. This means in particular that string are passed by copy
by default. For composite objects such as arrays, a value
has to
be provided to describe how each element has to be converted (for
instance you can write Array (Value (Int, Var "root"))
for an
array of integers, array int
using the DSL). Be aware that in
this sub-description, the "root"
variable is hidden and now
describes the "root"
of the element being converted, not the root
of the collection.Here are some example lenses and their meaning:
Value (Float, Var "root")
means that an OCaml float is converted
to a JavaScript Number.Value (Int, Field (Var "root", "x"))
means that the value is an
integer and is located in the "x"
field of the JavaScript
value. If such an expression is used in a type definition, it means
that a value 1234
in OCaml will be converted as an object { x:
1234 }
in JavaScript, and vice versa.Tuple [ Value (Int, Field (Var "root", "x")) ; Value (Int, Field (Var "root", "y")) ]
maps a pair (12, 34)
of integers to a JavaScript object { x: 12, y: 34 }
.Writing lenses in the DSL is slightly different from writing their
AST, using a little trick to increase conciseness. The DSL only
provides primitives of types value
and storage
, plus an infix
notation @@
that we call a rerooting operator.
value
, which contains one or more
occurences of Var "root"
.storage
, by which all aforementionned
occurences will be replaced.For each basic mapping
(e.g. Int
), the DSL provides a value
of
the same (lowercased) name describing a single value of this type
(e.g. val int : value = Value (Int, Var "root")
).
This way, the same keyword (e.g. int
) can be used to describe a
standalone value
, or on the left of an @@
for its mapping
part
(e.g. int @@ field root "x"
).
The @@
operator can actually take any value
as its left operand, making
some descriptions simpler and / or more concise. For instance, the following
tuple [ float @@ field (field root "pos") "x" ; float @@ field (field root "pos") "y" ]
could be alternatively written
tuple [ float @@ field root "x" ; float @@ field root "y" ] @@ field root "pos"
producing the same AST.
TODO
For the user, handling of external JavaScript sources basically
works as follows. When compiling a program using bindings a
b
and
c
, by simply using goji jslink a b c -o mylibs.js
, the programmer
obtains a single JavaScript entry point mylibs.js
to refer to in its
HTML source code.
For this to work, binding authors have to explain how to grab
library sources in binding descriptions. This is done by writing a
script using primitives from the Goji.Grab
module which will
download / unpack the sources and put them in a specific form, as
explained just after. Such a script can be provided for every
registered component using the ~grab
parameter.
At generation time, goji runs these scripts and packs the resulting
files in an archive inside the OCamlFind directory. The jslink
command uses OCamlFind to locate these archives, before unpacking them
and merging their contents.
Scripts have to respect the following rules:
goji_entry.js
is present, all the files are
archived and stored in the OCamlFind package directory.At jslink
time, all archives are extracted into the current
directory (unless the option is passed). An exception is made for
goji_entry.js
files, which are not extracted directly but
concatenated into the file specified with -o
.
N.B. It is up to the script to ensure that there is no filename clash between libraries, for instance by using subdirectories. This is a bit of a shame, but there is no simple and uniform way to handle modularity in JavaScript, so this task has to be done manually.