| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::borrow::Cow; |
| use std::io; |
| |
| use crate::ast; |
| use crate::parser::ArgKind; |
| |
| pub type Result = io::Result<()>; |
| |
| pub struct Codegen<W: io::Write> { |
| w: W, |
| } |
| |
| impl<W: io::Write> Codegen<W> { |
| pub fn new(w: W) -> Codegen<W> { |
| Codegen { w } |
| } |
| |
| pub fn codegen(&mut self, protocol: ast::Protocol, dependencies: &[String]) -> Result { |
| self.codegen_protocol(protocol, dependencies) |
| } |
| |
| fn codegen_protocol(&mut self, protocol: ast::Protocol, dependencies: &[String]) -> Result { |
| writeln!(self.w, "// GENERATED FILE -- DO NOT EDIT")?; |
| if let Some(ref c) = protocol.copyright { |
| writeln!(self.w, "//")?; |
| for line in c.trim().lines() { |
| writeln!(self.w, "// {}", line.trim())?; |
| } |
| } |
| writeln!( |
| self.w, |
| " |
| #![allow(warnings)] |
| use bitflags::*; |
| use failure; |
| use fuchsia_wayland_core::{{ArgKind, Arg, Enum, Fixed, FromArgs, IntoMessage, Message, |
| MessageGroupSpec, MessageHeader, MessageSpec, MessageType, |
| NewId, NewObject, ObjectId, EncodeError, DecodeError, |
| Interface }};" |
| )?; |
| for dep in dependencies.iter() { |
| writeln!(self.w, "use {}::*;", dep)?; |
| } |
| |
| for interface in protocol.interfaces.into_iter() { |
| // Most symbols will be defined in a nested module, but re-export |
| // some into the top-level namespace. |
| // |
| // Ex, for wl_display: |
| // |
| // pub mod wl_display { |
| // pub enum Request { ... } |
| // pub enum Event { ... } |
| // pub struct WlDisplay; |
| // } |
| // |
| // pub use wl_display::WlDisplay; |
| // pub use wl_display::Request as WlDisplayRequest; |
| // pub use wl_display::Event as WlDisplayEvent; |
| writeln!(self.w, "pub mod {} {{", interface.name)?; |
| writeln!(self.w, "use super::*;")?; |
| self.codegen_interface_trait(&interface)?; |
| self.codegen_message_enum( |
| "Request", |
| &interface, |
| &interface.requests, |
| format_dispatch_arg_rust, |
| )?; |
| self.codegen_message_enum( |
| "Event", |
| &interface, |
| &interface.events, |
| format_wire_arg_rust, |
| )?; |
| self.codegen_impl_event(&interface.events)?; |
| self.codegen_from_args(&interface.requests)?; |
| self.codegen_enum_types(&interface)?; |
| writeln!(self.w, "}} // mod {}", interface.name)?; |
| writeln!(self.w, "")?; |
| writeln!( |
| self.w, |
| "pub use crate::{}::{};", |
| interface.name, |
| interface.rust_name() |
| )?; |
| writeln!( |
| self.w, |
| "pub use crate::{}::Request as {}Request;", |
| interface.name, |
| interface.rust_name() |
| )?; |
| writeln!( |
| self.w, |
| "pub use crate::{}::Event as {}Event;", |
| interface.name, |
| interface.rust_name() |
| )?; |
| } |
| Ok(()) |
| } |
| |
| /// Emits an enum that describes the set of messages for a single interface. |
| /// Each interface will have a message enum for both Requests and Events. |
| /// |
| /// Ex: |
| /// pub enum MyInterfaceRequest { |
| /// Request1 { arg1: u32 }, |
| /// Request2 { name: String}, |
| /// } |
| fn codegen_message_enum<F: Fn(&ast::Arg) -> Cow<str>>( |
| &mut self, name: &str, interface: &ast::Interface, messages: &Vec<ast::Message>, |
| arg_formatter: F, |
| ) -> Result { |
| writeln!(self.w, "#[derive(Debug)]")?; |
| writeln!(self.w, "pub enum {enum_name} {{", enum_name = name)?; |
| for message in messages.iter() { |
| if let Some(ref d) = message.description { |
| self.codegen_description(d, " ")?; |
| } |
| if message.args.is_empty() { |
| // For messages without args, emit a marker enum variant. |
| // Ex: |
| // Request::Message, |
| writeln!(self.w, " {},", message.rust_name())?; |
| } else { |
| // For messages with args, emit a struct enum variant with an |
| // entry for each arg: |
| // Ex: |
| // Request::Message { |
| // arg1: u32, |
| // arg2: String, |
| // }, |
| writeln!(self.w, " {} {{", message.rust_name())?; |
| for arg in message.args.iter() { |
| if let Some(ref summary) = arg.summary { |
| for line in summary.lines() { |
| writeln!(self.w, " /// {}", line.trim())?; |
| } |
| } |
| writeln!( |
| self.w, |
| " {arg_name}: {arg_type},", |
| arg_name = arg.rust_name(), |
| arg_type = arg_formatter(&arg) |
| )?; |
| } |
| writeln!(self.w, " }},")?; |
| } |
| } |
| writeln!(self.w, "}}")?; |
| writeln!(self.w, "")?; |
| |
| writeln!(self.w, "impl MessageType for {} {{", name)?; |
| |
| // Generate the log method: |
| // |
| // fn log(&self, this: ObjectId) -> String { |
| // let mut string = String::new(); |
| // match *self { |
| // WlInterface::Message { ref arg } => |
| // format!("wl_interface@{}::message(arg: {:?})", this, arg), |
| // ... |
| // } |
| // } |
| writeln!(self.w, " fn log(&self, this: ObjectId) -> String {{")?; |
| writeln!(self.w, " match *self {{")?; |
| for message in messages.iter() { |
| writeln!(self.w, " {}::{} {{", name, message.rust_name())?; |
| for arg in message.args.iter() { |
| writeln!(self.w, " ref {},", arg.rust_name())?; |
| } |
| writeln!(self.w, " }} => {{")?; |
| |
| // We're using format strings to build a format string, so this is |
| // a little confusing. |message_args| are the set of strings that |
| // will be joined to form the format string literal. |format_args| |
| // are the rust expressions that will be used by the format string. |
| // |
| // Anytime we put a '{{}}' into |message_args|, we'll need a |
| // corresponding expression pushed to |format_args|. |
| // |
| // We'll end up with something like: |
| // format!("some_interface@3::message1(arg1: {}, arg2: {})", arg1, arg2) |
| write!( |
| self.w, |
| " format!(\"{}@{{:?}}::{}(", |
| interface.name, message.name |
| )?; |
| let mut message_args = vec![]; |
| let mut format_args: Vec<Cow<str>> = vec!["this".into()]; |
| for arg in message.args.iter() { |
| match arg.kind { |
| ArgKind::Array => { |
| message_args.push(format!("{}: Array[{{}}]", arg.name)); |
| format_args.push(format!("{}.len()", arg.rust_name()).into()); |
| } |
| ArgKind::Fd => { |
| message_args.push(format!("{}: <handle>", arg.name)); |
| } |
| _ => { |
| message_args.push(format!("{}: {{:?}}", arg.name)); |
| format_args.push(arg.rust_name().into()); |
| } |
| } |
| } |
| writeln!( |
| self.w, |
| "{})\", {})", |
| message_args.join(", "), |
| format_args.join(", ") |
| )?; |
| writeln!(self.w, " }}")?; |
| } |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, "}}")?; |
| Ok(()) |
| } |
| |
| /// Generates an impl for the Event trait for a set of messages. This |
| /// will be the code that allows the message type to be serialized into |
| /// a Message that can be sent over channel. |
| /// |
| /// Ex: |
| /// impl IntoMessage for Event { |
| /// fn into_message(self, id: u32) -> Result<Message, Self::Error> { |
| /// let mut header = MessageHeader {...}; |
| /// let mut message = Message::new(); |
| /// message.write_header(&header); |
| /// match self { |
| /// MyInterfaceEvent::Event1 { uint_arg } => { |
| /// message.write_arg(Arg::Uint(uint_arg))?; |
| /// header.opcode = 0; |
| /// }, |
| /// // ... Encode other events... |
| /// } |
| /// |
| /// // Rewrite header with proper ordinal & length. |
| /// header.length = msg.bytes().len() as u16; |
| /// message.rewind(); |
| /// message.write_header(&header); |
| /// } |
| /// } |
| fn codegen_impl_event(&mut self, messages: &Vec<ast::Message>) -> Result { |
| write!( |
| self.w, |
| "\ |
| impl IntoMessage for Event {{ |
| type Error = EncodeError; |
| fn into_message(self, id: u32) -> Result<Message, Self::Error> {{ |
| let mut header = MessageHeader {{ |
| sender: id, |
| opcode: 0, |
| length: 0, |
| }}; |
| let mut msg = Message::new(); |
| msg.write_header(&header)?; |
| match self {{" |
| )?; |
| |
| for (op, event) in messages.iter().enumerate() { |
| write!( |
| self.w, |
| " |
| Event::{message_name} {{\n", |
| message_name = to_camel_case(&event.name) |
| )?; |
| for arg in event.args.iter() { |
| write!( |
| self.w, |
| " {arg_name},\n", |
| arg_name = arg.rust_name() |
| )?; |
| } |
| write!(self.w, " }} => {{\n")?; |
| for arg in event.args.iter() { |
| write!( |
| self.w, |
| " msg.write_arg({arg})?;\n", |
| arg = format_wire_arg(&arg, &arg.name) |
| )?; |
| } |
| write!( |
| self.w, |
| " header.opcode = {opcode}; |
| }},", |
| opcode = op |
| )?; |
| } |
| write!( |
| self.w, |
| " |
| }} |
| header.length = msg.bytes().len() as u16; |
| msg.rewind(); |
| msg.write_header(&header)?; |
| Ok(msg) |
| }} |
| }}\n" |
| ) |
| } |
| |
| fn codegen_from_args(&mut self, messages: &Vec<ast::Message>) -> Result { |
| write!( |
| self.w, |
| "\ |
| impl FromArgs for Request {{ |
| fn from_args(op: u16, mut args: Vec<Arg>) -> Result<Self, failure::Error> {{ |
| match op {{", |
| )?; |
| |
| for (op, message) in messages.iter().enumerate() { |
| write!( |
| self.w, |
| " |
| {opcode} /* {op_name} */ => {{ |
| let mut iter = args.into_iter(); |
| Ok(Request::{message_name} {{\n", |
| opcode = op, |
| op_name = message.name, |
| message_name = to_camel_case(&message.name) |
| )?; |
| for arg in message.args.iter() { |
| writeln!( |
| self.w, |
| " {}: iter.next() |
| .ok_or(DecodeError::InsufficientArgs)? |
| .{},", |
| arg.rust_name(), |
| arg_to_primitive(&arg) |
| )?; |
| } |
| write!( |
| self.w, |
| " |
| }}) |
| }}," |
| )?; |
| } |
| write!( |
| self.w, |
| " |
| _ => {{ |
| Err(DecodeError::InvalidOpcode(op).into()) |
| }}, |
| }} |
| }} |
| }}\n" |
| ) |
| } |
| |
| /// Generates a trait for each interface. |
| /// |
| /// Ex: |
| /// pub struct MyInterface; |
| /// |
| /// impl Interface for MyInterface { |
| /// const NAME: &'static str = "my_interface"; |
| /// const VERSION: u32 = 0; |
| /// type Request = MyInterfaceRequest; |
| /// type Event = MyInterfaceEvent; |
| /// } |
| fn codegen_interface_trait(&mut self, interface: &ast::Interface) -> Result { |
| let camel_name = to_camel_case(&interface.name); |
| if let Some(ref d) = interface.description { |
| self.codegen_description(d, "")?; |
| } |
| writeln!(self.w, "#[derive(Debug)]")?; |
| writeln!(self.w, "pub struct {};", camel_name)?; |
| writeln!(self.w, "")?; |
| writeln!(self.w, "impl Interface for {} {{", camel_name)?; |
| writeln!( |
| self.w, |
| " const NAME: &'static str = \"{}\";", |
| interface.name |
| )?; |
| writeln!(self.w, " const VERSION: u32 = {};", interface.version)?; |
| write!(self.w, " const REQUESTS: MessageGroupSpec = ")?; |
| self.codegen_message_group_spec(&interface.requests)?; |
| write!(self.w, " const EVENTS: MessageGroupSpec = ")?; |
| self.codegen_message_group_spec(&interface.events)?; |
| writeln!(self.w, " type Request = Request;")?; |
| writeln!(self.w, " type Event = Event;")?; |
| writeln!(self.w, "}}")?; |
| writeln!(self.w, "")?; |
| Ok(()) |
| } |
| |
| fn codegen_message_group_spec(&mut self, messages: &Vec<ast::Message>) -> Result { |
| writeln!(self.w, "MessageGroupSpec(&[")?; |
| for m in messages.iter() { |
| writeln!(self.w, " // {}", m.name)?; |
| writeln!(self.w, " MessageSpec(&[")?; |
| for arg in m.args.iter() { |
| writeln!(self.w, " {},", format_arg_kind(&arg))?; |
| } |
| writeln!(self.w, " ]),")?; |
| } |
| writeln!(self.w, " ]);")?; |
| Ok(()) |
| } |
| |
| fn codegen_enum_types(&mut self, interface: &ast::Interface) -> Result { |
| for e in interface.enums.iter() { |
| if e.bitfield { |
| self.codegen_bitflags_enum(e)?; |
| } else { |
| self.codegen_value_enum(e)?; |
| } |
| } |
| Ok(()) |
| } |
| |
| fn codegen_value_enum(&mut self, e: &ast::Enum) -> Result { |
| if let Some(ref d) = e.description { |
| self.codegen_description(d, "")?; |
| } |
| writeln!(self.w, "#[derive(Copy, Clone, Debug, Eq, PartialEq)]")?; |
| writeln!(self.w, "#[repr(u32)]")?; |
| writeln!(self.w, "pub enum {} {{", e.rust_name())?; |
| for entry in e.entries.iter() { |
| if let Some(ref s) = entry.summary { |
| for l in s.lines() { |
| writeln!(self.w, " /// {},", l.trim())?; |
| } |
| } |
| writeln!(self.w, " {},", entry.rust_name())?; |
| } |
| writeln!(self.w, "}}")?; |
| writeln!(self.w, "")?; |
| writeln!(self.w, "impl {} {{", e.rust_name())?; |
| writeln!(self.w, " pub fn from_bits(v: u32) -> Option<Self> {{")?; |
| writeln!(self.w, " match v {{")?; |
| for entry in e.entries.iter() { |
| writeln!( |
| self.w, |
| " {} => Some({}::{}),", |
| entry.value, |
| e.rust_name(), |
| entry.rust_name() |
| )?; |
| } |
| writeln!(self.w, " _ => None,")?; |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, "")?; |
| writeln!(self.w, " pub fn bits(&self) -> u32 {{")?; |
| writeln!(self.w, " *self as u32")?; |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, "}}")?; |
| Ok(()) |
| } |
| |
| fn codegen_bitflags_enum(&mut self, e: &ast::Enum) -> Result { |
| writeln!(self.w, "::bitflags::bitflags! {{")?; |
| if let Some(ref d) = e.description { |
| self.codegen_description(d, " ")?; |
| } |
| writeln!(self.w, " pub struct {}: u32 {{", e.rust_name())?; |
| for entry in e.entries.iter() { |
| if let Some(ref s) = entry.summary { |
| for l in s.lines() { |
| writeln!(self.w, " /// {},", l.trim())?; |
| } |
| } |
| writeln!( |
| self.w, |
| " const {} = {};", |
| entry.rust_name(), |
| entry.value |
| )?; |
| } |
| writeln!(self.w, " }}")?; |
| writeln!(self.w, "}}")?; |
| Ok(()) |
| } |
| |
| fn codegen_description(&mut self, d: &ast::Description, prefix: &str) -> Result { |
| writeln!(self.w, "")?; |
| for s in d.summary.as_str().trim().lines() { |
| writeln!(self.w, "{}/// {}", prefix, s.trim())?; |
| } |
| writeln!(self.w, "{}///", prefix)?; |
| for s in d.description.trim().lines() { |
| writeln!(self.w, "{}/// {}", prefix, s.trim())?; |
| } |
| Ok(()) |
| } |
| } |
| |
| fn to_camel_case(s: &str) -> String { |
| s.split('_') |
| .filter(|s| s.len() > 0) |
| .map(|s| s[..1].to_uppercase() + &s[1..]) |
| .collect() |
| } |
| |
| /// Enums can be referenced outside of the interface that defines them. When |
| /// arguments are tagged with an enum, they'll provide the path to the enum |
| /// in the form <interface>.<enum>. |
| /// |
| /// For example, 'wl_output.transform' refers to the enum named 'transform' |
| /// that's defined in the 'wl_output' interface. |
| /// |
| /// Since our rust modules already mirror this structure, we can simply use a |
| /// relative path to the enum when it's defined in the same interface, or |
| /// reference the interface module for foreign enums. |
| /// |
| /// Ex: |
| /// Within the 'wl_output' module, the 'transform' enum can be referred to |
| /// as just 'Transform'. |
| /// |
| /// When 'wl_output' is referred to from another module we can use the crate- |
| /// relative path 'crate::wl_output::Transform'. |
| /// |
| /// Note we could always use the crate-relative path, but that would require |
| /// passing the 'interface' parameter around to lots of logic that otherwise |
| /// doesn't care. |
| fn enum_path(name: &str) -> String { |
| let parts: Vec<&str> = name.splitn(2, ".").collect(); |
| if parts.len() == 1 { |
| to_camel_case(name) |
| } else { |
| format!("crate::{}::{}", parts[0], to_camel_case(parts[1])) |
| } |
| } |
| |
| fn format_dispatch_arg_rust(arg: &ast::Arg) -> Cow<str> { |
| if let Some(ref enum_type) = arg.enum_type { |
| return format!("Enum<{}>", enum_path(enum_type)).into(); |
| } |
| match arg.kind { |
| ArgKind::Int => "i32".into(), |
| ArgKind::Uint => "u32".into(), |
| ArgKind::Fixed => "Fixed".into(), |
| ArgKind::String => "String".into(), |
| ArgKind::Object => "ObjectId".into(), |
| ArgKind::NewId => { |
| if let Some(interface) = &arg.interface { |
| format!("NewObject<{}>", to_camel_case(&interface)).into() |
| } else { |
| "ObjectId".into() |
| } |
| } |
| ArgKind::Array => "Vec<u8>".into(), |
| ArgKind::Fd => "fuchsia_zircon::Handle".into(), |
| } |
| } |
| |
| fn format_wire_arg_rust(arg: &ast::Arg) -> Cow<str> { |
| if let Some(ref enum_type) = arg.enum_type { |
| return enum_path(enum_type).into(); |
| } |
| match arg.kind { |
| ArgKind::Int => "i32", |
| ArgKind::Uint => "u32", |
| ArgKind::Fixed => "Fixed", |
| ArgKind::String => "String", |
| ArgKind::Object => "ObjectId", |
| ArgKind::NewId => "NewId", |
| ArgKind::Array => "Vec<u8>", |
| ArgKind::Fd => "fuchsia_zircon::Handle", |
| } |
| .into() |
| } |
| |
| fn format_arg_kind(arg: &ast::Arg) -> &'static str { |
| match arg.kind { |
| ArgKind::Int => "ArgKind::Int", |
| ArgKind::Uint => "ArgKind::Uint", |
| ArgKind::Fixed => "ArgKind::Fixed", |
| ArgKind::String => "ArgKind::String", |
| ArgKind::Object => "ArgKind::Object", |
| ArgKind::NewId => "ArgKind::NewId", |
| ArgKind::Array => "ArgKind::Array", |
| ArgKind::Fd => "ArgKind::Handle", |
| } |
| } |
| |
| fn format_wire_arg(arg: &ast::Arg, var: &str) -> String { |
| if arg.enum_type.is_some() { |
| return format!("Arg::Uint({}.bits())", var); |
| } |
| match arg.kind { |
| ArgKind::Int => format!("Arg::Int({})", var), |
| ArgKind::Uint => format!("Arg::Uint({})", var), |
| ArgKind::Fixed => format!("Arg::Fixed({})", var), |
| ArgKind::String => format!("Arg::String({})", var), |
| ArgKind::Object => format!("Arg::Object({})", var), |
| ArgKind::NewId => format!("Arg::NewId({})", var), |
| ArgKind::Array => format!("Arg::Array({})", var), |
| ArgKind::Fd => format!("Arg::Handle({})", var), |
| } |
| } |
| |
| fn arg_to_primitive(arg: &ast::Arg) -> String { |
| if let Some(ref enum_type) = arg.enum_type { |
| return format!( |
| "as_uint().map(|i| match {}::from_bits(i) {{ |
| Some(e) => Enum::Recognized(e), |
| None => Enum::Unrecognized(i), |
| }})?", |
| enum_path(enum_type) |
| ); |
| } |
| match arg.kind { |
| ArgKind::Int => "as_int()?", |
| ArgKind::Uint => "as_uint()?", |
| ArgKind::Fixed => "as_fixed()?.into()", |
| ArgKind::String => "as_string()?", |
| ArgKind::Object => "as_object()?", |
| ArgKind::NewId => "as_new_id()?.into()", |
| ArgKind::Array => "as_array()?", |
| ArgKind::Fd => "as_handle()?", |
| } |
| .to_string() |
| } |
| |
| /// Helper trait for transforming wayland protocol names into the rust |
| /// counterparts. |
| /// |
| /// Ex, wl_display is written WlDisplay in rust code. |
| trait RustName { |
| fn rust_name(&self) -> String; |
| } |
| |
| impl RustName for ast::EnumEntry { |
| // some wayland enums are just numbers, which would result in illegal rust |
| // symbols. If we see a name that starts with a number we'll just prefix |
| // with '_'. |
| fn rust_name(&self) -> String { |
| let is_digit = self.name.chars().next().map_or(false, |c| c.is_digit(10)); |
| let prefix = if is_digit { "_" } else { "" }; |
| format!("{}{}", prefix, to_camel_case(&self.name)) |
| } |
| } |
| |
| fn is_rust_keyword(s: &str) -> bool { |
| match s { |
| "as" | "break" | "const" | "continue" | "crate" | "dyn" | "else" | "enum" | "extern" |
| | "false" | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" |
| | "move" | "mut" | "pub" | "ref" | "return" | "Self" | "self" | "static" | "struct" |
| | "super" | "trait" | "true" | "type" | "unsafe" | "use" | "where" | "while" |
| | "abstract" | "async" | "await" | "become" | "box" | "do" | "final" | "macro" |
| | "override" | "priv" | "try" | "typeof" | "unsized" | "virtual" | "yield" => true, |
| _ => false, |
| } |
| } |
| |
| impl RustName for ast::Arg { |
| fn rust_name(&self) -> String { |
| if is_rust_keyword(&self.name) { |
| format!("r#{}", self.name) |
| } else { |
| self.name.to_owned() |
| } |
| } |
| } |
| |
| impl RustName for ast::Message { |
| fn rust_name(&self) -> String { |
| to_camel_case(&self.name) |
| } |
| } |
| |
| impl RustName for ast::Enum { |
| fn rust_name(&self) -> String { |
| to_camel_case(&self.name) |
| } |
| } |
| |
| impl RustName for ast::Interface { |
| fn rust_name(&self) -> String { |
| to_camel_case(&self.name) |
| } |
| } |