mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-24 18:43:33 +03:00
Implement support for WebIDL dictionaries
This commit adds support for generating bindings for dictionaries defined in WebIDL. Dictionaries are associative arrays which are simply objects in JS with named keys and some values. In Rust given a dictionary like: dictionary Foo { long field; }; we'll generate a struct like: pub struct Foo { obj: js_sys::Object, } impl Foo { pub fn new() -> Foo { /* make a blank object */ } pub fn field(&mut self, val: i32) -> &mut Self { // set the field using `js_sys::Reflect` } } // plus a bunch of AsRef, From, and wasm abi impls At the same time this adds support for partial dictionaries and dictionary inheritance. All dictionary fields are optional by default and hence only have builder-style setters, but dictionaries can also have required fields. Required fields are exposed as arguments to the `new` constructor. Closes #241
This commit is contained in:
parent
13fe2b4aca
commit
d6e48195b3
@ -21,6 +21,10 @@ pub struct Program {
|
||||
pub consts: Vec<Const>,
|
||||
/// rust submodules
|
||||
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
|
||||
@ -253,6 +257,21 @@ pub struct Module {
|
||||
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 {
|
||||
pub(crate) fn shared(&self) -> Result<shared::Program, Diagnostic> {
|
||||
Ok(shared::Program {
|
||||
|
@ -74,6 +74,9 @@ impl TryToTokens for ast::Program {
|
||||
errors.push(e);
|
||||
}
|
||||
}
|
||||
for d in self.dictionaries.iter() {
|
||||
d.to_tokens(tokens);
|
||||
}
|
||||
|
||||
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
|
||||
/// symbol name as well as attributes around the descriptor function itself.
|
||||
struct Descriptor<'a, T>(&'a Ident, T);
|
||||
|
@ -84,6 +84,7 @@ impl ImportedTypes for ast::Program {
|
||||
{
|
||||
self.imports.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*
|
||||
/// defined.
|
||||
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
|
||||
F: Fn(&Ident) -> bool;
|
||||
}
|
||||
|
||||
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
|
||||
F: Fn(&Ident) -> bool,
|
||||
{
|
||||
self.imports.remove_undefined_imports(is_defined);
|
||||
self.consts.remove_undefined_imports(is_defined);
|
||||
let a = self.imports.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
|
||||
T: ImportedTypeReferences,
|
||||
{
|
||||
fn remove_undefined_imports<F>(&mut self, is_defined: &F)
|
||||
fn remove_undefined_imports<F>(&mut self, is_defined: &F) -> bool
|
||||
where
|
||||
F: Fn(&Ident) -> bool,
|
||||
{
|
||||
let before = self.len();
|
||||
self.retain(|x| {
|
||||
let mut all_defined = true;
|
||||
x.imported_type_references(&mut |id| {
|
||||
@ -336,5 +361,6 @@ where
|
||||
});
|
||||
all_defined
|
||||
});
|
||||
before != self.len()
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ path = 'lib.rs'
|
||||
|
||||
[build-dependencies]
|
||||
wasm-bindgen-webidl = { path = '../webidl' }
|
||||
env_logger = "0.5"
|
||||
|
||||
[dev-dependencies]
|
||||
js-sys = { path = '../js-sys' }
|
||||
@ -19,4 +20,3 @@ wasm-bindgen-test = { path = '../test' }
|
||||
[[test]]
|
||||
name = 'wasm'
|
||||
path = 'main.rs'
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
extern crate wasm_bindgen_webidl;
|
||||
extern crate env_logger;
|
||||
|
||||
use std::env;
|
||||
use std::fs;
|
||||
@ -6,6 +7,7 @@ use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let idls = fs::read_dir(".")
|
||||
.unwrap()
|
||||
.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 simple;
|
||||
pub mod throws;
|
||||
pub mod dictionary;
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use weedle::{DictionaryDefinition, PartialDictionaryDefinition};
|
||||
use weedle::argument::Argument;
|
||||
use weedle::attribute::ExtendedAttribute;
|
||||
use weedle::interface::{StringifierOrStatic, Special};
|
||||
@ -24,13 +25,13 @@ use util::camel_case_ident;
|
||||
#[derive(Default)]
|
||||
pub(crate) struct FirstPassRecord<'src> {
|
||||
pub(crate) interfaces: BTreeMap<&'src str, InterfaceData<'src>>,
|
||||
pub(crate) dictionaries: BTreeSet<&'src str>,
|
||||
pub(crate) enums: BTreeSet<&'src str>,
|
||||
/// The mixins, mapping their name to the webidl ast node for the mixin.
|
||||
pub(crate) mixins: BTreeMap<&'src str, MixinData<'src>>,
|
||||
pub(crate) typedefs: BTreeMap<&'src str, &'src weedle::types::Type<'src>>,
|
||||
pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>,
|
||||
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.
|
||||
@ -61,6 +62,13 @@ pub(crate) struct NamespaceData<'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)]
|
||||
pub(crate) enum OperationId<'src> {
|
||||
Constructor,
|
||||
@ -99,6 +107,7 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> {
|
||||
|
||||
match self {
|
||||
Dictionary(dictionary) => dictionary.first_pass(record, ()),
|
||||
PartialDictionary(dictionary) => dictionary.first_pass(record, ()),
|
||||
Enum(enum_) => enum_.first_pass(record, ()),
|
||||
IncludesStatement(includes) => includes.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> {
|
||||
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, (): ()) -> Result<()> {
|
||||
if util::is_chrome_only(&self.attributes) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !record.dictionaries.insert(self.identifier.0) {
|
||||
info!("Encountered multiple dictionary declarations: {}", self.identifier.0);
|
||||
}
|
||||
record.dictionaries.entry(self.identifier.0)
|
||||
.or_default()
|
||||
.definition = Some(self);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@ -153,7 +167,7 @@ impl<'src> FirstPass<'src, ()> for weedle::IncludesStatementDefinition<'src> {
|
||||
record
|
||||
.includes
|
||||
.entry(self.lhs_identifier.0)
|
||||
.or_insert_with(Default::default)
|
||||
.or_default()
|
||||
.insert(self.rhs_identifier.0);
|
||||
|
||||
Ok(())
|
||||
@ -372,7 +386,7 @@ impl<'src> FirstPass<'src, ()> for weedle::InterfaceMixinDefinition<'src>{
|
||||
let mixin_data = record
|
||||
.mixins
|
||||
.entry(self.identifier.0)
|
||||
.or_insert_with(Default::default);
|
||||
.or_default();
|
||||
mixin_data.partial = false;
|
||||
mixin_data.members.extend(&self.members.body);
|
||||
}
|
||||
|
@ -285,7 +285,7 @@ impl<'a> ToIdlType<'a> for Identifier<'a> {
|
||||
idl_type.to_idl_type(record)
|
||||
} else if record.interfaces.contains_key(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))
|
||||
} else if record.enums.contains(self.0) {
|
||||
Some(IdlType::Enum(self.0))
|
||||
@ -467,7 +467,8 @@ impl<'a> IdlType<'a> {
|
||||
IdlType::Float32Array => Some(array("f32", 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()));
|
||||
if pos == TypePosition::Argument {
|
||||
Some(shared_ref(ty))
|
||||
@ -475,7 +476,6 @@ impl<'a> IdlType<'a> {
|
||||
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::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::path::Path;
|
||||
|
||||
use backend::ast;
|
||||
use backend::TryToTokens;
|
||||
use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports};
|
||||
use backend::defined::ImportedTypeReferences;
|
||||
use backend::util::{ident_ty, rust_ident, raw_ident, wrap_import_function};
|
||||
use failure::ResultExt;
|
||||
use heck::{ShoutySnakeCase, SnakeCase};
|
||||
use proc_macro2::{Ident, Span};
|
||||
use weedle::argument::Argument;
|
||||
use weedle::attribute::{ExtendedAttribute, ExtendedAttributeList};
|
||||
use weedle::dictionary::DictionaryMember;
|
||||
|
||||
use first_pass::{FirstPass, FirstPassRecord, OperationId};
|
||||
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.
|
||||
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![
|
||||
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
|
||||
"usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option",
|
||||
@ -121,10 +128,15 @@ fn compile_ast(mut ast: backend::ast::Program) -> String {
|
||||
].into_iter()
|
||||
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
|
||||
);
|
||||
ast.imported_type_definitions(&mut |id| {
|
||||
defined.insert(id.clone());
|
||||
});
|
||||
ast.remove_undefined_imports(&|id| defined.contains(id));
|
||||
loop {
|
||||
let mut defined = builtin.clone();
|
||||
ast.imported_type_definitions(&mut |id| {
|
||||
defined.insert(id.clone());
|
||||
});
|
||||
if !ast.remove_undefined_imports(&|id| defined.contains(id)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let mut tokens = proc_macro2::TokenStream::new();
|
||||
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::PartialInterfaceMixin(_)
|
||||
| weedle::Definition::IncludesStatement(..)
|
||||
| weedle::Definition::PartialDictionary(..)
|
||||
| weedle::Definition::PartialNamespace(..)=> {
|
||||
// handled in the first pass
|
||||
}
|
||||
@ -188,6 +201,9 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> {
|
||||
weedle::Definition::Namespace(namespace) => {
|
||||
namespace.webidl_parse(program, first_pass, ())?
|
||||
}
|
||||
weedle::Definition::Dictionary(dict) => {
|
||||
dict.webidl_parse(program, first_pass, ())?
|
||||
}
|
||||
|
||||
// TODO
|
||||
weedle::Definition::Callback(..) => {
|
||||
@ -196,12 +212,6 @@ impl<'src> WebidlParse<'src, ()> for weedle::Definition<'src> {
|
||||
weedle::Definition::CallbackInterface(..) => {
|
||||
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(())
|
||||
}
|
||||
@ -353,8 +363,8 @@ impl<'src> WebidlParse<'src, &'src weedle::InterfaceDefinition<'src>> for Extend
|
||||
overloaded,
|
||||
same_argument_names,
|
||||
&match first_pass.convert_arguments(arguments) {
|
||||
Some(arguments) => arguments,
|
||||
None => return,
|
||||
Some(arguments) => arguments
|
||||
},
|
||||
IdlType::Interface(interface.identifier.0),
|
||||
kind,
|
||||
@ -944,3 +954,114 @@ impl<'src> WebidlParse<'src, (&'src str, &'src mut backend::ast::Module)> for we
|
||||
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 {
|
||||
// everything is a `JsValue`!
|
||||
fn instanceof(_val: &JsValue) -> bool { true }
|
||||
|
Loading…
Reference in New Issue
Block a user