Skip to content

Commit

Permalink
tools
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr committed Jul 20, 2023
1 parent 7c34dee commit 3ad6946
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 295 deletions.
53 changes: 31 additions & 22 deletions crates/tools/lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::collections::BTreeMap;
use metadata::RowReader;

pub enum CallingConvention {
Stdcall(usize),
Expand Down Expand Up @@ -50,28 +51,36 @@ fn combine_libraries(
reader: &metadata::Reader,
libraries: &mut BTreeMap<String, BTreeMap<String, CallingConvention>>,
) {
for namespace in reader.namespaces() {
for method in reader.namespace_functions(namespace) {
let library = reader.method_def_module_name(method);
let impl_map = reader
.method_def_impl_map(method)
.expect("ImplMap not found");
let flags = reader.impl_map_flags(impl_map);
let name = reader.impl_map_import_name(impl_map).to_string();
if flags.contains(metadata::PInvokeAttributes::CallConvPlatformapi) {
let params = reader.method_def_size(namespace, method);
libraries
.entry(library)
.or_default()
.insert(name, CallingConvention::Stdcall(params));
} else if flags.contains(metadata::PInvokeAttributes::CallConvCdecl) {
libraries
.entry(library)
.or_default()
.insert(name, CallingConvention::Cdecl);
} else {
unimplemented!();
}
for item in reader.items(&Default::default()) {
let metadata::Item::Fn(method, namespace) = item else {
continue;
};

let library = reader.method_def_module_name(method);
let impl_map = reader
.method_def_impl_map(method)
.expect("ImplMap not found");
let flags = reader.impl_map_flags(impl_map);
let name = reader.impl_map_import_name(impl_map).to_string();

// TODO: don't include these in metadata to begin with
if name.starts_with('#') || library == "forceinline" {
continue;
}

if flags.contains(metadata::PInvokeAttributes::CallConvPlatformapi) {
let params = reader.method_def_size(&namespace, method);
libraries
.entry(library)
.or_default()
.insert(name, CallingConvention::Stdcall(params));
} else if flags.contains(metadata::PInvokeAttributes::CallConvCdecl) {
libraries
.entry(library)
.or_default()
.insert(name, CallingConvention::Cdecl);
} else {
unimplemented!();
}
}
}
24 changes: 12 additions & 12 deletions crates/tools/riddle/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod args;
mod error;
mod idl;
mod rd;
mod rust;
mod tokens;
mod tree;
Expand Down Expand Up @@ -33,11 +33,11 @@ fn run() -> Result<()> {
r#"Usage: riddle.exe [options...]
Options:
--in <path> Path to files and directories containing .winmd and .idl files
--out <path> Path to .winmd, .idl, or .rs file to generate
--in <path> Path to files and directories containing .winmd and .rd files
--out <path> Path to .winmd, .rd, or .rs file to generate
--filter <namespace> Namespaces to include or !exclude in output
--config <key=value> Override a configuration value
--format Format .idl files only
--format Format .rd files only
--etc <path> File containing command line options
"#
);
Expand Down Expand Up @@ -98,17 +98,17 @@ Options:
));
}

let input = filter_input(&input, &["idl"])?;
let input = filter_input(&input, &["rd"])?;

if input.is_empty() {
return Err(Error::new("no .idl inputs"));
return Err(Error::new("no .rd inputs"));
}

for path in &input {
read_file_text(path)
.and_then(|source| idl::File::parse_str(&source))
.and_then(|source| rd::File::parse_str(&source))
.and_then(|file| write_to_file(path, file.fmt()))
.map_err(|_| Error::new("failed to format .idl file").with_path(path))?;
.map_err(|err| err.with_path(path))?;
}

return Ok(());
Expand All @@ -133,10 +133,10 @@ Options:
winmd::verify(&reader, &filter)?;

match extension(&output) {
"idl" => idl::from_reader(&reader, &filter, config, &output)?,
"rd" => rd::from_reader(&reader, &filter, config, &output)?,
"winmd" => winmd::from_reader(&reader, &filter, config, &output)?,
"rs" => rust::from_reader(&reader, &filter, config, &output)?,
_ => return Err(Error::new("output extension must be one of winmd/idl/rs")),
_ => return Err(Error::new("output extension must be one of winmd/rd/rs")),
}

let elapsed = time.elapsed().as_secs_f32();
Expand Down Expand Up @@ -195,7 +195,7 @@ fn filter_input(input: &[&str], extensions: &[&str]) -> Result<Vec<String>> {
}

fn read_input(input: &[&str]) -> Result<Vec<metadata::File>> {
let input = filter_input(input, &["winmd", "idl"])?;
let input = filter_input(input, &["winmd", "rd"])?;

if input.is_empty() {
return Err(Error::new("no inputs"));
Expand Down Expand Up @@ -239,7 +239,7 @@ fn read_file_lines(path: &str) -> Result<Vec<String>> {

fn read_idl_file(path: &str) -> Result<metadata::File> {
read_file_text(path)
.and_then(|source| idl::File::parse_str(&source))
.and_then(|source| rd::File::parse_str(&source))
.and_then(|file| file.into_winmd())
.map(|bytes| {
// TODO: Write bytes to file if you need to debug the intermediate .winmd file like so:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use crate::Result;
use syn::spanned::Spanned;
pub use to_idl::from_reader;

// TODO: may want to finally get rid of `syn` as it also doesn't support preserving code comments

impl File {
pub fn parse_str(input: &str) -> Result<Self> {
Ok(syn::parse_str::<Self>(input)?)
Expand All @@ -13,7 +15,7 @@ impl File {
// Note: this isn't called automatically by `parse_str` to avoid canonicalizing when we're merely formatting IDL.
pub fn canonicalize(&mut self) -> Result<()> {
// TODO maybe we rewrite the `File` here to resolve any `super` references and use declarations so that
// subsequently the idl-to-winmd conversion can just assume everything's fully qualified?
// subsequently the rd-to-winmd conversion can just assume everything's fully qualified?
// * super can't refer to something outside of the IDL file
// * use declarations are only used for unqualified names that aren't defined in the IDL file
// * use declarations don't support globs and must name all externally defined types
Expand All @@ -32,11 +34,6 @@ impl File {
}
}

// TODO: always set the winrt bit on the assembly but only set the winrt bit on the TypeDef if its a WinRT type.
// Also, use a file-level attribute in the IDL file to indicate whether it contains WinRT or Win32 types
// e.g. #![win32|winrt] - with default being winrt - that way Win32 and WinRT types could conceivably share a
// namespace but live in separate IDL files to simplify the IDL syntax.

// The value of the IDL-specific memory representation is that it allows for constructs that are not modeled in the abstract Module
// tree such as the use declarations and if we get rid of it we'd always "format" IDL by stripping out any of that into a single
// canonical form which would not be very friendly to developers.
Expand All @@ -50,8 +47,9 @@ pub struct File {

#[derive(Clone)]
pub struct Module {
pub attributes: Vec<syn::Attribute>, // winrt/win32
pub name: String,
pub winrt: bool,
pub attributes: Vec<syn::Attribute>,
pub namespace: String,
pub members: Vec<ModuleMember>,
}

Expand All @@ -68,7 +66,7 @@ pub enum ModuleMember {
impl ModuleMember {
pub fn name(&self) -> &str {
match self {
Self::Module(module) => &module.name,
Self::Module(module) => crate::extension(&module.namespace),
Self::Interface(member) => &member.name,
Self::Struct(member) => &member.name,
Self::Enum(member) => &member.name,
Expand Down Expand Up @@ -121,10 +119,17 @@ impl syn::parse::Parse for File {
let mut references = vec![];
let mut modules = vec![];
while !input.is_empty() {
let attributes: Vec<syn::Attribute> = input.call(syn::Attribute::parse_outer)?;
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![mod]) {
modules.push(input.parse()?);
modules.push(Module::parse(None, attributes, input)?);
} else if lookahead.peek(syn::Token![use]) {
if let Some(attribute) = attributes.first() {
return Err(syn::Error::new(
attribute.span(),
"module attributes not supported",
));
}
references.push(input.parse()?);
} else {
return Err(lookahead.error());
Expand All @@ -137,36 +142,85 @@ impl syn::parse::Parse for File {
}
}

impl syn::parse::Parse for Module {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
impl Module {
fn name(&self) -> &str {
self.namespace
.rsplit_once('.')
.map_or(&self.namespace, |(_, name)| name)
}

fn parse(
parent: Option<(&str, bool)>,
attributes: Vec<syn::Attribute>,
input: syn::parse::ParseStream,
) -> syn::Result<Self> {
input.parse::<syn::Token![mod]>()?;
let name = input.parse::<syn::Ident>()?.to_string();
let (namespace, mut winrt) = if let Some((namespace, winrt)) = parent {
(format!("{namespace}.{name}"), winrt)
} else {
(name, false)
};

let len = attributes.len();

if len == 1 {
if let syn::Meta::Path(path) = &attributes[0].meta {
if path.segments.len() == 1 {
match path.segments[0].ident.to_string().as_str() {
"winrt" => winrt = true,
"win32" => winrt = false,
_ => {
return Err(syn::Error::new(
attributes[0].span(),
"unsupported module attributes",
))
}
}
}
}
}

if len > 1 {
return Err(syn::Error::new(
attributes[1].span(),
"unsupported module attributes",
));
}

// TODO: uncomment when ready to enforce this
// if len == 0 && parent.is_none() {
// return Err(syn::Error::new(
// input.span(),
// "#[win32] or #[winrt] module attribute required",
// ))
// }

let content;
syn::braced!(content in input);
let mut members = vec![];
while !content.is_empty() {
members.push(content.parse::<ModuleMember>()?);
members.push(ModuleMember::parse((&namespace, winrt), &content)?);
}
Ok(Self {
attributes: vec![],
name,
winrt,
attributes,
namespace,
members,
})
}
}

impl syn::parse::Parse for ModuleMember {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
impl ModuleMember {
fn parse(parent: (&str, bool), input: syn::parse::ParseStream) -> syn::Result<Self> {
let attributes: Vec<syn::Attribute> = input.call(syn::Attribute::parse_outer)?;
let lookahead = input.lookahead1();
if lookahead.peek(syn::Token![mod]) {
if let Some(attribute) = attributes.first() {
return Err(syn::Error::new(
attribute.span(),
"module attributes not supported",
));
}
Ok(ModuleMember::Module(input.parse()?))
Ok(ModuleMember::Module(Module::parse(
Some(parent),
attributes,
input,
)?))
} else if lookahead.peek(interface) {
Ok(ModuleMember::Interface(Interface::parse(
attributes, input,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::tokens::{quote, to_ident, TokenStream};
use crate::{idl, Error, Result, Tree};
use crate::{rd, Error, Result, Tree};
use metadata::RowReader;

pub fn from_reader(
reader: &metadata::Reader,
Expand All @@ -19,7 +20,7 @@ pub fn from_reader(

let tree = Tree::new(writer.reader, writer.filter);
let tokens = writer.tree(&tree);
let file = idl::File::parse_str(&tokens.into_string())?;
let file = rd::File::parse_str(&tokens.into_string())?;
crate::write_to_file(output, file.fmt())
}

Expand Down Expand Up @@ -62,8 +63,8 @@ impl<'a> Writer<'a> {
);
let types = self
.reader
.namespace_types(tree.namespace, self.filter)
.map(|def| self.type_def(def));
.namespace_items(tree.namespace, self.filter)
.map(|item| self.item(item));

quote! {
mod #name {
Expand All @@ -74,6 +75,13 @@ impl<'a> Writer<'a> {
}
}

fn item(&self, item: metadata::Item) -> TokenStream {
match item {
metadata::Item::Type(def) => self.type_def(def),
rest => unimplemented!("{rest:?}"),
}
}

fn type_def(&self, def: metadata::TypeDef) -> TokenStream {
if let Some(extends) = self.reader.type_def_extends(def) {
if extends.namespace == "System" {
Expand Down
Loading

0 comments on commit 3ad6946

Please sign in to comment.