1#![deny(warnings)]
2
3use alloc::{borrow::Cow, format, str::FromStr, sync::Arc};
4#[cfg(feature = "std")]
5use alloc::{boxed::Box, string::ToString};
6use core::fmt;
7
8pub use miden_assembly::{
9 Library as CompiledLibrary, LibraryNamespace, LibraryPath, LibraryPathComponent,
10};
11use miden_base_sys::masl::tx::MidenTxKernelLibrary;
12#[cfg(feature = "std")]
13use miden_core::utils::Deserializable;
14use miden_stdlib::StdLibrary;
15use midenc_hir_symbol::sync::LazyLock;
16
17use crate::{diagnostics::Report, PathBuf, Session};
18#[cfg(feature = "std")]
19use crate::{
20 diagnostics::{IntoDiagnostic, WrapErr},
21 Path,
22};
23
24pub static STDLIB: LazyLock<Arc<CompiledLibrary>> =
25 LazyLock::new(|| Arc::new(StdLibrary::default().into()));
26pub static BASE: LazyLock<Arc<CompiledLibrary>> =
27 LazyLock::new(|| Arc::new(MidenTxKernelLibrary::default().into()));
28
29#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
31pub enum LibraryKind {
32 #[default]
34 Mast,
35 Masm,
37 Masp,
39}
40impl fmt::Display for LibraryKind {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Self::Mast => f.write_str("mast"),
44 Self::Masm => f.write_str("masm"),
45 Self::Masp => f.write_str("masp"),
46 }
47 }
48}
49impl FromStr for LibraryKind {
50 type Err = ();
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 match s {
54 "mast" | "masl" => Ok(Self::Mast),
55 "masm" => Ok(Self::Masm),
56 "masp" => Ok(Self::Masp),
57 _ => Err(()),
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct LinkLibrary {
65 pub name: Cow<'static, str>,
72 pub path: Option<PathBuf>,
74 pub kind: LibraryKind,
79}
80impl LinkLibrary {
81 #[cfg(not(feature = "std"))]
82 pub fn load(&self, _session: &Session) -> Result<CompiledLibrary, Report> {
83 match self.name.as_ref() {
85 "std" => Ok((*STDLIB).as_ref().clone()),
86 "base" => Ok((*BASE).as_ref().clone()),
87 name => Err(Report::msg(format!(
88 "link library '{name}' cannot be loaded: compiler was built without standard \
89 library"
90 ))),
91 }
92 }
93
94 #[cfg(feature = "std")]
95 pub fn load(&self, session: &Session) -> Result<CompiledLibrary, Report> {
96 if let Some(path) = self.path.as_deref() {
97 return self.load_from_path(path, session);
98 }
99
100 match self.name.as_ref() {
102 "std" => return Ok((*STDLIB).as_ref().clone()),
103 "base" => return Ok((*BASE).as_ref().clone()),
104 _ => (),
105 }
106
107 let path = self.find(session)?;
109
110 self.load_from_path(&path, session)
111 }
112
113 #[cfg(feature = "std")]
114 fn load_from_path(&self, path: &Path, session: &Session) -> Result<CompiledLibrary, Report> {
115 match self.kind {
116 LibraryKind::Masm => {
117 let ns = LibraryNamespace::new(&self.name)
118 .into_diagnostic()
119 .wrap_err_with(|| format!("invalid library namespace '{}'", &self.name))?;
120 let assembler = miden_assembly::Assembler::new(session.source_manager.clone())
121 .with_debug_mode(true);
122 CompiledLibrary::from_dir(path, ns, assembler)
123 }
124 LibraryKind::Mast => CompiledLibrary::deserialize_from_file(path).map_err(|err| {
125 Report::msg(format!(
126 "failed to deserialize library from '{}': {err}",
127 path.display()
128 ))
129 }),
130 LibraryKind::Masp => {
131 let bytes = std::fs::read(path).into_diagnostic()?;
132 let package =
133 miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| {
134 Report::msg(format!(
135 "failed to load Miden package from {}: {e}",
136 path.display()
137 ))
138 })?;
139 let lib = match package.mast {
140 miden_mast_package::MastArtifact::Executable(_) => {
141 return Err(Report::msg(format!(
142 "Expected Miden package to contain a Library, got Program: '{}'",
143 path.display()
144 )))
145 }
146 miden_mast_package::MastArtifact::Library(lib) => lib.clone(),
147 };
148 Ok((*lib).clone())
149 }
150 }
151 }
152
153 #[cfg(feature = "std")]
154 fn find(&self, session: &Session) -> Result<PathBuf, Report> {
155 use std::fs;
156
157 for search_path in session.options.search_paths.iter() {
158 let reader = fs::read_dir(search_path).map_err(|err| {
159 Report::msg(format!(
160 "invalid library search path '{}': {err}",
161 search_path.display()
162 ))
163 })?;
164 for entry in reader {
165 let Ok(entry) = entry else {
166 continue;
167 };
168 let path = entry.path();
169 let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
170 continue;
171 };
172 if stem != self.name.as_ref() {
173 continue;
174 }
175
176 match self.kind {
177 LibraryKind::Mast => {
178 if !path.is_file() {
179 return Err(Report::msg(format!(
180 "unable to load MAST library from '{}': not a file",
181 path.display()
182 )));
183 }
184 }
185 LibraryKind::Masm => {
186 if !path.is_dir() {
187 return Err(Report::msg(format!(
188 "unable to load Miden Assembly library from '{}': not a directory",
189 path.display()
190 )));
191 }
192 }
193 LibraryKind::Masp => {
194 if !path.is_file() {
195 return Err(Report::msg(format!(
196 "unable to load Miden Assembly package from '{}': not a file",
197 path.display()
198 )));
199 }
200 }
201 }
202 return Ok(path);
203 }
204 }
205
206 Err(Report::msg(format!(
207 "unable to locate library '{}' using any of the provided search paths",
208 &self.name
209 )))
210 }
211}
212
213#[cfg(feature = "std")]
214impl clap::builder::ValueParserFactory for LinkLibrary {
215 type Parser = LinkLibraryParser;
216
217 fn value_parser() -> Self::Parser {
218 LinkLibraryParser
219 }
220}
221
222#[cfg(feature = "std")]
223#[doc(hidden)]
224#[derive(Clone)]
225pub struct LinkLibraryParser;
226
227#[cfg(feature = "std")]
228impl clap::builder::TypedValueParser for LinkLibraryParser {
229 type Value = LinkLibrary;
230
231 fn possible_values(
232 &self,
233 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
234 use clap::builder::PossibleValue;
235
236 Some(Box::new(
237 [
238 PossibleValue::new("masm").help("A Miden Assembly project directory"),
239 PossibleValue::new("masl").help("A compiled MAST library file"),
240 ]
241 .into_iter(),
242 ))
243 }
244
245 fn parse_ref(
252 &self,
253 _cmd: &clap::Command,
254 _arg: Option<&clap::Arg>,
255 value: &std::ffi::OsStr,
256 ) -> Result<Self::Value, clap::error::Error> {
257 use clap::error::{Error, ErrorKind};
258
259 let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
260 let (kind, name) = value
261 .split_once('=')
262 .map(|(kind, name)| (Some(kind), name))
263 .unwrap_or((None, value));
264
265 if name.is_empty() {
266 return Err(Error::raw(
267 ErrorKind::ValueValidation,
268 "invalid link library: must specify a name or path",
269 ));
270 }
271
272 let maybe_path = Path::new(name);
273 let extension = maybe_path.extension().map(|ext| ext.to_str().unwrap());
274 let kind = match kind {
275 Some(kind) if !kind.is_empty() => kind.parse::<LibraryKind>().map_err(|_| {
276 Error::raw(ErrorKind::InvalidValue, format!("'{kind}' is not a valid library kind"))
277 })?,
278 Some(_) | None => match extension {
279 Some(kind) => kind.parse::<LibraryKind>().map_err(|_| {
280 Error::raw(
281 ErrorKind::InvalidValue,
282 format!("'{kind}' is not a valid library kind"),
283 )
284 })?,
285 None => LibraryKind::default(),
286 },
287 };
288
289 if maybe_path.is_absolute() {
290 let meta = maybe_path.metadata().map_err(|err| {
291 Error::raw(
292 ErrorKind::ValueValidation,
293 format!(
294 "invalid link library: unable to load '{}': {err}",
295 maybe_path.display()
296 ),
297 )
298 })?;
299
300 match kind {
301 LibraryKind::Mast if !meta.is_file() => {
302 return Err(Error::raw(
303 ErrorKind::ValueValidation,
304 format!("invalid link library: '{}' is not a file", maybe_path.display()),
305 ));
306 }
307 LibraryKind::Masm if !meta.is_dir() => {
308 return Err(Error::raw(
309 ErrorKind::ValueValidation,
310 format!(
311 "invalid link library: kind 'masm' was specified, but '{}' is not a \
312 directory",
313 maybe_path.display()
314 ),
315 ));
316 }
317 _ => (),
318 }
319
320 let name = maybe_path.file_stem().unwrap().to_str().unwrap().to_string();
321
322 Ok(LinkLibrary {
323 name: name.into(),
324 path: Some(maybe_path.to_path_buf()),
325 kind,
326 })
327 } else if extension.is_some() {
328 let name = name.strip_suffix(unsafe { extension.unwrap_unchecked() }).unwrap();
329 let mut name = name.to_string();
330 name.pop();
331
332 Ok(LinkLibrary {
333 name: name.into(),
334 path: None,
335 kind,
336 })
337 } else {
338 Ok(LinkLibrary {
339 name: name.to_string().into(),
340 path: None,
341 kind,
342 })
343 }
344 }
345}