mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Generate TypeScript by default instead of JS
This is what's needed in the immediate future anyway, so let's do that!
This commit is contained in:
parent
34e4cfa95d
commit
85cdb51719
37
README.md
37
README.md
@ -18,10 +18,10 @@ Notable features of this project includes:
|
||||
* Managing arguments between JS/Rust (strings, numbers, classes, etc)
|
||||
* Importing JS functions with richer types (strings)
|
||||
* Receiving arbitrary JS objects in Rust, passing them through to JS
|
||||
* Generates Typescript for now instead of JS (although that may come later)
|
||||
|
||||
Planned features include:
|
||||
|
||||
* An optional flag to generate Typescript bindings
|
||||
* Field setters/getters in JS through Rust functions
|
||||
* ... and more coming soon!
|
||||
|
||||
@ -104,19 +104,26 @@ set of JS bindings as well. Let's invoke it!
|
||||
|
||||
```
|
||||
$ wasm-bindgen target/wasm32-unknown-unknown/release/js_hello_world.wasm \
|
||||
--output-js hello.js \
|
||||
--output-ts hello.ts \
|
||||
--output-wasm hello.wasm
|
||||
```
|
||||
|
||||
This'll create a `hello.js` which binds the functions described in
|
||||
`js_hello_world.wasm`, and the `hello.wasm` will be a little smaller than the
|
||||
input `js_hello_world.wasm`, but it's otherwise equivalent. Note that `hello.js`
|
||||
isn't very pretty so to read it you'll probably want to run it through a JS
|
||||
formatter.
|
||||
This'll create a `hello.ts` (a TypeScript file) which binds the functions
|
||||
described in `js_hello_world.wasm`, and the `hello.wasm` will be a little
|
||||
smaller than the input `js_hello_world.wasm`, but it's otherwise equivalent.
|
||||
Note that `hello.ts` isn't very pretty so to read it you'll probably want to run
|
||||
it through a formatter.
|
||||
|
||||
Note that `hello.js` uses ES6 modules for easier integration into transpilers
|
||||
like webpack/rollup/babel/etc. We're going to test out the syntax in the browser
|
||||
with `index.html` below, but your browser may not natively support ES6 modules
|
||||
Typically you'll be feeding this typescript into a larger build system, and
|
||||
often you'll be using this with your own typescript project as well. For now
|
||||
though we'll just want the JS output, so let's convert it real quick:
|
||||
|
||||
```
|
||||
$ npm install typescript @types/webassembly-js-api @types/text-encoding
|
||||
$ ./node_modules/typescript/bin/tsc hello.ts --lib es6 -m es2015
|
||||
```
|
||||
|
||||
Below we'll be using ES6 modules, but your browser may not support them natively
|
||||
just yet. To see more information about this, you can browse
|
||||
[online](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
|
||||
|
||||
@ -285,12 +292,10 @@ this][bindings]. You can view them in action like so:
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(bytes => {
|
||||
return instantiate(bytes, {
|
||||
env: {
|
||||
bar_on_reset(s, token) {
|
||||
console.log(token);
|
||||
console.log(`this instance of bar was reset to ${s}`);
|
||||
},
|
||||
}
|
||||
bar_on_reset(s, token) {
|
||||
console.log(token);
|
||||
console.log(`this instance of bar was reset to ${s}`);
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(mod => {
|
||||
|
@ -46,20 +46,22 @@ pub fn project() -> Project {
|
||||
|
||||
("Cargo.lock".to_string(), lockfile),
|
||||
|
||||
("run.js".to_string(), r#"
|
||||
var fs = require("fs");
|
||||
var out = require("./out.compat");
|
||||
var test = require("./test.compat");
|
||||
var wasm = fs.readFileSync("out.wasm");
|
||||
var process = require("process");
|
||||
("run.ts".to_string(), r#"
|
||||
import * as fs from "fs";
|
||||
import * as process from "process";
|
||||
|
||||
out.instantiate(wasm, test.imports).then(m => {
|
||||
test.test(m);
|
||||
if (m.assertHeapAndStackEmpty)
|
||||
m.assertHeapAndStackEmpty();
|
||||
}).catch(function(error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
import { instantiate } from "./out";
|
||||
import * as test from "./test";
|
||||
|
||||
var wasm = fs.readFileSync("out.wasm");
|
||||
|
||||
instantiate(wasm, test.imports).then(m => {
|
||||
test.test(m);
|
||||
if (m.assertHeapAndStackEmpty)
|
||||
m.assertHeapAndStackEmpty();
|
||||
}).catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
"#.to_string()),
|
||||
],
|
||||
@ -78,7 +80,7 @@ pub fn root() -> PathBuf {
|
||||
return me
|
||||
}
|
||||
|
||||
fn babel() -> PathBuf {
|
||||
fn typescript() -> PathBuf {
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
let mut me = env::current_exe().unwrap();
|
||||
@ -86,7 +88,7 @@ fn babel() -> PathBuf {
|
||||
me.pop(); // chop off `deps`
|
||||
me.pop(); // chop off `debug` / `release`
|
||||
let install_dir = me.clone();
|
||||
me.push("node_modules/babel-cli/bin/babel.js");
|
||||
me.push("node_modules/typescript/bin/tsc");
|
||||
|
||||
INIT.call_once(|| {
|
||||
if !me.exists() {
|
||||
@ -99,8 +101,9 @@ fn babel() -> PathBuf {
|
||||
};
|
||||
run(npm
|
||||
.arg("install")
|
||||
.arg("babel-cli")
|
||||
.arg("babel-preset-env")
|
||||
.arg("typescript")
|
||||
.arg("@types/node")
|
||||
.arg("@types/webassembly-js-api")
|
||||
.current_dir(&install_dir), "npm");
|
||||
assert!(me.exists());
|
||||
}
|
||||
@ -151,20 +154,21 @@ impl Project {
|
||||
.debug(true)
|
||||
.generate()
|
||||
.expect("failed to run bindgen");
|
||||
obj.write_js_to(root.join("out.js")).expect("failed to write js");
|
||||
obj.write_ts_to(root.join("out.ts")).expect("failed to write ts");
|
||||
obj.write_wasm_to(root.join("out.wasm")).expect("failed to write wasm");
|
||||
|
||||
let mut cmd = Command::new("node");
|
||||
cmd.arg(babel())
|
||||
.arg(root.join("out.js"))
|
||||
.arg("--presets").arg("env")
|
||||
.arg("--out-file").arg(root.join("out.compat.js"));
|
||||
run(&mut cmd, "node");
|
||||
let mut cmd = Command::new("node");
|
||||
cmd.arg(babel())
|
||||
.arg(root.join("test.js"))
|
||||
.arg("--presets").arg("env")
|
||||
.arg("--out-file").arg(root.join("test.compat.js"));
|
||||
cmd.arg(typescript())
|
||||
.current_dir(&target_dir)
|
||||
.arg(root.join("run.ts"))
|
||||
.arg("--strict")
|
||||
.arg("--noImplicitAny")
|
||||
.arg("--strictNullChecks")
|
||||
.arg("--strictFunctionTypes")
|
||||
.arg("--noUnusedLocals")
|
||||
.arg("--noImplicitReturns")
|
||||
.arg("--lib")
|
||||
.arg("es6");
|
||||
run(&mut cmd, "node");
|
||||
|
||||
let mut cmd = Command::new("node");
|
||||
|
@ -1,486 +0,0 @@
|
||||
use shared;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Js {
|
||||
expose_global_memory: bool,
|
||||
expose_global_exports: bool,
|
||||
expose_get_string_from_wasm: bool,
|
||||
expose_pass_string_to_wasm: bool,
|
||||
expose_assert_num: bool,
|
||||
expose_assert_class: bool,
|
||||
expose_token: bool,
|
||||
expose_objects: bool,
|
||||
exports: Vec<(String, String)>,
|
||||
classes: Vec<String>,
|
||||
imports: Vec<String>,
|
||||
pub nodejs: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Js {
|
||||
|
||||
pub fn generate_program(&mut self, program: &shared::Program) {
|
||||
for f in program.free_functions.iter() {
|
||||
self.generate_free_function(f);
|
||||
}
|
||||
for s in program.structs.iter() {
|
||||
self.generate_struct(s);
|
||||
}
|
||||
for s in program.imports.iter() {
|
||||
self.generate_import(s);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
||||
let ret = self.generate_function(&format!("function {}", func.name),
|
||||
&func.name,
|
||||
false,
|
||||
&func.arguments,
|
||||
func.ret.as_ref());
|
||||
|
||||
self.exports.push((func.name.clone(), ret));
|
||||
}
|
||||
|
||||
pub fn generate_struct(&mut self, s: &shared::Struct) {
|
||||
let mut dst = String::new();
|
||||
self.expose_token = true;
|
||||
self.expose_global_exports = true;
|
||||
dst.push_str(&format!("
|
||||
class {} {{
|
||||
constructor(ptr, sym) {{
|
||||
_checkToken(sym);
|
||||
this.__wasmPtr = ptr;
|
||||
}}
|
||||
|
||||
free() {{
|
||||
const ptr = this.__wasmPtr;
|
||||
this.__wasmPtr = 0;
|
||||
exports.{}(ptr);
|
||||
}}
|
||||
", s.name, s.free_function()));
|
||||
|
||||
for function in s.functions.iter() {
|
||||
let f = self.generate_function(
|
||||
&format!("static {}", function.name),
|
||||
&function.struct_function_export_name(&s.name),
|
||||
false,
|
||||
&function.arguments,
|
||||
function.ret.as_ref(),
|
||||
);
|
||||
dst.push_str(&f);
|
||||
dst.push_str("\n");
|
||||
}
|
||||
for method in s.methods.iter() {
|
||||
let f = self.generate_function(
|
||||
&format!("{}", method.function.name),
|
||||
&method.function.struct_function_export_name(&s.name),
|
||||
true,
|
||||
&method.function.arguments,
|
||||
method.function.ret.as_ref(),
|
||||
);
|
||||
dst.push_str(&f);
|
||||
dst.push_str("\n");
|
||||
}
|
||||
dst.push_str("}\n");
|
||||
self.classes.push(dst);
|
||||
self.exports.push((s.name.clone(), s.name.clone()));
|
||||
}
|
||||
|
||||
fn generate_function(&mut self,
|
||||
name: &str,
|
||||
wasm_name: &str,
|
||||
is_method: bool,
|
||||
arguments: &[shared::Type],
|
||||
ret: Option<&shared::Type>) -> String {
|
||||
let mut dst = format!("{}(", name);
|
||||
let mut passed_args = String::new();
|
||||
let mut arg_conversions = String::new();
|
||||
let mut destructors = String::new();
|
||||
|
||||
if is_method {
|
||||
passed_args.push_str("this.__wasmPtr");
|
||||
}
|
||||
|
||||
for (i, arg) in arguments.iter().enumerate() {
|
||||
let name = format!("arg{}", i);
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
dst.push_str(&name);
|
||||
|
||||
let mut pass = |arg: &str| {
|
||||
if passed_args.len() > 0 {
|
||||
passed_args.push_str(", ");
|
||||
}
|
||||
passed_args.push_str(arg);
|
||||
};
|
||||
match *arg {
|
||||
shared::Type::Number => {
|
||||
self.expose_assert_num = true;
|
||||
arg_conversions.push_str(&format!("_assertNum({});\n", name));
|
||||
pass(&name)
|
||||
}
|
||||
shared::Type::BorrowedStr |
|
||||
shared::Type::String => {
|
||||
self.expose_global_exports = true;
|
||||
self.expose_pass_string_to_wasm = true;
|
||||
arg_conversions.push_str(&format!("\
|
||||
const [ptr{i}, len{i}] = passStringToWasm({arg});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
pass(&format!("len{}", i));
|
||||
if let shared::Type::BorrowedStr = *arg {
|
||||
destructors.push_str(&format!("\n\
|
||||
exports.__wbindgen_free(ptr{i}, len{i});\n\
|
||||
", i = i));
|
||||
}
|
||||
}
|
||||
shared::Type::ByRef(ref s) |
|
||||
shared::Type::ByMutRef(ref s) => {
|
||||
self.expose_assert_class = true;
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = _assertClass({arg}, {struct_});
|
||||
", i = i, arg = name, struct_ = s));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
shared::Type::ByValue(ref s) => {
|
||||
self.expose_assert_class = true;
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = _assertClass({arg}, {struct_});
|
||||
{arg}.__wasmPtr = 0;
|
||||
", i = i, arg = name, struct_ = s));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
shared::Type::JsObject => {
|
||||
self.expose_objects = true;
|
||||
arg_conversions.push_str(&format!("\
|
||||
const idx{i} = addHeapObject({arg});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("idx{}", i));
|
||||
}
|
||||
shared::Type::JsObjectRef => {
|
||||
self.expose_objects = true;
|
||||
arg_conversions.push_str(&format!("\
|
||||
const idx{i} = addBorrowedObject({arg});
|
||||
", i = i, arg = name));
|
||||
destructors.push_str("popBorrowedObject();\n");
|
||||
pass(&format!("idx{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
let convert_ret = match ret {
|
||||
None |
|
||||
Some(&shared::Type::Number) => format!("return ret;"),
|
||||
Some(&shared::Type::JsObject) => {
|
||||
self.expose_objects = true;
|
||||
format!("return takeObject(ret);")
|
||||
}
|
||||
Some(&shared::Type::JsObjectRef) |
|
||||
Some(&shared::Type::BorrowedStr) |
|
||||
Some(&shared::Type::ByMutRef(_)) |
|
||||
Some(&shared::Type::ByRef(_)) => panic!(),
|
||||
Some(&shared::Type::ByValue(ref name)) => {
|
||||
format!("\
|
||||
return new {name}(ret, token);
|
||||
", name = name)
|
||||
}
|
||||
Some(&shared::Type::String) => {
|
||||
self.expose_get_string_from_wasm = true;
|
||||
self.expose_global_exports = true;
|
||||
format!("
|
||||
const ptr = exports.__wbindgen_boxed_str_ptr(ret);
|
||||
const len = exports.__wbindgen_boxed_str_len(ret);
|
||||
const realRet = getStringFromWasm(ptr, len);
|
||||
exports.__wbindgen_boxed_str_free(ret);
|
||||
return realRet;
|
||||
")
|
||||
}
|
||||
};
|
||||
dst.push_str(") {\n ");
|
||||
dst.push_str(&arg_conversions);
|
||||
self.expose_global_exports = true;
|
||||
if destructors.len() == 0 {
|
||||
dst.push_str(&format!("\
|
||||
const ret = exports.{f}({passed});
|
||||
{convert_ret}
|
||||
", f = wasm_name, passed = passed_args, convert_ret = convert_ret));
|
||||
} else {
|
||||
dst.push_str(&format!("\
|
||||
try {{
|
||||
const ret = exports.{f}({passed});
|
||||
{convert_ret}
|
||||
}} finally {{
|
||||
{destructors}
|
||||
}}
|
||||
", f = wasm_name, passed = passed_args, destructors = destructors,
|
||||
convert_ret = convert_ret));
|
||||
}
|
||||
dst.push_str("}");
|
||||
return dst
|
||||
}
|
||||
|
||||
pub fn generate_import(&mut self, import: &shared::Function) {
|
||||
let mut dst = String::new();
|
||||
|
||||
dst.push_str(&format!("const {0} = imports.env.{0};\n", import.name));
|
||||
dst.push_str(&format!("imports.env.{0} = function {0}_shim(", import.name));
|
||||
|
||||
let mut invocation = String::new();
|
||||
for (i, arg) in import.arguments.iter().enumerate() {
|
||||
if invocation.len() > 0 {
|
||||
invocation.push_str(", ");
|
||||
}
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
match *arg {
|
||||
shared::Type::Number => {
|
||||
invocation.push_str(&format!("arg{}", i));
|
||||
dst.push_str(&format!("arg{}", i));
|
||||
}
|
||||
shared::Type::BorrowedStr => {
|
||||
self.expose_get_string_from_wasm = true;
|
||||
invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i));
|
||||
dst.push_str(&format!("ptr{0}, len{0}", i));
|
||||
}
|
||||
shared::Type::JsObject => {
|
||||
self.expose_objects = true;
|
||||
invocation.push_str(&format!("takeObject(arg{})", i));
|
||||
dst.push_str(&format!("arg{}", i));
|
||||
}
|
||||
shared::Type::JsObjectRef => {
|
||||
self.expose_objects = true;
|
||||
invocation.push_str(&format!("getObject(arg{})", i));
|
||||
dst.push_str(&format!("arg{}", i));
|
||||
}
|
||||
shared::Type::String |
|
||||
shared::Type::ByRef(_) |
|
||||
shared::Type::ByMutRef(_) |
|
||||
shared::Type::ByValue(_) => {
|
||||
panic!("unsupported type in import");
|
||||
}
|
||||
}
|
||||
}
|
||||
dst.push_str(") {\n");
|
||||
dst.push_str(&format!("return {}({});\n}}", import.name, invocation));
|
||||
|
||||
self.imports.push(dst);
|
||||
}
|
||||
|
||||
pub fn to_string(&mut self) -> String {
|
||||
let mut globals = String::new();
|
||||
let mut real_globals = String::new();
|
||||
if self.expose_global_memory ||
|
||||
self.expose_pass_string_to_wasm ||
|
||||
self.expose_get_string_from_wasm
|
||||
{
|
||||
globals.push_str("const memory = obj.instance.exports.memory;\n");
|
||||
}
|
||||
if self.expose_global_exports ||
|
||||
self.expose_pass_string_to_wasm ||
|
||||
self.expose_get_string_from_wasm
|
||||
{
|
||||
globals.push_str("const exports = obj.instance.exports;\n");
|
||||
}
|
||||
if self.expose_token {
|
||||
globals.push_str("\
|
||||
const token = Symbol('foo');
|
||||
function _checkToken(sym) {
|
||||
if (token !== sym)
|
||||
throw new Error('cannot invoke `new` directly');
|
||||
}
|
||||
");
|
||||
}
|
||||
if self.expose_assert_num {
|
||||
globals.push_str("\
|
||||
function _assertNum(n) {
|
||||
if (typeof(n) !== 'number')
|
||||
throw new Error('expected a number argument');
|
||||
}
|
||||
");
|
||||
}
|
||||
if self.expose_pass_string_to_wasm {
|
||||
if self.nodejs {
|
||||
globals.push_str("
|
||||
function passStringToWasm(arg) {
|
||||
if (typeof(arg) !== 'string')
|
||||
throw new Error('expected a string argument');
|
||||
const buf = Buffer.from(arg);
|
||||
const len = buf.length;
|
||||
const ptr = exports.__wbindgen_malloc(len);
|
||||
let array = new Uint8Array(memory.buffer);
|
||||
buf.copy(array, ptr);
|
||||
return [ptr, len];
|
||||
}
|
||||
");
|
||||
} else {
|
||||
globals.push_str("
|
||||
function passStringToWasm(arg) {
|
||||
if (typeof(arg) !== 'string')
|
||||
throw new Error('expected a string argument');
|
||||
const buf = new TextEncoder('utf-8').encode(arg);
|
||||
const len = buf.length;
|
||||
const ptr = exports.__wbindgen_malloc(len);
|
||||
let array = new Uint8Array(memory.buffer);
|
||||
array.set(buf, ptr);
|
||||
return [ptr, len];
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
||||
if self.expose_get_string_from_wasm {
|
||||
real_globals.push_str("let getStringFromWasm = null;\n");
|
||||
if self.nodejs {
|
||||
globals.push_str("
|
||||
getStringFromWasm = function getStringFromWasm(ptr, len) {
|
||||
const mem = new Uint8Array(memory.buffer);
|
||||
const buf = Buffer.from(mem.slice(ptr, ptr + len));
|
||||
const ret = buf.toString();
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
} else {
|
||||
globals.push_str("
|
||||
getStringFromWasm = function getStringFromWasm(ptr, len) {
|
||||
const mem = new Uint8Array(memory.buffer);
|
||||
const slice = mem.slice(ptr, ptr + len);
|
||||
const ret = new TextDecoder('utf-8').decode(slice);
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
||||
if self.expose_assert_class {
|
||||
globals.push_str("
|
||||
function _assertClass(instance, klass) {
|
||||
if (!(instance instanceof klass))
|
||||
throw new Error(`expected instance of ${klass.name}`);
|
||||
return instance.__wasmPtr;
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
if self.expose_objects {
|
||||
real_globals.push_str("
|
||||
let stack = [];
|
||||
let slab = [];
|
||||
let slab_next = 0;
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (slab_next == slab.length) {
|
||||
slab.push(slab.length + 1);
|
||||
}
|
||||
const idx = slab_next;
|
||||
slab_next = slab[idx];
|
||||
slab[idx] = { obj, cnt: 1 };
|
||||
return idx << 1;
|
||||
}
|
||||
|
||||
function addBorrowedObject(obj) {
|
||||
stack.push(obj);
|
||||
return ((stack.length - 1) << 1) | 1;
|
||||
}
|
||||
|
||||
function popBorrowedObject() {
|
||||
stack.pop();
|
||||
}
|
||||
|
||||
function getObject(idx) {
|
||||
if (idx & 1 == 1) {
|
||||
return stack[idx >> 1];
|
||||
} else {
|
||||
return slab[idx >> 1].obj;
|
||||
}
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropRef(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function cloneRef(idx) {
|
||||
// If this object is on the stack promote it to the heap.
|
||||
if (idx & 1 == 1) {
|
||||
return addHeapObject(getObject(idx));
|
||||
}
|
||||
|
||||
// Otherwise if the object is on the heap just bump the
|
||||
// refcount and move on
|
||||
slab[idx >> 1].cnt += 1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
function dropRef(idx) {
|
||||
if (idx & 1 == 1)
|
||||
throw new Error('cannot drop ref of stack objects');
|
||||
|
||||
// Decrement our refcount, but if it's still larger than one
|
||||
// keep going
|
||||
let obj = slab[idx >> 1];
|
||||
obj.cnt -= 1;
|
||||
if (obj.cnt > 0)
|
||||
return;
|
||||
|
||||
// If we hit 0 then free up our space in the slab
|
||||
slab[idx >> 1] = slab_next;
|
||||
slab_next = idx >> 1;
|
||||
}
|
||||
");
|
||||
|
||||
if self.debug {
|
||||
self.exports.push(
|
||||
(
|
||||
"assertHeapAndStackEmpty".to_string(),
|
||||
"function() {
|
||||
if (stack.length > 0)
|
||||
throw new Error('stack is not empty');
|
||||
for (let i = 0; i < slab.length; i++) {
|
||||
if (typeof(slab[i]) !== 'number')
|
||||
throw new Error('slab is not empty');
|
||||
}
|
||||
}".to_string(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mut exports = String::new();
|
||||
for class in self.classes.iter() {
|
||||
exports.push_str(class);
|
||||
exports.push_str("\n");
|
||||
}
|
||||
for &(ref name, ref body) in self.exports.iter() {
|
||||
exports.push_str("obj.");
|
||||
exports.push_str(name);
|
||||
exports.push_str(" = ");
|
||||
exports.push_str(body);
|
||||
exports.push_str(";\n");
|
||||
}
|
||||
let mut imports = String::new();
|
||||
for import in self.imports.iter() {
|
||||
imports.push_str(import);
|
||||
imports.push_str("\n");
|
||||
}
|
||||
|
||||
if self.expose_objects {
|
||||
imports.push_str("
|
||||
imports.env.__wasm_bindgen_object_clone_ref = cloneRef;
|
||||
imports.env.__wasm_bindgen_object_drop_ref = dropRef;
|
||||
");
|
||||
}
|
||||
format!("
|
||||
{}
|
||||
function xform(obj) {{
|
||||
{}
|
||||
{}
|
||||
return obj;
|
||||
}}
|
||||
export function instantiate(bytes, imports) {{
|
||||
{}
|
||||
return WebAssembly.instantiate(bytes, imports).then(xform);
|
||||
}}
|
||||
", real_globals, globals, exports, imports)
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ use std::io::Write;
|
||||
use failure::{Error, ResultExt};
|
||||
use parity_wasm::elements::*;
|
||||
|
||||
mod js;
|
||||
mod ts;
|
||||
|
||||
pub struct Bindgen {
|
||||
path: Option<PathBuf>,
|
||||
@ -69,16 +69,16 @@ impl Bindgen {
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn write_js_to<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||
self._write_js_to(path.as_ref())
|
||||
pub fn write_ts_to<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||
self._write_ts_to(path.as_ref())
|
||||
}
|
||||
|
||||
fn _write_js_to(&self, path: &Path) -> Result<(), Error> {
|
||||
let js = self.generate_js();
|
||||
fn _write_ts_to(&self, path: &Path) -> Result<(), Error> {
|
||||
let ts = self.generate_ts();
|
||||
let mut f = File::create(path).with_context(|_| {
|
||||
format!("failed to create file at {:?}", path)
|
||||
})?;
|
||||
f.write_all(js.as_bytes()).with_context(|_| {
|
||||
f.write_all(ts.as_bytes()).with_context(|_| {
|
||||
format!("failed to write file at {:?}", path)
|
||||
})?;
|
||||
Ok(())
|
||||
@ -95,12 +95,12 @@ impl Object {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn generate_js(&self) -> String {
|
||||
let mut js = js::Js::default();
|
||||
js.nodejs = self.nodejs;
|
||||
js.debug = self.debug;
|
||||
js.generate_program(&self.program);
|
||||
js.to_string()
|
||||
pub fn generate_ts(&self) -> String {
|
||||
let mut ts = ts::Js::default();
|
||||
ts.nodejs = self.nodejs;
|
||||
ts.debug = self.debug;
|
||||
ts.generate_program(&self.program, &self.module);
|
||||
ts.to_string(&self.module)
|
||||
}
|
||||
}
|
||||
|
||||
|
795
crates/wasm-bindgen-cli-support/src/ts.rs
Normal file
795
crates/wasm-bindgen-cli-support/src/ts.rs
Normal file
@ -0,0 +1,795 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use shared;
|
||||
use parity_wasm::elements::*;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Js {
|
||||
globals: String,
|
||||
exposed_globals: HashSet<&'static str>,
|
||||
exports: Vec<(String, String, String)>,
|
||||
classes: Vec<String>,
|
||||
imports: Vec<(String, String, String)>,
|
||||
pub nodejs: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Js {
|
||||
|
||||
pub fn generate_program(&mut self,
|
||||
program: &shared::Program,
|
||||
_wasm: &Module) {
|
||||
for f in program.free_functions.iter() {
|
||||
self.generate_free_function(f);
|
||||
}
|
||||
for s in program.structs.iter() {
|
||||
self.generate_struct(s);
|
||||
}
|
||||
for s in program.imports.iter() {
|
||||
self.generate_import(s);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_free_function(&mut self, func: &shared::Function) {
|
||||
let (js, ts) = self.generate_function("function",
|
||||
&func.name,
|
||||
&func.name,
|
||||
false,
|
||||
&func.arguments,
|
||||
func.ret.as_ref());
|
||||
|
||||
self.exports.push((func.name.clone(), js, ts));
|
||||
}
|
||||
|
||||
pub fn generate_struct(&mut self, s: &shared::Struct) {
|
||||
let mut dst = String::new();
|
||||
self.expose_check_token();
|
||||
self.expose_wasm_exports();
|
||||
dst.push_str(&format!("
|
||||
export class {} {{
|
||||
constructor(public __wasmPtr: number, sym: Symbol) {{
|
||||
_checkToken(sym);
|
||||
}}
|
||||
|
||||
free(): void {{
|
||||
const ptr = this.__wasmPtr;
|
||||
this.__wasmPtr = 0;
|
||||
wasm_exports.{}(ptr);
|
||||
}}
|
||||
", s.name, s.free_function()));
|
||||
|
||||
for function in s.functions.iter() {
|
||||
let (js, _ts) = self.generate_function(
|
||||
"static",
|
||||
&function.name,
|
||||
&function.struct_function_export_name(&s.name),
|
||||
false,
|
||||
&function.arguments,
|
||||
function.ret.as_ref(),
|
||||
);
|
||||
dst.push_str(&js);
|
||||
dst.push_str("\n");
|
||||
}
|
||||
for method in s.methods.iter() {
|
||||
let (js, _ts) = self.generate_function(
|
||||
"",
|
||||
&method.function.name,
|
||||
&method.function.struct_function_export_name(&s.name),
|
||||
true,
|
||||
&method.function.arguments,
|
||||
method.function.ret.as_ref(),
|
||||
);
|
||||
dst.push_str(&js);
|
||||
dst.push_str("\n");
|
||||
}
|
||||
dst.push_str("}\n");
|
||||
self.classes.push(dst);
|
||||
|
||||
let ts_export = format!("{0}: typeof {0};", s.name);
|
||||
self.exports.push((s.name.clone(), s.name.clone(), ts_export));
|
||||
}
|
||||
|
||||
fn generate_function(&mut self,
|
||||
prefix: &str,
|
||||
name: &str,
|
||||
wasm_name: &str,
|
||||
is_method: bool,
|
||||
arguments: &[shared::Type],
|
||||
ret: Option<&shared::Type>) -> (String, String) {
|
||||
let mut dst = format!("{}(", name);
|
||||
let mut passed_args = String::new();
|
||||
let mut arg_conversions = String::new();
|
||||
let mut destructors = String::new();
|
||||
|
||||
if is_method {
|
||||
passed_args.push_str("this.__wasmPtr");
|
||||
}
|
||||
|
||||
for (i, arg) in arguments.iter().enumerate() {
|
||||
let name = format!("arg{}", i);
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
}
|
||||
dst.push_str(&name);
|
||||
dst.push_str(": ");
|
||||
|
||||
let mut pass = |arg: &str| {
|
||||
if passed_args.len() > 0 {
|
||||
passed_args.push_str(", ");
|
||||
}
|
||||
passed_args.push_str(arg);
|
||||
};
|
||||
match *arg {
|
||||
shared::Type::Number => {
|
||||
dst.push_str("number");
|
||||
self.expose_assert_num();
|
||||
arg_conversions.push_str(&format!("_assertNum({});\n", name));
|
||||
pass(&name)
|
||||
}
|
||||
shared::Type::BorrowedStr |
|
||||
shared::Type::String => {
|
||||
dst.push_str("string");
|
||||
self.expose_pass_string_to_wasm();
|
||||
arg_conversions.push_str(&format!("\
|
||||
const [ptr{i}, len{i}] = passStringToWasm({arg});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("ptr{}", i));
|
||||
pass(&format!("len{}", i));
|
||||
if let shared::Type::BorrowedStr = *arg {
|
||||
self.expose_wasm_exports();
|
||||
destructors.push_str(&format!("\n\
|
||||
wasm_exports.__wbindgen_free(ptr{i}, len{i});\n\
|
||||
", i = i));
|
||||
}
|
||||
}
|
||||
shared::Type::ByRef(ref s) |
|
||||
shared::Type::ByMutRef(ref s) => {
|
||||
dst.push_str(s);
|
||||
self.expose_assert_class();
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = _assertClass({arg}, {struct_});
|
||||
", i = i, arg = name, struct_ = s));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
shared::Type::ByValue(ref s) => {
|
||||
dst.push_str(s);
|
||||
self.expose_assert_class();
|
||||
arg_conversions.push_str(&format!("\
|
||||
const ptr{i} = _assertClass({arg}, {struct_});
|
||||
{arg}.__wasmPtr = 0;
|
||||
", i = i, arg = name, struct_ = s));
|
||||
pass(&format!("ptr{}", i));
|
||||
}
|
||||
shared::Type::JsObject => {
|
||||
dst.push_str("any");
|
||||
self.expose_add_heap_object();
|
||||
arg_conversions.push_str(&format!("\
|
||||
const idx{i} = addHeapObject({arg});
|
||||
", i = i, arg = name));
|
||||
pass(&format!("idx{}", i));
|
||||
}
|
||||
shared::Type::JsObjectRef => {
|
||||
dst.push_str("any");
|
||||
self.expose_borrowed_objects();
|
||||
arg_conversions.push_str(&format!("\
|
||||
const idx{i} = addBorrowedObject({arg});
|
||||
", i = i, arg = name));
|
||||
destructors.push_str("popBorrowedObject();\n");
|
||||
pass(&format!("idx{}", i));
|
||||
}
|
||||
}
|
||||
}
|
||||
dst.push_str("): ");
|
||||
let convert_ret = match ret {
|
||||
None => {
|
||||
dst.push_str("void");
|
||||
format!("return ret;")
|
||||
}
|
||||
Some(&shared::Type::Number) => {
|
||||
dst.push_str("number");
|
||||
format!("return ret;")
|
||||
}
|
||||
Some(&shared::Type::JsObject) => {
|
||||
dst.push_str("any");
|
||||
self.expose_take_object();
|
||||
format!("return takeObject(ret);")
|
||||
}
|
||||
Some(&shared::Type::JsObjectRef) |
|
||||
Some(&shared::Type::BorrowedStr) |
|
||||
Some(&shared::Type::ByMutRef(_)) |
|
||||
Some(&shared::Type::ByRef(_)) => panic!(),
|
||||
Some(&shared::Type::ByValue(ref name)) => {
|
||||
dst.push_str(name);
|
||||
format!("\
|
||||
return new {name}(ret, token);
|
||||
", name = name)
|
||||
}
|
||||
Some(&shared::Type::String) => {
|
||||
dst.push_str("string");
|
||||
self.expose_get_string_from_wasm();
|
||||
self.expose_wasm_exports();
|
||||
format!("
|
||||
const ptr = wasm_exports.__wbindgen_boxed_str_ptr(ret);
|
||||
const len = wasm_exports.__wbindgen_boxed_str_len(ret);
|
||||
const realRet = getStringFromWasm(ptr, len);
|
||||
wasm_exports.__wbindgen_boxed_str_free(ret);
|
||||
return realRet;
|
||||
")
|
||||
}
|
||||
};
|
||||
let mut dst_ts = dst.clone();
|
||||
dst_ts.push_str(";");
|
||||
dst.push_str(" {\n ");
|
||||
dst.push_str(&arg_conversions);
|
||||
self.expose_wasm_exports();
|
||||
if destructors.len() == 0 {
|
||||
dst.push_str(&format!("\
|
||||
const ret = wasm_exports.{f}({passed});
|
||||
{convert_ret}
|
||||
", f = wasm_name, passed = passed_args, convert_ret = convert_ret));
|
||||
} else {
|
||||
dst.push_str(&format!("\
|
||||
try {{
|
||||
const ret = wasm_exports.{f}({passed});
|
||||
{convert_ret}
|
||||
}} finally {{
|
||||
{destructors}
|
||||
}}
|
||||
", f = wasm_name, passed = passed_args, destructors = destructors,
|
||||
convert_ret = convert_ret));
|
||||
}
|
||||
dst.push_str("}");
|
||||
(format!("{} {}", prefix, dst), dst_ts)
|
||||
}
|
||||
|
||||
pub fn generate_import(&mut self, import: &shared::Function) {
|
||||
let mut dst = String::new();
|
||||
let mut ts_dst = String::new();
|
||||
|
||||
dst.push_str(&format!("function {0}_shim(", import.name));
|
||||
|
||||
ts_dst.push_str(&import.name);
|
||||
ts_dst.push_str("(");
|
||||
|
||||
let mut invocation = String::new();
|
||||
for (i, arg) in import.arguments.iter().enumerate() {
|
||||
if invocation.len() > 0 {
|
||||
invocation.push_str(", ");
|
||||
}
|
||||
if i > 0 {
|
||||
dst.push_str(", ");
|
||||
ts_dst.push_str(", ");
|
||||
}
|
||||
ts_dst.push_str(&format!("arg{}: ", i));
|
||||
match *arg {
|
||||
shared::Type::Number => {
|
||||
ts_dst.push_str("number");
|
||||
invocation.push_str(&format!("arg{}", i));
|
||||
dst.push_str(&format!("arg{}: number", i));
|
||||
}
|
||||
shared::Type::BorrowedStr => {
|
||||
ts_dst.push_str("string");
|
||||
self.expose_get_string_from_wasm();
|
||||
invocation.push_str(&format!("getStringFromWasm(ptr{0}, len{0})", i));
|
||||
dst.push_str(&format!("ptr{0}: number, len{0}: number", i));
|
||||
}
|
||||
shared::Type::JsObject => {
|
||||
ts_dst.push_str("any");
|
||||
self.expose_take_object();
|
||||
invocation.push_str(&format!("takeObject(arg{})", i));
|
||||
dst.push_str(&format!("arg{}: number", i));
|
||||
}
|
||||
shared::Type::JsObjectRef => {
|
||||
ts_dst.push_str("any");
|
||||
self.expose_get_object();
|
||||
invocation.push_str(&format!("getObject(arg{})", i));
|
||||
dst.push_str(&format!("arg{}: number", i));
|
||||
}
|
||||
shared::Type::String |
|
||||
shared::Type::ByRef(_) |
|
||||
shared::Type::ByMutRef(_) |
|
||||
shared::Type::ByValue(_) => {
|
||||
panic!("unsupported type in import");
|
||||
}
|
||||
}
|
||||
}
|
||||
ts_dst.push_str("): ");
|
||||
dst.push_str("): ");
|
||||
match import.ret {
|
||||
Some(shared::Type::Number) => {
|
||||
ts_dst.push_str("number");
|
||||
dst.push_str("number");
|
||||
}
|
||||
None => {
|
||||
ts_dst.push_str("void");
|
||||
dst.push_str("void");
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
ts_dst.push_str("\n");
|
||||
dst.push_str(" {\n");
|
||||
dst.push_str(&format!("return imports.{}({});\n}}", import.name, invocation));
|
||||
|
||||
self.imports.push((import.name.clone(), dst, ts_dst));
|
||||
}
|
||||
|
||||
pub fn to_string(&mut self, m: &Module) -> String {
|
||||
if self.debug {
|
||||
self.expose_global_slab();
|
||||
self.expose_global_stack();
|
||||
self.exports.push(
|
||||
(
|
||||
"assertHeapAndStackEmpty".to_string(),
|
||||
"function(): void {
|
||||
if (stack.length > 0)
|
||||
throw new Error('stack is not empty');
|
||||
for (let i = 0; i < slab.length; i++) {
|
||||
if (typeof(slab[i]) !== 'number')
|
||||
throw new Error('slab is not empty');
|
||||
}
|
||||
}".to_string(),
|
||||
"assertHeapAndStackEmpty(): void;\n".to_string(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for class in self.classes.iter() {
|
||||
self.globals.push_str(class);
|
||||
self.globals.push_str("\n");
|
||||
}
|
||||
let mut typescript_exports = String::new();
|
||||
let mut exports = format!("\
|
||||
{{
|
||||
module,
|
||||
instance,
|
||||
");
|
||||
for &(ref name, ref body, ref ts_export) in self.exports.iter() {
|
||||
exports.push_str(name);
|
||||
exports.push_str(": ");
|
||||
exports.push_str(body);
|
||||
exports.push_str(",\n");
|
||||
typescript_exports.push_str(ts_export);
|
||||
typescript_exports.push_str("\n");
|
||||
}
|
||||
exports.push_str("}");
|
||||
let mut imports = String::new();
|
||||
let mut typescript_imports = String::new();
|
||||
for &(ref import, ref val, ref ts_import) in self.imports.iter() {
|
||||
imports.push_str(import);
|
||||
imports.push_str(":");
|
||||
imports.push_str(val);
|
||||
imports.push_str(",\n");
|
||||
typescript_imports.push_str(ts_import);
|
||||
typescript_imports.push_str("\n");
|
||||
}
|
||||
|
||||
if self.wasm_import_needed("__wasm_bindgen_object_clone_ref", m) {
|
||||
self.expose_add_heap_object();
|
||||
self.expose_get_object();
|
||||
imports.push_str("
|
||||
__wasm_bindgen_object_clone_ref: function(idx: number): number {
|
||||
// If this object is on the stack promote it to the heap.
|
||||
if ((idx & 1) === 1) {
|
||||
return addHeapObject(getObject(idx));
|
||||
}
|
||||
|
||||
// Otherwise if the object is on the heap just bump the
|
||||
// refcount and move on
|
||||
const val = slab[idx >> 1];
|
||||
if (typeof(val) === 'number')
|
||||
throw new Error('corrupt slab');
|
||||
val.cnt += 1;
|
||||
return idx;
|
||||
},
|
||||
");
|
||||
}
|
||||
|
||||
if self.wasm_import_needed("__wasm_bindgen_object_drop_ref", m) {
|
||||
self.expose_drop_ref();
|
||||
imports.push_str("__wasm_bindgen_object_drop_ref: dropRef,\n");
|
||||
}
|
||||
|
||||
let mut writes = String::new();
|
||||
if self.exposed_globals.contains(&"memory") {
|
||||
writes.push_str("memory = exports.memory;\n");
|
||||
}
|
||||
if self.exposed_globals.contains(&"wasm_exports") {
|
||||
writes.push_str("wasm_exports = exports;\n");
|
||||
}
|
||||
format!("
|
||||
{globals}
|
||||
|
||||
interface WasmImportsTop {{
|
||||
env: WasmImports,
|
||||
}}
|
||||
|
||||
interface WasmImports {{
|
||||
{wasm_imports}
|
||||
}}
|
||||
|
||||
interface WasmExports {{
|
||||
{wasm_exports}
|
||||
}}
|
||||
|
||||
export interface Imports {{
|
||||
{typescript_imports}
|
||||
}}
|
||||
|
||||
export interface Exports {{
|
||||
module: WebAssembly.Module;
|
||||
instance: WebAssembly.Module;
|
||||
{typescript_exports}
|
||||
}}
|
||||
|
||||
function xform(obj: WebAssembly.ResultObject): Exports {{
|
||||
let {{ module, instance }} = obj;
|
||||
let {{ exports }} = instance;
|
||||
{writes}
|
||||
return {exports};
|
||||
}}
|
||||
export function instantiate(bytes: any, imports: Imports): Promise<Exports> {{
|
||||
let wasm_imports: WasmImportsTop = {{
|
||||
env: {{
|
||||
{imports}
|
||||
}},
|
||||
}};
|
||||
return WebAssembly.instantiate(bytes, wasm_imports).then(xform);
|
||||
}}
|
||||
",
|
||||
globals = self.globals,
|
||||
exports = exports,
|
||||
imports = imports,
|
||||
writes = writes,
|
||||
typescript_imports = typescript_imports,
|
||||
typescript_exports = typescript_exports,
|
||||
wasm_imports = self.typescript_wasm_imports(m),
|
||||
wasm_exports = self.typescript_wasm_exports(m),
|
||||
)
|
||||
}
|
||||
|
||||
fn wasm_import_needed(&self, name: &str, m: &Module) -> bool {
|
||||
let imports = match m.import_section() {
|
||||
Some(s) => s,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
imports.entries().iter().any(|i| {
|
||||
i.module() == "env" && i.field() == name
|
||||
})
|
||||
}
|
||||
|
||||
fn typescript_wasm_imports(&self, m: &Module) -> String {
|
||||
let imports = match m.import_section() {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
let types = match m.type_section() {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
|
||||
let mut ts = String::new();
|
||||
for import in imports.entries() {
|
||||
assert_eq!(import.module(), "env");
|
||||
|
||||
let ty = match *import.external() {
|
||||
External::Function(i) => {
|
||||
match types.types()[i as usize] {
|
||||
Type::Function(ref t) => t,
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
ts.push_str(import.field());
|
||||
ts.push_str("(");
|
||||
// TODO: probably match `arg` to catch exhaustive errors in the
|
||||
// future
|
||||
for (i, _arg) in ty.params().iter().enumerate() {
|
||||
if i > 0 {
|
||||
ts.push_str(", ");
|
||||
}
|
||||
ts.push_str(&format!("arg{}: number", i));
|
||||
}
|
||||
ts.push_str("): ");
|
||||
if ty.return_type().is_none() {
|
||||
ts.push_str("void");
|
||||
} else {
|
||||
ts.push_str("number");
|
||||
}
|
||||
ts.push_str(";\n");
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
fn typescript_wasm_exports(&self, m: &Module) -> String {
|
||||
let imported_functions = match m.import_section() {
|
||||
Some(s) => s.functions(),
|
||||
None => 0,
|
||||
};
|
||||
let functions = match m.function_section() {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
let types = match m.type_section() {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
let exports = match m.export_section() {
|
||||
Some(s) => s,
|
||||
None => return String::new(),
|
||||
};
|
||||
|
||||
let mut ts = String::new();
|
||||
for export in exports.entries() {
|
||||
let fn_idx = match *export.internal() {
|
||||
Internal::Function(i) => i as usize,
|
||||
_ => continue,
|
||||
};
|
||||
assert!(fn_idx >= imported_functions);
|
||||
let function = &functions.entries()[fn_idx - imported_functions];
|
||||
let ty = match types.types()[function.type_ref() as usize] {
|
||||
Type::Function(ref t) => t,
|
||||
};
|
||||
|
||||
ts.push_str(export.field());
|
||||
ts.push_str("(");
|
||||
// TODO: probably match `arg` to catch exhaustive errors in the
|
||||
// future
|
||||
for (i, _arg) in ty.params().iter().enumerate() {
|
||||
if i > 0 {
|
||||
ts.push_str(", ");
|
||||
}
|
||||
ts.push_str(&format!("arg{}: number", i));
|
||||
}
|
||||
ts.push_str("): ");
|
||||
if ty.return_type().is_none() {
|
||||
ts.push_str("void");
|
||||
} else {
|
||||
ts.push_str("number");
|
||||
}
|
||||
ts.push_str(";\n");
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
fn expose_drop_ref(&mut self) {
|
||||
if !self.exposed_globals.insert("drop_ref") {
|
||||
return
|
||||
}
|
||||
self.expose_global_slab();
|
||||
self.expose_global_slab_next();
|
||||
self.globals.push_str("
|
||||
function dropRef(idx: number): void {
|
||||
if ((idx & 1) == 1)
|
||||
throw new Error('cannot drop ref of stack objects');
|
||||
|
||||
// Decrement our refcount, but if it's still larger than one
|
||||
// keep going
|
||||
let obj = slab[idx >> 1];
|
||||
if (typeof(obj) === 'number')
|
||||
throw new Error('corrupt slab');
|
||||
obj.cnt -= 1;
|
||||
if (obj.cnt > 0)
|
||||
return;
|
||||
|
||||
// If we hit 0 then free up our space in the slab
|
||||
slab[idx >> 1] = slab_next;
|
||||
slab_next = idx >> 1;
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_global_stack(&mut self) {
|
||||
if !self.exposed_globals.insert("stack") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("
|
||||
let stack: any[] = [];
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_global_slab(&mut self) {
|
||||
if !self.exposed_globals.insert("slab") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("
|
||||
let slab: ({ obj: any, cnt: number } | number)[] = [];
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_global_slab_next(&mut self) {
|
||||
if !self.exposed_globals.insert("slab_next") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("
|
||||
let slab_next: number = 0;
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_get_object(&mut self) {
|
||||
if !self.exposed_globals.insert("get_object") {
|
||||
return
|
||||
}
|
||||
self.expose_global_stack();
|
||||
self.expose_global_slab();
|
||||
self.globals.push_str("
|
||||
function getObject(idx: number): any {
|
||||
if ((idx & 1) === 1) {
|
||||
return stack[idx >> 1];
|
||||
} else {
|
||||
const val = slab[idx >> 1];
|
||||
if (typeof(val) === 'number')
|
||||
throw new Error('corrupt slab');
|
||||
return val.obj;
|
||||
}
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_global_memory(&mut self) {
|
||||
if !self.exposed_globals.insert("memory") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("let memory: WebAssembly.Memory;\n");
|
||||
}
|
||||
|
||||
fn expose_wasm_exports(&mut self) {
|
||||
if !self.exposed_globals.insert("wasm_exports") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("let wasm_exports: WasmExports;\n");
|
||||
}
|
||||
|
||||
fn expose_check_token(&mut self) {
|
||||
if !self.exposed_globals.insert("check_token") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("\
|
||||
const token = Symbol('foo');
|
||||
function _checkToken(sym: Symbol): void {
|
||||
if (token !== sym)
|
||||
throw new Error('cannot invoke `new` directly');
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_assert_num(&mut self) {
|
||||
if !self.exposed_globals.insert("assert_num") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("\
|
||||
function _assertNum(n: number): void {
|
||||
if (typeof(n) !== 'number')
|
||||
throw new Error('expected a number argument');
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_pass_string_to_wasm(&mut self) {
|
||||
if !self.exposed_globals.insert("pass_string_to_wasm") {
|
||||
return
|
||||
}
|
||||
self.expose_wasm_exports();
|
||||
self.expose_global_memory();
|
||||
if self.nodejs {
|
||||
self.globals.push_str("
|
||||
function passStringToWasm(arg: string): [number, number] {
|
||||
if (typeof(arg) !== 'string')
|
||||
throw new Error('expected a string argument');
|
||||
const buf = Buffer.from(arg);
|
||||
const len = buf.length;
|
||||
const ptr = wasm_exports.__wbindgen_malloc(len);
|
||||
buf.copy(Buffer.from(memory.buffer), ptr);
|
||||
return [ptr, len];
|
||||
}
|
||||
");
|
||||
} else {
|
||||
self.globals.push_str("
|
||||
function passStringToWasm(arg: string): [number, number] {
|
||||
if (typeof(arg) !== 'string')
|
||||
throw new Error('expected a string argument');
|
||||
const buf = new TextEncoder('utf-8').encode(arg);
|
||||
const len = buf.length;
|
||||
const ptr = wasm_exports.__wbindgen_malloc(len);
|
||||
let array = new Uint8Array(memory.buffer);
|
||||
array.set(buf, ptr);
|
||||
return [ptr, len];
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
fn expose_get_string_from_wasm(&mut self) {
|
||||
if !self.exposed_globals.insert("get_string_from_wasm") {
|
||||
return
|
||||
}
|
||||
if self.nodejs {
|
||||
self.expose_global_memory();
|
||||
self.globals.push_str("
|
||||
function getStringFromWasm(ptr: number, len: number): string {
|
||||
const buf = Buffer.from(memory.buffer).slice(ptr, ptr + len);
|
||||
const ret = buf.toString();
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
} else {
|
||||
self.expose_global_memory();
|
||||
self.globals.push_str("
|
||||
function getStringFromWasm(ptr: number, len: number): string {
|
||||
const mem = new Uint8Array(memory.buffer);
|
||||
const slice = mem.slice(ptr, ptr + len);
|
||||
const ret = new TextDecoder('utf-8').decode(slice);
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
||||
|
||||
fn expose_assert_class(&mut self) {
|
||||
if !self.exposed_globals.insert("assert_class") {
|
||||
return
|
||||
}
|
||||
self.globals.push_str("
|
||||
function _assertClass(instance: any, klass: any) {
|
||||
if (!(instance instanceof klass))
|
||||
throw new Error(`expected instance of ${klass.name}`);
|
||||
return instance.__wasmPtr;
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_borrowed_objects(&mut self) {
|
||||
if !self.exposed_globals.insert("borrowed_objects") {
|
||||
return
|
||||
}
|
||||
self.expose_global_stack();
|
||||
self.globals.push_str("
|
||||
function addBorrowedObject(obj: any): number {
|
||||
stack.push(obj);
|
||||
return ((stack.length - 1) << 1) | 1;
|
||||
}
|
||||
|
||||
function popBorrowedObject(): void {
|
||||
stack.pop();
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_take_object(&mut self) {
|
||||
if !self.exposed_globals.insert("take_object") {
|
||||
return
|
||||
}
|
||||
self.expose_get_object();
|
||||
self.expose_drop_ref();
|
||||
self.globals.push_str("
|
||||
function takeObject(idx: number): any {
|
||||
const ret = getObject(idx);
|
||||
dropRef(idx);
|
||||
return ret;
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
fn expose_add_heap_object(&mut self) {
|
||||
if !self.exposed_globals.insert("add_heap_object") {
|
||||
return
|
||||
}
|
||||
self.expose_global_slab();
|
||||
self.expose_global_slab_next();
|
||||
self.globals.push_str("
|
||||
function addHeapObject(obj: any): number {
|
||||
if (slab_next == slab.length) {
|
||||
slab.push(slab.length + 1);
|
||||
}
|
||||
const idx = slab_next;
|
||||
const next = slab[idx];
|
||||
if (typeof(next) !== 'number')
|
||||
throw new Error('corrupt slab');
|
||||
slab_next = next;
|
||||
slab[idx] = { obj, cnt: 1 };
|
||||
return idx << 1;
|
||||
}
|
||||
");
|
||||
}
|
||||
}
|
@ -16,14 +16,14 @@ Usage:
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
--output-js FILE Output JS file
|
||||
--output-ts FILE Output TypeScript file
|
||||
--output-wasm FILE Output WASM file
|
||||
--nodejs Generate output for node.js, not the browser
|
||||
";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Args {
|
||||
flag_output_js: Option<PathBuf>,
|
||||
flag_output_ts: Option<PathBuf>,
|
||||
flag_output_wasm: Option<PathBuf>,
|
||||
flag_nodejs: bool,
|
||||
arg_input: PathBuf,
|
||||
@ -38,10 +38,10 @@ fn main() {
|
||||
b.input_path(&args.arg_input);
|
||||
b.nodejs(args.flag_nodejs);
|
||||
let ret = b.generate().expect("failed to generate bindings");
|
||||
if let Some(ref js) = args.flag_output_js {
|
||||
ret.write_js_to(js).expect("failed to write JS output file");
|
||||
if let Some(ref ts) = args.flag_output_ts {
|
||||
ret.write_ts_to(ts).expect("failed to write TypeScript output file");
|
||||
} else {
|
||||
println!("{}", ret.generate_js());
|
||||
println!("{}", ret.generate_ts());
|
||||
}
|
||||
if let Some(ref wasm) = args.flag_output_wasm {
|
||||
ret.write_wasm_to(wasm).expect("failed to write wasm output file");
|
||||
|
@ -31,10 +31,13 @@ fn simple() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import * as assert from "assert";
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export function test(wasm) {
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
const r = wasm.Foo.new();
|
||||
assert.strictEqual(r.add(0), 0);
|
||||
assert.strictEqual(r.add(1), 1);
|
||||
@ -91,10 +94,13 @@ fn strings() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import * as assert from "assert";
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export function test(wasm) {
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
const r = wasm.Foo.new();
|
||||
r.set(3);
|
||||
let bar = r.bar('baz');
|
||||
@ -143,9 +149,10 @@ fn exceptions() {
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
import * as assert from "assert";
|
||||
var assert = require("assert");
|
||||
|
||||
export function test(wasm) {
|
||||
exports.imports = {};
|
||||
exports.test = function(wasm) {
|
||||
assert.throws(() => new wasm.A(), /cannot invoke `new` directly/);
|
||||
let a = wasm.A.new();
|
||||
a.free();
|
||||
@ -161,6 +168,63 @@ fn exceptions() {
|
||||
assert.throws(() => c.foo(d), /expected instance of A/);
|
||||
d.free();
|
||||
c.free();
|
||||
};
|
||||
"#)
|
||||
.file("test.d.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export const imports: Imports;
|
||||
|
||||
export function test(wasm: Exports): void;
|
||||
"#)
|
||||
.test();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pass_one_to_another() {
|
||||
test_support::project()
|
||||
.file("src/lib.rs", r#"
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate wasm_bindgen;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
wasm_bindgen! {
|
||||
pub struct A {}
|
||||
|
||||
impl A {
|
||||
pub fn new() -> A {
|
||||
A {}
|
||||
}
|
||||
|
||||
pub fn foo(&self, _other: &B) {
|
||||
}
|
||||
|
||||
pub fn bar(&self, _other: B) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct B {}
|
||||
|
||||
impl B {
|
||||
pub fn new() -> B {
|
||||
B {}
|
||||
}
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
let a = wasm.A.new();
|
||||
let b = wasm.B.new();
|
||||
a.foo(b);
|
||||
a.bar(b);
|
||||
a.free();
|
||||
}
|
||||
"#)
|
||||
.test();
|
||||
|
@ -23,29 +23,28 @@ fn simple() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = null;
|
||||
let ANOTHER_ARG = null;
|
||||
let ARG: string | null = null;
|
||||
let ANOTHER_ARG: number | null = null;
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
assert.strictEqual(s, "foo");
|
||||
ARG = s;
|
||||
},
|
||||
another(s) {
|
||||
assert.strictEqual(ANOTHER_ARG, null);
|
||||
assert.strictEqual(s, 21);
|
||||
ANOTHER_ARG = s;
|
||||
return 35;
|
||||
},
|
||||
export const imports: Imports = {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
assert.strictEqual(s, "foo");
|
||||
ARG = s;
|
||||
},
|
||||
another(s) {
|
||||
assert.strictEqual(ANOTHER_ARG, null);
|
||||
assert.strictEqual(s, 21);
|
||||
ANOTHER_ARG = s;
|
||||
return 35;
|
||||
},
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
export function test(wasm: Exports) {
|
||||
assert.strictEqual(ARG, null);
|
||||
wasm.bar("foo");
|
||||
assert.strictEqual(ARG, "foo");
|
||||
|
@ -19,21 +19,20 @@ fn simple() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = null;
|
||||
let ARG: string | null = null;
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
ARG = s;
|
||||
},
|
||||
export const imports: Imports = {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
ARG = s;
|
||||
},
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
export function test(wasm: Exports) {
|
||||
assert.strictEqual(ARG, null);
|
||||
let sym = Symbol('test');
|
||||
wasm.bar(sym);
|
||||
@ -62,21 +61,20 @@ fn owned() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = null;
|
||||
let ARG: Symbol | null = null;
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
ARG = s;
|
||||
},
|
||||
export const imports: Imports = {
|
||||
foo(s) {
|
||||
assert.strictEqual(ARG, null);
|
||||
ARG = s;
|
||||
},
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
export function test(wasm: Exports) {
|
||||
assert.strictEqual(ARG, null);
|
||||
let sym = Symbol('test');
|
||||
wasm.bar(sym);
|
||||
@ -114,22 +112,21 @@ fn clone() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = Symbol('test');
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo1(s) { assert.strictEqual(s, ARG); },
|
||||
foo2(s) { assert.strictEqual(s, ARG); },
|
||||
foo3(s) { assert.strictEqual(s, ARG); },
|
||||
foo4(s) { assert.strictEqual(s, ARG); },
|
||||
foo5(s) { assert.strictEqual(s, ARG); },
|
||||
},
|
||||
export const imports: Imports = {
|
||||
foo1(s) { assert.strictEqual(s, ARG); },
|
||||
foo2(s) { assert.strictEqual(s, ARG); },
|
||||
foo3(s) { assert.strictEqual(s, ARG); },
|
||||
foo4(s) { assert.strictEqual(s, ARG); },
|
||||
foo5(s) { assert.strictEqual(s, ARG); },
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
export function test(wasm: Exports) {
|
||||
wasm.bar(ARG);
|
||||
}
|
||||
"#)
|
||||
@ -162,21 +159,20 @@ fn promote() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
import * as assert from "assert";
|
||||
|
||||
let ARG = Symbol('test');
|
||||
|
||||
export const imports = {
|
||||
env: {
|
||||
foo1(s) { assert.strictEqual(s, ARG); },
|
||||
foo2(s) { assert.strictEqual(s, ARG); },
|
||||
foo3(s) { assert.strictEqual(s, ARG); },
|
||||
foo4(s) { assert.strictEqual(s, ARG); },
|
||||
},
|
||||
export const imports: Imports = {
|
||||
foo1(s) { assert.strictEqual(s, ARG); },
|
||||
foo2(s) { assert.strictEqual(s, ARG); },
|
||||
foo3(s) { assert.strictEqual(s, ARG); },
|
||||
foo4(s) { assert.strictEqual(s, ARG); },
|
||||
};
|
||||
|
||||
export function test(wasm) {
|
||||
export function test(wasm: Exports) {
|
||||
wasm.bar(ARG);
|
||||
}
|
||||
"#)
|
||||
|
@ -24,10 +24,13 @@ fn add() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import * as assert from "assert";
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export function test(wasm) {
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
assert.strictEqual(wasm.add(1, 2), 3);
|
||||
assert.strictEqual(wasm.add(2, 3), 5);
|
||||
assert.strictEqual(wasm.add3(2), 5);
|
||||
@ -58,8 +61,12 @@ fn string_arguments() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
export function test(wasm) {
|
||||
.file("test.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
wasm.assert_foo("foo");
|
||||
wasm.assert_foo_and_bar("foo2", "bar");
|
||||
}
|
||||
@ -89,10 +96,13 @@ fn return_a_string() {
|
||||
}
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
.file("test.ts", r#"
|
||||
import * as assert from "assert";
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export function test(wasm) {
|
||||
export const imports: Imports = {};
|
||||
|
||||
export function test(wasm: Exports) {
|
||||
assert.strictEqual(wasm.clone("foo"), "foo");
|
||||
assert.strictEqual(wasm.clone("another"), "another");
|
||||
assert.strictEqual(wasm.concat("a", "b", 3), "a b 3");
|
||||
@ -118,12 +128,20 @@ fn exceptions() {
|
||||
}
|
||||
"#)
|
||||
.file("test.js", r#"
|
||||
import * as assert from "assert";
|
||||
var assert = require("assert");
|
||||
|
||||
export function test(wasm) {
|
||||
exports.imports = {};
|
||||
exports.test = function(wasm) {
|
||||
assert.throws(() => wasm.foo('a'), /expected a number argument/);
|
||||
assert.throws(() => wasm.bar(3), /expected a string argument/);
|
||||
}
|
||||
};
|
||||
"#)
|
||||
.file("test.d.ts", r#"
|
||||
import { Exports, Imports } from "./out";
|
||||
|
||||
export const imports: Imports;
|
||||
|
||||
export function test(wasm: Exports): void;
|
||||
"#)
|
||||
.test();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user