From 0edf380c797020c246ad7137b8969b0c6dcebb61 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 22 Jul 2024 01:41:45 -0700 Subject: [PATCH] refactor fayalite::cli to expose subcommands --- crates/fayalite/examples/blinky.rs | 5 +- crates/fayalite/src/cli.rs | 153 +++++++++++++++++++++-------- 2 files changed, 117 insertions(+), 41 deletions(-) diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 80973a3..fcc0c5d 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -1,5 +1,6 @@ use clap::Parser; use fayalite::{ + cli, clock::{Clock, ClockDomain}, hdl_module, int::{DynUInt, DynUIntType, IntCmp, IntTypeTrait, UInt}, @@ -41,10 +42,10 @@ struct Cli { #[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))] clock_frequency: u64, #[command(subcommand)] - cli: fayalite::cli::Cli, + cli: cli::Cli, } -fn main() { +fn main() -> cli::Result { let cli = Cli::parse(); cli.cli.run(blinky(cli.clock_frequency)) } diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs index eed7832..0113c8f 100644 --- a/crates/fayalite/src/cli.rs +++ b/crates/fayalite/src/cli.rs @@ -4,20 +4,97 @@ use crate::{ intern::Interned, module::Module, }; -use clap::{Parser, Subcommand, ValueHint}; -use std::{path::PathBuf, process::exit}; +use clap::{Args, Parser, Subcommand, ValueHint}; +use std::{fmt, path::PathBuf}; + +enum CliErrorImpl { + IoError(std::io::Error), +} + +pub type Result = std::result::Result; + +pub struct CliError(CliErrorImpl); + +impl fmt::Debug for CliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + CliErrorImpl::IoError(e) => e.fmt(f), + } + } +} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match &self.0 { + CliErrorImpl::IoError(e) => e.fmt(f), + } + } +} + +impl std::error::Error for CliError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match &self.0 { + CliErrorImpl::IoError(e) => e.source(), + } + } +} + +impl From for CliError { + fn from(value: std::io::Error) -> Self { + CliError(CliErrorImpl::IoError(value)) + } +} + +pub trait RunPhase { + fn run(self, arg: Arg) -> Result; +} + +#[derive(Args)] +#[non_exhaustive] +pub struct FirrtlArgs { + /// the directory to put the generated FIRRTL and associated files in + #[arg(short, long, value_hint = ValueHint::DirPath)] + pub output: PathBuf, + /// the stem of the generated .fir file, e.g. to get foo.fir, pass --file-stem=foo + #[arg(long)] + pub file_stem: Option, +} + +impl FirrtlArgs { + fn run_impl(self, top_module: Module) -> Result { + firrtl::export( + firrtl::FileBackend { + dir_path: self.output, + top_fir_file_stem: self.file_stem, + }, + &top_module, + )?; + Ok(()) + } +} + +impl RunPhase> for FirrtlArgs +where + T::Type: BundleType, +{ + fn run(self, top_module: Module) -> Result { + self.run_impl(top_module.canonical()) + } +} + +impl RunPhase>> for FirrtlArgs +where + T::Type: BundleType, +{ + fn run(self, top_module: Interned>) -> Result { + self.run(*top_module) + } +} #[derive(Subcommand)] enum CliCommand { /// Generate FIRRTL - Firrtl { - /// the directory to put the generated FIRRTL and associated files in - #[arg(short, long, value_hint = ValueHint::DirPath)] - output: PathBuf, - /// the stem of the generated .fir file, e.g. to get foo.fir, pass --file-stem=foo - #[arg(long)] - file_stem: Option, - }, + Firrtl(FirrtlArgs), } /// a simple CLI @@ -25,26 +102,29 @@ enum CliCommand { /// Use like: /// /// ```no_run -/// # use fayalite::{hdl_module, cli::Cli}; +/// # use fayalite::hdl_module; /// # #[hdl_module] /// # fn my_module() {} -/// fn main() { -/// Cli::parse().run(my_module()); +/// use fayalite::cli; +/// +/// fn main() -> cli::Result { +/// cli::Cli::parse().run(my_module()) /// } /// ``` /// /// You can also use it with a larger [`clap`]-based CLI like so: /// /// ```no_run -/// # use fayalite::{hdl_module}; +/// # use fayalite::hdl_module; /// # #[hdl_module] /// # fn my_module() {} /// use clap::{Subcommand, Parser}; +/// use fayalite::cli; /// /// #[derive(Subcommand)] /// pub enum Cmd { /// #[command(flatten)] -/// Fayalite(fayalite::cli::Cli), +/// Fayalite(cli::Cli), /// MySpecialCommand { /// #[arg(long)] /// foo: bool, @@ -54,14 +134,15 @@ enum CliCommand { /// #[derive(Parser)] /// pub struct Cli { /// #[command(subcommand)] -/// cmd: Cmd, // or just use fayalite::cli::Cli directly +/// cmd: Cmd, // or just use cli::Cli directly if you don't need more subcommands /// } /// -/// fn main() { +/// fn main() -> cli::Result { /// match Cli::parse().cmd { -/// Cmd::Fayalite(v) => v.run(my_module()), +/// Cmd::Fayalite(v) => v.run(my_module())?, /// Cmd::MySpecialCommand { foo } => println!("special: foo={foo}"), /// } +/// Ok(()) /// } /// ``` #[derive(Parser)] @@ -86,33 +167,27 @@ impl clap::Subcommand for Cli { } } +impl RunPhase for Cli +where + FirrtlArgs: RunPhase, +{ + fn run(self, arg: T) -> Result { + match self.subcommand { + CliCommand::Firrtl(firrtl_args) => firrtl_args.run(arg), + } + } +} + impl Cli { /// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`] pub fn parse() -> Self { clap::Parser::parse() } - fn run_impl(self, top_module: Module) -> ! { - match self.subcommand { - CliCommand::Firrtl { output, file_stem } => { - let result = firrtl::export( - firrtl::FileBackend { - dir_path: output, - top_fir_file_stem: file_stem, - }, - &top_module, - ); - if let Err(e) = result { - eprintln!("Error: {:?}", eyre::Report::new(e)); - exit(1); - } - } - } - exit(0) - } - pub fn run(self, top_module: Interned>) -> ! + /// forwards to [`RunPhase::run()`] so you don't have to import [`RunPhase`] + pub fn run(self, top_module: T) -> Result where - T::Type: BundleType, + Self: RunPhase, { - self.run_impl(top_module.canonical()) + RunPhase::run(self, top_module) } }