/*! # `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)] #![doc(html_root_url = "https://docs.rs/wasm-bindgen-webidl/0.2")] extern crate failure; #[macro_use] extern crate failure_derive; extern crate heck; #[macro_use] extern crate log; extern crate proc_macro2; #[macro_use] extern crate quote; #[macro_use] extern crate syn; extern crate wasm_bindgen_backend as backend; extern crate webidl; mod first_pass; mod util; mod error; use std::collections::BTreeSet; use std::fs; use std::io::{self, Read}; use std::iter::FromIterator; use std::path::Path; use backend::TryToTokens; use backend::defined::{ImportedTypeDefinitions, RemoveUndefinedImports}; use backend::util::{ident_ty, rust_ident, wrap_import_function}; use failure::{ResultExt, Fail}; use heck::{ShoutySnakeCase}; use first_pass::{FirstPass, FirstPassRecord}; use util::{ApplyTypedefs, public, webidl_const_ty_to_syn_ty, webidl_const_v_to_backend_const_v, camel_case_ident, mdn_doc}; pub use error::{Error, ErrorKind, Result}; /// Parse the WebIDL at the given path into a wasm-bindgen AST. fn parse_file(webidl_path: &Path) -> Result<backend::ast::Program> { let file = fs::File::open(webidl_path).context(ErrorKind::OpeningWebIDLFile)?; let mut file = io::BufReader::new(file); let mut source = String::new(); file.read_to_string(&mut source).context(ErrorKind::ReadingWebIDLFile)?; parse(&source) } /// Parse a string of WebIDL source text into a wasm-bindgen AST. fn parse(webidl_source: &str) -> Result<backend::ast::Program> { let definitions = match webidl::parse_string(webidl_source) { Ok(def) => def, Err(e) => { let kind = match &e { webidl::ParseError::InvalidToken { location } => { ErrorKind::ParsingWebIDLSourcePos(*location) } webidl::ParseError::UnrecognizedToken { token: Some((start, ..)), .. } => { ErrorKind::ParsingWebIDLSourcePos(*start) } webidl::ParseError::ExtraToken { token: (start, ..) } => { ErrorKind::ParsingWebIDLSourcePos(*start) }, _ => ErrorKind::ParsingWebIDLSource }; return Err(e.context(kind).into()); } }; let mut first_pass_record = Default::default(); definitions.first_pass(&mut first_pass_record, ())?; let mut program = Default::default(); definitions.webidl_parse(&mut program, &first_pass_record, ())?; 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<String> { 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<String> { let ast = parse(webidl_source)?; Ok(compile_ast(ast)) } /// Run codegen on the AST to generate rust code. fn compile_ast(mut ast: backend::ast::Program) -> String { let mut defined = BTreeSet::from_iter( vec![ "str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize", "f32", "f64", "Result", "String", "Vec", "Option", "ArrayBuffer", ].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)); let mut tokens = proc_macro2::TokenStream::new(); if let Err(e) = ast.try_to_tokens(&mut tokens) { e.panic(); } tokens.to_string() } /// The main trait for parsing WebIDL AST into wasm-bindgen AST. trait WebidlParse<Ctx> { /// Parse `self` into wasm-bindgen AST, and insert it into `program`. fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, context: Ctx, ) -> Result<()>; } impl WebidlParse<()> for [webidl::ast::Definition] { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { for def in self { def.webidl_parse(program, first_pass, ())?; } Ok(()) } } impl WebidlParse<()> for webidl::ast::Definition { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { match self { webidl::ast::Definition::Enum(enumeration) => { enumeration.webidl_parse(program, first_pass, ())? } webidl::ast::Definition::Includes(includes) => { includes.webidl_parse(program, first_pass, ())? } webidl::ast::Definition::Interface(interface) => { interface.webidl_parse(program, first_pass, ())? } // TODO webidl::ast::Definition::Callback(..) | webidl::ast::Definition::Dictionary(..) | webidl::ast::Definition::Implements(..) | webidl::ast::Definition::Namespace(..) => { warn!("Unsupported WebIDL definition: {:?}", self) } webidl::ast::Definition::Mixin(_) | webidl::ast::Definition::Typedef(_) => { // handled in the first pass } } Ok(()) } } impl WebidlParse<()> for webidl::ast::Includes { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { match first_pass.mixins.get(&self.includee) { Some(mixin) => { if let Some(non_partial) = mixin.non_partial { for member in &non_partial.members { member.webidl_parse(program, first_pass, &self.includer)?; } } for partial in &mixin.partials { for member in &partial.members { member.webidl_parse(program, first_pass, &self.includer)?; } } } None => warn!("Tried to include missing mixin {}", self.includee), } Ok(()) } } impl WebidlParse<()> for webidl::ast::Interface { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { match self { webidl::ast::Interface::NonPartial(interface) => { interface.webidl_parse(program, first_pass, ()) } webidl::ast::Interface::Partial(interface) => { interface.webidl_parse(program, first_pass, ()) } // TODO webidl::ast::Interface::Callback(..) => { warn!("Unsupported WebIDL interface: {:?}", self); Ok(()) } } } } impl WebidlParse<()> for webidl::ast::NonPartialInterface { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } if util::is_no_interface_object(&self.extended_attributes) { return Ok(()); } let doc_comment = Some(format!("The `{}` object\n\n{}", &self.name, mdn_doc(&self.name, None))); program.imports.push(backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Type(backend::ast::ImportType { vis: public(), name: rust_ident(camel_case_ident(&self.name).as_str()), attrs: Vec::new(), doc_comment, }), }); for extended_attribute in &self.extended_attributes { extended_attribute.webidl_parse(program, first_pass, self)?; } for member in &self.members { member.webidl_parse(program, first_pass, &self.name)?; } Ok(()) } } impl WebidlParse<()> for webidl::ast::PartialInterface { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, (): (), ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } if !first_pass.interfaces.contains_key(&self.name) { warn!( "Partial interface {} missing non-partial interface", self.name ); } for member in &self.members { member.webidl_parse(program, first_pass, &self.name)?; } Ok(()) } } impl<'a> WebidlParse<&'a webidl::ast::NonPartialInterface> for webidl::ast::ExtendedAttribute { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, interface: &'a webidl::ast::NonPartialInterface, ) -> Result<()> { let mut add_constructor = |arguments: &[webidl::ast::Argument], class: &str| { let arguments = &arguments .iter() .map(|argument| argument.apply_typedefs(first_pass)) .collect::<Vec<_>>(); let (overloaded, same_argument_names) = first_pass.get_operation_overloading( arguments, ::first_pass::OperationId::Constructor, &interface.name, ); let self_ty = ident_ty(rust_ident(camel_case_ident(&interface.name).as_str())); let kind = backend::ast::ImportFunctionKind::Method { class: class.to_string(), ty: self_ty.clone(), kind: backend::ast::MethodKind::Constructor, }; let structural = false; // Constructors aren't annotated with `[Throws]` extended attributes // (how could they be, since they themselves are extended // attributes?) so we must conservatively assume that they can // always throw. // // From https://heycam.github.io/webidl/#Constructor (emphasis // mine): // // > The prose definition of a constructor must either return an IDL // > value of a type corresponding to the interface the // > `[Constructor]` extended attribute appears on, **or throw an // > exception**. let throws = true; first_pass .create_function( "new", overloaded, same_argument_names, arguments .iter() .map(|arg| (&*arg.name, &*arg.type_, arg.variadic)), Some(self_ty), kind, structural, throws, None, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); }; match self { webidl::ast::ExtendedAttribute::ArgumentList( webidl::ast::ArgumentListExtendedAttribute { arguments, name }, ) if name == "Constructor" => { add_constructor(arguments, &interface.name) } webidl::ast::ExtendedAttribute::NoArguments(webidl::ast::Other::Identifier(name)) if name == "Constructor" => { add_constructor(&[], &interface.name) } webidl::ast::ExtendedAttribute::NamedArgumentList( webidl::ast::NamedArgumentListExtendedAttribute { lhs_name, rhs_arguments, rhs_name, }, ) if lhs_name == "NamedConstructor" => { add_constructor(rhs_arguments, rhs_name) } 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, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { match self { webidl::ast::InterfaceMember::Attribute(attr) => { attr.webidl_parse(program, first_pass, self_name) } webidl::ast::InterfaceMember::Operation(op) => { op.webidl_parse(program, first_pass, self_name) } webidl::ast::InterfaceMember::Const(cnst) => { cnst.webidl_parse(program, first_pass, self_name) } webidl::ast::InterfaceMember::Iterable(iterable) => { iterable.webidl_parse(program, first_pass, self_name) } // TODO | webidl::ast::InterfaceMember::Maplike(_) | webidl::ast::InterfaceMember::Setlike(_) => { warn!("Unsupported WebIDL interface member: {:?}", self); Ok(()) } } } } impl<'a> WebidlParse<&'a str> for webidl::ast::MixinMember { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { match self { webidl::ast::MixinMember::Attribute(attr) => { attr.webidl_parse(program, first_pass, self_name) } webidl::ast::MixinMember::Operation(op) => { op.webidl_parse(program, first_pass, self_name) } // TODO webidl::ast::MixinMember::Const(_) => { 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, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { match self { webidl::ast::Attribute::Regular(attr) => { attr.webidl_parse(program, first_pass, self_name) } webidl::ast::Attribute::Static(attr) => { attr.webidl_parse(program, first_pass, self_name) } // TODO 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, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { match self { webidl::ast::Operation::Regular(op) => op.webidl_parse(program, first_pass, self_name), webidl::ast::Operation::Static(op) => op.webidl_parse(program, first_pass, self_name), // TODO webidl::ast::Operation::Special(_) | 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, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } let is_structural = util::is_structural(&self.extended_attributes); let throws = util::throws(&self.extended_attributes); first_pass .create_getter( &self.name, &self.type_.apply_typedefs(first_pass), self_name, false, is_structural, throws, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); if !self.read_only { first_pass .create_setter( &self.name, &self.type_.apply_typedefs(first_pass), self_name, false, is_structural, throws, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); } Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::Iterable { fn webidl_parse( &self, _program: &mut backend::ast::Program, _first_pass: &FirstPassRecord<'_>, _self_name: &'a str, ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } /* TODO let throws = util::throws(&self.extended_attributes); let return_value = webidl::ast::ReturnType::NonVoid(self.value_type.clone()); let args = []; first_pass .create_basic_method( &args, Some(&"values".to_string()), &return_value, self_name, false, false, // Should be false ) .map(wrap_import_function) .map(|import| program.imports.push(import)); first_pass .create_basic_method( &args, Some(&"keys".to_string()), &return_value, // Should be a number self_name, false, false, // Should be false ) .map(wrap_import_function) .map(|import| program.imports.push(import)); */ Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::StaticAttribute { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } let is_structural = util::is_structural(&self.extended_attributes); let throws = util::throws(&self.extended_attributes); first_pass .create_getter( &self.name, &self.type_.apply_typedefs(first_pass), self_name, true, is_structural, throws, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); if !self.read_only { first_pass .create_setter( &self.name, &self.type_.apply_typedefs(first_pass), self_name, true, is_structural, throws, ) .map(wrap_import_function) .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, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } let throws = util::throws(&self.extended_attributes); first_pass .create_basic_method( &self .arguments .iter() .map(|argument| argument.apply_typedefs(first_pass)) .collect::<Vec<_>>(), self.name.as_ref(), &self.return_type.apply_typedefs(first_pass), self_name, false, throws, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::StaticOperation { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { if util::is_chrome_only(&self.extended_attributes) { return Ok(()); } let throws = util::throws(&self.extended_attributes); first_pass .create_basic_method( &self .arguments .iter() .map(|argument| argument.apply_typedefs(first_pass)) .collect::<Vec<_>>(), self.name.as_ref(), &self.return_type.apply_typedefs(first_pass), self_name, true, throws, ) .map(wrap_import_function) .map(|import| program.imports.push(import)); Ok(()) } } impl<'a> WebidlParse<()> for webidl::ast::Enum { fn webidl_parse( &self, program: &mut backend::ast::Program, _: &FirstPassRecord<'_>, (): (), ) -> Result<()> { program.imports.push(backend::ast::Import { module: None, version: None, js_namespace: None, kind: backend::ast::ImportKind::Enum(backend::ast::ImportEnum { vis: public(), name: rust_ident(camel_case_ident(&self.name).as_str()), variants: self .variants .iter() .map(|v| if !v.is_empty() { rust_ident(camel_case_ident(&v).as_str()) } else { rust_ident("None") } ) .collect(), variant_values: self.variants.clone(), rust_attrs: vec![parse_quote!(#[derive(Copy, Clone, PartialEq, Debug)])], }), }); Ok(()) } } impl<'a> WebidlParse<&'a str> for webidl::ast::Const { fn webidl_parse( &self, program: &mut backend::ast::Program, first_pass: &FirstPassRecord<'_>, self_name: &'a str, ) -> Result<()> { let ty = webidl_const_ty_to_syn_ty(&self.type_.apply_typedefs(first_pass)); program.consts.push(backend::ast::Const { vis: public(), name: rust_ident(self.name.to_shouty_snake_case().as_str()), class: Some(rust_ident(camel_case_ident(&self_name).as_str())), ty, value: webidl_const_v_to_backend_const_v(&self.value), }); Ok(()) } }