1use std::{
2 borrow::Cow,
3 fmt,
4 path::{Path as FsPath, PathBuf},
5 str::FromStr,
6 sync::Arc,
7};
8
9use miden_assembly::SourceManager;
10use miden_assembly_syntax::{
11 Library, Path as LibraryPath,
12 diagnostics::{IntoDiagnostic, Report},
13};
14
15use crate::config::DebuggerConfig;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct LinkLibrary {
20 pub name: Cow<'static, str>,
27 pub path: Option<PathBuf>,
29 pub kind: LibraryKind,
34}
35
36#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
38pub enum LibraryKind {
39 #[default]
41 Masp,
42 Masm,
44}
45impl fmt::Display for LibraryKind {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::Masm => f.write_str("masm"),
49 Self::Masp => f.write_str("masp"),
50 }
51 }
52}
53impl FromStr for LibraryKind {
54 type Err = ();
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 match s {
58 "masm" => Ok(Self::Masm),
59 "masp" => Ok(Self::Masp),
60 _ => Err(()),
61 }
62 }
63}
64
65impl LinkLibrary {
66 pub fn name(&self) -> &str {
68 self.name.as_ref()
69 }
70
71 pub fn load(
72 &self,
73 config: &DebuggerConfig,
74 source_manager: Arc<dyn SourceManager>,
75 ) -> Result<Arc<Library>, Report> {
76 if let Some(path) = self.path.as_deref() {
77 return self.load_from_path(path, source_manager);
78 }
79
80 let path = self.find(config)?;
82
83 self.load_from_path(&path, source_manager)
84 }
85
86 fn load_from_path(
87 &self,
88 path: &FsPath,
89 source_manager: Arc<dyn SourceManager>,
90 ) -> Result<Arc<Library>, Report> {
91 match self.kind {
92 LibraryKind::Masm => {
93 let ns = LibraryPath::validate(self.name.as_ref()).map_err(|err| {
94 Report::msg(format!("invalid library namespace '{}': {err}", &self.name))
95 })?;
96
97 let modules = miden_assembly_syntax::parser::read_modules_from_dir(
98 path,
99 ns,
100 source_manager.clone(),
101 false,
102 )?;
103
104 miden_assembly::Assembler::new(source_manager)
105 .assemble_library(modules)
106 .map(Arc::new)
107 }
108 LibraryKind::Masp => {
109 use miden_core::serde::Deserializable;
110 let bytes = std::fs::read(path).into_diagnostic()?;
111 let package =
112 miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
113 Report::msg(format!(
114 "failed to load Miden package from {}: {e}",
115 path.display()
116 ))
117 })?;
118 let lib = match package.mast {
119 miden_mast_package::MastArtifact::Executable(_) => {
120 return Err(Report::msg(format!(
121 "Expected Miden package to contain a Library, got Program: '{}'",
122 path.display()
123 )));
124 }
125 miden_mast_package::MastArtifact::Library(lib) => lib.clone(),
126 };
127 Ok(lib)
128 }
129 }
130 }
131
132 fn find(&self, config: &DebuggerConfig) -> Result<PathBuf, Report> {
133 use std::fs;
134
135 let toolchain_dir = config.toolchain_dir();
136 let search_paths = toolchain_dir
137 .iter()
138 .chain(config.search_path.iter())
139 .chain(config.working_dir.iter());
140
141 for search_path in search_paths {
142 let reader = fs::read_dir(search_path).map_err(|err| {
143 Report::msg(format!(
144 "invalid library search path '{}': {err}",
145 search_path.display()
146 ))
147 })?;
148 for entry in reader {
149 let Ok(entry) = entry else {
150 continue;
151 };
152 let path = entry.path();
153 let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
154 continue;
155 };
156 if stem != self.name.as_ref() {
157 continue;
158 }
159
160 match self.kind {
161 LibraryKind::Masp => {
162 if !path.is_file() {
163 return Err(Report::msg(format!(
164 "unable to load Miden Assembly package from '{}': not a file",
165 path.display()
166 )));
167 }
168 }
169 LibraryKind::Masm => {
170 if !path.is_dir() {
171 return Err(Report::msg(format!(
172 "unable to load Miden Assembly library from '{}': not a directory",
173 path.display()
174 )));
175 }
176 }
177 }
178 return Ok(path);
179 }
180 }
181
182 Err(Report::msg(format!(
183 "unable to locate library '{}' using any of the provided search paths",
184 &self.name
185 )))
186 }
187}
188
189#[cfg(feature = "tui")]
190impl clap::builder::ValueParserFactory for LinkLibrary {
191 type Parser = LinkLibraryParser;
192
193 fn value_parser() -> Self::Parser {
194 LinkLibraryParser
195 }
196}
197
198#[cfg(feature = "tui")]
199#[doc(hidden)]
200#[derive(Clone)]
201pub struct LinkLibraryParser;
202
203#[cfg(feature = "tui")]
204impl clap::builder::TypedValueParser for LinkLibraryParser {
205 type Value = LinkLibrary;
206
207 fn possible_values(
208 &self,
209 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
210 use clap::builder::PossibleValue;
211
212 Some(Box::new(
213 [
214 PossibleValue::new("masm").help("A Miden Assembly project directory"),
215 PossibleValue::new("masp").help("A compiled Miden package file"),
216 ]
217 .into_iter(),
218 ))
219 }
220
221 fn parse_ref(
228 &self,
229 _cmd: &clap::Command,
230 _arg: Option<&clap::Arg>,
231 value: &std::ffi::OsStr,
232 ) -> Result<Self::Value, clap::error::Error> {
233 use clap::error::{Error, ErrorKind};
234
235 let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
236 let (kind, name) = value
237 .split_once('=')
238 .map(|(kind, name)| (Some(kind), name))
239 .unwrap_or((None, value));
240
241 if name.is_empty() {
242 return Err(Error::raw(
243 ErrorKind::ValueValidation,
244 "invalid link library: must specify a name or path",
245 ));
246 }
247
248 let maybe_path = FsPath::new(name);
249 let extension = maybe_path.extension().map(|ext| ext.to_str().unwrap());
250 let kind = match kind {
251 Some(kind) if !kind.is_empty() => kind.parse::<LibraryKind>().map_err(|_| {
252 Error::raw(ErrorKind::InvalidValue, format!("'{kind}' is not a valid library kind"))
253 })?,
254 Some(_) | None => match extension {
255 Some(kind) => kind.parse::<LibraryKind>().map_err(|_| {
256 Error::raw(
257 ErrorKind::InvalidValue,
258 format!("'{kind}' is not a valid library kind"),
259 )
260 })?,
261 None => LibraryKind::default(),
262 },
263 };
264
265 if maybe_path.is_absolute() {
266 let meta = maybe_path.metadata().map_err(|err| {
267 Error::raw(
268 ErrorKind::ValueValidation,
269 format!(
270 "invalid link library: unable to load '{}': {err}",
271 maybe_path.display()
272 ),
273 )
274 })?;
275
276 match kind {
277 LibraryKind::Masp if !meta.is_file() => {
278 return Err(Error::raw(
279 ErrorKind::ValueValidation,
280 format!("invalid link library: '{}' is not a file", maybe_path.display()),
281 ));
282 }
283 LibraryKind::Masm if !meta.is_dir() => {
284 return Err(Error::raw(
285 ErrorKind::ValueValidation,
286 format!(
287 "invalid link library: kind 'masm' was specified, but '{}' is not a \
288 directory",
289 maybe_path.display()
290 ),
291 ));
292 }
293 _ => (),
294 }
295
296 let name = maybe_path.file_stem().unwrap().to_str().unwrap().to_string();
297
298 Ok(LinkLibrary {
299 name: name.into(),
300 path: Some(maybe_path.to_path_buf()),
301 kind,
302 })
303 } else if extension.is_some() {
304 let name = name.strip_suffix(unsafe { extension.unwrap_unchecked() }).unwrap();
305 let mut name = name.to_string();
306 name.pop();
307
308 Ok(LinkLibrary {
309 name: name.into(),
310 path: None,
311 kind,
312 })
313 } else {
314 Ok(LinkLibrary {
315 name: name.to_string().into(),
316 path: None,
317 kind,
318 })
319 }
320 }
321}