Skip to main content

microcad_lang/parse/
source_file.rs

1// Copyright © 2024-2026 The µcad authors <info@ucad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::{parse::*, parser::*, rc::*, tree_display::*};
5use microcad_syntax::ast;
6use std::fs::read_to_string;
7
8impl SourceFile {
9    /// Load µcad source file from given `path`
10    pub fn load(
11        path: impl AsRef<std::path::Path> + std::fmt::Debug,
12    ) -> Result<Rc<Self>, ParseErrorsWithSource> {
13        let (source, error) = Self::load_with_name(&path, Self::name_from_path(&path));
14        match error {
15            Some(error) => Err(error),
16            None => Ok(source),
17        }
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    ) -> (Rc<Self>, Option<ParseErrorsWithSource>) {
25        let path = path.as_ref();
26        log::trace!(
27            "{load} file {path} [{name}]",
28            path = path.display(),
29            load = crate::mark!(LOAD)
30        );
31
32        let buf = match read_to_string(path) {
33            Ok(buf) => buf,
34            Err(error) => {
35                let error = ParseError::LoadSource(name.src_ref(), path.into(), error);
36                let mut source_file =
37                    SourceFile::new(None, StatementList::default(), String::new(), 0);
38                source_file.name = name;
39                return (Rc::new(source_file), Some(error.into()));
40            }
41        };
42
43        let (mut source_file, errors) = Self::load_inner(None, path, &buf);
44        source_file.name = name;
45        log::debug!(
46            "Successfully loaded external file {} to {}",
47            path.display(),
48            source_file.name
49        );
50
51        (Rc::new(source_file), errors)
52    }
53
54    /// Create `SourceFile` from string
55    /// The hash of the result will be of `crate::from_str!()`.
56    pub fn load_from_str(
57        name: Option<&str>,
58        path: impl AsRef<std::path::Path>,
59        source_str: &str,
60    ) -> Result<Rc<Self>, Vec<ParseError>> {
61        let (source, error) = Self::load_inner(name, path, source_str);
62        match error {
63            Some(error) => Err(error.errors),
64            None => Ok(Rc::new(source)),
65        }
66    }
67
68    fn load_inner(
69        name: Option<&str>,
70        path: impl AsRef<std::path::Path>,
71        source_str: &str,
72    ) -> (Self, Option<ParseErrorsWithSource>) {
73        log::trace!("{load} source from string", load = crate::mark!(LOAD));
74        let parse_context = ParseContext::new(source_str);
75
76        let dummy_source = || {
77            let mut source = SourceFile::new(
78                None,
79                StatementList::default(),
80                source_str.into(),
81                parse_context.source_file_hash,
82            );
83            source.filename = Some(path.as_ref().into());
84            source
85        };
86
87        let ast = match build_ast(source_str, &parse_context) {
88            Ok(ast) => ast,
89            Err(error) => {
90                return (dummy_source(), Some(error));
91            }
92        };
93
94        let mut source_file =
95            match Self::from_ast(&ast, &parse_context).map_err(|error| vec![error]) {
96                Ok(source_file) => source_file,
97                Err(errors) => {
98                    return (
99                        dummy_source(),
100                        Some(ParseErrorsWithSource {
101                            errors,
102                            source_code: Some(source_str.into()),
103                            source_hash: parse_context.source_file_hash,
104                        }),
105                    );
106                }
107            };
108        if let Some(name) = name {
109            source_file.set_name(QualifiedName::from_id(Identifier::no_ref(name)));
110        } else {
111            source_file.set_name(Self::name_from_path(&path));
112        };
113        source_file.set_filename(path);
114        log::debug!("Successfully loaded source from string");
115        log::trace!("Syntax tree:\n{}", FormatTree(&source_file));
116        (source_file, None)
117    }
118
119    /// Get the source file name from path.
120    pub fn name_from_path(path: impl AsRef<std::path::Path>) -> QualifiedName {
121        QualifiedName::from_id(Identifier::no_ref(
122            &path
123                .as_ref()
124                .file_stem()
125                .expect("illegal file name")
126                .to_string_lossy(),
127        ))
128    }
129}
130
131impl FromAst for SourceFile {
132    type AstNode = ast::SourceFile;
133
134    fn from_ast(node: &Self::AstNode, context: &ParseContext) -> Result<Self, ParseError> {
135        Ok(SourceFile::new(
136            None, // todo
137            StatementList::from_ast(&node.statements, context)?,
138            context.source.into(),
139            context.source_file_hash,
140        ))
141    }
142}
143
144#[test]
145fn parse_source_file() {
146    let source_file = SourceFile::load_from_str(
147        None,
148        "test.µcad",
149        r#"use std::log::info;
150            part Foo(r: Scalar) {
151                info("Hello, world, {r}!");
152            }
153            Foo(20.0);
154            "#,
155    )
156    .expect("test error");
157
158    assert_eq!(source_file.statements.len(), 3);
159}