mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-31 01:11:06 +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)
|
* Managing arguments between JS/Rust (strings, numbers, classes, etc)
|
||||||
* Importing JS functions with richer types (strings)
|
* Importing JS functions with richer types (strings)
|
||||||
* Receiving arbitrary JS objects in Rust, passing them through to JS
|
* 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:
|
Planned features include:
|
||||||
|
|
||||||
* An optional flag to generate Typescript bindings
|
|
||||||
* Field setters/getters in JS through Rust functions
|
* Field setters/getters in JS through Rust functions
|
||||||
* ... and more coming soon!
|
* ... 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 \
|
$ wasm-bindgen target/wasm32-unknown-unknown/release/js_hello_world.wasm \
|
||||||
--output-js hello.js \
|
--output-ts hello.ts \
|
||||||
--output-wasm hello.wasm
|
--output-wasm hello.wasm
|
||||||
```
|
```
|
||||||
|
|
||||||
This'll create a `hello.js` which binds the functions described in
|
This'll create a `hello.ts` (a TypeScript file) which binds the functions
|
||||||
`js_hello_world.wasm`, and the `hello.wasm` will be a little smaller than the
|
described in `js_hello_world.wasm`, and the `hello.wasm` will be a little
|
||||||
input `js_hello_world.wasm`, but it's otherwise equivalent. Note that `hello.js`
|
smaller than the input `js_hello_world.wasm`, but it's otherwise equivalent.
|
||||||
isn't very pretty so to read it you'll probably want to run it through a JS
|
Note that `hello.ts` isn't very pretty so to read it you'll probably want to run
|
||||||
formatter.
|
it through a formatter.
|
||||||
|
|
||||||
Note that `hello.js` uses ES6 modules for easier integration into transpilers
|
Typically you'll be feeding this typescript into a larger build system, and
|
||||||
like webpack/rollup/babel/etc. We're going to test out the syntax in the browser
|
often you'll be using this with your own typescript project as well. For now
|
||||||
with `index.html` below, but your browser may not natively support ES6 modules
|
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
|
just yet. To see more information about this, you can browse
|
||||||
[online](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import).
|
[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(resp => resp.arrayBuffer())
|
||||||
.then(bytes => {
|
.then(bytes => {
|
||||||
return instantiate(bytes, {
|
return instantiate(bytes, {
|
||||||
env: {
|
bar_on_reset(s, token) {
|
||||||
bar_on_reset(s, token) {
|
console.log(token);
|
||||||
console.log(token);
|
console.log(`this instance of bar was reset to ${s}`);
|
||||||
console.log(`this instance of bar was reset to ${s}`);
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(mod => {
|
.then(mod => {
|
||||||
|
@ -46,20 +46,22 @@ pub fn project() -> Project {
|
|||||||
|
|
||||||
("Cargo.lock".to_string(), lockfile),
|
("Cargo.lock".to_string(), lockfile),
|
||||||
|
|
||||||
("run.js".to_string(), r#"
|
("run.ts".to_string(), r#"
|
||||||
var fs = require("fs");
|
import * as fs from "fs";
|
||||||
var out = require("./out.compat");
|
import * as process from "process";
|
||||||
var test = require("./test.compat");
|
|
||||||
var wasm = fs.readFileSync("out.wasm");
|
|
||||||
var process = require("process");
|
|
||||||
|
|
||||||
out.instantiate(wasm, test.imports).then(m => {
|
import { instantiate } from "./out";
|
||||||
test.test(m);
|
import * as test from "./test";
|
||||||
if (m.assertHeapAndStackEmpty)
|
|
||||||
m.assertHeapAndStackEmpty();
|
var wasm = fs.readFileSync("out.wasm");
|
||||||
}).catch(function(error) {
|
|
||||||
console.error(error);
|
instantiate(wasm, test.imports).then(m => {
|
||||||
process.exit(1);
|
test.test(m);
|
||||||
|
if (m.assertHeapAndStackEmpty)
|
||||||
|
m.assertHeapAndStackEmpty();
|
||||||
|
}).catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
"#.to_string()),
|
"#.to_string()),
|
||||||
],
|
],
|
||||||
@ -78,7 +80,7 @@ pub fn root() -> PathBuf {
|
|||||||
return me
|
return me
|
||||||
}
|
}
|
||||||
|
|
||||||
fn babel() -> PathBuf {
|
fn typescript() -> PathBuf {
|
||||||
static INIT: Once = ONCE_INIT;
|
static INIT: Once = ONCE_INIT;
|
||||||
|
|
||||||
let mut me = env::current_exe().unwrap();
|
let mut me = env::current_exe().unwrap();
|
||||||
@ -86,7 +88,7 @@ fn babel() -> PathBuf {
|
|||||||
me.pop(); // chop off `deps`
|
me.pop(); // chop off `deps`
|
||||||
me.pop(); // chop off `debug` / `release`
|
me.pop(); // chop off `debug` / `release`
|
||||||
let install_dir = me.clone();
|
let install_dir = me.clone();
|
||||||
me.push("node_modules/babel-cli/bin/babel.js");
|
me.push("node_modules/typescript/bin/tsc");
|
||||||
|
|
||||||
INIT.call_once(|| {
|
INIT.call_once(|| {
|
||||||
if !me.exists() {
|
if !me.exists() {
|
||||||
@ -99,8 +101,9 @@ fn babel() -> PathBuf {
|
|||||||
};
|
};
|
||||||
run(npm
|
run(npm
|
||||||
.arg("install")
|
.arg("install")
|
||||||
.arg("babel-cli")
|
.arg("typescript")
|
||||||
.arg("babel-preset-env")
|
.arg("@types/node")
|
||||||
|
.arg("@types/webassembly-js-api")
|
||||||
.current_dir(&install_dir), "npm");
|
.current_dir(&install_dir), "npm");
|
||||||
assert!(me.exists());
|
assert!(me.exists());
|
||||||
}
|
}
|
||||||
@ -151,20 +154,21 @@ impl Project {
|
|||||||
.debug(true)
|
.debug(true)
|
||||||
.generate()
|
.generate()
|
||||||
.expect("failed to run bindgen");
|
.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");
|
obj.write_wasm_to(root.join("out.wasm")).expect("failed to write wasm");
|
||||||
|
|
||||||
let mut cmd = Command::new("node");
|
let mut cmd = Command::new("node");
|
||||||
cmd.arg(babel())
|
cmd.arg(typescript())
|
||||||
.arg(root.join("out.js"))
|
.current_dir(&target_dir)
|
||||||
.arg("--presets").arg("env")
|
.arg(root.join("run.ts"))
|
||||||
.arg("--out-file").arg(root.join("out.compat.js"));
|
.arg("--strict")
|
||||||
run(&mut cmd, "node");
|
.arg("--noImplicitAny")
|
||||||
let mut cmd = Command::new("node");
|
.arg("--strictNullChecks")
|
||||||
cmd.arg(babel())
|
.arg("--strictFunctionTypes")
|
||||||
.arg(root.join("test.js"))
|
.arg("--noUnusedLocals")
|
||||||
.arg("--presets").arg("env")
|
.arg("--noImplicitReturns")
|
||||||
.arg("--out-file").arg(root.join("test.compat.js"));
|
.arg("--lib")
|
||||||
|
.arg("es6");
|
||||||
run(&mut cmd, "node");
|
run(&mut cmd, "node");
|
||||||
|
|
||||||
let mut cmd = Command::new("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 failure::{Error, ResultExt};
|
||||||
use parity_wasm::elements::*;
|
use parity_wasm::elements::*;
|
||||||
|
|
||||||
mod js;
|
mod ts;
|
||||||
|
|
||||||
pub struct Bindgen {
|
pub struct Bindgen {
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
@ -69,16 +69,16 @@ impl Bindgen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
pub fn write_js_to<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
pub fn write_ts_to<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
|
||||||
self._write_js_to(path.as_ref())
|
self._write_ts_to(path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _write_js_to(&self, path: &Path) -> Result<(), Error> {
|
fn _write_ts_to(&self, path: &Path) -> Result<(), Error> {
|
||||||
let js = self.generate_js();
|
let ts = self.generate_ts();
|
||||||
let mut f = File::create(path).with_context(|_| {
|
let mut f = File::create(path).with_context(|_| {
|
||||||
format!("failed to create file at {:?}", path)
|
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)
|
format!("failed to write file at {:?}", path)
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -95,12 +95,12 @@ impl Object {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_js(&self) -> String {
|
pub fn generate_ts(&self) -> String {
|
||||||
let mut js = js::Js::default();
|
let mut ts = ts::Js::default();
|
||||||
js.nodejs = self.nodejs;
|
ts.nodejs = self.nodejs;
|
||||||
js.debug = self.debug;
|
ts.debug = self.debug;
|
||||||
js.generate_program(&self.program);
|
ts.generate_program(&self.program, &self.module);
|
||||||
js.to_string()
|
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:
|
Options:
|
||||||
-h --help Show this screen.
|
-h --help Show this screen.
|
||||||
--output-js FILE Output JS file
|
--output-ts FILE Output TypeScript file
|
||||||
--output-wasm FILE Output WASM file
|
--output-wasm FILE Output WASM file
|
||||||
--nodejs Generate output for node.js, not the browser
|
--nodejs Generate output for node.js, not the browser
|
||||||
";
|
";
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct Args {
|
struct Args {
|
||||||
flag_output_js: Option<PathBuf>,
|
flag_output_ts: Option<PathBuf>,
|
||||||
flag_output_wasm: Option<PathBuf>,
|
flag_output_wasm: Option<PathBuf>,
|
||||||
flag_nodejs: bool,
|
flag_nodejs: bool,
|
||||||
arg_input: PathBuf,
|
arg_input: PathBuf,
|
||||||
@ -38,10 +38,10 @@ fn main() {
|
|||||||
b.input_path(&args.arg_input);
|
b.input_path(&args.arg_input);
|
||||||
b.nodejs(args.flag_nodejs);
|
b.nodejs(args.flag_nodejs);
|
||||||
let ret = b.generate().expect("failed to generate bindings");
|
let ret = b.generate().expect("failed to generate bindings");
|
||||||
if let Some(ref js) = args.flag_output_js {
|
if let Some(ref ts) = args.flag_output_ts {
|
||||||
ret.write_js_to(js).expect("failed to write JS output file");
|
ret.write_ts_to(ts).expect("failed to write TypeScript output file");
|
||||||
} else {
|
} else {
|
||||||
println!("{}", ret.generate_js());
|
println!("{}", ret.generate_ts());
|
||||||
}
|
}
|
||||||
if let Some(ref wasm) = args.flag_output_wasm {
|
if let Some(ref wasm) = args.flag_output_wasm {
|
||||||
ret.write_wasm_to(wasm).expect("failed to write wasm output file");
|
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 * 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();
|
const r = wasm.Foo.new();
|
||||||
assert.strictEqual(r.add(0), 0);
|
assert.strictEqual(r.add(0), 0);
|
||||||
assert.strictEqual(r.add(1), 1);
|
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 * 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();
|
const r = wasm.Foo.new();
|
||||||
r.set(3);
|
r.set(3);
|
||||||
let bar = r.bar('baz');
|
let bar = r.bar('baz');
|
||||||
@ -143,9 +149,10 @@ fn exceptions() {
|
|||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
.file("test.js", r#"
|
.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/);
|
assert.throws(() => new wasm.A(), /cannot invoke `new` directly/);
|
||||||
let a = wasm.A.new();
|
let a = wasm.A.new();
|
||||||
a.free();
|
a.free();
|
||||||
@ -161,6 +168,63 @@ fn exceptions() {
|
|||||||
assert.throws(() => c.foo(d), /expected instance of A/);
|
assert.throws(() => c.foo(d), /expected instance of A/);
|
||||||
d.free();
|
d.free();
|
||||||
c.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();
|
.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";
|
import * as assert from "assert";
|
||||||
|
|
||||||
let ARG = null;
|
let ARG: string | null = null;
|
||||||
let ANOTHER_ARG = null;
|
let ANOTHER_ARG: number | null = null;
|
||||||
|
|
||||||
export const imports = {
|
export const imports: Imports = {
|
||||||
env: {
|
foo(s) {
|
||||||
foo(s) {
|
assert.strictEqual(ARG, null);
|
||||||
assert.strictEqual(ARG, null);
|
assert.strictEqual(s, "foo");
|
||||||
assert.strictEqual(s, "foo");
|
ARG = s;
|
||||||
ARG = s;
|
},
|
||||||
},
|
another(s) {
|
||||||
another(s) {
|
assert.strictEqual(ANOTHER_ARG, null);
|
||||||
assert.strictEqual(ANOTHER_ARG, null);
|
assert.strictEqual(s, 21);
|
||||||
assert.strictEqual(s, 21);
|
ANOTHER_ARG = s;
|
||||||
ANOTHER_ARG = s;
|
return 35;
|
||||||
return 35;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm: Exports) {
|
||||||
assert.strictEqual(ARG, null);
|
assert.strictEqual(ARG, null);
|
||||||
wasm.bar("foo");
|
wasm.bar("foo");
|
||||||
assert.strictEqual(ARG, "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";
|
import * as assert from "assert";
|
||||||
|
|
||||||
let ARG = null;
|
let ARG: string | null = null;
|
||||||
|
|
||||||
export const imports = {
|
export const imports: Imports = {
|
||||||
env: {
|
foo(s) {
|
||||||
foo(s) {
|
assert.strictEqual(ARG, null);
|
||||||
assert.strictEqual(ARG, null);
|
ARG = s;
|
||||||
ARG = s;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm: Exports) {
|
||||||
assert.strictEqual(ARG, null);
|
assert.strictEqual(ARG, null);
|
||||||
let sym = Symbol('test');
|
let sym = Symbol('test');
|
||||||
wasm.bar(sym);
|
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";
|
import * as assert from "assert";
|
||||||
|
|
||||||
let ARG = null;
|
let ARG: Symbol | null = null;
|
||||||
|
|
||||||
export const imports = {
|
export const imports: Imports = {
|
||||||
env: {
|
foo(s) {
|
||||||
foo(s) {
|
assert.strictEqual(ARG, null);
|
||||||
assert.strictEqual(ARG, null);
|
ARG = s;
|
||||||
ARG = s;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm: Exports) {
|
||||||
assert.strictEqual(ARG, null);
|
assert.strictEqual(ARG, null);
|
||||||
let sym = Symbol('test');
|
let sym = Symbol('test');
|
||||||
wasm.bar(sym);
|
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";
|
import * as assert from "assert";
|
||||||
|
|
||||||
let ARG = Symbol('test');
|
let ARG = Symbol('test');
|
||||||
|
|
||||||
export const imports = {
|
export const imports: Imports = {
|
||||||
env: {
|
foo1(s) { assert.strictEqual(s, ARG); },
|
||||||
foo1(s) { assert.strictEqual(s, ARG); },
|
foo2(s) { assert.strictEqual(s, ARG); },
|
||||||
foo2(s) { assert.strictEqual(s, ARG); },
|
foo3(s) { assert.strictEqual(s, ARG); },
|
||||||
foo3(s) { assert.strictEqual(s, ARG); },
|
foo4(s) { assert.strictEqual(s, ARG); },
|
||||||
foo4(s) { assert.strictEqual(s, ARG); },
|
foo5(s) { assert.strictEqual(s, ARG); },
|
||||||
foo5(s) { assert.strictEqual(s, ARG); },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm: Exports) {
|
||||||
wasm.bar(ARG);
|
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";
|
import * as assert from "assert";
|
||||||
|
|
||||||
let ARG = Symbol('test');
|
let ARG = Symbol('test');
|
||||||
|
|
||||||
export const imports = {
|
export const imports: Imports = {
|
||||||
env: {
|
foo1(s) { assert.strictEqual(s, ARG); },
|
||||||
foo1(s) { assert.strictEqual(s, ARG); },
|
foo2(s) { assert.strictEqual(s, ARG); },
|
||||||
foo2(s) { assert.strictEqual(s, ARG); },
|
foo3(s) { assert.strictEqual(s, ARG); },
|
||||||
foo3(s) { assert.strictEqual(s, ARG); },
|
foo4(s) { assert.strictEqual(s, ARG); },
|
||||||
foo4(s) { assert.strictEqual(s, ARG); },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function test(wasm) {
|
export function test(wasm: Exports) {
|
||||||
wasm.bar(ARG);
|
wasm.bar(ARG);
|
||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
|
@ -24,10 +24,13 @@ fn add() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
.file("test.js", r#"
|
.file("test.ts", r#"
|
||||||
import * as assert from "assert";
|
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(1, 2), 3);
|
||||||
assert.strictEqual(wasm.add(2, 3), 5);
|
assert.strictEqual(wasm.add(2, 3), 5);
|
||||||
assert.strictEqual(wasm.add3(2), 5);
|
assert.strictEqual(wasm.add3(2), 5);
|
||||||
@ -58,8 +61,12 @@ fn string_arguments() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
.file("test.js", r#"
|
.file("test.ts", r#"
|
||||||
export function test(wasm) {
|
import { Exports, Imports } from "./out";
|
||||||
|
|
||||||
|
export const imports: Imports = {};
|
||||||
|
|
||||||
|
export function test(wasm: Exports) {
|
||||||
wasm.assert_foo("foo");
|
wasm.assert_foo("foo");
|
||||||
wasm.assert_foo_and_bar("foo2", "bar");
|
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 * 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("foo"), "foo");
|
||||||
assert.strictEqual(wasm.clone("another"), "another");
|
assert.strictEqual(wasm.clone("another"), "another");
|
||||||
assert.strictEqual(wasm.concat("a", "b", 3), "a b 3");
|
assert.strictEqual(wasm.concat("a", "b", 3), "a b 3");
|
||||||
@ -118,12 +128,20 @@ fn exceptions() {
|
|||||||
}
|
}
|
||||||
"#)
|
"#)
|
||||||
.file("test.js", r#"
|
.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.foo('a'), /expected a number argument/);
|
||||||
assert.throws(() => wasm.bar(3), /expected a string 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();
|
.test();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user