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