microcad_lang/parse/
source_file.rs

1// Copyright © 2024-2025 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{parse::*, parser::*, rc::*, tree_display::*};
5use std::io::Read;
6
7impl SourceFile {
8    /// Load µcad source file from given `path`
9    pub fn load(path: impl AsRef<std::path::Path> + std::fmt::Debug) -> ParseResult<Rc<Self>> {
10        Self::load_with_name(&path, Self::name_from_path(&path))
11    }
12
13    /// Load µcad source file from given `path`
14    pub fn load_with_name(
15        path: impl AsRef<std::path::Path> + std::fmt::Debug,
16        name: QualifiedName,
17    ) -> ParseResult<Rc<Self>> {
18        log::trace!("{load} file {path:?} [{name}]", load = crate::mark!(LOAD));
19
20        let mut file = match std::fs::File::open(&path) {
21            Ok(file) => file,
22            _ => {
23                return Err(ParseError::LoadSource(Refer::new(
24                    path.as_ref().into(),
25                    name.src_ref(),
26                )))
27            }
28        };
29
30        let mut buf = String::new();
31        if let Err(err) = file.read_to_string(&mut buf) {
32            return Err(ParseError::IoError(Refer::new(err, name.src_ref())));
33        }
34
35        let mut source_file: Self = Parser::parse_rule(crate::parser::Rule::source_file, &buf, 0)?;
36        assert_ne!(source_file.hash, 0);
37        source_file.set_filename(path.as_ref());
38        source_file.name = name;
39        log::debug!(
40            "Successfully loaded external file {} to {}",
41            path.as_ref().to_string_lossy(),
42            source_file.name
43        );
44        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
45
46        Ok(Rc::new(source_file))
47    }
48
49    /// Create `SourceFile` from string
50    /// The hash of the result will be of `crate::from_str!()`.
51    pub fn load_from_str(
52        name: Option<&str>,
53        path: impl AsRef<std::path::Path>,
54        source_str: &str,
55    ) -> ParseResult<Rc<Self>> {
56        log::trace!("{load} source from string", load = crate::mark!(LOAD));
57        let mut source_file: Self =
58            Parser::parse_rule(crate::parser::Rule::source_file, source_str, 0)?;
59        if let Some(name) = name {
60            source_file.set_name(QualifiedName::from_id(Identifier::no_ref(name)));
61        } else {
62            source_file.set_name(Self::name_from_path(&path));
63        };
64        source_file.set_filename(path);
65        log::debug!("Successfully loaded source from string");
66        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
67        Ok(Rc::new(source_file))
68    }
69
70    fn calculate_hash(value: &str) -> u64 {
71        use std::hash::{Hash, Hasher};
72        let mut hasher = rustc_hash::FxHasher::default();
73        value.hash(&mut hasher);
74        hasher.finish()
75    }
76
77    /// Get the source file name from path.
78    pub fn name_from_path(path: impl AsRef<std::path::Path>) -> QualifiedName {
79        QualifiedName::from_id(Identifier::no_ref(
80            &path
81                .as_ref()
82                .file_stem()
83                .expect("illegal file name")
84                .to_string_lossy(),
85        ))
86    }
87}
88
89impl Parse for SourceFile {
90    fn parse(mut pair: Pair) -> ParseResult<Self> {
91        // calculate hash over complete file content
92        let hash = Self::calculate_hash(pair.as_str());
93        pair.set_source_hash(hash);
94
95        Ok(SourceFile::new(
96            crate::find_rule_opt!(pair, doc_block),
97            crate::find_rule!(pair, statement_list)?,
98            pair.as_span().as_str().to_string(),
99            hash,
100        ))
101    }
102}
103
104#[test]
105fn parse_source_file() {
106    let source_file = Parser::parse_rule::<SourceFile>(
107        Rule::source_file,
108        r#"use std::log::info;
109            part Foo(r: Scalar) {
110                info("Hello, world, {r}!");
111            }
112            Foo(20.0);
113            "#,
114        0,
115    )
116    .expect("test error");
117
118    assert_eq!(source_file.statements.len(), 3);
119}