Compiling to JavaScript
Compiling to JavaScript
Most Crow code can be compiled to JavaScript.
Functions that can't be used in a JavaScript build have an extern
modifier.
(But js extern
functions can only be used in a JavaScript build).
That will be explained in Extern functions.
The following example uses the alert
global function from JavaScript.
Build commands
Script
crow build foo.crow --out foo.js
outputs a file
suitable for use with a <script>
tag.
As with a C or machine code build, the details of the script are opaque;
it contains only what it needs to run main
.
This will also generate a source map file foo.js.map
which browsers will automatically use for debugging the .js
file if you serve it.
The source map contains all source content in it, so it's all you need for debugging.
If you don't need to debug, you can delete the .map
file.
Modules
For easier debugging, you can build to a directory full of modules:
crow build foo.crow --out js:foo
.
This is slower to load in the browser,
but easier to debug as each output module corresponds to a single Crow module.
Node.js
crow build foo.crow --out node-js:foo
is similar to a module build, but it can run in Node.js.
This is only really useful for debugging JS code locally in an IDE.
It doesn't support Node-specific features like file system access.
The only difference is it uses global
instead of window
,
adds a package.json
, and gives the main file a shebang.
crow run foo.crow --node-js
builds, runs, then deletes the build.
Using JavaScript values
Operations on JavaScript values are in the js module.
The usual way to initiate contact with JavaScript is to access a global variable through js-global
.
A JavaScript value will have the type js-any
.
This is a "top" type, since any value can convert to it using as-js
.
Then cast
converts it back to any type.
Casts are unchecked. See the "type translation" section below to see how JS types can be cast to Crow types.
Most operations on js-any
are unsafe
;
mark the use trusted
if you're sure of what it does.
Be sure to also use summon
if appropriate.
The above program is equivalent to console.log(Number.POSITIVE_INFINITY)
in JavaScript.
As shown above, subscript
gets a property from a JS object.
As in JS, this will return undefined
if the property does not exist.
To call a JS function, use call
.
However, in JS, taking a function off of an object makes it lose its this
value,
so you generally want to use call-property
instead.
Here we need to specify some types when dealing with JS.
call-property
takes a js-any
(the object) and
string
(the property name), but the other arguments could be anything,
so we need ::string
to disambiguate.
Type translation
To interact with JS code, you'll need to understand what JS values can cast to what Crow types and vice versa.
Crow type | JavaScript type |
bool |
boolean |
string , symbol |
string |
int8 through int64
nat8 through nat64
|
bigint |
float32 , float64
|
number |
t[] , t mut[] , t mut-slice |
Array
Be sure to only pass these to JS code that will follow Crow's rules: Never mutate a t[] ,
and don't do anything that would change the length of a t mut-slice
(e.g., push , pop , or splice ).
|
Function types (r function(x p) ) |
function with the same number of parameters. |
Lambda types (r data(x p) , r shared(x p) ,
r mut(x p) )
|
function with a single parameter, which might be a Crow tuple. |
t future |
Promise |
js-any |
As the name implies, this could be any value. |
The above chart shows that if you know a JS value is a number
, you can cast it to a
float32
or float64
,
but casting to a nat
may cause problems.
Crow functions still have Crow semantics.
For example, nat32
adddition will still throw on 32-bit overflow,
and float32
operations have 32-bit precision.
Type checks
Compiling to JavaScript does insert some type checks.
This can help detect errors coming from erroneous cast
s.
However, it's not guaranteed to detect all errors!
Asynchrony
JavaScript code needs to await
any time it calls an
async
function.
When compiling to JavaScript, Crow will automatically mark functions with async
and their calls with await
if it detects that they my use asynchrony.
However, it defaults to assuming that any JavaScript function you call is synchronous.
If you call a JavaScript async
function, treat this like a function returning a
future
(which is as a Promise
)
and call await
on it.
Runtime
When Crow compiles to JavaScript, it doesn't include the Crow runtime
(meaning the allocator and task scheduler).
The JavaScript runtime follows all the rules of a Crow runtime limited to a single thread.
Crow "fibers" are implemented using JS async functions.
A Crow shared
lambda just compiles to a normal JS function;
with only a single thread, there is no need for any exclusion.
How do I?
Import a module
Currently, there's no way to import
a JS module.
If you want to use a JS library, store it in a global variable so Crow can access it.
Define a class
There is no syntax for this, but js/util has a function make-class
.
(You can also eval
a class
expression if you just want to
write the entire class in JavaScript.)
Do exotic JS operations
For other JS operations with no Crow analog, you could use eval
from js.
Use the DOM
js/dom has functions for this.