1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
use std::{ffi::OsString, path::PathBuf, sync::Arc};

use clap::{ColorChoice, Parser};
use midenc_session::{
    diagnostics::{DefaultSourceManager, Emitter},
    ColorChoice as MDColorChoice, InputFile, LinkLibrary, Options, ProjectType, Session, TargetEnv,
};

/// Run a compiled Miden program with the Miden VM
#[derive(Default, Debug, Parser)]
#[command(name = "debug")]
pub struct Debugger {
    /// The working directory for the compiler
    ///
    /// By default this will be the working directory the compiler is executed from
    #[arg(long, value_name = "DIR", help_heading = "Output")]
    pub working_dir: Option<PathBuf>,
    /// The path to the root directory of the Miden toolchain libraries
    ///
    /// By default this is assumed to be ~/.miden/toolchains/<version>
    #[arg(
        long,
        value_name = "DIR",
        env = "MIDENC_SYSROOT",
        help_heading = "Compiler"
    )]
    pub sysroot: Option<PathBuf>,
    /// Whether, and how, to color terminal output
    #[arg(
        long,
        value_enum,
        default_value_t = ColorChoice::Auto,
        default_missing_value = "auto",
        num_args(0..=1),
        help_heading = "Diagnostics"
    )]
    pub color: ColorChoice,
    /// The target environment to compile for
    #[arg(
        long,
        value_name = "TARGET",
        default_value_t = TargetEnv::Base,
        help_heading = "Compiler"
    )]
    pub target: TargetEnv,
    /// Specify the function to call as the entrypoint for the program
    #[arg(long, help_heading = "Compiler", hide(true))]
    pub entrypoint: Option<String>,
    /// Specify one or more search paths for link libraries requested via `-l`
    #[arg(
        long = "search-path",
        short = 'L',
        value_name = "PATH",
        help_heading = "Linker"
    )]
    pub search_path: Vec<PathBuf>,
    /// Link compiled projects to the specified library NAME.
    ///
    /// The optional KIND can be provided to indicate what type of library it is.
    ///
    /// NAME must either be an absolute path (with extension when applicable), or
    /// a library namespace (no extension). The former will be used as the path
    /// to load the library, without looking for it in the library search paths,
    /// while the latter will be located in the search path based on its KIND.
    ///
    /// See below for valid KINDs:
    #[arg(
        long = "link-library",
        short = 'l',
        value_name = "[KIND=]NAME",
        value_delimiter = ',',
        default_value_ifs([
            ("target", "base", "std"),
            ("target", "rollup", "std,base"),
        ]),
        next_line_help(true),
        help_heading = "Linker"
    )]
    pub link_libraries: Vec<LinkLibrary>,
}

impl Debugger {
    /// Construct a [Compiler] programatically
    pub fn new_session<I, A, S>(inputs: I, emitter: Option<Arc<dyn Emitter>>, argv: A) -> Session
    where
        I: IntoIterator<Item = InputFile>,
        A: IntoIterator<Item = S>,
        S: Into<OsString> + Clone,
    {
        let argv = [OsString::from("run")]
            .into_iter()
            .chain(argv.into_iter().map(|arg| arg.into()));
        let mut matches = <Self as clap::CommandFactory>::command()
            .try_get_matches_from(argv)
            .unwrap_or_else(|err| err.exit());

        let opts = <Self as clap::FromArgMatches>::from_arg_matches_mut(&mut matches)
            .map_err(format_error::<Self>)
            .unwrap_or_else(|err| err.exit());

        let inputs = inputs.into_iter().collect();
        opts.into_session(inputs, emitter)
    }

    /// Use this configuration to obtain a [Session] used for compilation
    pub fn into_session(
        self,
        inputs: Vec<InputFile>,
        emitter: Option<Arc<dyn Emitter>>,
    ) -> Session {
        let cwd = self
            .working_dir
            .unwrap_or_else(|| std::env::current_dir().expect("no working directory available"));

        // Map clap color choices to internal color choice
        let color = match self.color {
            ColorChoice::Auto => MDColorChoice::Auto,
            ColorChoice::Always => MDColorChoice::Always,
            ColorChoice::Never => MDColorChoice::Never,
        };

        // Consolidate all compiler options
        let mut options = Options::new(None, self.target, ProjectType::Program, cwd, self.sysroot)
            .with_color(color);
        options.search_paths = self.search_path;
        options.link_libraries = self.link_libraries;
        options.entrypoint = self.entrypoint;

        let target_dir = std::env::temp_dir();
        let source_manager = Arc::new(DefaultSourceManager::default());
        Session::new(inputs, None, None, target_dir, options, emitter, source_manager)
    }
}

fn format_error<I: clap::CommandFactory>(err: clap::Error) -> clap::Error {
    let mut cmd = I::command();
    err.format(&mut cmd)
}