mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-11-24 14:42:35 +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>,
|
||||
/// 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(())
|
||||
}
|
||||
@ -355,8 +365,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,
|
||||
@ -950,3 +960,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