microcad_lang/parse/
source_file.rs1use crate::{parse::*, parser::*};
5use microcad_lang_base::{FormatTree, SrcReferrer};
6use microcad_syntax::ast;
7use std::fs::read_to_string;
8
9impl SourceFile {
10 pub fn load(
12 path: impl AsRef<std::path::Path> + std::fmt::Debug,
13 ) -> Result<std::rc::Rc<Self>, ParseErrorsWithSource> {
14 let (source, error) = Self::load_with_name(&path, Self::name_from_path(&path));
15 match error {
16 Some(error) => Err(error),
17 None => Ok(source),
18 }
19 }
20
21 pub fn load_with_name(
23 path: impl AsRef<std::path::Path> + std::fmt::Debug,
24 name: QualifiedName,
25 ) -> (std::rc::Rc<Self>, Option<ParseErrorsWithSource>) {
26 let path = path.as_ref();
27 log::trace!(
28 "{load} file {path} [{name}]",
29 path = path.display(),
30 load = microcad_lang_base::mark!(LOAD)
31 );
32
33 let buf = match read_to_string(path) {
34 Ok(buf) => buf,
35 Err(error) => {
36 let error = ParseError::LoadSource(name.src_ref(), path.into(), error);
37 let mut source_file =
38 SourceFile::new(None, StatementList::default(), String::new(), 0);
39 source_file.name = name;
40 return (std::rc::Rc::new(source_file), Some(error.into()));
41 }
42 };
43
44 let (mut source_file, errors) = Self::load_inner(None, path, &buf);
45 source_file.name = name;
46 log::debug!(
47 "Successfully loaded external file {} to {}",
48 path.display(),
49 source_file.name
50 );
51
52 (std::rc::Rc::new(source_file), errors)
53 }
54
55 pub fn load_from_str(
58 name: Option<&str>,
59 path: impl AsRef<std::path::Path>,
60 source_str: &str,
61 ) -> Result<std::rc::Rc<Self>, Vec<ParseError>> {
62 let (source, error) = Self::load_inner(name, path, source_str);
63 match error {
64 Some(error) => Err(error.errors),
65 None => Ok(std::rc::Rc::new(source)),
66 }
67 }
68
69 pub fn load_from_str_with_recovery(
72 name: Option<&str>,
73 path: impl AsRef<std::path::Path>,
74 source_str: &str,
75 ) -> (std::rc::Rc<Self>, Option<Vec<ParseError>>) {
76 let (source, error) = Self::load_inner(name, path, source_str);
77 (std::rc::Rc::new(source), error.map(|err| err.errors))
78 }
79
80 fn load_inner(
81 name: Option<&str>,
82 path: impl AsRef<std::path::Path>,
83 source_str: &str,
84 ) -> (Self, Option<ParseErrorsWithSource>) {
85 log::trace!(
86 "{load} source from string",
87 load = microcad_lang_base::mark!(LOAD)
88 );
89 let parse_context = ParseContext::new(source_str);
90
91 let dummy_source = || {
92 let mut source = SourceFile::new(
93 None,
94 StatementList::default(),
95 source_str.into(),
96 parse_context.source_file_hash,
97 );
98 source.filename = Some(path.as_ref().into());
99 source
100 };
101
102 let ast = match build_ast(source_str, &parse_context) {
103 Ok(ast) => ast,
104 Err(error) => {
105 return (dummy_source(), Some(error));
106 }
107 };
108
109 let mut source_file =
110 match Self::from_ast(&ast, &parse_context).map_err(|error| vec![error]) {
111 Ok(source_file) => source_file,
112 Err(errors) => {
113 return (
114 dummy_source(),
115 Some(ParseErrorsWithSource {
116 errors,
117 source_code: Some(source_str.into()),
118 source_hash: parse_context.source_file_hash,
119 }),
120 );
121 }
122 };
123 if let Some(name) = name {
124 source_file.set_name(QualifiedName::from_id(Identifier::no_ref(name)));
125 } else {
126 source_file.set_name(Self::name_from_path(&path));
127 };
128 source_file.set_filename(path);
129 log::debug!("Successfully loaded source from string");
130 log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
131 (source_file, None)
132 }
133
134 pub fn name_from_path(path: impl AsRef<std::path::Path>) -> QualifiedName {
136 QualifiedName::from_id(Identifier::no_ref(
137 &path
138 .as_ref()
139 .file_stem()
140 .expect("illegal file name")
141 .to_string_lossy(),
142 ))
143 }
144}
145
146impl FromAst for SourceFile {
147 type AstNode = ast::SourceFile;
148
149 fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
150 Ok(SourceFile::new(
151 None, StatementList::from_ast(&node.statements, context)?,
153 context.source.into(),
154 context.source_file_hash,
155 ))
156 }
157}
158
159#[test]
160fn parse_source_file() {
161 let source_file = SourceFile::load_from_str(
162 None,
163 "test.µcad",
164 r#"use std::log::info;
165 part Foo(r: Scalar) {
166 info("Hello, world, {r}!");
167 }
168 Foo(20.0);
169 "#,
170 )
171 .expect("test error");
172
173 assert_eq!(source_file.statements.len(), 3);
174}