microcad_lang/syntax/source_file/
mod.rs1use crate::syntax::*;
7use microcad_lang_base::{SrcRef, SrcReferrer, TreeDisplay, TreeState};
8use miette::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
9
10#[derive(Clone, Default)]
12pub struct SourceFile {
13 pub doc: Option<DocBlock>,
15 pub name: QualifiedName,
17 pub statements: StatementList,
19 pub filename: Option<std::path::PathBuf>,
21 pub source: String,
23
24 pub hash: u64,
29}
30
31impl SourceFile {
32 pub fn new(
34 doc: Option<DocBlock>,
35 statements: StatementList,
36 source: String,
37 hash: u64,
38 ) -> Self {
39 Self {
40 doc,
41 statements,
42 source,
43 hash,
44 ..Default::default()
45 }
46 }
47 pub fn filename(&self) -> std::path::PathBuf {
49 self.filename.clone().unwrap_or(std::path::PathBuf::from(
50 microcad_lang_base::invalid_no_ansi!(SOURCE),
51 ))
52 }
53
54 pub fn set_filename(&mut self, path: impl AsRef<std::path::Path>) {
56 assert!(self.filename.is_none());
57 self.filename = Some(
58 path.as_ref()
59 .canonicalize()
60 .unwrap_or(path.as_ref().to_path_buf()),
61 )
62 }
63
64 pub fn filename_as_str(&self) -> &str {
66 self.filename
67 .as_ref()
68 .map(|f| f.to_str().expect("File name error {filename:?}"))
69 .unwrap_or(microcad_lang_base::invalid!(SOURCE))
70 }
71
72 pub fn id(&self) -> Identifier {
74 self.name.last().unwrap_or(&Identifier::none()).clone()
75 }
76
77 pub fn get_line(&self, line: usize) -> Option<&str> {
81 self.source.lines().nth(line)
82 }
83
84 pub fn get_code(&self, src_ref: &SrcRef) -> &str {
88 let range = &src_ref.as_ref().expect("source reference empty").range;
89 &self.source[range.start..range.end]
90 }
91
92 pub fn num_lines(&self) -> usize {
94 self.source.lines().count()
95 }
96
97 pub fn set_name(&mut self, name: QualifiedName) {
99 self.name = name
100 }
101}
102
103impl std::fmt::Display for SourceFile {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 self.statements.iter().try_for_each(|s| writeln!(f, "{s}"))
106 }
107}
108
109impl std::fmt::Debug for SourceFile {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 self.statements
112 .iter()
113 .try_for_each(|s| writeln!(f, "{s:?}"))
114 }
115}
116
117impl TreeDisplay for SourceFile {
118 fn tree_print(&self, f: &mut std::fmt::Formatter, mut depth: TreeState) -> std::fmt::Result {
119 writeln!(
120 f,
121 "{:depth$}SourceFile '{:?}' ({}):",
122 "",
123 self.id(),
124 self.filename_as_str()
125 )?;
126 depth.indent();
127 if let Some(doc) = &self.doc {
128 doc.tree_print(f, depth)?;
129 }
130 self.statements
131 .iter()
132 .try_for_each(|s| s.tree_print(f, depth))
133 }
134}
135
136impl SrcReferrer for SourceFile {
137 fn src_ref(&self) -> SrcRef {
138 SrcRef::new(0..self.num_lines(), 0, 0, self.hash)
139 }
140}
141
142#[test]
143fn load_source_file_wrong_location() {
144 let source_file = SourceFile::load("I do not exist.µcad");
145 if let Err(err) = source_file {
146 log::info!("{err}");
147 } else {
149 panic!("Does file exist?");
150 }
151}
152
153pub struct MietteSourceFile<'a> {
155 source: &'a str,
156 name: String,
157 line_offset: usize,
158}
159
160impl MietteSourceFile<'static> {
161 pub fn invalid() -> Self {
163 MietteSourceFile {
164 source: microcad_lang_base::invalid_no_ansi!(FILE),
165 name: microcad_lang_base::invalid_no_ansi!(FILE).into(),
166 line_offset: 0,
167 }
168 }
169}
170
171impl SourceFile {
172 pub fn miette_source<'a>(&'a self, path: String, line_offset: usize) -> MietteSourceFile<'a> {
174 MietteSourceFile {
175 source: &self.source,
176 name: path,
177 line_offset,
178 }
179 }
180}
181
182impl SourceCode for MietteSourceFile<'_> {
183 fn read_span<'a>(
184 &'a self,
185 span: &SourceSpan,
186 context_lines_before: usize,
187 context_lines_after: usize,
188 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
189 let inner_contents =
190 self.source
191 .read_span(span, context_lines_before, context_lines_after)?;
192 let contents = MietteSpanContents::new_named(
193 self.name.clone(),
194 inner_contents.data(),
195 *inner_contents.span(),
196 inner_contents.line() + self.line_offset,
197 inner_contents.column(),
198 inner_contents.line_count(),
199 )
200 .with_language("µcad");
201 Ok(Box::new(contents))
202 }
203}