Skip to main content

microcad_lang/lower/lower/
source.rs

1// Copyright © 2024-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4use crate::lower::{Lower, LowerContext, LowerError, LowerErrorsWithSource, ir};
5
6use microcad_lang_base::{Hashed, SrcReferrer, Url, virtual_url};
7use microcad_lang_parse::ast;
8
9impl ir::Source {
10    pub fn from_source(source: &ast::Source) -> Result<std::rc::Rc<Self>, LowerError> {
11        let context = LowerContext::new(source.code.as_str()).with_line_offset(source.line_offset);
12        Ok(std::rc::Rc::new(Self {
13            doc: None,
14            statements: ir::StatementList::lower(&source.ast.statements, &context)?,
15            source: source.code.clone(),
16            name: ir::QualifiedName::default(),
17            url: source.url.clone(),
18            line_offset: source.line_offset,
19        }))
20    }
21
22    /// Load µcad source file from given `path`
23    pub fn load(
24        path: impl AsRef<std::path::Path> + std::fmt::Debug,
25    ) -> Result<std::rc::Rc<Self>, LowerErrorsWithSource> {
26        let (source, error) = Self::load_with_name(&path, Self::name_from_path(&path));
27        match error {
28            Some(error) => Err(error),
29            None => Ok(source),
30        }
31    }
32
33    /// Load µcad source file from given `path`
34    pub fn load_with_name(
35        path: impl AsRef<std::path::Path> + std::fmt::Debug,
36        name: ir::QualifiedName,
37    ) -> (std::rc::Rc<Self>, Option<LowerErrorsWithSource>) {
38        let path = path.as_ref();
39        log::trace!(
40            "{load} file {path} [{name}]",
41            path = path.display(),
42            load = microcad_lang_base::mark!(LOAD)
43        );
44
45        let buf = match std::fs::read_to_string(path) {
46            Ok(buf) => buf,
47            Err(error) => {
48                let error = LowerError::LoadSource(name.src_ref(), path.into(), error);
49                let mut source_file = ir::Source::new(
50                    None,
51                    ir::StatementList::default(),
52                    Hashed::new(String::new()),
53                    Url::from_file_path(path).unwrap_or(virtual_url("invalid")),
54                );
55                source_file.name = name;
56                return (std::rc::Rc::new(source_file), Some(error.into()));
57            }
58        };
59
60        let (mut source_file, errors) = Self::load_inner(None, path, &buf, 0);
61        source_file.name = name;
62        log::debug!(
63            "Successfully loaded external file {} to {}",
64            path.display(),
65            source_file.name
66        );
67
68        (std::rc::Rc::new(source_file), errors)
69    }
70
71    /// Create `SourceFile` from string
72    /// The hash of the result will be of `crate::from_str!()`.
73    pub fn load_from_str(
74        name: Option<&str>,
75        path: impl AsRef<std::path::Path>,
76        source_str: &str,
77    ) -> Result<std::rc::Rc<Self>, Vec<LowerError>> {
78        let (source, error) = Self::load_inner(name, path, source_str, 0);
79        match error {
80            Some(error) => Err(error.errors),
81            None => Ok(std::rc::Rc::new(source)),
82        }
83    }
84
85    fn load_inner(
86        name: Option<&str>,
87        path: impl AsRef<std::path::Path>,
88        source_str: &str,
89        line_offset: u32,
90    ) -> (Self, Option<LowerErrorsWithSource>) {
91        log::trace!(
92            "{load} source from string",
93            load = microcad_lang_base::mark!(LOAD)
94        );
95        let lower_context = LowerContext::new(source_str).with_line_offset(line_offset);
96
97        let dummy_source = || {
98            ir::Source::new(
99                None,
100                ir::StatementList::default(),
101                Hashed::new(source_str.into()),
102                Url::from_file_path(&path).unwrap_or(virtual_url("dummy")),
103            )
104            .with_line_offset(line_offset)
105        };
106
107        let ast = match crate::lower::lower::build_ast(source_str, &lower_context) {
108            Ok(ast) => ast,
109            Err(error) => {
110                return (dummy_source(), Some(error));
111            }
112        };
113
114        let mut source_file = match Self::lower(&ast, &lower_context).map_err(|error| vec![error]) {
115            Ok(source_file) => source_file,
116            Err(errors) => {
117                return (
118                    dummy_source(),
119                    Some(LowerErrorsWithSource {
120                        errors,
121                        source_code: Some(Hashed::new(source_str.into())),
122                    }),
123                );
124            }
125        };
126        if let Some(name) = name {
127            source_file.set_name(ir::Identifier::no_ref(name).into());
128        } else {
129            source_file.set_name(Self::name_from_path(&path));
130        };
131        source_file.set_filename(path);
132        log::debug!("Successfully loaded source from string");
133        (source_file.with_line_offset(line_offset), None)
134    }
135
136    /// Get the source file name from path.
137    pub fn name_from_path(path: impl AsRef<std::path::Path>) -> ir::QualifiedName {
138        ir::Identifier::no_ref(
139            &path
140                .as_ref()
141                .file_stem()
142                .expect("illegal file name")
143                .to_string_lossy(),
144        )
145        .into()
146    }
147}
148
149impl Lower for ir::Source {
150    type AstNode = ast::Program;
151
152    fn lower(node: &Self::AstNode, context: &LowerContext) -> Result<Self, LowerError> {
153        Ok(ir::Source::new(
154            None, // todo
155            ir::StatementList::lower(&node.statements, context)?,
156            Hashed::new(context.source.to_string()),
157            microcad_lang_base::virtual_url("name"),
158        ))
159    }
160}