mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-28 14:27:36 +03:00
Merge pull request #702 from alexcrichton/dictionaries
Implement support for WebIDL dictionaries
This commit is contained in:
commit
25b6f5d982
@ -21,6 +21,10 @@ pub struct Program {
|
|||||||
pub consts: Vec<Const>,
|
pub consts: Vec<Const>,
|
||||||
/// rust submodules
|
/// rust submodules
|
||||||
pub modules: Vec<Module>,
|
pub modules: Vec<Module>,
|
||||||
|
/// "dictionaries", generated for WebIDL, which are basically just "typed
|
||||||
|
/// objects" in the sense that they represent a JS object with a particular
|
||||||
|
/// shape in JIT parlance.
|
||||||
|
pub dictionaries: Vec<Dictionary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A rust to js interface. Allows interaction with rust objects/functions
|
/// A rust to js interface. Allows interaction with rust objects/functions
|
||||||
@ -253,6 +257,21 @@ pub struct Module {
|
|||||||
pub imports: Vec<Import>,
|
pub imports: Vec<Import>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Dictionary {
|
||||||
|
pub name: Ident,
|
||||||
|
pub fields: Vec<DictionaryField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DictionaryField {
|
||||||
|
pub name: Ident,
|
||||||
|
pub required: bool,
|
||||||
|
pub ty: syn::Type,
|
||||||
|
}
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
|
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
|
||||||
Ok(shared::Program {
|
Ok(shared::Program {
|
||||||
|
@ -74,6 +74,9 @@ impl TryToTokens for ast::Program {
|
|||||||
errors.push(e);
|
errors.push(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for d in self.dictionaries.iter() {
|
||||||
|
d.to_tokens(tokens);
|
||||||
|
}
|
||||||
|
|
||||||
Diagnostic::from_vec(errors)?;
|
Diagnostic::from_vec(errors)?;
|
||||||
|
|
||||||
@ -1135,6 +1138,153 @@ impl<'a> TryToTokens for ast::Module {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ast::Dictionary {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let name = &self.name;
|
||||||
|
let mut methods = TokenStream::new();
|
||||||
|
for field in self.fields.iter() {
|
||||||
|
field.to_tokens(&mut methods);
|
||||||
|
}
|
||||||
|
let required_names = &self.fields.iter()
|
||||||
|
.filter(|f| f.required)
|
||||||
|
.map(|f| &f.name)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let required_types = &self.fields.iter()
|
||||||
|
.filter(|f| f.required)
|
||||||
|
.map(|f| &f.ty)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let required_names2 = required_names;
|
||||||
|
let required_names3 = required_names;
|
||||||
|
|
||||||
|
let const_name = Ident::new(&format!("_CONST_{}", name), Span::call_site());
|
||||||
|
(quote! {
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct #name {
|
||||||
|
obj: ::js_sys::Object,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl #name {
|
||||||
|
pub fn new(#(#required_names: #required_types),*) -> #name {
|
||||||
|
let mut _ret = #name { obj: ::js_sys::Object::new() };
|
||||||
|
#(_ret.#required_names2(#required_names3);)*
|
||||||
|
return _ret
|
||||||
|
}
|
||||||
|
|
||||||
|
#methods
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(bad_style)]
|
||||||
|
const #const_name: () = {
|
||||||
|
use js_sys::Object;
|
||||||
|
use wasm_bindgen::describe::WasmDescribe;
|
||||||
|
use wasm_bindgen::convert::*;
|
||||||
|
use wasm_bindgen::{JsValue, JsCast};
|
||||||
|
use wasm_bindgen::__rt::core::mem::ManuallyDrop;
|
||||||
|
|
||||||
|
// interop w/ JsValue
|
||||||
|
impl From<#name> for JsValue {
|
||||||
|
fn from(val: #name) -> JsValue {
|
||||||
|
val.obj.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<JsValue> for #name {
|
||||||
|
fn as_ref(&self) -> &JsValue { self.obj.as_ref() }
|
||||||
|
}
|
||||||
|
impl AsMut<JsValue> for #name {
|
||||||
|
fn as_mut(&mut self) -> &mut JsValue { self.obj.as_mut() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boundary conversion impls
|
||||||
|
impl WasmDescribe for #name {
|
||||||
|
fn describe() {
|
||||||
|
Object::describe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoWasmAbi for #name {
|
||||||
|
type Abi = <Object as IntoWasmAbi>::Abi;
|
||||||
|
fn into_abi(self, extra: &mut Stack) -> Self::Abi {
|
||||||
|
self.obj.into_abi(extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoWasmAbi for &'a #name {
|
||||||
|
type Abi = <&'a Object as IntoWasmAbi>::Abi;
|
||||||
|
fn into_abi(self, extra: &mut Stack) -> Self::Abi {
|
||||||
|
(&self.obj).into_abi(extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromWasmAbi for #name {
|
||||||
|
type Abi = <Object as FromWasmAbi>::Abi;
|
||||||
|
unsafe fn from_abi(abi: Self::Abi, extra: &mut Stack) -> Self {
|
||||||
|
#name { obj: Object::from_abi(abi, extra) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OptionIntoWasmAbi for #name {
|
||||||
|
fn none() -> Self::Abi { Object::none() }
|
||||||
|
}
|
||||||
|
impl<'a> OptionIntoWasmAbi for &'a #name {
|
||||||
|
fn none() -> Self::Abi { <&'a Object>::none() }
|
||||||
|
}
|
||||||
|
impl OptionFromWasmAbi for #name {
|
||||||
|
fn is_none(abi: &Self::Abi) -> bool { Object::is_none(abi) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RefFromWasmAbi for #name {
|
||||||
|
type Abi = <Object as RefFromWasmAbi>::Abi;
|
||||||
|
type Anchor = ManuallyDrop<#name>;
|
||||||
|
|
||||||
|
unsafe fn ref_from_abi(js: Self::Abi, extra: &mut Stack) -> Self::Anchor {
|
||||||
|
let tmp = <Object as RefFromWasmAbi>::ref_from_abi(js, extra);
|
||||||
|
ManuallyDrop::new(#name {
|
||||||
|
obj: ManuallyDrop::into_inner(tmp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JsCast for #name {
|
||||||
|
fn instanceof(val: &JsValue) -> bool {
|
||||||
|
Object::instanceof(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unchecked_from_js(val: JsValue) -> Self {
|
||||||
|
#name { obj: Object::unchecked_from_js(val) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unchecked_from_js_ref(val: &JsValue) -> &Self {
|
||||||
|
unsafe { &*(val as *const JsValue as *const #name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unchecked_from_js_mut(val: &mut JsValue) -> &mut Self {
|
||||||
|
unsafe { &mut *(val as *mut JsValue as *mut #name) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}).to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ast::DictionaryField {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let name = &self.name;
|
||||||
|
let ty = &self.ty;
|
||||||
|
(quote! {
|
||||||
|
pub fn #name(&mut self, val: #ty) -> &mut Self {
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
::js_sys::Reflect::set(
|
||||||
|
self.obj.as_ref(),
|
||||||
|
&JsValue::from(stringify!(#name)),
|
||||||
|
&JsValue::from(val),
|
||||||
|
);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}).to_tokens(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Emits the necessary glue tokens for "descriptor", generating an appropriate
|
/// Emits the necessary glue tokens for "descriptor", generating an appropriate
|
||||||
/// symbol name as well as attributes around the descriptor function itself.
|
/// symbol name as well as attributes around the descriptor function itself.
|
||||||
struct Descriptor<'a, T>(&'a Ident, T);
|
struct Descriptor<'a, T>(&'a Ident, T);
|
||||||
|
@ -84,6 +84,7 @@ impl ImportedTypes for ast::Program {
|
|||||||
{
|
{
|
||||||
self.imports.imported_types(f);
|
self.imports.imported_types(f);
|
||||||
self.consts.imported_types(f);
|
self.consts.imported_types(f);
|
||||||
|
self.dictionaries.imported_types(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,21 +299,44 @@ impl ImportedTypes for ast::Const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ImportedTypes for ast::Dictionary {
|
||||||
|
fn imported_types<F>(&self, f: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&Ident, ImportedTypeKind),
|
||||||
|
{
|
||||||
|
f(&self.name, ImportedTypeKind::Definition);
|
||||||
|
for field in self.fields.iter() {
|
||||||
|
field.imported_types(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImportedTypes for ast::DictionaryField {
|
||||||
|
fn imported_types<F>(&self, f: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&Ident, ImportedTypeKind),
|
||||||
|
{
|
||||||
|
self.ty.imported_types(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove any methods, statics, &c, that reference types that are *not*
|
/// Remove any methods, statics, &c, that reference types that are *not*
|
||||||
/// defined.
|
/// defined.
|
||||||
pub trait RemoveUndefinedImports {
|
pub trait RemoveUndefinedImports {
|
||||||
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
|
fn remove_undefined_imports<F>(&mut self, is_defined: &F) -> bool
|
||||||
where
|
where
|
||||||
F: Fn(&Ident) -> bool;
|
F: Fn(&Ident) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RemoveUndefinedImports for ast::Program {
|
impl RemoveUndefinedImports for ast::Program {
|
||||||
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
|
fn remove_undefined_imports<F>(&mut self, is_defined: &F) -> bool
|
||||||
where
|
where
|
||||||
F: Fn(&Ident) -> bool,
|
F: Fn(&Ident) -> bool,
|
||||||
{
|
{
|
||||||
self.imports.remove_undefined_imports(is_defined);
|
let a = self.imports.remove_undefined_imports(is_defined);
|
||||||
self.consts.remove_undefined_imports(is_defined);
|
let b = self.consts.remove_undefined_imports(is_defined);
|
||||||
|
let c = self.dictionaries.remove_undefined_imports(is_defined);
|
||||||
|
a || b || c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,10 +344,11 @@ impl<T> RemoveUndefinedImports for Vec<T>
|
|||||||
where
|
where
|
||||||
T: ImportedTypeReferences,
|
T: ImportedTypeReferences,
|
||||||
{
|
{
|
||||||
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
|
fn remove_undefined_imports<F>(&mut self, is_defined: &F) -> bool
|
||||||
where
|
where
|
||||||
F: Fn(&Ident) -> bool,
|
F: Fn(&Ident) -> bool,
|
||||||
{
|
{
|
||||||
|
let before = self.len();
|
||||||
self.retain(|x| {
|
self.retain(|x| {
|
||||||
let mut all_defined = true;
|
let mut all_defined = true;
|
||||||
x.imported_type_references(&mut |id| {
|
x.imported_type_references(&mut |id| {
|
||||||
@ -336,5 +361,6 @@ where
|
|||||||
});
|
});
|
||||||
all_defined
|
all_defined
|
||||||
});
|
});
|
||||||
|
before != self.len()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ path = 'lib.rs'
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
wasm-bindgen-webidl = { path = '../webidl' }
|
wasm-bindgen-webidl = { path = '../webidl' }
|
||||||
|
env_logger = "0.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
js-sys = { path = '../js-sys' }
|
js-sys = { path = '../js-sys' }
|
||||||
@ -19,4 +20,3 @@ wasm-bindgen-test = { path = '../test' }
|
|||||||
[[test]]
|
[[test]]
|
||||||
name = 'wasm'
|
name = 'wasm'
|
||||||
path = 'main.rs'
|
path = 'main.rs'
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
extern crate wasm_bindgen_webidl;
|
extern crate wasm_bindgen_webidl;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@ -6,6 +7,7 @@ use std::path::PathBuf;
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
env_logger::init();
|
||||||
let idls = fs::read_dir(".")
|
let idls = fs::read_dir(".")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|f| f.unwrap().path())
|
.map(|f| f.unwrap().path())
|
||||||
|
22
crates/webidl-tests/dictionary.js
Normal file
22
crates/webidl-tests/dictionary.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
global.assert_dict_c = function(c) {
|
||||||
|
assert.strictEqual(c.a, 1);
|
||||||
|
assert.strictEqual(c.b, 2);
|
||||||
|
assert.strictEqual(c.c, 3);
|
||||||
|
assert.strictEqual(c.d, 4);
|
||||||
|
assert.strictEqual(c.e, 5);
|
||||||
|
assert.strictEqual(c.f, 6);
|
||||||
|
assert.strictEqual(c.g, 7);
|
||||||
|
assert.strictEqual(c.h, 8);
|
||||||
|
};
|
||||||
|
|
||||||
|
global.mk_dict_a = function() {
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
global.assert_dict_required = function(c) {
|
||||||
|
assert.strictEqual(c.a, 3);
|
||||||
|
assert.strictEqual(c.b, "a");
|
||||||
|
assert.strictEqual(c.c, 4);
|
||||||
|
};
|
54
crates/webidl-tests/dictionary.rs
Normal file
54
crates/webidl-tests/dictionary.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
use wasm_bindgen_test::*;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/dictionary.rs"));
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern {
|
||||||
|
fn assert_dict_c(c: &C);
|
||||||
|
#[wasm_bindgen(js_name = assert_dict_c)]
|
||||||
|
fn assert_dict_c2(c: C);
|
||||||
|
#[wasm_bindgen(js_name = assert_dict_c)]
|
||||||
|
fn assert_dict_c3(c: Option<&C>);
|
||||||
|
#[wasm_bindgen(js_name = assert_dict_c)]
|
||||||
|
fn assert_dict_c4(c: Option<C>);
|
||||||
|
fn mk_dict_a() -> A;
|
||||||
|
#[wasm_bindgen(js_name = mk_dict_a)]
|
||||||
|
fn mk_dict_a2() -> Option<A>;
|
||||||
|
fn assert_dict_required(r: &Required);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn smoke() {
|
||||||
|
A::new().c(1).g(2).h(3).d(4);
|
||||||
|
B::new().c(1).g(2).h(3).d(4).a(5).b(6);
|
||||||
|
|
||||||
|
let mut c = C::new();
|
||||||
|
c.a(1).b(2).c(3).d(4).e(5).f(6).g(7).h(8);
|
||||||
|
assert_dict_c(&c);
|
||||||
|
assert_dict_c2(c.clone());
|
||||||
|
assert_dict_c3(Some(&c));
|
||||||
|
assert_dict_c4(Some(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn get_dict() {
|
||||||
|
mk_dict_a();
|
||||||
|
assert!(mk_dict_a2().is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn casing() {
|
||||||
|
CamelCaseMe::new().snake_case_me(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn many_types() {
|
||||||
|
ManyTypes::new()
|
||||||
|
.a("a");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn required() {
|
||||||
|
assert_dict_required(Required::new(3, "a").c(4));
|
||||||
|
}
|
47
crates/webidl-tests/dictionary.webidl
vendored
Normal file
47
crates/webidl-tests/dictionary.webidl
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// example from https://heycam.github.io/webidl/#idl-dictionaries
|
||||||
|
dictionary B : A {
|
||||||
|
long b;
|
||||||
|
long a;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary A {
|
||||||
|
long c;
|
||||||
|
long g;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary C : B {
|
||||||
|
long e;
|
||||||
|
long f;
|
||||||
|
};
|
||||||
|
|
||||||
|
partial dictionary A {
|
||||||
|
long h;
|
||||||
|
long d;
|
||||||
|
};
|
||||||
|
|
||||||
|
// case needs changing
|
||||||
|
dictionary camel_case_me {
|
||||||
|
long snakeCaseMe;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary ManyTypes {
|
||||||
|
DOMString a;
|
||||||
|
octet n1;
|
||||||
|
byte n2;
|
||||||
|
unsigned short n3;
|
||||||
|
short n4;
|
||||||
|
unsigned long n5;
|
||||||
|
long n6;
|
||||||
|
// TODO: needs fixing
|
||||||
|
// OtherDict c;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary OtherDict {
|
||||||
|
long a;
|
||||||
|
};
|
||||||
|
|
||||||
|
dictionary Required {
|
||||||
|
required DOMString b;
|
||||||
|
required long a;
|
||||||
|
long c;
|
||||||
|
};
|
@ -11,3 +11,4 @@ pub mod enums;
|
|||||||
pub mod namespace;
|
pub mod namespace;
|
||||||
pub mod simple;
|
pub mod simple;
|
||||||
pub mod throws;
|
pub mod throws;
|
||||||
|
pub mod dictionary;
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
|
|
||||||
|
use weedle::{DictionaryDefinition, PartialDictionaryDefinition};
|
||||||
use weedle::argument::Argument;
|
use weedle::argument::Argument;
|
||||||
use weedle::attribute::ExtendedAttribute;
|
use weedle::attribute::ExtendedAttribute;
|
||||||
use weedle::interface::{StringifierOrStatic, Special};
|
use weedle::interface::{StringifierOrStatic, Special};
|
||||||
@ -24,13 +25,13 @@ use util::camel_case_ident;
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct FirstPassRecord<'src> {
|
pub(crate) struct FirstPassRecord<'src> {
|
||||||
pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>,
|
pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>,
|
||||||
pub(crate) dictionaries: BTreeSet<&'src str>,
|
|
||||||
pub(crate) enums: BTreeSet<&'src str>,
|
pub(crate) enums: BTreeSet<&'src str>,
|
||||||
/// The mixins, mapping their name to the webidl ast node for the mixin.
|
/// The mixins, mapping their name to the webidl ast node for the mixin.
|
||||||
pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>,
|
pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>,
|
||||||
pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>,
|
pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>,
|
||||||
pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>,
|
pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>,
|
||||||
pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>,
|
pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>,
|
||||||
|
pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We need to collect interface data during the first pass, to be used later.
|
/// We need to collect interface data during the first pass, to be used later.
|
||||||
@ -61,6 +62,13 @@ pub(crate) struct NamespaceData<'src> {
|
|||||||
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
|
pub(crate) operations: BTreeMap<OperationId<'src>, OperationData<'src>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct DictionaryData<'src> {
|
||||||
|
/// Whether only partial namespaces were encountered
|
||||||
|
pub(crate) partials: Vec<&'src PartialDictionaryDefinition<'src>>,
|
||||||
|
pub(crate) definition: Option<&'src DictionaryDefinition<'src>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
|
||||||
pub(crate) enum OperationId<'src> {
|
pub(crate) enum OperationId<'src> {
|
||||||
Constructor,
|
Constructor,
|
||||||
@ -99,6 +107,7 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> {
|
|||||||
|
|
||||||
match self {
|
match self {
|
||||||
Dictionary(dictionary) => dictionary.first_pass(record, ()),
|
Dictionary(dictionary) => dictionary.first_pass(record, ()),
|
||||||
|
PartialDictionary(dictionary) => dictionary.first_pass(record, ()),
|
||||||
Enum(enum_) => enum_.first_pass(record, ()),
|
Enum(enum_) => enum_.first_pass(record, ()),
|
||||||
IncludesStatement(includes) => includes.first_pass(record, ()),
|
IncludesStatement(includes) => includes.first_pass(record, ()),
|
||||||
Interface(interface) => interface.first_pass(record, ()),
|
Interface(interface) => interface.first_pass(record, ()),
|
||||||
@ -118,14 +127,19 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> {
|
|||||||
|
|
||||||
impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> {
|
impl<'src> FirstPass<'src, ()> for weedle::DictionaryDefinition<'src> {
|
||||||
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> {
|
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> {
|
||||||
if util::is_chrome_only(&self.attributes) {
|
record.dictionaries.entry(self.identifier.0)
|
||||||
return Ok(());
|
.or_default()
|
||||||
}
|
.definition = Some(self);
|
||||||
|
Ok(())
|
||||||
if !record.dictionaries.insert(self.identifier.0) {
|
}
|
||||||
info!("Encountered multiple dictionary declarations: {}", self.identifier.0);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
impl<'src> FirstPass<'src, ()> for weedle::PartialDictionaryDefinition<'src> {
|
||||||
|
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> {
|
||||||
|
record.dictionaries.entry(self.identifier.0)
|
||||||
|
.or_default()
|
||||||
|
.partials
|
||||||
|
.push(self);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +167,7 @@ impl<'src> FirstPass<'src, ()> for weedle::IncludesStatementDefinition<'src> {
|
|||||||
record
|
record
|
||||||
.includes
|
.includes
|
||||||
.entry(self.lhs_identifier.0)
|
.entry(self.lhs_identifier.0)
|
||||||
.or_insert_with(Default::default)
|
.or_default()
|
||||||
.insert(self.rhs_identifier.0);
|
.insert(self.rhs_identifier.0);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -372,7 +386,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src>{
|
|||||||
let mixin_data = record
|
let mixin_data = record
|
||||||
.mixins
|
.mixins
|
||||||
.entry(self.identifier.0)
|
.entry(self.identifier.0)
|
||||||
.or_insert_with(Default::default);
|
.or_default();
|
||||||
mixin_data.partial = false;
|
mixin_data.partial = false;
|
||||||
mixin_data.members.extend(&self.members.body);
|
mixin_data.members.extend(&self.members.body);
|
||||||
}
|
}
|
||||||
|
@ -285,7 +285,7 @@ impl<'a> ToIdlType<'a> for Identifier<'a> {
|
|||||||
idl_type.to_idl_type(record)
|
idl_type.to_idl_type(record)
|
||||||
} else if record.interfaces.contains_key(self.0) {
|
} else if record.interfaces.contains_key(self.0) {
|
||||||
Some(IdlType::Interface(self.0))
|
Some(IdlType::Interface(self.0))
|
||||||
} else if record.dictionaries.contains(self.0) {
|
} else if record.dictionaries.contains_key(self.0) {
|
||||||
Some(IdlType::Dictionary(self.0))
|
Some(IdlType::Dictionary(self.0))
|
||||||
} else if record.enums.contains(self.0) {
|
} else if record.enums.contains(self.0) {
|
||||||
Some(IdlType::Enum(self.0))
|
Some(IdlType::Enum(self.0))
|
||||||
@ -467,7 +467,8 @@ impl<'a> IdlType<'a> {
|
|||||||
IdlType::Float32Array => Some(array("f32", pos)),
|
IdlType::Float32Array => Some(array("f32", pos)),
|
||||||
IdlType::Float64Array => Some(array("f64", pos)),
|
IdlType::Float64Array => Some(array("f64", pos)),
|
||||||
|
|
||||||
IdlType::Interface(name) => {
|
IdlType::Interface(name) |
|
||||||
|
IdlType::Dictionary(name) => {
|
||||||
let ty = ident_ty(rust_ident(camel_case_ident(name).as_str()));
|
let ty = ident_ty(rust_ident(camel_case_ident(name).as_str()));
|
||||||
if pos == TypePosition::Argument {
|
if pos == TypePosition::Argument {
|
||||||
Some(shared_ref(ty))
|
Some(shared_ref(ty))
|
||||||
@ -475,7 +476,6 @@ impl<'a> IdlType<'a> {
|
|||||||
Some(ty)
|
Some(ty)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
IdlType::Dictionary(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))),
|
|
||||||
IdlType::Enum(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))),
|
IdlType::Enum(name) => Some(ident_ty(rust_ident(camel_case_ident(name).as_str()))),
|
||||||
|
|
||||||
IdlType::Nullable(idl_type) => Some(option_ty(idl_type.to_syn_type(pos)?)),
|
IdlType::Nullable(idl_type) => Some(option_ty(idl_type.to_syn_type(pos)?)),
|
||||||
|
@ -35,14 +35,17 @@ use std::io::{self, Read};
|
|||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use backend::ast;
|
||||||
use backend::TryToTokens;
|
use backend::TryToTokens;
|
||||||
use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
||||||
|
use backend::defined::ImportedTypeReferences;
|
||||||
use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function};
|
use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function};
|
||||||
use failure::ResultExt;
|
use failure::ResultExt;
|
||||||
use heck::{ShoutySnakeCase, SnakeCase};
|
use heck::{ShoutySnakeCase, SnakeCase};
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use weedle::argument::Argument;
|
use weedle::argument::Argument;
|
||||||
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
|
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
|
||||||
|
use weedle::dictionary::DictionaryMember;
|
||||||
|
|
||||||
use first_pass::{FirstPass, FirstPassRecord, OperationId};
|
use first_pass::{FirstPass, FirstPassRecord, OperationId};
|
||||||
use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc};
|
use util::{public, webidl_const_v_to_backend_const_v, TypePosition, camel_case_ident, mdn_doc};
|
||||||
@ -113,7 +116,11 @@ pub fn compile(webidl_source: &str) -> Result<String> {
|
|||||||
|
|
||||||
/// Run codegen on the AST to generate rust code.
|
/// Run codegen on the AST to generate rust code.
|
||||||
fn compile_ast(mut ast: backend::ast::Program) -> String {
|
fn compile_ast(mut ast: backend::ast::Program) -> String {
|
||||||
let mut defined = BTreeSet::from_iter(
|
// Iteratively prune all entries from the AST which reference undefined
|
||||||
|
// fields. Each pass may remove definitions of types and so we need to
|
||||||
|
// reexecute this pass to see if we need to keep removing types until we
|
||||||
|
// reach a steady state.
|
||||||
|
let builtin = BTreeSet::from_iter(
|
||||||
vec![
|
vec![
|
||||||
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
|
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
|
||||||
"usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option",
|
"usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option",
|
||||||
@ -121,10 +128,15 @@ fn compile_ast(mut ast: backend::ast::Program) -> String {
|
|||||||
].into_iter()
|
].into_iter()
|
||||||
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
|
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
|
||||||
);
|
);
|
||||||
ast.imported_type_definitions(&mut |id| {
|
loop {
|
||||||
defined.insert(id.clone());
|
let mut defined = builtin.clone();
|
||||||
});
|
ast.imported_type_definitions(&mut |id| {
|
||||||
ast.remove_undefined_imports(&|id| defined.contains(id));
|
defined.insert(id.clone());
|
||||||
|
});
|
||||||
|
if !ast.remove_undefined_imports(&|id| defined.contains(id)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut tokens = proc_macro2::TokenStream::new();
|
let mut tokens = proc_macro2::TokenStream::new();
|
||||||
if let Err(e) = ast.try_to_tokens(&mut tokens) {
|
if let Err(e) = ast.try_to_tokens(&mut tokens) {
|
||||||
@ -179,6 +191,7 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> {
|
|||||||
| weedle::Definition::InterfaceMixin(_)
|
| weedle::Definition::InterfaceMixin(_)
|
||||||
| weedle::Definition::PartialInterfaceMixin(_)
|
| weedle::Definition::PartialInterfaceMixin(_)
|
||||||
| weedle::Definition::IncludesStatement(..)
|
| weedle::Definition::IncludesStatement(..)
|
||||||
|
| weedle::Definition::PartialDictionary(..)
|
||||||
| weedle::Definition::PartialNamespace(..)=> {
|
| weedle::Definition::PartialNamespace(..)=> {
|
||||||
// handled in the first pass
|
// handled in the first pass
|
||||||
}
|
}
|
||||||
@ -188,6 +201,9 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> {
|
|||||||
weedle::Definition::Namespace(namespace) => {
|
weedle::Definition::Namespace(namespace) => {
|
||||||
namespace.webidl_parse(program, first_pass, ())?
|
namespace.webidl_parse(program, first_pass, ())?
|
||||||
}
|
}
|
||||||
|
weedle::Definition::Dictionary(dict) => {
|
||||||
|
dict.webidl_parse(program, first_pass, ())?
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
weedle::Definition::Callback(..) => {
|
weedle::Definition::Callback(..) => {
|
||||||
@ -196,12 +212,6 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> {
|
|||||||
weedle::Definition::CallbackInterface(..) => {
|
weedle::Definition::CallbackInterface(..) => {
|
||||||
warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self)
|
warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self)
|
||||||
}
|
}
|
||||||
weedle::Definition::Dictionary(..) => {
|
|
||||||
warn!("Unsupported WebIDL Dictionary definition: {:?}", self)
|
|
||||||
}
|
|
||||||
weedle::Definition::PartialDictionary(..) => {
|
|
||||||
warn!("Unsupported WebIDL PartialDictionary definition: {:?}", self)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -355,8 +365,8 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend
|
|||||||
overloaded,
|
overloaded,
|
||||||
same_argument_names,
|
same_argument_names,
|
||||||
&match first_pass.convert_arguments(arguments) {
|
&match first_pass.convert_arguments(arguments) {
|
||||||
|
Some(arguments) => arguments,
|
||||||
None => return,
|
None => return,
|
||||||
Some(arguments) => arguments
|
|
||||||
},
|
},
|
||||||
IdlType::Interface(interface.identifier.0),
|
IdlType::Interface(interface.identifier.0),
|
||||||
kind,
|
kind,
|
||||||
@ -950,3 +960,114 @@ impl<'src> WebidlParse<'src, (&'src str, &'src mut backend::ast::Module)> for we
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tons more data for what's going on here at
|
||||||
|
// https://www.w3.org/TR/WebIDL-1/#idl-dictionaries
|
||||||
|
impl<'src> WebidlParse<'src, ()> for weedle::DictionaryDefinition<'src> {
|
||||||
|
fn webidl_parse(
|
||||||
|
&'src self,
|
||||||
|
program: &mut backend::ast::Program,
|
||||||
|
first_pass: &FirstPassRecord<'src>,
|
||||||
|
(): (),
|
||||||
|
) -> Result<()> {
|
||||||
|
if util::is_chrome_only(&self.attributes) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut fields = Vec::new();
|
||||||
|
if !push_members(first_pass, self.identifier.0, &mut fields) {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
program.dictionaries.push(ast::Dictionary {
|
||||||
|
name: rust_ident(&camel_case_ident(self.identifier.0)),
|
||||||
|
fields,
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
|
||||||
|
fn push_members<'src>(
|
||||||
|
data: &FirstPassRecord<'src>,
|
||||||
|
dict: &'src str,
|
||||||
|
dst: &mut Vec<ast::DictionaryField>,
|
||||||
|
) -> bool {
|
||||||
|
let dict_data = &data.dictionaries[&dict];
|
||||||
|
let definition = dict_data.definition.unwrap();
|
||||||
|
|
||||||
|
// > The order of the dictionary members on a given dictionary is
|
||||||
|
// > such that inherited dictionary members are ordered before
|
||||||
|
// > non-inherited members ...
|
||||||
|
if let Some(parent) = &definition.inheritance {
|
||||||
|
if !push_members(data, parent.identifier.0, dst) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// > ... and the dictionary members on the one dictionary
|
||||||
|
// > definition (including any partial dictionary definitions) are
|
||||||
|
// > ordered lexicographically by the Unicode codepoints that
|
||||||
|
// > comprise their identifiers.
|
||||||
|
let start = dst.len();
|
||||||
|
let members = definition.members.body.iter();
|
||||||
|
let partials = dict_data.partials.iter().flat_map(|d| &d.members.body);
|
||||||
|
for member in members.chain(partials) {
|
||||||
|
match mkfield(data, member) {
|
||||||
|
Some(f) => dst.push(f),
|
||||||
|
None => {
|
||||||
|
warn!(
|
||||||
|
"unsupported dictionary field {:?}",
|
||||||
|
(dict, member.identifier.0),
|
||||||
|
);
|
||||||
|
// If this is required then we can't support the
|
||||||
|
// dictionary at all, but if it's not required we can
|
||||||
|
// avoid generating bindings for the field and keep
|
||||||
|
// going otherwise.
|
||||||
|
if member.required.is_some() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Note that this sort isn't *quite* right in that it is sorting
|
||||||
|
// based on snake case instead of the original casing which could
|
||||||
|
// produce inconsistent results, but should work well enough for
|
||||||
|
// now!
|
||||||
|
dst[start..].sort_by_key(|f| f.name.clone());
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mkfield<'src>(
|
||||||
|
data: &FirstPassRecord<'src>,
|
||||||
|
field: &'src DictionaryMember<'src>,
|
||||||
|
) -> Option<ast::DictionaryField> {
|
||||||
|
// use argument position now as we're just binding setters
|
||||||
|
let ty = field.type_.to_idl_type(data)?.to_syn_type(TypePosition::Argument)?;
|
||||||
|
|
||||||
|
// Slice types aren't supported because they don't implement
|
||||||
|
// `Into<JsValue>`
|
||||||
|
if let syn::Type::Reference(ty) = &ty {
|
||||||
|
match &*ty.elem {
|
||||||
|
syn::Type::Slice(_) => return None,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similarly i64/u64 aren't supported because they don't
|
||||||
|
// implement `Into<JsValue>`
|
||||||
|
let mut any_64bit = false;
|
||||||
|
ty.imported_type_references(&mut |i| {
|
||||||
|
any_64bit = any_64bit || i == "u64" || i == "i64";
|
||||||
|
});
|
||||||
|
if any_64bit {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(ast::DictionaryField {
|
||||||
|
required: field.required.is_some(),
|
||||||
|
name: rust_ident(&field.identifier.0.to_snake_case()),
|
||||||
|
ty,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
15
src/lib.rs
15
src/lib.rs
@ -350,6 +350,21 @@ impl From<bool> for JsValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, T> From<&'a T> for JsValue where T: JsCast {
|
||||||
|
fn from(s: &'a T) -> JsValue {
|
||||||
|
s.as_ref().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for JsValue where JsValue: From<T> {
|
||||||
|
fn from(s: Option<T>) -> JsValue {
|
||||||
|
match s {
|
||||||
|
Some(s) => s.into(),
|
||||||
|
None => JsValue::undefined(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl JsCast for JsValue {
|
impl JsCast for JsValue {
|
||||||
// everything is a `JsValue`!
|
// everything is a `JsValue`!
|
||||||
fn instanceof(_val: &JsValue) -> bool { true }
|
fn instanceof(_val: &JsValue) -> bool { true }
|
||||||
|
Loading…
Reference in New Issue
Block a user