1use alloc::{
2 borrow::Cow,
3 boxed::Box,
4 string::{String, ToString},
5 sync::Arc,
6 vec::Vec,
7};
8
9use miden_debug_types::{SourceContent, SourceFile, SourceLanguage, SourceManager, Uri};
10
11use crate::{
12 Path, PathBuf,
13 ast::{Module, ModuleKind},
14 diagnostics::{IntoDiagnostic, NamedSource, Report, SourceCode, WrapErr, report},
15};
16
17#[derive(Debug, Clone)]
22pub struct ParseOptions {
23 pub kind: ModuleKind,
27 pub warnings_as_errors: bool,
29 pub path: Option<Arc<Path>>,
37}
38
39impl Default for ParseOptions {
40 fn default() -> Self {
41 Self {
42 kind: ModuleKind::Executable,
43 warnings_as_errors: false,
44 path: None,
45 }
46 }
47}
48impl ParseOptions {
49 pub fn new(kind: ModuleKind, path: impl AsRef<Path>) -> Self {
55 Self {
56 kind,
57 path: Some(path.as_ref().into()),
58 ..Default::default()
59 }
60 }
61
62 pub fn for_library() -> Self {
64 Self {
65 kind: ModuleKind::Library,
66 ..Default::default()
67 }
68 }
69
70 pub fn for_kernel() -> Self {
72 Self {
73 kind: ModuleKind::Kernel,
74 ..Default::default()
75 }
76 }
77}
78
79pub trait Parse: Sized {
90 #[inline]
94 fn parse(self, source_manager: Arc<dyn SourceManager>) -> Result<Box<Module>, Report> {
95 self.parse_with_options(source_manager, ParseOptions::default())
96 }
97
98 fn parse_with_options(
106 self,
107 source_manager: Arc<dyn SourceManager>,
108 options: ParseOptions,
109 ) -> Result<Box<Module>, Report>;
110}
111
112impl Parse for Module {
116 #[inline(always)]
117 fn parse_with_options(
118 self,
119 source_manager: Arc<dyn SourceManager>,
120 options: ParseOptions,
121 ) -> Result<Box<Module>, Report> {
122 Box::new(self).parse_with_options(source_manager, options)
123 }
124}
125
126impl Parse for &Module {
127 #[inline(always)]
128 fn parse_with_options(
129 self,
130 source_manager: Arc<dyn SourceManager>,
131 options: ParseOptions,
132 ) -> Result<Box<Module>, Report> {
133 Box::new(self.clone()).parse_with_options(source_manager, options)
134 }
135}
136
137impl Parse for Box<Module> {
138 fn parse_with_options(
139 mut self,
140 _source_manager: Arc<dyn SourceManager>,
141 options: ParseOptions,
142 ) -> Result<Box<Module>, Report> {
143 let actual = self.kind();
144 if actual == options.kind {
145 if let Some(path) = options.path {
146 self.set_path(path);
147 }
148 Ok(self)
149 } else {
150 Err(report!(
151 "compilation failed: expected a {} module, but got a {actual} module",
152 options.kind
153 ))
154 }
155 }
156}
157
158impl Parse for Arc<Module> {
159 #[inline(always)]
160 fn parse_with_options(
161 self,
162 source_manager: Arc<dyn SourceManager>,
163 options: ParseOptions,
164 ) -> Result<Box<Module>, Report> {
165 Box::new(Arc::unwrap_or_clone(self)).parse_with_options(source_manager, options)
166 }
167}
168
169impl Parse for Arc<SourceFile> {
173 fn parse_with_options(
174 self,
175 source_manager: Arc<dyn SourceManager>,
176 options: ParseOptions,
177 ) -> Result<Box<Module>, Report> {
178 let source_file = source_manager.copy_into(&self);
179 let path = match options.path {
180 Some(path) => path,
181 None => source_file
182 .uri()
183 .path()
184 .parse::<PathBuf>()
185 .map(|p| p.into())
186 .into_diagnostic()
187 .wrap_err("cannot parse module as it has an invalid path/name")?,
188 };
189 let mut parser = Module::parser(options.kind);
190 parser.set_warnings_as_errors(options.warnings_as_errors);
191 parser.parse(path, source_file, source_manager)
192 }
193}
194
195impl Parse for &str {
196 #[inline(always)]
197 fn parse_with_options(
198 self,
199 source_manager: Arc<dyn SourceManager>,
200 options: ParseOptions,
201 ) -> Result<Box<Module>, Report> {
202 self.to_string().into_boxed_str().parse_with_options(source_manager, options)
203 }
204}
205
206impl Parse for &String {
207 #[inline(always)]
208 fn parse_with_options(
209 self,
210 source_manager: Arc<dyn SourceManager>,
211 options: ParseOptions,
212 ) -> Result<Box<Module>, Report> {
213 self.clone().into_boxed_str().parse_with_options(source_manager, options)
214 }
215}
216
217impl Parse for String {
218 fn parse_with_options(
219 self,
220 source_manager: Arc<dyn SourceManager>,
221 options: ParseOptions,
222 ) -> Result<Box<Module>, Report> {
223 self.into_boxed_str().parse_with_options(source_manager, options)
224 }
225}
226
227impl Parse for Box<str> {
228 fn parse_with_options(
229 self,
230 source_manager: Arc<dyn SourceManager>,
231 options: ParseOptions,
232 ) -> Result<Box<Module>, Report> {
233 let path = options.path.as_deref().unwrap_or_else(|| match options.kind {
234 ModuleKind::Library => Path::new("nofile"),
235 ModuleKind::Executable => Path::exec_path(),
236 ModuleKind::Kernel => Path::kernel_path(),
237 });
238 let name = Uri::from(path.as_str().to_string().into_boxed_str());
239 let mut parser = Module::parser(options.kind);
240 parser.set_warnings_as_errors(options.warnings_as_errors);
241 let content = SourceContent::new(SourceLanguage::Masm, name.clone(), self);
242 let source_file = source_manager.load_from_raw_parts(name, content);
243 parser.parse(path, source_file, source_manager)
244 }
245}
246
247impl Parse for Cow<'_, str> {
248 #[inline(always)]
249 fn parse_with_options(
250 self,
251 source_manager: Arc<dyn SourceManager>,
252 options: ParseOptions,
253 ) -> Result<Box<Module>, Report> {
254 self.into_owned().into_boxed_str().parse_with_options(source_manager, options)
255 }
256}
257
258impl Parse for &[u8] {
262 #[inline]
263 fn parse_with_options(
264 self,
265 source_manager: Arc<dyn SourceManager>,
266 options: ParseOptions,
267 ) -> Result<Box<Module>, Report> {
268 core::str::from_utf8(self)
269 .map_err(|err| {
270 Report::from(crate::parser::ParsingError::from_utf8_error(Default::default(), err))
271 .with_source_code(self.to_vec())
272 })
273 .wrap_err("parsing failed: invalid source code")
274 .and_then(|source| source.parse_with_options(source_manager, options))
275 }
276}
277
278impl Parse for Vec<u8> {
279 #[inline]
280 fn parse_with_options(
281 self,
282 source_manager: Arc<dyn SourceManager>,
283 options: ParseOptions,
284 ) -> Result<Box<Module>, Report> {
285 String::from_utf8(self)
286 .map_err(|err| {
287 let error = crate::parser::ParsingError::from_utf8_error(
288 Default::default(),
289 err.utf8_error(),
290 );
291 Report::from(error).with_source_code(err.into_bytes())
292 })
293 .wrap_err("parsing failed: invalid source code")
294 .and_then(|source| source.into_boxed_str().parse_with_options(source_manager, options))
295 }
296}
297impl Parse for Box<[u8]> {
298 #[inline(always)]
299 fn parse_with_options(
300 self,
301 source_manager: Arc<dyn SourceManager>,
302 options: ParseOptions,
303 ) -> Result<Box<Module>, Report> {
304 Vec::from(self).parse_with_options(source_manager, options)
305 }
306}
307
308impl<T> Parse for NamedSource<T>
309where
310 T: SourceCode + AsRef<[u8]>,
311{
312 fn parse_with_options(
313 self,
314 source_manager: Arc<dyn SourceManager>,
315 options: ParseOptions,
316 ) -> Result<Box<Module>, Report> {
317 let path = match options.path {
318 Some(path) => path,
319 None => self
320 .name()
321 .parse::<PathBuf>()
322 .map(|p| p.into())
323 .into_diagnostic()
324 .wrap_err("cannot parse module as it has an invalid path/name")?,
325 };
326 let content = core::str::from_utf8(self.inner().as_ref())
327 .map_err(|err| {
328 let error = crate::parser::ParsingError::from_utf8_error(Default::default(), err);
329 Report::from(error)
330 })
331 .wrap_err("parsing failed: expected source code to be valid utf-8")?;
332 let name = Uri::from(self.name());
333 let content = SourceContent::new(
334 SourceLanguage::Masm,
335 name.clone(),
336 content.to_string().into_boxed_str(),
337 );
338 let source_file = source_manager.load_from_raw_parts(name, content);
339 let mut parser = Module::parser(options.kind);
340 parser.set_warnings_as_errors(options.warnings_as_errors);
341 parser.parse(path, source_file, source_manager)
342 }
343}
344
345#[cfg(feature = "std")]
349impl Parse for &std::path::Path {
350 fn parse_with_options(
351 self,
352 source_manager: Arc<dyn SourceManager>,
353 options: ParseOptions,
354 ) -> Result<Box<Module>, Report> {
355 use std::path::Component;
356
357 use miden_debug_types::SourceManagerExt;
358
359 use crate::{Path, PathError};
360
361 let path = match options.path {
362 Some(path) => path,
363 None => {
364 let mut buf = match options.kind {
365 ModuleKind::Library => PathBuf::default(),
366 ModuleKind::Executable => Path::exec_path().to_path_buf(),
367 ModuleKind::Kernel => Path::kernel_path().to_path_buf(),
368 };
369 self.components()
370 .skip_while(|component| {
371 matches!(
372 component,
373 Component::Prefix(_)
374 | Component::RootDir
375 | Component::ParentDir
376 | Component::CurDir
377 )
378 })
379 .try_for_each(|component| {
380 let part: &str = component
381 .as_os_str()
382 .to_str()
383 .ok_or(PathError::InvalidUtf8)
384 .and_then(|s| Path::validate(s).map(|_| s))
385 .into_diagnostic()
386 .wrap_err("invalid module path")?;
387 buf.push(part);
388
389 Ok::<(), Report>(())
390 })?;
391
392 buf.into()
393 },
394 };
395 let source_file = source_manager
396 .load_file(self)
397 .into_diagnostic()
398 .wrap_err("source manager is unable to load file")?;
399 let mut parser = Module::parser(options.kind);
400 parser.parse(path, source_file, source_manager)
401 }
402}
403
404#[cfg(feature = "std")]
405impl Parse for std::path::PathBuf {
406 #[inline(always)]
407 fn parse_with_options(
408 self,
409 source_manager: Arc<dyn SourceManager>,
410 options: ParseOptions,
411 ) -> Result<Box<Module>, Report> {
412 self.as_path().parse_with_options(source_manager, options)
413 }
414}