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