diff --git a/crates/libs/metadata/src/writer/imp/mod.rs b/crates/libs/metadata/src/writer/imp/mod.rs index d4a9182dd7..adecde371a 100644 --- a/crates/libs/metadata/src/writer/imp/mod.rs +++ b/crates/libs/metadata/src/writer/imp/mod.rs @@ -71,7 +71,7 @@ pub fn write(name: &str, winrt: bool, definitions: &[Item], assemblies: &[&str]) Item::Enum(ty) => { strings.insert(&ty.namespace); strings.insert(&ty.name); - let enum_type = Type::named(&ty.namespace, &ty.name); + let enum_type = Type::Named((ty.namespace.clone(), ty.name.clone())); blobs.insert(field_blob(&enum_type, definitions, references)); blobs.insert(field_blob(&value_to_type(&ty.constants[0].value), definitions, references)); ty.constants.iter().for_each(|constant| { @@ -138,7 +138,7 @@ pub fn write(name: &str, winrt: bool, definitions: &[Item], assemblies: &[&str]) FieldList: tables.Field.len() as _, MethodList: tables.MethodDef.len() as _, }); - let enum_type = Type::named(&ty.namespace, &ty.name); + let enum_type = Type::Named((ty.namespace.clone(), ty.name.clone())); let flags = FieldAttributes::PRIVATE | FieldAttributes::SPECIAL | FieldAttributes::RUNTIME_SPECIAL; tables.Field.push2(tables::Field { Flags: flags.0, diff --git a/crates/libs/metadata/src/writer/mod.rs b/crates/libs/metadata/src/writer/mod.rs index d64447bab8..8dcf332b8d 100644 --- a/crates/libs/metadata/src/writer/mod.rs +++ b/crates/libs/metadata/src/writer/mod.rs @@ -18,82 +18,40 @@ pub struct Struct { pub fields: Vec, } -impl Struct { - pub fn item(namespace: &str, name: &str, fields: Vec) -> Item { - Item::Struct(Self { namespace: namespace.to_string(), name: name.to_string(), fields }) - } -} - pub struct Enum { pub namespace: String, pub name: String, pub constants: Vec, } -impl Enum { - pub fn item(namespace: &str, name: &str, constants: Vec) -> Item { - Item::Enum(Self { namespace: namespace.to_string(), name: name.to_string(), constants }) - } -} - pub struct Interface { pub namespace: String, pub name: String, pub methods: Vec, } -impl Interface { - pub fn item(namespace: &str, name: &str, methods: Vec) -> Item { - Item::Interface(Self { namespace: namespace.to_string(), name: name.to_string(), methods }) - } -} - pub struct Field { pub name: String, pub ty: Type, } -impl Field { - pub fn new(name: &str, ty: Type) -> Self { - Self { name: name.to_string(), ty } - } -} - pub struct Constant { pub name: String, pub value: Value, } -impl Constant { - pub fn new(name: &str, value: Value) -> Self { - Self { name: name.to_string(), value } - } -} - pub struct Method { pub name: String, pub return_type: Type, pub params: Vec, } -impl Method { - pub fn new(name: &str, return_type: Type, params: Vec) -> Self { - Self { name: name.to_string(), return_type, params } - } -} - pub struct Param { pub name: String, pub ty: Type, pub flags: ParamFlags, } -impl Param { - pub fn new(name: &str, ty: Type, flags: ParamFlags) -> Self { - Self { name: name.to_string(), ty, flags } - } -} - flags!(ParamFlags, u32); impl ParamFlags { pub const INPUT: Self = Self(0x1); @@ -121,12 +79,6 @@ pub enum Type { Named((String, String)), } -impl Type { - pub fn named(namespace: &str, name: &str) -> Self { - Self::Named((namespace.to_string(), name.to_string())) - } -} - pub enum Value { Bool(bool), U8(u8), diff --git a/crates/tests/metadata/tests/writer.rs b/crates/tests/metadata/tests/writer.rs deleted file mode 100644 index 8a17b7fbeb..0000000000 --- a/crates/tests/metadata/tests/writer.rs +++ /dev/null @@ -1,22 +0,0 @@ -#[test] -fn writer() { - // ildasm %temp%\test_metadata.winmd - let temp_file = std::env::temp_dir().join("test_metadata.winmd"); - { - use metadata::writer::*; - - let mut items = vec![]; - - items.push(Struct::item("test_metadata", "A", vec![Field::new("A1", Type::F32), Field::new("A2", Type::F64)])); - items.push(Struct::item("test_metadata", "B", vec![Field::new("B1", Type::F32), Field::new("B2", Type::named("test_metadata", "A"))])); - - items.push(Enum::item("test_metadata", "C", vec![Constant::new("C1", Value::I32(1)), Constant::new("C2", Value::I32(2))])); - items.push(Enum::item("test_metadata", "D", vec![Constant::new("D1", Value::I32(3)), Constant::new("D2", Value::I32(4))])); - - items.push(Interface::item("test_metadata", "E", vec![Method::new("E1", Type::Void, vec![]), Method::new("E2", Type::I32, vec![Param::new("p1", Type::I32, ParamFlags::INPUT), Param::new("p2", Type::F32, ParamFlags::OUTPUT), Param::new("p3", Type::F32, ParamFlags::INPUT | ParamFlags::OUTPUT)])])); - items.push(Interface::item("test_metadata", "F", vec![Method::new("F1", Type::Void, vec![]), Method::new("F2", Type::Void, vec![])])); - - let buffer = write("test_metadata", true, &items, &[]); - std::fs::write(temp_file, buffer).unwrap(); - } -} diff --git a/crates/tests/riddle/src/lib.rs b/crates/tests/riddle/src/lib.rs index 8b13789179..3db6442ed2 100644 --- a/crates/tests/riddle/src/lib.rs +++ b/crates/tests/riddle/src/lib.rs @@ -1 +1,21 @@ +use std::process::Command; +pub fn run_riddle(input: &str) -> String { + let mut command = Command::new("cargo.exe"); + command.arg("install").arg("--path").arg("../../tools/riddle"); + + if !command.status().unwrap().success() { + panic!("Failed to install riddle"); + } + + let output = std::env::temp_dir().join(std::path::Path::new(input).with_extension("winmd").file_name().expect("file_name failed")).to_string_lossy().into_owned(); + + let mut command = Command::new("riddle.exe"); + command.arg("-input").arg(input).arg("-output").arg(&output); + + if !command.status().unwrap().success() { + panic!("Failed to run riddle"); + } + + output +} diff --git a/crates/tests/riddle/src/basic.rs b/crates/tests/riddle/tests/basic.idl similarity index 100% rename from crates/tests/riddle/src/basic.rs rename to crates/tests/riddle/tests/basic.idl diff --git a/crates/tests/riddle/tests/basic.rs b/crates/tests/riddle/tests/basic.rs index 80818e998d..d122762b69 100644 --- a/crates/tests/riddle/tests/basic.rs +++ b/crates/tests/riddle/tests/basic.rs @@ -1,25 +1,9 @@ -use std::process::Command; +use test_riddle::run_riddle; use windows_metadata::reader::*; #[test] -fn basic() { - // For visual inspection: ildasm.exe %temp%\test_riddle.winmd - let output = std::env::temp_dir().join("test_riddle.winmd").to_string_lossy().into_owned(); - - let mut command = Command::new("cargo.exe"); - command.arg("install").arg("--path").arg("../../tools/riddle"); - - if !command.status().unwrap().success() { - panic!("Failed to install riddle"); - } - - let mut command = Command::new("riddle.exe"); - command.arg("-input").arg("src/basic.rs").arg("-output").arg(&output); - - if !command.status().unwrap().success() { - panic!("Failed to run riddle"); - } - +fn riddle_basic() { + let output = run_riddle("tests/basic.idl"); let files = File::with_default(&[&output]).expect("Failed to open winmd files"); let reader = &Reader::new(&files); diff --git a/crates/tests/riddle/tests/enum.idl b/crates/tests/riddle/tests/enum.idl new file mode 100644 index 0000000000..27016a44e4 --- /dev/null +++ b/crates/tests/riddle/tests/enum.idl @@ -0,0 +1,8 @@ +mod TypeNamespace { + enum TypeName { + A, + B, + C = 42, + D, + } +} diff --git a/crates/tests/riddle/tests/enum.rs b/crates/tests/riddle/tests/enum.rs new file mode 100644 index 0000000000..c9f34506d6 --- /dev/null +++ b/crates/tests/riddle/tests/enum.rs @@ -0,0 +1,26 @@ +use test_riddle::run_riddle; +use windows_metadata::reader::*; + +#[test] +fn riddle_enum() { + let output = run_riddle("tests/enum.idl"); + let files = File::with_default(&[&output]).expect("Failed to open winmd files"); + let reader = &Reader::new(&files); + + let def = reader.get(TypeName::new("TypeNamespace", "TypeName")).next().expect("Type missing"); + assert_eq!(reader.type_def_kind(def), TypeKind::Enum); + + let fields: Vec = reader.type_def_fields(def).collect(); + assert_eq!(fields.len(), 5); + assert_eq!(reader.field_name(fields[0]), "value__"); + + assert_eq!(reader.field_name(fields[1]), "A"); + assert_eq!(reader.field_name(fields[2]), "B"); + assert_eq!(reader.field_name(fields[3]), "C"); + assert_eq!(reader.field_name(fields[4]), "D"); + + assert!(matches!(reader.constant_value(reader.field_constant(fields[1]).expect("missing constant")), Value::I32(value) if value == 0)); + assert!(matches!(reader.constant_value(reader.field_constant(fields[2]).expect("missing constant")), Value::I32(value) if value == 1)); + assert!(matches!(reader.constant_value(reader.field_constant(fields[3]).expect("missing constant")), Value::I32(value) if value == 42)); + assert!(matches!(reader.constant_value(reader.field_constant(fields[4]).expect("missing constant")), Value::I32(value) if value == 43)); +} diff --git a/crates/tests/riddle/tests/struct.idl b/crates/tests/riddle/tests/struct.idl new file mode 100644 index 0000000000..a4c4245a37 --- /dev/null +++ b/crates/tests/riddle/tests/struct.idl @@ -0,0 +1,8 @@ +mod TypeNamespace { + struct TypeName{ + a: i32, + b: f32, + c: u64, + d: f64, + } +} diff --git a/crates/tests/riddle/tests/struct.rs b/crates/tests/riddle/tests/struct.rs new file mode 100644 index 0000000000..5895dccfc7 --- /dev/null +++ b/crates/tests/riddle/tests/struct.rs @@ -0,0 +1,25 @@ +use test_riddle::run_riddle; +use windows_metadata::reader::*; + +#[test] +fn riddle_struct() { + let output = run_riddle("tests/struct.idl"); + let files = File::with_default(&[&output]).expect("Failed to open winmd files"); + let reader = &Reader::new(&files); + + let def = reader.get(TypeName::new("TypeNamespace", "TypeName")).next().expect("Type missing"); + assert_eq!(reader.type_def_kind(def), TypeKind::Struct); + + let fields: Vec = reader.type_def_fields(def).collect(); + assert_eq!(fields.len(), 4); + + assert_eq!(reader.field_name(fields[0]), "a"); + assert_eq!(reader.field_name(fields[1]), "b"); + assert_eq!(reader.field_name(fields[2]), "c"); + assert_eq!(reader.field_name(fields[3]), "d"); + + assert!(matches!(reader.field_type(fields[0], None), Type::I32)); + assert!(matches!(reader.field_type(fields[1], None), Type::F32)); + assert!(matches!(reader.field_type(fields[2], None), Type::U64)); + assert!(matches!(reader.field_type(fields[3], None), Type::F64)); +} diff --git a/crates/tools/riddle/src/main.rs b/crates/tools/riddle/src/main.rs index 6189437ea8..86c0dab2b6 100644 --- a/crates/tools/riddle/src/main.rs +++ b/crates/tools/riddle/src/main.rs @@ -1,90 +1,8 @@ +mod syntax; use metadata::writer; use std::io::Read; -use syn::{parse::*, *}; - -mod keywords { - syn::custom_keyword!(interface); -} - -#[derive(Debug)] -struct Module { - pub name: Ident, - pub members: Vec, -} - -impl Parse for Module { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - let name = input.parse::()?; - let content; - braced!(content in input); - let mut members = Vec::new(); - while !content.is_empty() { - members.push(content.parse::()?); - } - Ok(Self { name, members }) - } -} - -#[derive(Debug)] -enum ModuleMember { - Module(Module), - Interface(Interface), -} - -impl Parse for ModuleMember { - fn parse(input: ParseStream) -> Result { - let lookahead = input.lookahead1(); - if lookahead.peek(Token![mod]) { - Ok(ModuleMember::Module(input.parse::()?)) - } else if lookahead.peek(keywords::interface) { - Ok(ModuleMember::Interface(input.parse::()?)) - } else { - Err(lookahead.error()) - } - } -} - -#[derive(Debug)] -struct Interface { - pub name: Ident, - pub methods: Vec, -} - -impl Parse for Interface { - fn parse(input: ParseStream) -> Result { - input.parse::()?; - let name = input.parse::()?; - let content; - braced!(content in input); - let mut methods = Vec::new(); - while !content.is_empty() { - methods.push(content.parse::()?); - } - Ok(Self { name, methods }) - } -} - -fn module_to_writer(namespace: &str, module: &Module, items: &mut Vec) -> Result<()> { - for member in &module.members { - match member { - ModuleMember::Module(module) => module_to_writer(&format!("{namespace}.{}", module.name), module, items)?, - ModuleMember::Interface(interface) => interface_to_writer(namespace, interface, items)?, - } - } - Ok(()) -} - -fn interface_to_writer(namespace: &str, interface: &Interface, items: &mut Vec) -> Result<()> { - let mut methods = Vec::new(); - - for method in &interface.methods { - methods.push(writer::Method { name: method.sig.ident.to_string(), return_type: writer::Type::Void, params: vec![] }); - } - - items.push(writer::Interface::item(namespace, &interface.name.to_string(), methods)); - Ok(()) -} +use syn::*; +use syntax::*; fn main() { if let Err(message) = run() { @@ -147,9 +65,10 @@ fn run() -> ToolResult { let mut source = String::new(); file.read_to_string(&mut source).map_err(|_| format!("failed to read `{filename}`"))?; - if let Err(error) = parse_str::(&source).and_then(|module| module_to_writer(&module.name.to_string(), &module, &mut items)) { + if let Err(error) = parse_str::(&source).and_then(|module| module.to_writer(module.name.to_string(), &mut items)) { let start = error.span().start(); - return Err(format!("{error}\n --> {}:{:?}:{:?} ", filename, start.line, start.column)); + let filename = std::fs::canonicalize(filename).map_err(|_| format!("failed to canonicalize `{filename}`"))?; + return Err(format!("{error}\n --> {}:{:?}:{:?} ", filename.to_string_lossy().trim_start_matches(r#"\\?\"#), start.line, start.column)); } } diff --git a/crates/tools/riddle/src/syntax.rs b/crates/tools/riddle/src/syntax.rs new file mode 100644 index 0000000000..a773f58008 --- /dev/null +++ b/crates/tools/riddle/src/syntax.rs @@ -0,0 +1,184 @@ +use metadata::writer; +use syn::{parse::*, spanned::*, *}; + +mod keywords { + syn::custom_keyword!(interface); +} + +pub trait ToWriter { + fn to_writer(&self, namespace: String, items: &mut Vec) -> Result<()>; +} + +pub struct Module { + pub name: Ident, + pub members: Vec, +} + +impl Parse for Module { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + let name = input.parse::()?; + let content; + braced!(content in input); + let mut members = vec![]; + while !content.is_empty() { + members.push(content.parse::()?); + } + Ok(Self { name, members }) + } +} + +impl ToWriter for Module { + fn to_writer(&self, namespace: String, items: &mut Vec) -> Result<()> { + for member in &self.members { + match member { + ModuleMember::Module(member) => member.to_writer(format!("{namespace}.{}", member.name), items)?, + ModuleMember::Interface(member) => member.to_writer(namespace.clone(), items)?, + ModuleMember::Struct(member) => member.to_writer(namespace.clone(), items)?, + ModuleMember::Enum(member) => member.to_writer(namespace.clone(), items)?, + } + } + Ok(()) + } +} + +pub enum ModuleMember { + Module(Module), + Interface(Interface), + Struct(ItemStruct), + Enum(ItemEnum), +} + +impl Parse for ModuleMember { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![mod]) { + Ok(ModuleMember::Module(input.parse()?)) + } else if lookahead.peek(keywords::interface) { + Ok(ModuleMember::Interface(input.parse()?)) + } else if lookahead.peek(Token![struct]) { + Ok(ModuleMember::Struct(input.parse()?)) + } else if lookahead.peek(Token![enum]) { + Ok(ModuleMember::Enum(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +pub struct Interface { + pub name: Ident, + pub methods: Vec, +} + +impl Parse for Interface { + fn parse(input: ParseStream) -> Result { + input.parse::()?; + let name = input.parse::()?; + let content; + braced!(content in input); + let mut methods = vec![]; + while !content.is_empty() { + methods.push(content.parse::()?); + } + Ok(Self { name, methods }) + } +} + +impl ToWriter for Interface { + fn to_writer(&self, namespace: String, items: &mut Vec) -> Result<()> { + let mut methods = vec![]; + + for method in &self.methods { + methods.push(writer::Method { name: method.sig.ident.to_string(), return_type: writer::Type::Void, params: vec![] }); + } + + items.push(writer::Item::Interface(writer::Interface { namespace, name: self.name.to_string(), methods })); + Ok(()) + } +} + +impl ToWriter for ItemStruct { + fn to_writer(&self, namespace: String, items: &mut Vec) -> Result<()> { + let mut fields = vec![]; + + let Fields::Named(named) = &self.fields else { + return Err(Error::new(self.fields.span(), "expected named fields")); + }; + + for field in &named.named { + fields.push(writer::Field { name: field.ident.as_ref().unwrap().to_string(), ty: type_to_type(&field.ty)? }); + } + + items.push(writer::Item::Struct(writer::Struct { namespace, name: self.ident.to_string(), fields })); + Ok(()) + } +} + +impl ToWriter for ItemEnum { + fn to_writer(&self, namespace: String, items: &mut Vec) -> Result<()> { + let mut constants = vec![]; + let mut value = 0; + + // TODO: need to read the `#[repr(u8)]` attribute infer the underlying type + + for variant in &self.variants { + if let Some((_, discriminant)) = &variant.discriminant { + let Expr::Lit(discriminant) = discriminant else { + return Err(Error::new(discriminant.span(), "expected literal discriminant")); + }; + + let Lit::Int(discriminant) = &discriminant.lit else { + return Err(Error::new(discriminant.lit.span(), "expected integer discriminant")); + }; + + value = discriminant.base10_parse()?; + } + + constants.push(writer::Constant { name: variant.ident.to_string(), value: writer::Value::I32(value) }); + value += 1; + } + + items.push(writer::Item::Enum(writer::Enum { namespace, name: self.ident.to_string(), constants })); + Ok(()) + } +} + +fn type_to_type(ty: &Type) -> Result { + let Type::Path(path) = ty else { + return Err(Error::new(ty.span(), "expected type path")); + }; + + let mut name = String::new(); + + for segment in &path.path.segments { + if !name.is_empty() { + name.push('.'); + } + name.push_str(&segment.ident.to_string()); + } + + let ty = match name.as_str() { + "bool" => writer::Type::Bool, + "i8" => writer::Type::I8, + "u8" => writer::Type::U8, + "i16" => writer::Type::I16, + "u16" => writer::Type::U16, + "i32" => writer::Type::I32, + "u32" => writer::Type::U32, + "i64" => writer::Type::I64, + "u64" => writer::Type::U64, + "f32" => writer::Type::F32, + "f64" => writer::Type::F64, + "isize" => writer::Type::ISize, + "usize" => writer::Type::USize, + _ => { + let Some((namespace, name)) = name.rsplit_once('.') else { + return Err(Error::new(path.span(), "expected type")); + }; + writer::Type::Named((namespace.to_string(), name.to_string())) + } + }; + + Ok(ty) +}