From f3d224e26a6fb906b682c83927ada6ef928f474f Mon Sep 17 00:00:00 2001 From: Adrien Burgun Date: Fri, 24 Jun 2022 17:47:49 +0200 Subject: [PATCH] :sparkles: :fire: Build Script to automatically generate AnyTile --- stackline/Cargo.toml | 3 + stackline/build.rs | 100 ++++++++++++++++++++++++++ stackline/src/tile/anytile.doc.rs | 6 ++ stackline/src/tile/mod.rs | 25 ++++--- stackline/{src/tile => tiles}/wire.rs | 0 5 files changed, 121 insertions(+), 13 deletions(-) create mode 100644 stackline/build.rs create mode 100644 stackline/src/tile/anytile.doc.rs rename stackline/{src/tile => tiles}/wire.rs (100%) diff --git a/stackline/Cargo.toml b/stackline/Cargo.toml index 2ca276e..b6b2695 100755 --- a/stackline/Cargo.toml +++ b/stackline/Cargo.toml @@ -14,6 +14,9 @@ enum_dispatch = "0.3" colored = "2.0" criterion = { version = "0.3.5", features = ["html_reports"] } +[build-dependencies] +syn = {version = "1.0", features = ["full", "parsing"] } + [[bench]] name = "dispatch" harness = false diff --git a/stackline/build.rs b/stackline/build.rs new file mode 100644 index 0000000..1b533b4 --- /dev/null +++ b/stackline/build.rs @@ -0,0 +1,100 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use syn::{Item, Type, ItemImpl}; + +// This script reads the contents of any rust file in the `tiles/` directory, +// and gathers any type that implements `Tile`. These types are then put into +// the `AnyTile` enum and written to `$OUT_DIR/anytile.rs`. +// +// Any file with a type implementing `Tile` in tiles/ will be imported privately and its type will be re-exported. +// +// Known limitations: +// - only impls in the format "impl Tile for X" are accepted (X must not contain any "::") + +// TODO: generate a kind of Reflection API for AnyTile + +fn parse_impl_tile(item: &ItemImpl) -> Option { + let (_, trait_, _) = item.trait_.as_ref()?; + let ident = trait_.get_ident()?; + + if ident.to_string() == "Tile" { + if let Type::Path(path) = &*item.self_ty { + let name = path.path.get_ident().map(|i| i.to_string())?; + return Some(name); + } + } + + None +} + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("anytile.rs"); + + let mut names: Vec = Vec::new(); + let mut files: Vec<(PathBuf, Vec)> = Vec::new(); + + // Read and parse the contents of every .rs file in tiles/ + + for entry in fs::read_dir("tiles/").expect("Error while reading tiles/") { + let entry = entry.expect("Error while reading tiles/"); + let src_path = entry.path(); + if let Some("rs") = src_path.extension().and_then(|x| x.to_str()) { + let contents = fs::read_to_string(src_path.clone()) + .expect(&format!("Couldn't read {:?}", src_path)); + let mut local_names: Vec = Vec::new(); + + let syntax = syn::parse_file(&contents) + .expect(&format!("Unable to parse file {:?}", src_path)); + + for item in syntax.items.iter() { + match item { + Item::Impl(item) => { + if let Some(name) = parse_impl_tile(item) { + local_names.push(name); + } + } + _ => {} + } + } + + if local_names.len() > 0 { + let canonical = fs::canonicalize(src_path.clone()) + .expect(&format!("Couldn't canonicalize {:?}", src_path)); + for name in local_names.iter() { + names.push(name.clone()); + } + files.push((canonical, local_names)); + } + } + } + + // Generate code + + let mut res = String::from("use enum_dispatch::enum_dispatch;\n\n"); + + for file in files { + let mod_name = file.0.as_path().file_stem().map(|x| x.to_str()).flatten().expect(&format!("Couldn't extract valid UTF-8 filename from path {:?}", file)); + let path = file.0.as_path().to_str().expect("Invalid UTF-8 path"); + + res += &format!("#[path = \"{}\"]\nmod {};\n", path, mod_name); + res += &format!("pub use {}::{{", mod_name); + for name in file.1 { + res += &format!("{}, ", name); + } + res += "};\n\n"; + } + + res += &fs::read_to_string("src/tile/anytile.doc.rs").expect("Couldn't read src/tile/anytile.doc.rs"); + res += "#[derive(Clone, Debug)]\n"; + res += "#[enum_dispatch]\n"; + res += "pub enum AnyTile {\n"; + + for name in names { + res += &format!(" {0}({0}),\n", name); + } + res += "}\n"; + + fs::write(dest_path.clone(), &res).expect(&format!("Couldn't write to {:?}", dest_path)); +} diff --git a/stackline/src/tile/anytile.doc.rs b/stackline/src/tile/anytile.doc.rs new file mode 100644 index 0000000..34942de --- /dev/null +++ b/stackline/src/tile/anytile.doc.rs @@ -0,0 +1,6 @@ +// This file contains the documentation for AnyTile, see build.rs for more information. + +/// An enum containing all the available [`Tile`s](Tile), which is generated at compile time. +/// +/// Implements [`Tile`] using `enum_dispatch`. +/// To create an instance of `AnyTile`, you can use [`AnyTile::from`] with one of its member types. diff --git a/stackline/src/tile/mod.rs b/stackline/src/tile/mod.rs index 4b08779..231bd0f 100644 --- a/stackline/src/tile/mod.rs +++ b/stackline/src/tile/mod.rs @@ -1,22 +1,21 @@ use super::*; use dyn_clone::{clone_box, DynClone}; -use enum_dispatch::enum_dispatch; - -mod wire; -pub use wire::*; mod full; pub use full::*; -// TODO: implement a build.rs to auto-generate AnyTile - -#[derive(Clone, Debug)] -#[enum_dispatch] -pub enum AnyTile { - Wire(Wire), - Diode(Diode), - Resistor(Resistor), -} +// Generated rust file containing the AnyTile enum, which combines the structs implementing Tile found in tiles/. +// Its definition looks like this: +// #[derive(Clone, Debug)] +// #[enum_dispatch] +// pub enum AnyTile { +// Wire(Wire), +// Diode(Diode), +// /* snip */ +// } +// +// Note that all the implementing types will be read from the files in tiles/ and re-exported. +include!(concat!(env!("OUT_DIR"), "/anytile.rs")); #[enum_dispatch(AnyTile)] pub trait Tile: DynClone + std::fmt::Debug { diff --git a/stackline/src/tile/wire.rs b/stackline/tiles/wire.rs similarity index 100% rename from stackline/src/tile/wire.rs rename to stackline/tiles/wire.rs