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