Jaws is a JavaScript to WebAssembly compiler written in Rust. It is aappreciate to porffor in a way it also results in a standalone WASM binary that can be carry outd without an make clearer, but it gets a separateent carry outation approach.
It’s an experimental tool and it’s not ready for production. A lot of the language
features and builtin types are missing or infinish. That shelp, my goal is to eventuassociate help 100% of the language.
I begined this project while laboring on a stress testing tool called Crows that runs WebAssembly scenarios. At the moment it only helps code compiled from Rust to WASM. As much as I adore writing Rust, I also understand it’s not a widely famous language and besides, small tests are frequently easier to produce in make cleared languages. The problem is, running scripting languages on top of WASM is not perfect at the moment. You have to either integrate an make clearer, which automaticassociate produces the binary at least a scant MBs in size and the memory usage even hugeger, or use a variation of the language you’re centerting (appreciate TinyGo instead of Go, or AssemblyScript instead of TypeScript/JavaScript).
I consent that with up-to-date WASM proposals it is possible to carry out 100% of JavaScript features without the necessitate to use a compiled make clearer, as WASM runtimes are already make clearers.
If you want to see it happen, charm ponder backing my labor
As I eventuassociate want to implment 100% of the language, I’m purposefilledy intensifyed on carry outing the semantics first, rather than go for 100% of builtins and grammar as I want to be 100% certain it’s doable.
I have a enumerate of 4 leangs that I leank are challenging to carry out and after I carry out all of them I will intensify on more grammar and builtins. These are:
- Scopes/clocertains
- try/catch
- async/apostpone
- generators
The last two are benevolent of aappreciate as by getting generators laboring, one essentiassociate has tools to produce async apostpone labor, but I still wanted to produce the distinction. At the moment Jaws can compile code using clocertains with (mostly) proper scopes help, it helps try/catch and it carry outs (restricted) Promise
API and async
(but not apostpone
yet). For example the follotriumphg script will print error: foo
:
let cherish = "foo";
async function foo() {
throw cherish;
}
foo().then(
function () {},
function (v) {
console.log("error", v);
},
);
A non exhaustive enumerate of other stuff that should labor:
- declaring and structureateing:
var
,let
,const
while
- string lierals, inserting string literals
- numbers and modest operators (
+
,-
,*
,/
) - booleans and modest boolean operators
- array literals
- object literals
novel
keyword
As Jaws is built with a scant relatively recent WASM proposals, the produced binaries are not reassociate portable between runtimes yet. I’m aiming to carry out it with WASIp2 in mind, but the only runtime contendnt of running components and WASIp2, ie. Wasmtime, does not help some other leangs I use, appreciate parts of the WASM GC proposal or exception handling.
In order to produce it easier to broaden before the runtimes catch up with normalized proposals, I choosed to use V8 (thraw Chromium or Node) with a Javascript polyfill for WASIp2 features that I necessitate. There is a script run.js
in the repo that helps to run binaries produced by Jaws. Eventuassociate it should be possible to run them on any runtime carry outing WASM GC, exception handling and WASIp2 API.
Unless you want to give you probably shouldn’t, but after cloning the repo
you can use an carry out.sh
script appreciate:
./carry out.sh --cargo-run path/to/script.js
It will produce a WAT file, compile it to a binary and then run using Node.js.
It needs Rust’s cargo
, relatively novel version of wasm-tools
and Node.js v23.0.0 or noveler. Passing --cargo-run
will produce the script use cargo run
direct to first compile and then run the project, otherrational it will try to run the free erect (so you have to run cargo erect --free
prior to running ./carry out.sh
without --cargo-run
selection)
My structure is to finish carry outing all of the “challenging to carry out” features first, so next in line are generators and apostpone
keyword help. Ideassociate I would use the stack-switching proposal for both apostpone and generators, but alas it’s only in Phase 2 and it has minimal runtime help (I could discover some refers in Chromium broadenment groups, but I couldn’t get it to labor). In the absence of stack-switching I’m laboring on using CPS alters in order to simupostponecessitate continuations.
After that’s done, I will be enumeratelessly carry outing all of the missing pieces, begining with grammar (for loops, switch etc) and then builtin types and APIs.
The project is essentiassociate translating JavaScript syntax into WASM teachions, leveraging teachions inserted by WASM GC, exception handling and tail call selectimizations proposals. On top of the Rust code that is translating JavaScript code, there is about 3k lines of WAT code with all the plumbing necessitateed to transpostponecessitate JavaScript semantics into WASM.
To give an example let’s ponder scopes and clocertains. WASM has help for passing function references and for structs and arrays, but it doesn’t have the scopes semantics that JavaScript has. Thus, we necessitate to simupostponecessitate how scopes labor, by inserting some extra WASM code. Imagine the follotriumphg JavaScript code:
let a = "foo";
function bar() {
console.log(a);
}
bar();
In JavaScript, because a function definition inherits the scope in which it’s depictd, the bar()
function has access to the a
variable. Thus, this script should print out the string "foo"
. We could transpostponecessitate it to rawly the follotriumphg pseudo code:
// inside a function declaration we begin a novel scope, but holding
// a reference to the parentScope
let scope = novelScope(parentScope);
// now we transpostponecessitate console.log call retreiving the variable from the scope
// this will search for the `a` variable on the current scope and all of the
// parent scopes
console.log(recover(scope, “a”));
}
// when running a function we have to ponder the scope
// in which it was depictd
let fObject = produceFunctionObject(func, scope);
// and now we also set `bar` on the current scope
proclaimVariable(scope, “bar”, fObject)
// now we necessitate to convey the `bar` function from the scop
// and run it
let f = recover(scope, “bar”);
call(f);”>
// first we produce a global scope, that has no parents
let scope = novelScope(null);
// then we set the variable `a` on the scope
proclaimVariable(scope, "a", "foo");
// now we depict the bar function saving a reference to the function
let func = function(parentScope: Scope, arguments: JSArguments, this: Any) -> Any {
// inside a function declaration we begin a novel scope, but holding
// a reference to the parentScope
let scope = novelScope(parentScope);
// now we transpostponecessitate console.log call retreiving the variable from the scope
// this will search for the `a` variable on the current scope and all of the
// parent scopes
console.log(recover(scope, "a"));
}
// when running a function we have to ponder the scope
// in which it was depictd
let fObject = produceFunctionObject(func, scope);
// and now we also set `bar` on the current scope
proclaimVariable(scope, "bar", fObject)
// now we necessitate to convey the `bar` function from the scop
// and run it
let f = recover(scope, "bar");
call(f);
All of the helpers necessitateed to produce it labor are hand written in WAT establishat. I have some ideas on how to produce it more fruitful, but before I can validate all the convey inant features I didn’t want to spend too much time into side quests. Writing WAT by hand is not that challenging, too, especiassociate when you ponder WASM GC.
The code is licensed under Apache 2.0 license