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, Debug, 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    pub 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    /// Return filename of loaded file or `<no file>`
29    pub fn filename(&self) -> std::path::PathBuf {
30        self.filename
31            .clone()
32            .unwrap_or(std::path::PathBuf::from(crate::invalid_no_ansi!(SOURCE)))
33    }
34
35    /// Return filename of loaded file or `<no file>`
36    pub fn filename_as_str(&self) -> &str {
37        self.filename
38            .as_ref()
39            .map(|f| f.to_str().expect("File name error {filename:?}"))
40            .unwrap_or(crate::invalid!(SOURCE))
41    }
42
43    /// Return the module name from the file name
44    pub fn id(&self) -> Identifier {
45        match &self.filename {
46            Some(filename) => Identifier(Refer::new(
47                filename
48                    .file_stem()
49                    .expect("cannot get file stem")
50                    .to_str()
51                    .expect("File name error {filename:?}")
52                    .into(),
53                SrcRef::new(0..0, 0, 0, self.hash),
54            )),
55            None => Identifier::none(),
56        }
57    }
58
59    /// get a specific line
60    ///
61    /// - `line`: line number beginning at `0`
62    pub fn get_line(&self, line: usize) -> Option<&str> {
63        self.source.lines().nth(line)
64    }
65
66    /// return number of source code lines
67    pub fn num_lines(&self) -> usize {
68        self.source.lines().count()
69    }
70}
71impl std::fmt::Display for SourceFile {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        self.statements.iter().try_for_each(|s| writeln!(f, "{s}"))
74    }
75}
76
77impl TreeDisplay for SourceFile {
78    fn tree_print(&self, f: &mut std::fmt::Formatter, mut depth: TreeState) -> std::fmt::Result {
79        writeln!(
80            f,
81            "{:depth$}SourceFile '{:?}' ({}):",
82            "",
83            self.id(),
84            self.filename_as_str()
85        )?;
86        depth.indent();
87        self.statements
88            .iter()
89            .try_for_each(|s| s.tree_print(f, depth))
90    }
91}
92
93impl SrcReferrer for SourceFile {
94    fn src_ref(&self) -> crate::src_ref::SrcRef {
95        SrcRef::new(0..self.num_lines(), 0, 0, self.hash)
96    }
97}
98
99#[test]
100fn load_source_file_wrong_location() {
101    let source_file = SourceFile::load("I do not exist.µcad");
102    if let Err(err) = source_file {
103        log::info!("{err}");
104        //assert_eq!(format!("{err}"), "Cannot load source file");
105    } else {
106        panic!("Does file exist?");
107    }
108}