/*! # `wasm_bindgen_webidl` Converts WebIDL into wasm-bindgen's internal AST form, so that bindings can be emitted for the types and methods described in the WebIDL. */ #![deny(missing_docs)] #![deny(missing_debug_implementations)] extern crate failure; extern crate heck; #[macro_use] extern crate log; extern crate proc_macro2; extern crate quote; extern crate syn; extern crate wasm_bindgen_backend as backend; extern crate webidl; use std::fs; use std::io::{self, Read}; use std::iter; use std::path::Path; use failure::ResultExt; use heck::CamelCase; use quote::ToTokens; mod util; use util::{create_function, ident_ty, raw_ident, rust_ident, webidl_ty_to_syn_ty, TypePosition}; /// Either `Ok(t)` or `Err(failure::Error)`. pub type Result = ::std::result::Result; /// Parse the WebIDL at the given path into a wasm-bindgen AST. pub fn parse_file(webidl_path: &Path) -> Result { let file = fs::File::open(webidl_path).context("opening WebIDL file")?; let mut file = io::BufReader::new(file); let mut source = String::new(); file.read_to_string(&mut source) .context("reading WebIDL file")?; parse(&source) } /// Parse a string of WebIDL source text into a wasm-bindgen AST. pub fn parse(webidl_source: &str) -> Result { let definitions = webidl::parse_string(webidl_source).context("parsing WebIDL source text")?; let mut program = backend::ast::Program::default(); definitions.webidl_parse(&mut program, ())?; Ok(program) } /// Compile the given WebIDL file into Rust source text containing /// `wasm-bindgen` bindings to the things described in the WebIDL. pub fn compile_file(webidl_path: &Path) -> Result { let ast = parse_file(webidl_path)?; Ok(compile_ast(&ast)) } /// Compile the given WebIDL source text into Rust source text containing /// `wasm-bindgen` bindings to the things described in the WebIDL. pub fn compile(webidl_source: &str) -> Result { let ast = parse(webidl_source)?; Ok(compile_ast(&ast)) } fn compile_ast(ast: &backend::ast::Program) -> String { let mut tokens = proc_macro2::TokenStream::new(); ast.to_tokens(&mut tokens); tokens.to_string() } trait WebidlParse { fn webidl_parse(&self, program: &mut backend::ast::Program, context: Ctx) -> Result<()>; } impl WebidlParse<()> for Vec { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { for def in self { def.webidl_parse(program, ())?; } Ok(()) } } impl WebidlParse<()> for webidl::ast::Definition { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { match *self { webidl::ast::Definition::Interface(ref interface) => { interface.webidl_parse(program, ()) } webidl::ast::Definition::Typedef(ref typedef) => typedef.webidl_parse(program, ()), // TODO webidl::ast::Definition::Callback(..) | webidl::ast::Definition::Dictionary(..) | webidl::ast::Definition::Enum(..) | webidl::ast::Definition::Implements(..) | webidl::ast::Definition::Includes(..) | webidl::ast::Definition::Mixin(..) | webidl::ast::Definition::Namespace(..) => { warn!("Unsupported WebIDL definition: {:?}", self); Ok(()) } } } } impl WebidlParse<()> for webidl::ast::Interface { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { match *self { webidl::ast::Interface::NonPartial(ref interface) => { interface.webidl_parse(program, ()) } // TODO webidl::ast::Interface::Callback(..) | webidl::ast::Interface::Partial(..) => { warn!("Unsupported WebIDL interface: {:?}", self); Ok(()) } } } } impl WebidlParse<()> for webidl::ast::Typedef { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { let dest = rust_ident(&self.name); let src = match webidl_ty_to_syn_ty(&self.type_, TypePosition::Return) { Some(src) => src, None => { warn!( "typedef's source type is not yet supported: {:?}. Skipping typedef {:?}", *self.type_, self ); return Ok(()); } }; program.type_aliases.push(backend::ast::TypeAlias { vis: syn::Visibility::Public(syn::VisPublic { pub_token: Default::default(), }), dest, src, }); Ok(()) } } impl WebidlParse<()> for webidl::ast::NonPartialInterface { fn webidl_parse(&self, program: &mut backend::ast::Program, _: ()) -> Result<()> { program.imports.push(backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Type(backend::ast::ImportType { vis: syn::Visibility::Public(syn::VisPublic { pub_token: Default::default(), }), name: rust_ident(&self.name), }), }); for extended_attribute in &self.extended_attributes { extended_attribute.webidl_parse(program, self)?; } for member in &self.members { member.webidl_parse(program, &self.name)?; } Ok(()) } } impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::ExtendedAttribute { fn webidl_parse( &self, program: &mut backend::ast::Program, interface: &'a webidl::ast::NonPartialInterface, ) -> Result<()> { let mut add_constructor = |arguments: &[webidl::ast::Argument]| { let self_ty = ident_ty(rust_ident(&interface.name)); let kind = backend::ast::ImportFunctionKind::JsConstructor { class: interface.name.to_string(), ty: self_ty.clone(), }; create_function( "new", arguments .iter() .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), kind, Some(self_ty), vec![backend::ast::BindgenAttr::Constructor], ).map(|function| { program.imports.push(backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Function(function), }) }) }; match self { webidl::ast::ExtendedAttribute::ArgumentList( webidl::ast::ArgumentListExtendedAttribute { arguments, name }, ) if name == "Constructor" => { add_constructor(&*arguments); } webidl::ast::ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) if name == "Constructor" => { add_constructor(&[] as &[_]); } webidl::ast::ExtendedAttribute::ArgumentList(_) | webidl::ast::ExtendedAttribute::Identifier(_) | webidl::ast::ExtendedAttribute::IdentifierList(_) | webidl::ast::ExtendedAttribute::NamedArgumentList(_) | webidl::ast::ExtendedAttribute::NoArguments(_) => { warn!("Unsupported WebIDL extended attribute: {:?}", self); } } Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::InterfaceMember { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { match *self { webidl::ast::InterfaceMember::Attribute(ref attr) => { attr.webidl_parse(program, self_name) } webidl::ast::InterfaceMember::Operation(ref op) => op.webidl_parse(program, self_name), // TODO webidl::ast::InterfaceMember::Const(_) | webidl::ast::InterfaceMember::Iterable(_) | webidl::ast::InterfaceMember::Maplike(_) | webidl::ast::InterfaceMember::Setlike(_) => { warn!("Unsupported WebIDL interface member: {:?}", self); Ok(()) } } } } impl<'a> WebidlParse<&'a str> for webidl::ast::Attribute { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { match *self { webidl::ast::Attribute::Regular(ref attr) => attr.webidl_parse(program, self_name), // TODO webidl::ast::Attribute::Static(_) | webidl::ast::Attribute::Stringifier(_) => { warn!("Unsupported WebIDL attribute: {:?}", self); Ok(()) } } } } impl<'a> WebidlParse<&'a str> for webidl::ast::Operation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { match *self { webidl::ast::Operation::Regular(ref op) => op.webidl_parse(program, self_name), // TODO webidl::ast::Operation::Special(_) | webidl::ast::Operation::Static(_) | webidl::ast::Operation::Stringifier(_) => { warn!("Unsupported WebIDL operation: {:?}", self); Ok(()) } } } } impl<'a> WebidlParse<&'a str> for webidl::ast::RegularAttribute { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { fn create_getter( this: &webidl::ast::RegularAttribute, self_name: &str, ) -> Option { let ret = match webidl_ty_to_syn_ty(&this.type_, TypePosition::Return) { None => { warn!("Attribute's type does not yet support reading: {:?}. Skipping getter binding for {:?}", this.type_, this); return None; } Some(ty) => Some(ty), }; let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), }; create_function( &this.name, iter::empty(), kind, ret, vec![backend::ast::BindgenAttr::Getter(Some(raw_ident( &this.name, )))], ).map(|function| backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Function(function), }) } fn create_setter( this: &webidl::ast::RegularAttribute, self_name: &str, ) -> Option { let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), }; create_function( &format!("set_{}", this.name.to_camel_case()), iter::once((&*this.name, &*this.type_, false)), kind, None, vec![backend::ast::BindgenAttr::Setter(Some(raw_ident( &this.name, )))], ).map(|function| backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Function(function), }) } create_getter(self, self_name).map(|import| program.imports.push(import)); if !self.read_only { create_setter(self, self_name).map(|import| program.imports.push(import)); } Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::RegularOperation { fn webidl_parse(&self, program: &mut backend::ast::Program, self_name: &'a str) -> Result<()> { let name = match self.name { None => { warn!( "Operations without a name are unsupported. Skipping {:?}", self ); return Ok(()); } Some(ref name) => name, }; let kind = backend::ast::ImportFunctionKind::Method { class: self_name.to_string(), ty: ident_ty(rust_ident(self_name)), }; let ret = match self.return_type { webidl::ast::ReturnType::Void => None, webidl::ast::ReturnType::NonVoid(ref ty) => { match webidl_ty_to_syn_ty(ty, TypePosition::Return) { None => { warn!( "Operation's return type is not yet supported: {:?}. Skipping bindings for {:?}", ty, self ); return Ok(()); } Some(ty) => Some(ty), } } }; create_function( &name, self.arguments .iter() .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), kind, ret, Vec::new(), ).map(|function| { program.imports.push(backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Function(function), }) }); Ok(()) } }