#lang scribble/manual
@(require (for-label (planet dyoo/js-vm/ffi/ffi))
unstable/scribble)
@title[#:tag "ffi"]{Racket to Javascript FFI}
@defmodule/this-package[ffi/ffi]
This API provides a foriegn function interface that allows javascript to be
accessed directly and explicitly through @racketmodname[racket]. This allows
access to native javascript functionality and allows external javascript APIs
to be used in @racketmodname[racket] code without modifying the source code of
the runtime infrastructure that evaluates the code.
The contents of this module need to run in a Javascript context.
@;-----------------------------------------------------------------------------
@section{Type Conversions}
These procedures are designed to convert between @racketmodname[racket] and
javascript types.
@defproc[(scheme->prim-js
[v (or/c boolean? char? real? string? symbol? vector?)])
js-value?]{
Converts a @racketmodname[racket] value to a javascript value using the
following rules:
@itemize[
@item{When @racket[v] is a boolean, string, or inexact number,
@racket[scheme->prim-js] converts directly to the javascript boolean, string,
or number with the same value.}
@item{When @racket[v] is an exact number, @racket[scheme->prim-js] returns a
floating point number equivalent to @racket[(scheme->prim-js (exact->inexact
v))] if @racket[v] is in [-9e15, 9e15]. Otherwise it will raise an
@racket[exn:fail:contract] exception.}
@item{When @racket[v] is a char, @racket[scheme->prim-js] returns a
on-character javascript string corresponding to
@racket[(scheme->prim-js (string v))].}
@item{When @racket[v] is a symbol, @racket[scheme->prim-js] returns a
javascript string corresponding to @racket[(scheme->prim-js (symbol->string
v))].}
@item{When @racket[v] is a vector, @racket[scheme->prim-js] returns a
javascript array whose elements are @racket[eq?] to the elements of the
vector.}
]}
The @racket[scheme->prim-js] procedure cannot convert a procedure because
@racketmodname[racket] procedures cannot be converted to javascript in a
straightforward manner. When @racketmodname[racket] procedures are evaluated,
they are evaluated in continuation passing style, and hence any javascript
procedure constructed from a @racketmodname[racket] procedure must either be in
CPS or must return void.
@defproc[(procedure->cps-js-fun [proc procedure?]) js-function?]{
Converts a @racketmodname[racket] procedure to a javascript function. The
javascript function returned is in continuation passing style and takes a
continuation as its first argument. When called, the javascript function passes
the return value of @racket[proc] applied to all arguments but the first to the
function's first argument.}
@defproc[(procedure->void-js-fun [proc procedure?]) js-function?]{
Converts a @racketmodname[racket] procedure to a javascript function. The
javascript function will apply @racket[proc] to its arguments, and then ignore
@racket[proc]'s return value and return undefined (javascript's version of
returning void).}
@defproc[(prim-js->scheme [v js-value?])
(or/c boolean? inexact-real? string? vector?)]{
Converts a javascript value to its corresponding @racketmodname[racket] value.
@itemize[
@item{When @racket[v] is a javascript boolean or string,
@racket[prim-js->scheme] returns the @racketmodname[racket] equivalent of
that value.}
@item{When @racket[v] is a number, @racket[prim-js->scheme] returns an inexact
number with the same value as the javascript number.}
@item{When @racket[v] is an array, @racket[prim-js->scheme] returns a vector
whose elements are @racket[eq?] to the elements of @racket[v].}
]
If @racket[v] is not an array, boolean, function, number, or string,
@racket[prim-js->scheme] will raise an @racket[exn:fail:contract] exception.}
@;-----------------------------------------------------------------------------
@section{Checking Javascript Types and Values}
The following procedures are useful for checking the values and types of
javascript values.
@defproc[(js-value? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a javascript value, @racket[#f]
otherwise.}
@defproc[(js-object? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a javascript function, @racket[#f]
otherwise.}
@defproc[(js-function? [x any/c]) boolean?]{
Returns @racket[#t] if @racket[x] is a javascript object, @racket[#f]
otherwise.}
@defproc[(js-=== [v1 js-value?] [v2 js-value?]) boolean?]{
Returns @racket[#t] if @racket[v1] and @racket[v2] are equal according to
javascript's @racketidfont{===}. Otherwise returns @racket[#f].}
@defproc[(js-typeof [v js-value?]) string?]{
Returns a string representing the javascript type of @racket[v]. The string is
obtained by calling javascript's @racketidfont{typeof} on @racket[v].}
@defproc[(js-instanceof [v js-value?] [type js-function?]) boolean?]{
Returns @racket[#t] if @racket[v] is an instance of the javascript type
@racket[type] and @racket[#f] otherwise.}
@defproc[(js-null? [v js-value?]) boolean?]{
Produces true if v is null.
}
@defproc[(js-undefined? [v js-value?]) boolean?]{
Produces true if v is undefined.
}
@;-----------------------------------------------------------------------------
@section{Accessing Javascript Values}
The following procedures are useful for accessing javascript values from
external APIs as well as constructing new values to pass to other javascript
functions.
@defproc[(js-get-global-value [name string?]) js-value?]{
Interprets @racket[name] as an identifier, and returns the javascript value
bound to that identifier in the global scope. If no such value exists, then a
js-value containing javascript's @racketidfont{undefined} is returned.
So, for example, @racket[(js-get-global-value "setTimeout")] would return
javascript's @racketidfont{setTimeout} procedure.}
@defproc[(js-get-field [obj js-value?] [selector string?] ...+) js-value?]{
Begins by accessing the field of @racket[obj] named by the first
@racket[selector], then accesses the field named by the second
@racket[selector] of that object, and so-on until there are no more
@racket[selector]s.
If at any point this action would result in accessing a field of a
@racketmodname[racket] value or javascript's @racketidfont{undefined},
@racket[js-get-field] will raise an @racket[exn:fail:contract] exception.}
@defproc[(js-set-field![obj (or/c js-object? js-function?)]
[field string?]
[v any/c])
void?]{
Mutatively sets the field named @racket[field] of @racket[obj] to @racket[v]
and returns @|void-const|.}
@defproc[(js-new [constructor js-function?] [args any/c] ...) js-object?]{
Constructs a new instance of @racket[constructor] with @racket[args ...] as
arguments.}
@defproc[(js-make-hash [bindings (listof (list/c string? any/c)) empty])
js-object?]{
Constructs a new javascript hash table. For each element of @racket[bindings],
the element is put into the hash table with the @racket[first] as the key and
the @racket[second] as the value. If multiple elements in @racket[bindings]
have @racket[first]s that are @racket[equal?], then only the last binding will
exist (the others will be overwritten).}
@defthing[js-null js-value?]{The null value.}
@defthing[js-undefined js-value?]{The undefined value.}
@;-----------------------------------------------------------------------------
@section{Calling Javascript Functions}
@defproc[(js-call [f js-function?]
[this-arg (or/c js-object? false?)]
[args any/c] ...)
any]{
Applies @racket[f] to @racket[args ...] with @racket[this-arg] as the
@racketidfont{this} object. If @racket[this-arg] is @racket[#f], then
javascript's @racketidfont{null} is used. This is equivalent to javascript's
@racketfont{f.call(this-arg, args ...)}.}
@;-----------------------------------------------------------------------------
@;@section{Examples}
@;This section contains a few simple examples of how to use the FFI.
@;
@;Example One:
@;@racketblock[
@;(js-call (js-get-global-value "setTimeout")
@; #f
@; (procedure->void-js-fun
@; (lambda () (printf "tick!")))
@; (scheme->prim-js 1000))
@;]
@;This code will return @|void-const|, and after one second it will print
@;@racketoutput{tick!}. Note that this is non-blocking because javascript's
@;@racketidfont{setTimeout} is non-blocking.
@;Example Two:
@;@racketblock[
@;(define js-ht (js-make-hash '(("foo" 1) ("bar" 2))))
@;(js-set-field! js-ht "foo" 3)
@;]
@;After evaluation, @racket[js-ht] will be a javascript hash table with the
@;bindings of @racketidfont{js-ht}@racketparenfont{[}@racketvalfont{"foo"}
@;@racketparenfont{]}@racketidfont{ = }@racketvalfont{3} and
@;@racketidfont{js-ht}@racketparenfont{[}@racketvalfont{"bar"}
@;@racketparenfont{]}@racketidfont{ = }@racketvalfont{2}.