add char support (#206)

* add char support

* add char test

* remove __wbindgen_char fns

* re-order travis script

* update serve script

* remove binds to unused char functions

* add more wide character items to chars list

* remove unused code

* add char to readme

* remove built file
This commit is contained in:
Robert Masen
2018-05-22 12:34:41 -05:00
committed by Alex Crichton
parent 17861a45ab
commit 4ddd93d75d
20 changed files with 437 additions and 5 deletions

View File

@ -51,9 +51,12 @@ matrix:
(cd examples/dom && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
- |
(cd examples/wasm-in-wasm && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
- |
(cd examples/char && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
- |
(cd examples/closures && sed -i 's/: "webpack-dev-server"/: "webpack"/' package.json && ./build.sh)
install:
- curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
- source ~/.nvm/nvm.sh

View File

@ -44,6 +44,7 @@ members = [
"examples/no_modules",
"examples/add",
"examples/asm.js",
"examples/char",
]
[profile.release]

View File

@ -32,6 +32,7 @@ tys! {
ANYREF
ENUM
RUST_STRUCT
CHAR
}
#[derive(Debug)]
@ -57,6 +58,7 @@ pub enum Descriptor {
Anyref,
Enum,
RustStruct(String),
Char,
}
#[derive(Debug)]
@ -122,6 +124,7 @@ impl Descriptor {
.collect();
Descriptor::RustStruct(name)
}
CHAR => Descriptor::Char,
other => panic!("unknown descriptor: {}", other),
}
}

View File

@ -3,7 +3,7 @@ use failure::Error;
use super::{indent, Context};
use descriptor::{Descriptor, Function};
/// Helper struct for manfuacturing a shim in JS used to translate JS types to
/// Helper struct for manufacturing a shim in JS used to translate JS types to
/// Rust, aka pass from JS back into Rust
pub struct Js2Rust<'a, 'b: 'a> {
cx: &'a mut Context<'b>,
@ -210,6 +210,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
}
self.rust_arguments.push(format!("{} ? 1 : 0", name));
}
Descriptor::Char => {
self.js_arguments.push((name.clone(), "string".to_string()));
self.rust_arguments.push(format!("{}.codePointAt(0)", name))
},
Descriptor::Anyref => {
self.js_arguments.push((name.clone(), "any".to_string()));
self.cx.expose_add_heap_object();
@ -299,6 +303,10 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
self.ret_ty = "boolean".to_string();
self.ret_expr = format!("return (RET) !== 0;");
}
Descriptor::Char => {
self.ret_ty = "string".to_string();
self.ret_expr = format!("return String.fromCodePoint(RET)")
}
Descriptor::Anyref => {
self.ret_ty = "any".to_string();
self.cx.expose_take_object();
@ -316,7 +324,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
/// invoking, like `wasm.bar` or `this.f`.
///
/// Returns two strings, the first of which is the JS expression for the
/// generated function shim and the second is a TyepScript signature of rthe
/// generated function shim and the second is a TypeScript signature of the
/// JS expression.
pub fn finish(&self, prefix: &str, invoc: &str) -> (String, String) {
let js_args = self.js_arguments

View File

@ -3,7 +3,7 @@ use failure::Error;
use descriptor::{Descriptor, Function};
use super::{indent, Context, Js2Rust};
/// Helper struct for manfuacturing a shim in JS used to translate Rust types to
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
/// JS, then invoking an imported JS function.
pub struct Rust2Js<'a, 'b: 'a> {
cx: &'a mut Context<'b>,
@ -211,6 +211,9 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
let invoc_arg = match *arg {
ref d if d.is_number() => abi,
Descriptor::Boolean => format!("{} !== 0", abi),
Descriptor::Char => {
format!("String.fromCodePoint({})", abi)
}
Descriptor::Anyref => {
self.cx.expose_take_object();
format!("takeObject({})", abi)
@ -269,6 +272,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
}
self.ret_expr = match *ty {
Descriptor::Boolean => "return JS ? 1 : 0;".to_string(),
Descriptor::Char => "return JS.codePointAt(0)".to_string(),
Descriptor::Anyref => {
self.cx.expose_add_heap_object();
"return addHeapObject(JS);".to_string()

View File

@ -33,5 +33,6 @@ The examples here are:
numbers.
* `asm.js` - an example of using the `wasm2asm` tool from [binaryen] to convert
the generated WebAssembly to normal JS
* `char` - an example of passing the rust `char` type to and from the js `string` type
[binaryen]: https://github.com/WebAssembly/binaryen

4
examples/char/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
package-lock.json
char.js
char_bg.js
char_bg.wasm

14
examples/char/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "char"
version = "0.1.0"
authors = ["Robert Masen <r@robertmasen.com>"]
[lib]
crate-type = ["cdylib"]
[dependencies]
# Here we're using a path dependency to use what's already in this repository,
# but you'd use the commented out version below if you're copying this into your
# project.
wasm-bindgen = { path = "../.." }
#wasm-bindgen = "0.2"

13
examples/char/README.md Normal file
View File

@ -0,0 +1,13 @@
# Char
This directory is an example of how the `#[wasm_bindgen]` macro will convert the rust `char` type to a single code-point js `string`.
You can build the example locally with:
```
$ ./build.sh
```
Opening your web browser should display a single counter with a random character for it's `key` and 0 for its `count`. You can click the `+` button to increase a counter's count. By clicking on the "add counter" button you should see a new counter added to the list with a different random character for it's `key`.
Under the hood javascript is choosing a random character from an Array of characters and passing that to the rust Counter struct's constructor so the character you are seeing on the page has made the full round trip from js to rust and back to js.

22
examples/char/build.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/sh
set -ex
# Build the `hello_world.wasm` file using Cargo/rustc
cargo +nightly build --target wasm32-unknown-unknown
# Run the `wasm-bindgen` CLI tool to postprocess the wasm file emitted by the
# Rust compiler to emit the JS support glue that's necessary
#
# Here we're using the version of the CLI in this repository, but for external
# usage you'd use the commented out version below
cargo +nightly run --manifest-path ../../crates/cli/Cargo.toml \
--bin wasm-bindgen -- \
../../target/wasm32-unknown-unknown/debug/char.wasm --out-dir .
# wasm-bindgen ../../target/wasm32-unknown-unknown/hello_world.wasm --out-dir .
# Finally, package everything up using Webpack and start a server so we can
# browse the result
npm install
npm run serve

85
examples/char/chars.js Normal file
View File

@ -0,0 +1,85 @@
export let chars = [
"!","#","$","%","&","'","(",")","*","+",",",
"-",".","/","0","1","2","3","4","5","6","7",
"8","9",":",";","<","=",">","?","@","A","B",
"C","D","E","F","G","H","I","J","K","L","M",
"N","O","P","Q","R","S","T","U","V","W","X",
"Y","Z","[","]","^","_","`","a","b","c",
"d","e","f","g","h","i","j","k","l","m","n",
"o","p","q","r","s","t","u","v","w","x","y",
"z","{","|","}","~"," ","¡","¢","£","¤","¥",
"¦","§","¨","©","ª","«","¬","®","¯","°",
"±","²","³","´","µ","¶","·","¸","¹","º","»",
"¼","½","¾","¿","À","Á","Â","Ã","Ä","Å","Æ",
"Ç","È","É","Ê","Ë","Ì","Í","Î","Ï","Ð","Ñ",
"Ò","Ó","Ô","Õ","Ö","×","Ø","Ù","Ú","Û","Ü",
"Ý","Þ","ß","à","á","â","ã","ä","å","æ","ç",
"è","é","ê","ë","ì","í","î","ï","ð","ñ","ò",
"ó","ô","õ","ö","÷","ø","ù","ú","û","ü","ý",
"þ","ÿ","Ā","ā","Ă","ă","Ą","ą","Ć","ć","Ĉ",
"ĉ","Ċ","ċ","Č","č","Ď","ď","Đ","đ","Ē","ē",
"Ĕ","ĕ","Ė","ė","Ę","ę","Ě","ě","Ĝ","ĝ","Ğ",
"ğ","Ġ","ġ","Ģ","ģ","Ĥ","ĥ","Ħ","ħ","Ĩ","ĩ",
"Ī","ī","Ĭ","ĭ","Į","į","İ","ı","IJ","ij","Ĵ",
"ĵ","Ķ","ķ","ĸ","Ĺ","ĺ","Ļ","ļ","Ľ","ľ","Ŀ",
"ŀ","Ł","ł","Ń","ń","Ņ","ņ","Ň","ň","ʼn","Ŋ",
"ŋ","Ō","ō","Ŏ","ŏ","Ő","ő","Œ","œ","Ŕ","ŕ",
"Ŗ","ŗ","Ř","ř","Ś","ś","Ŝ","ŝ","Ş","ş","Š",
"š","Ţ","ţ","Ť","ť","Ŧ","ŧ","Ũ","ũ","Ū","ū",
"Ŭ","ŭ","Ů","ů","Ű","ű","Ų","ų","Ŵ","ŵ","Ŷ",
"ŷ","Ÿ","Ź","ź","Ż","ż","Ž","ž","ſ","ƀ","Ɓ",
"Ƃ","ƃ","Ƅ","ƅ","Ɔ","Ƈ","ƈ","Ɖ","Ɗ","Ƌ","ƌ",
"ƍ","Ǝ","Ə","Ɛ","Ƒ","ƒ","Ɠ","Ɣ","ƕ","Ɩ","Ɨ",
"Ƙ","ƙ","ƚ","ƛ","Ɯ","Ɲ","ƞ","Ɵ","Ơ","ơ","Ƣ",
"ƣ","Ƥ","ƥ","Ʀ","Ƨ","ƨ","Ʃ","ƪ","ƫ","Ƭ","ƭ",
"Ʈ","Ư","ư","Ʊ","Ʋ","Ƴ","ƴ","Ƶ","ƶ","Ʒ","Ƹ",
"ƹ","ƺ","ƻ","Ƽ","ƽ","ƾ","ƿ","ǀ","ǁ","ǂ","ǃ",
"DŽ","Dž","dž","LJ","Lj","lj","NJ","Nj","nj","Ǎ","ǎ",
"Ǐ","ǐ","Ǒ","ǒ","Ǔ","ǔ","Ǖ","ǖ","Ǘ","ǘ","Ǚ",
"ǚ","Ǜ","ǜ","ǝ","Ǟ","ǟ","Ǡ","ǡ","Ǣ","ǣ","Ǥ",
"ǥ","Ǧ","ǧ","Ǩ","ǩ","Ǫ","ǫ","Ǭ","ǭ","Ǯ","ǯ",
"ǰ","DZ","Dz","dz","Ǵ","ǵ","Ƕ","Ƿ","Ǹ","ǹ","Ǻ",
"ǻ","Ǽ","ǽ","Ǿ","ǿ","Ȁ","ȁ","Ȃ","ȃ","Ȅ","ȅ",
"Ȇ","ȇ","Ȉ","ȉ","Ȋ","ȋ","Ȍ","ȍ","Ȏ","ȏ","Ȑ",
"ȑ","Ȓ","ȓ","Ȕ","ȕ","Ȗ","ȗ","Ș","ș","Ț","ț",
"Ȝ","ȝ","Ȟ","ȟ","Ƞ","ȡ","Ȣ","ȣ","Ȥ","ȥ","Ȧ",
"ȧ","Ȩ","ȩ","Ȫ","ȫ","Ȭ","ȭ","Ȯ","ȯ","Ȱ","ȱ",
"Ȳ","ȳ","ȴ","ȵ","ȶ","ȷ","ȸ","ȹ","Ⱥ","Ȼ","ȼ",
"Ƚ","Ⱦ","ȿ","ɀ","Ɂ","ɂ","Ƀ","Ʉ","Ʌ","Ɇ","ɇ",
"Ɉ","ɉ","Ɋ","ɋ","Ɍ","ɍ","Ɏ","ɏ","ɐ","ɑ","ɒ",
"ɓ","ɔ","ɕ","ɖ","ɗ","ɘ","ə","ɚ","ɛ","ɜ","ɝ",
"ɞ","ɟ","ɠ","ɡ","ɢ","ɣ","ɤ","ɥ","ɦ","ɧ","ɨ",
"ɩ","ɪ","ɫ","ɬ","ɭ","ɮ","ɯ","ɰ","ɱ","ɲ","ɳ",
"ɴ","ɵ","ɶ","ɷ","ɸ","ɹ","ɺ","ɻ","ɼ","ɽ","ɾ",
"ɿ","ʀ","ʁ","ʂ","ʃ","ʄ","ʅ","ʆ","ʇ","ʈ","ʉ",
"ʊ","ʋ","ʌ","ʍ","ʎ","ʏ","ʐ","ʑ","ʒ","ʓ","ʔ",
"ʕ","ʖ","ʗ","ʘ","ʙ","ʚ","ʛ","ʜ","ʝ","ʞ","ʟ",
"ʠ","ʡ","ʢ","ʣ","ʤ","ʥ","ʦ","ʧ","ʨ","ʩ","ʪ",
"ʫ","ʬ","ʭ","ʮ","ʯ","Ͳ","ͳ","ʹ","͵","Ͷ","ͷ",
"ͺ","ͻ","ͼ","ͽ",";","Ϳ","΄","΅","Ά","·","Έ","Ή",
"Ί","Ό","Ύ","Ώ",
"ΐ","Α","Β","Γ","Δ","Ε","Ζ","Η","Θ","Ι","Κ",
"Λ","Μ","Ν","Ξ","Ο","Π","Ρ","Σ","Τ","Υ","Φ",
"Χ","Ψ","Ω","Ϊ","Ϋ","ά","έ","ή","ί","ΰ","α",
"β","γ","δ","ε","ζ","η","θ","ι","κ","λ","μ",
"ν","ξ","ο","π","ρ","ς","σ","τ","υ","φ","χ",
"ψ","ω","ϊ","ϋ","ό","ύ","ώ","Ϗ","ϐ","ϑ","ϒ",
"ϓ","ϔ","ϕ","ϖ","ϗ","Ϙ","ϙ","Ϛ","ϛ","Ϝ","ϝ",
"Ϟ","ϟ","Ϡ","ϡ","Ϣ","ϣ","Ϥ","ϥ","Ϧ","ϧ","Ϩ",
"ϩ","Ϫ","ϫ","Ϭ","ϭ","Ϯ","ϯ","ϰ","ϱ","ϲ","ϳ",
"Օ","Ֆ","🕧","🕨","🕩","🕪","🕫","🕬","🕭","🕮",
"🕯","🕰","🕱","🕲","🕳","🕴","🕵","🕶","🕷","🕸",
"🕹","🕺","🕻","🕼","🕽","🕾","🕿","🖀","🖁","🖂",
"🖃","🖄","🖅","🖆","🖇","🖈","🖉","🖊","🖋",
"🖌","🖍","🖎","🖏","🖐","🖑","🖒","🖓","🖔","🖕",
"🖖","🖗","🖘","🖙","🖚","🖛","🖜","🖝","🖞","🖟",
"🖠","🖡","🖢","🖣","🖤","🖥","🖦","🖧","🖨","🖩",
"🖪","🖫","🖬","🖭","🖮","🖯","🖰","🖱","🖲","🖳",
"🖴","🖵","🖶","🖷","🖸","🖹","🖺","🖻","🖼","🖽",
"🖾","🖿","🗀","🗁","🗂","🗃","🗄","🗅","🗆","🗇",
"🗈","🗉","🗊","🗋","🗌","🗍","🗎","🗏","🗐","🗑","🗒",
"🗓","🗔","🗕","🗖","🗗","🗘","🗙","🗚","🗛","🗜",
"🗝","🗞","🗟","🗠","🗡","🗢","🗣","🗤","🗥","🗦",
"🗧","🗨","🗩","🗪","🗫","🗬","🗭","🗮","🗯","🗰",
"🗱","🗲","🗳","🗴","🗵","🗶","🗷","🗸","🗹","🗺",
"🗻","🗼","🗽","🗾","🗿","😀"];

56
examples/char/index.html Normal file
View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Counter</title>
<style>
* {
font-family: sans-serif;
font-size: 16pt;
}
h1 {
font-size: 18pt;
font-weight: bold;
margin: 0;
}
button {
padding: 5px 10px;
border: none;
background: slategrey;
margin: 10px auto;
color: white;
}
body {
width: 400px;
margin: auto;
}
#container,
.counter {
display: flex;
flex-flow: column;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
}
.counter {
margin-bottom: 10px;
background: steelblue;
color: white;
align-items: center;
width: 100%;
}
.field {
display: flex;
flex-flow: row;
justify-content: space-around;
width: 100%;
}
</style>
</head>
<body>
<button id="add-counter" type="button">add counter</button>
<div id="container">
</div>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

73
examples/char/index.js Normal file
View File

@ -0,0 +1,73 @@
import {chars} from './chars.js'
let imp = import('./char.js')
let mod;
let counters = [];
imp.then(wasm => {
mod = wasm;
addCounter();
let b = document.getElementById('add-counter');
if (!b) throw new Error('Unable to find #add-counter');
b.addEventListener('click', ev => addCounter())
});
function addCounter() {
let ctr = mod.Counter.new(randomChar(), 0);
counters.push(ctr);
update();
}
function update() {
let container = document.getElementById('container');
if (!container) throw new Error('Unable to find #container in dom');
while (container.hasChildNodes()) {
if (container.lastChild.id == "add-counter") break;
container.removeChild(container.lastChild);
}
for (var i = 0; i < counters.length; i++) {
let counter = counters[i];
container.appendChild(newCounter(counter.key(), counter.count(), ev => {
counter.increment();
update();
}))
}
}
function randomChar() {
console.log('randomChar');
let idx = Math.floor(Math.random() * (chars.length - 1));
console.log('index', idx);
let ret = chars.splice(idx, 1)[0];
console.log('char', ret);
return ret;
}
function newCounter(key, value, cb) {
let container = document.createElement('div');
container.setAttribute('class', 'counter');
let title = document.createElement('h1');
title.appendChild(document.createTextNode('Counter ' + key));
container.appendChild(title);
container.appendChild(newField('Count', value));
let plus = document.createElement('button');
plus.setAttribute('type', 'button');
plus.setAttribute('class', 'plus-button');
plus.appendChild(document.createTextNode('+'));
plus.addEventListener('click', cb);
container.appendChild(plus);
return container;
}
function newField(key, value) {
let ret = document.createElement('div');
ret.setAttribute('class', 'field');
let name = document.createElement('span');
name.setAttribute('class', 'name');
name.appendChild(document.createTextNode(key));
ret.appendChild(name);
let val = document.createElement('span');
val.setAttribute('class', 'value');
val.appendChild(document.createTextNode(value));
ret.appendChild(val);
return ret;
}

View File

@ -0,0 +1,10 @@
{
"scripts": {
"serve": "webpack-dev-server"
},
"devDependencies": {
"webpack": "^4.8.3",
"webpack-cli": "^2.1.3",
"webpack-dev-server": "^3.1.4"
}
}

49
examples/char/src/lib.rs Normal file
View File

@ -0,0 +1,49 @@
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[derive(Debug)]
#[wasm_bindgen]
pub struct Counter {
key: char,
count: i32
}
#[wasm_bindgen]
impl Counter {
pub fn default() -> Counter {
log("Counter::default");
Self::new('a', 0)
}
pub fn new(key: char, count: i32) -> Counter {
log(&format!("Counter::new({}, {})", key, count));
Counter { key: key, count: count }
}
pub fn key(&self) -> char {
log("Counter.key()");
self.key
}
pub fn count(&self) -> i32 {
log("Counter.count");
self.count
}
pub fn increment(&mut self) {
log("Counter.increment");
self.count += 1;
}
pub fn update_key(&mut self, key: char) {
self.key = key;
}
}

View File

@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development"
};

View File

@ -1,10 +1,11 @@
//! This is mostly an internal module, no stability guarantees are provied. Use
//! This is mostly an internal module, no stability guarantees are provided. Use
//! at your own risk.
use core::mem::{self, ManuallyDrop};
use core::ops::{Deref, DerefMut};
use core::slice;
use core::str;
use core::char;
use {JsValue, throw};
use describe::*;
@ -134,6 +135,18 @@ impl FromWasmAbi for bool {
unsafe fn from_abi(js: u32, _extra: &mut Stack) -> bool { js != 0 }
}
impl IntoWasmAbi for char {
type Abi = u32;
fn into_abi(self, _extra: &mut Stack) -> u32 { self as u32 }
}
impl FromWasmAbi for char {
type Abi = u32;
unsafe fn from_abi(js: u32, _extra: &mut Stack) -> char {
char::from_u32_unchecked(js)
}
}
impl<T> IntoWasmAbi for *const T {
type Abi = u32;

View File

@ -37,6 +37,7 @@ tys! {
ANYREF
ENUM
RUST_STRUCT
CHAR
}
pub fn inform(a: u32) {
@ -71,6 +72,7 @@ simple! {
f32 => F32
f64 => F64
bool => BOOLEAN
char => CHAR
str => STRING
JsValue => ANYREF
}

60
tests/all/char.rs Normal file
View File

@ -0,0 +1,60 @@
use super::project;
#[test]
fn works() {
project()
.file("src/lib.rs", r#"
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "./test")]
extern {
fn js_parrot(c: char) -> char;
}
#[wasm_bindgen]
pub fn single_char() -> char { 'a' }
#[wasm_bindgen]
pub fn wide_char() -> char { '💩' }
#[wasm_bindgen]
pub fn parrot(c: char) -> char { c }
#[wasm_bindgen]
pub fn short_test(a: char) { assert_eq!(a, 'a'); }
#[wasm_bindgen]
pub fn wide_test(p: char) { assert_eq!(p, '💩'); }
#[wasm_bindgen]
pub fn char_round(c: char)-> char { js_parrot(c) }
"#)
.file("test.js", r#"
import * as wasm from './out';
function assertEq(a, b) {
console.log(a, '?=', b);
if (a === b)
return;
throw new Error('not equal');
}
export function test() {
assertEq(wasm.single_char(), 'a');
assertEq(wasm.wide_char(), '💩');
assertEq(wasm.parrot('Ղ'), 'Ղ');
assertEq(wasm.parrot('ҝ'), 'ҝ');
assertEq(wasm.parrot('Δ'), 'Δ');
assertEq(wasm.parrot('䉨'), '䉨');
assertEq(wasm.char_round('a'), 'a');
assertEq(wasm.char_round('㊻'), '㊻');
wasm.short_test('a');
wasm.wide_test('💩');
}
export function js_parrot(a) { return a; }
"#)
.test();
}

View File

@ -382,3 +382,4 @@ mod slice;
mod structural;
mod non_wasm;
mod u64;
mod char;