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