midenc_driver/
midenc.rs

1use std::{ffi::OsString, path::PathBuf, rc::Rc, sync::Arc};
2
3use clap::{Parser, Subcommand};
4use log::Log;
5use midenc_compile as compile;
6#[cfg(feature = "debug")]
7use midenc_debug as debugger;
8use midenc_hir::Context;
9use midenc_session::{
10    diagnostics::{Emitter, Report},
11    InputFile,
12};
13
14use crate::ClapDiagnostic;
15
16/// This struct provides the command-line interface used by `midenc`
17#[derive(Debug, Parser)]
18#[command(name = "midenc")]
19#[command(author, version, about = "A compiler for Miden Assembly", long_about = None)]
20pub struct Midenc {
21    #[command(subcommand)]
22    command: Commands,
23}
24
25#[derive(Debug, Subcommand)]
26enum Commands {
27    Compile {
28        /// The input file to compile
29        ///
30        /// You may specify `-` to read from stdin, otherwise you must provide a path
31        #[arg(required(true), value_name = "FILE")]
32        input: InputFile,
33        #[command(flatten)]
34        options: compile::Compiler,
35    },
36    /// Execute a compiled program or library, using the Miden VM.
37    #[cfg(feature = "debug")]
38    Run {
39        /// Specify the path to a Miden program file to execute.
40        ///
41        /// Miden Assembly programs are emitted by the compiler with a `.masl` extension.
42        ///
43        /// You may use `-` as a file name to read a file from stdin.
44        #[arg(required(true), value_name = "FILE")]
45        input: InputFile,
46        /// Specify the path to a file containing program inputs.
47        ///
48        /// Program inputs are stack and advice provider values which the program can
49        /// access during execution. The inputs file is a TOML file which describes
50        /// what the inputs are, or where to source them from.
51        ///
52        /// Example:
53        ///
54        /// [inputs]
55        ///
56        /// stack = [1, 2, 0x3]
57        ///
58        #[arg(long, value_name = "FILE")]
59        inputs: Option<debugger::DebuggerConfig>,
60        /// Number of outputs on the operand stack to print
61        #[arg(long, short = 'n', default_value_t = 16)]
62        num_outputs: usize,
63        /// Arguments to place on the operand stack before calling the program entrypoint.
64        ///
65        /// Arguments will be pushed on the operand stack in the order of appearance,
66        ///
67        /// Example: `-- a b` will push `a` on the stack, then `b`.
68        ///
69        /// These arguments must be valid field element values expressed in decimal format.
70        ///
71        /// NOTE: These arguments will override any stack values provided via --inputs
72        #[arg(last(true), value_name = "ARGV")]
73        args: Vec<debugger::Felt>,
74        #[command(flatten)]
75        options: debugger::Debugger,
76    },
77    /// Run a program under the interactive Miden VM debugger
78    ///
79    /// This command starts a TUI-based interactive debugger with the given program loaded.
80    #[cfg(feature = "debug")]
81    Debug {
82        /// Specify the path to a Miden program file to execute.
83        ///
84        /// Miden Assembly programs are emitted by the compiler with a `.masl` extension.
85        ///
86        /// You may use `-` as a file name to read a file from stdin.
87        #[arg(required(true), value_name = "FILE")]
88        input: InputFile,
89        /// Specify the path to a file containing program inputs.
90        ///
91        /// Program inputs are stack and advice provider values which the program can
92        /// access during execution. The inputs file is a TOML file which describes
93        /// what the inputs are, or where to source them from.
94        #[arg(long, value_name = "FILE")]
95        inputs: Option<debugger::DebuggerConfig>,
96        /// Arguments to place on the operand stack before calling the program entrypoint.
97        ///
98        /// Arguments will be pushed on the operand stack in the order of appearance,
99        ///
100        /// Example: `-- a b` will push `a` on the stack, then `b`.
101        ///
102        /// These arguments must be valid field element values expressed in decimal format.
103        ///
104        /// NOTE: These arguments will override any stack values provided via --inputs
105        #[arg(last(true), value_name = "ARGV")]
106        args: Vec<debugger::Felt>,
107        #[command(flatten)]
108        options: debugger::Debugger,
109    },
110}
111
112impl Midenc {
113    pub fn run<P, A>(
114        cwd: P,
115        args: A,
116        logger: Box<dyn Log>,
117        filter: log::LevelFilter,
118    ) -> Result<(), Report>
119    where
120        P: Into<PathBuf>,
121        A: IntoIterator<Item = OsString>,
122    {
123        Self::run_with_emitter(cwd, args, None, logger, filter)
124    }
125
126    pub fn run_with_emitter<P, A>(
127        cwd: P,
128        args: A,
129        emitter: Option<Arc<dyn Emitter>>,
130        logger: Box<dyn Log>,
131        filter: log::LevelFilter,
132    ) -> Result<(), Report>
133    where
134        P: Into<PathBuf>,
135        A: IntoIterator<Item = OsString>,
136    {
137        let command = <Self as clap::CommandFactory>::command();
138        let command = command.mut_subcommand("compile", midenc_session::flags::register_flags);
139
140        let mut matches = command.try_get_matches_from(args).map_err(ClapDiagnostic::from)?;
141        let compile_matches = matches.subcommand_matches("compile").cloned().unwrap_or_default();
142        let cli = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
143            .map_err(format_error::<Self>)
144            .map_err(ClapDiagnostic::from)?;
145
146        cli.invoke(cwd.into(), emitter, logger, filter, compile_matches)
147    }
148
149    fn invoke(
150        self,
151        cwd: PathBuf,
152        emitter: Option<Arc<dyn Emitter>>,
153        logger: Box<dyn Log>,
154        filter: log::LevelFilter,
155        matches: clap::ArgMatches,
156    ) -> Result<(), Report> {
157        match self.command {
158            Commands::Compile { input, mut options } => {
159                log::set_boxed_logger(logger)
160                    .unwrap_or_else(|err| panic!("failed to install logger: {err}"));
161                log::set_max_level(filter);
162                if options.working_dir.is_none() {
163                    options.working_dir = Some(cwd);
164                }
165                let session = Rc::new(
166                    options.into_session(vec![input], emitter).with_extra_flags(matches.into()),
167                );
168                let context = Rc::new(Context::new(session));
169                compile::compile(context)
170            }
171            #[cfg(feature = "debug")]
172            Commands::Run {
173                input,
174                inputs,
175                args,
176                num_outputs,
177                mut options,
178            } => {
179                log::set_boxed_logger(logger)
180                    .unwrap_or_else(|err| panic!("failed to install logger: {err}"));
181                log::set_max_level(filter);
182                if options.working_dir.is_none() {
183                    options.working_dir = Some(cwd);
184                }
185                let session = options.into_session(vec![input], emitter);
186                let args = args.into_iter().map(|felt| felt.0).collect();
187                debugger::run_noninteractively(inputs, args, num_outputs, Rc::new(session))
188            }
189            #[cfg(feature = "debug")]
190            Commands::Debug {
191                input,
192                inputs,
193                args,
194                mut options,
195            } => {
196                if options.working_dir.is_none() {
197                    options.working_dir = Some(cwd);
198                }
199                let session = options.into_session(vec![input], emitter);
200                let args = args.into_iter().map(|felt| felt.0).collect();
201                debugger::run(inputs, args, Rc::new(session), logger)
202            }
203        }
204    }
205}
206
207fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
208    let mut cmd = I::command();
209    err.format(&mut cmd)
210}