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    /// Documentation.
12    pub doc: Option<DocBlock>,
13    /// Qualified name of the file if loaded from externals
14    pub name: QualifiedName,
15    /// Root code body.
16    pub statements: StatementList,
17    /// Name of loaded file.
18    filename: Option<std::path::PathBuf>,
19    /// Source file string, TODO: might be a &'a str in the future
20    pub source: String,
21
22    /// Hash of the source file
23    ///
24    /// This hash is calculated from the source code itself
25    /// This is used to map `SrcRef` -> `SourceFile`
26    pub hash: u64,
27}
28
29impl SourceFile {
30    /// Create new source file from existing source.
31    pub fn new(
32        doc: Option<DocBlock>,
33        statements: StatementList,
34        source: String,
35        hash: u64,
36    ) -> Self {
37        Self {
38            doc,
39            statements,
40            source,
41            hash,
42            ..Default::default()
43        }
44    }
45    /// Return filename of loaded file or `<no file>`
46    pub fn filename(&self) -> std::path::PathBuf {
47        self.filename
48            .clone()
49            .unwrap_or(std::path::PathBuf::from(crate::invalid_no_ansi!(SOURCE)))
50    }
51
52    /// Return filename of loaded file or `<no file>`
53    pub fn set_filename(&mut self, path: impl AsRef<std::path::Path>) {
54        assert!(self.filename.is_none());
55        self.filename = Some(
56            path.as_ref()
57                .canonicalize()
58                .unwrap_or(path.as_ref().to_path_buf()),
59        )
60    }
61
62    /// Return filename of loaded file or `<no file>`
63    pub fn filename_as_str(&self) -> &str {
64        self.filename
65            .as_ref()
66            .map(|f| f.to_str().expect("File name error {filename:?}"))
67            .unwrap_or(crate::invalid!(SOURCE))
68    }
69
70    /// Return the module name from the file name
71    pub fn id(&self) -> Identifier {
72        self.name.last().unwrap_or(&Identifier::none()).clone()
73    }
74
75    /// get a specific line
76    ///
77    /// - `line`: line number beginning at `0`
78    pub fn get_line(&self, line: usize) -> Option<&str> {
79        self.source.lines().nth(line)
80    }
81
82    /// get a specific line
83    ///
84    /// - `line`: line number beginning at `0`
85    pub fn get_code(&self, src_ref: &SrcRef) -> &str {
86        let range = &src_ref.as_ref().expect("source reference empty").range;
87        &self.source[range.start..range.end]
88    }
89
90    /// return number of source code lines
91    pub fn num_lines(&self) -> usize {
92        self.source.lines().count()
93    }
94
95    /// Set file name.
96    pub fn set_name(&mut self, name: QualifiedName) {
97        self.name = name
98    }
99}
100
101impl std::fmt::Display for SourceFile {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        self.statements.iter().try_for_each(|s| writeln!(f, "{s}"))
104    }
105}
106
107impl std::fmt::Debug for SourceFile {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        self.statements
110            .iter()
111            .try_for_each(|s| writeln!(f, "{s:?}"))
112    }
113}
114
115impl TreeDisplay for SourceFile {
116    fn tree_print(&self, f: &mut std::fmt::Formatter, mut depth: TreeState) -> std::fmt::Result {
117        writeln!(
118            f,
119            "{:depth$}SourceFile '{:?}' ({}):",
120            "",
121            self.id(),
122            self.filename_as_str()
123        )?;
124        depth.indent();
125        if let Some(doc) = &self.doc {
126            doc.tree_print(f, depth)?;
127        }
128        self.statements
129            .iter()
130            .try_for_each(|s| s.tree_print(f, depth))
131    }
132}
133
134impl SrcReferrer for SourceFile {
135    fn src_ref(&self) -> crate::src_ref::SrcRef {
136        SrcRef::new(0..self.num_lines(), 0, 0, self.hash)
137    }
138}
139
140impl Doc for SourceFile {
141    fn doc(&self) -> Option<DocBlock> {
142        self.doc.clone()
143    }
144}
145
146#[test]
147fn load_source_file_wrong_location() {
148    let source_file = SourceFile::load("I do not exist.µcad");
149    if let Err(err) = source_file {
150        log::info!("{err}");
151        //assert_eq!(format!("{err}"), "Cannot load source file");
152    } else {
153        panic!("Does file exist?");
154    }
155}