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 casts.
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.