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        let name = QualifiedName::from_id(Identifier::no_ref(
11            &path
12                .as_ref()
13                .file_stem()
14                .expect("illegal file name")
15                .to_string_lossy(),
16        ));
17        Self::load_with_name(path, name)
18    }
19
20    /// Load µcad source file from given `path`
21    pub fn load_with_name(
22        path: impl AsRef<std::path::Path> + std::fmt::Debug,
23        name: QualifiedName,
24    ) -> ParseResult<Rc<Self>> {
25        log::trace!("{load} file {path:?} [{name}]", load = crate::mark!(LOAD));
26
27        let mut file = match std::fs::File::open(&path) {
28            Ok(file) => file,
29            _ => return Err(ParseError::LoadSource(path.as_ref().into())),
30        };
31
32        let mut buf = String::new();
33        file.read_to_string(&mut buf)?;
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.filename = Some(path.as_ref().to_path_buf());
38        source_file.name = name;
39        log::debug!(
40            "Successfully loaded 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(s: &str) -> ParseResult<Rc<Self>> {
52        log::trace!("{load} source from string", load = crate::mark!(LOAD));
53        let mut source_file: Self = Parser::parse_rule(crate::parser::Rule::source_file, s, 0)?;
54        log::debug!("Successfully loaded source from string");
55        source_file.filename = None;
56        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
57        Ok(Rc::new(source_file))
58    }
59
60    fn calculate_hash(value: &str) -> u64 {
61        use std::hash::{Hash, Hasher};
62        let mut hasher = std::collections::hash_map::DefaultHasher::new();
63        value.hash(&mut hasher);
64        hasher.finish()
65    }
66}
67
68impl Parse for SourceFile {
69    fn parse(mut pair: Pair) -> ParseResult<Self> {
70        // calculate hash over complete file content
71        let hash = Self::calculate_hash(pair.as_str());
72        pair.set_source_hash(hash);
73
74        Ok(SourceFile {
75            statements: crate::find_rule!(pair, statement_list)?,
76            filename: Default::default(),
77            source: pair.as_span().as_str().to_string(),
78            hash,
79            name: QualifiedName::default(),
80        })
81    }
82}
83
84#[test]
85fn parse_source_file() {
86    let source_file = Parser::parse_rule::<SourceFile>(
87        Rule::source_file,
88        r#"use std::log::info;
89            part Foo(r: Scalar) {
90                info("Hello, world, {r}!");
91            }
92            Foo(20.0);
93            "#,
94        0,
95    )
96    .expect("test error");
97
98    assert_eq!(source_file.statements.len(), 3);
99}