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