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            _ => return Err(ParseError::LoadSource(path.as_ref().into())),
23        };
24
25        let mut buf = String::new();
26        file.read_to_string(&mut buf)?;
27
28        let mut source_file: Self = Parser::parse_rule(crate::parser::Rule::source_file, &buf, 0)?;
29        assert_ne!(source_file.hash, 0);
30        source_file.set_filename(path.as_ref());
31        source_file.name = name;
32        log::debug!(
33            "Successfully loaded external file {} to {}",
34            path.as_ref().to_string_lossy(),
35            source_file.name
36        );
37        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
38
39        Ok(Rc::new(source_file))
40    }
41
42    /// Create `SourceFile` from string
43    /// The hash of the result will be of `crate::from_str!()`.
44    pub fn load_from_str(
45        name: &str,
46        path: impl AsRef<std::path::Path>,
47        source_str: &str,
48    ) -> ParseResult<Rc<Self>> {
49        log::trace!("{load} source from string", load = crate::mark!(LOAD));
50        let mut source_file: Self =
51            Parser::parse_rule(crate::parser::Rule::source_file, source_str, 0)?;
52        source_file.set_name(QualifiedName::from_id(Identifier::no_ref(name)));
53        source_file.set_filename(path);
54        log::debug!("Successfully loaded source from string");
55        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
56        Ok(Rc::new(source_file))
57    }
58
59    fn calculate_hash(value: &str) -> u64 {
60        use std::hash::{Hash, Hasher};
61        let mut hasher = rustc_hash::FxHasher::default();
62        value.hash(&mut hasher);
63        hasher.finish()
64    }
65
66    /// Get the source file name from path.
67    fn name_from_path(path: impl AsRef<std::path::Path>) -> QualifiedName {
68        QualifiedName::from_id(Identifier::no_ref(
69            &path
70                .as_ref()
71                .file_stem()
72                .expect("illegal file name")
73                .to_string_lossy(),
74        ))
75    }
76}
77
78impl Parse for SourceFile {
79    fn parse(mut pair: Pair) -> ParseResult<Self> {
80        // calculate hash over complete file content
81        let hash = Self::calculate_hash(pair.as_str());
82        pair.set_source_hash(hash);
83
84        Ok(SourceFile::new(
85            crate::find_rule_opt!(pair, doc_block),
86            crate::find_rule!(pair, statement_list)?,
87            pair.as_span().as_str().to_string(),
88            hash,
89        ))
90    }
91}
92
93#[test]
94fn parse_source_file() {
95    let source_file = Parser::parse_rule::<SourceFile>(
96        Rule::source_file,
97        r#"use std::log::info;
98            part Foo(r: Scalar) {
99                info("Hello, world, {r}!");
100            }
101            Foo(20.0);
102            "#,
103        0,
104    )
105    .expect("test error");
106
107    assert_eq!(source_file.statements.len(), 3);
108}