microcad_lang/syntax/source_file/
mod.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! µcad source file representation
5
6use crate::{src_ref::*, syntax::*};
7
8/// µcad source file
9#[derive(Clone, Default)]
10pub struct SourceFile {
11    /// Qualified name of the file if loaded from externals
12    pub name: QualifiedName,
13    /// Root code body.
14    pub statements: StatementList,
15    /// Name of loaded file.
16    filename: Option<std::path::PathBuf>,
17    /// Source file string, TODO: might be a &'a str in the future
18    pub source: String,
19
20    /// Hash of the source file
21    ///
22    /// This hash is calculated from the source code itself
23    /// This is used to map `SrcRef` -> `SourceFile`
24    pub hash: u64,
25}
26
27impl SourceFile {
28    /// Create new source file from existing source.
29    pub fn new(statements: StatementList, source: String, hash: u64) -> Self {
30        Self {
31            statements,
32            source,
33            hash,
34            ..Default::default()
35        }
36    }
37    /// Return filename of loaded file or `<no file>`
38    pub fn filename(&self) -> std::path::PathBuf {
39        self.filename
40            .clone()
41            .unwrap_or(std::path::PathBuf::from(crate::invalid_no_ansi!(SOURCE)))
42    }
43
44    /// Return filename of loaded file or `<no file>`
45    pub fn set_filename(&mut self, path: impl AsRef<std::path::Path>) {
46        assert!(self.filename.is_none());
47        self.filename = Some(path.as_ref().canonicalize().expect("path not found"))
48    }
49
50    /// Return filename of loaded file or `<no file>`
51    pub fn filename_as_str(&self) -> &str {
52        self.filename
53            .as_ref()
54            .map(|f| f.to_str().expect("File name error {filename:?}"))
55            .unwrap_or(crate::invalid!(SOURCE))
56    }
57
58    /// Return the module name from the file name
59    pub fn id(&self) -> Identifier {
60        self.name.last().unwrap_or(&Identifier::none()).clone()
61    }
62
63    /// get a specific line
64    ///
65    /// - `line`: line number beginning at `0`
66    pub fn get_line(&self, line: usize) -> Option<&str> {
67        self.source.lines().nth(line)
68    }
69
70    /// return number of source code lines
71    pub fn num_lines(&self) -> usize {
72        self.source.lines().count()
73    }
74
75    /// Set file name.
76    pub fn set_name(&mut self, name: QualifiedName) {
77        self.name = name
78    }
79}
80
81impl std::fmt::Display for SourceFile {
82    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83        self.statements.iter().try_for_each(|s| writeln!(f, "{s}"))
84    }
85}
86
87impl std::fmt::Debug for SourceFile {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        self.statements
90            .iter()
91            .try_for_each(|s| writeln!(f, "{s:?}"))
92    }
93}
94
95impl TreeDisplay for SourceFile {
96    fn tree_print(&self, f: &mut std::fmt::Formatter, mut depth: TreeState) -> std::fmt::Result {
97        writeln!(
98            f,
99            "{:depth$}SourceFile '{:?}' ({}):",
100            "",
101            self.id(),
102            self.filename_as_str()
103        )?;
104        depth.indent();
105        self.statements
106            .iter()
107            .try_for_each(|s| s.tree_print(f, depth))
108    }
109}
110
111impl SrcReferrer for SourceFile {
112    fn src_ref(&self) -> crate::src_ref::SrcRef {
113        SrcRef::new(0..self.num_lines(), 0, 0, self.hash)
114    }
115}
116
117#[test]
118fn load_source_file_wrong_location() {
119    let source_file = SourceFile::load("I do not exist.µcad");
120    if let Err(err) = source_file {
121        log::info!("{err}");
122        //assert_eq!(format!("{err}"), "Cannot load source file");
123    } else {
124        panic!("Does file exist?");
125    }
126}