Skip to main content

lutra_compiler/
check.rs

1use std::path::{Path, PathBuf};
2
3use itertools::Itertools;
4
5use crate::diagnostic::{Diagnostic, WithErrorInfo};
6use crate::error::Error;
7use crate::pr;
8use crate::project::SourceOverlay;
9use crate::project::SourceProvider;
10use crate::project::{self, Dependency};
11use crate::resolver::NS_STD;
12use crate::{SourceTree, diagnostic};
13use crate::{Span, error};
14
15#[cfg_attr(feature = "clap", derive(clap::Parser))]
16#[derive(Default)]
17pub struct CheckParams {}
18
19pub fn check(
20    mut source: project::SourceTree,
21    _: CheckParams,
22) -> Result<project::Project, error::Error> {
23    if source.is_empty() {
24        source.insert(PathBuf::from(""), "".into());
25    }
26
27    let std_lib = Dependency {
28        name: NS_STD.into(),
29        inner: compile_std_lib()?,
30    };
31
32    let mut project = parse(&source)?
33        .and_then(|ast| crate::resolver::resolve(ast, vec![std_lib]))
34        .map_err(|e| Error::from_diagnostics(e, &source))?;
35    project.source = source;
36    Ok(project)
37}
38
39pub fn check_overlay(
40    project: &project::Project,
41    overlay: &str,
42    overlay_name: Option<&str>,
43) -> Result<pr::Expr, error::Error> {
44    let source = crate::project::SourceOverlay::new(&project.source, overlay, overlay_name);
45
46    parse_overlay(&source)
47        .and_then(|expr| crate::resolver::resolve_overlay_expr(&project.root_module, expr))
48        .map_err(|e| Error::from_diagnostics(e, &source))
49}
50
51fn parse(tree: &SourceTree) -> Result<Result<pr::ModuleDef, Vec<Diagnostic>>, error::Error> {
52    // init the root module def
53    let mut root = pr::ModuleDef {
54        defs: Default::default(),
55    };
56
57    // parse and insert into the root
58    let mut diags = Vec::new();
59    for source_id in tree.get_ids() {
60        let (path, content) = tree.get_by_id(source_id).unwrap();
61
62        let module_path = os_path_to_mod_path(path)?;
63
64        let (parsed, errs, _) = crate::parser::parse_source(content, source_id);
65        diags.extend(errs);
66        if let Some(parsed) = parsed {
67            // TODO: improve these error messages
68
69            if module_path.is_empty() && parsed.is_submodule {
70                diags.push(
71                    Diagnostic::new_custom("cannot load the project root")
72                        .with_span(Some(Span {
73                            start: 0,
74                            len: 1,
75                            source_id,
76                        }))
77                        .push_hint(format!("file {} is a submodule", path.display())),
78                );
79            }
80            let included = module_path.is_empty() || parsed.is_submodule;
81            if included {
82                diags.extend(insert_module_at_path(&mut root, module_path, parsed.root));
83            }
84        }
85    }
86    Ok(if diags.is_empty() {
87        Ok(root)
88    } else {
89        Err(diags)
90    })
91}
92
93fn parse_overlay(overlay: &SourceOverlay) -> Result<pr::Expr, Vec<Diagnostic>> {
94    let id = SourceOverlay::overlay_id();
95    let (_path, content) = overlay.get_by_id(id).unwrap();
96    let (ast, diagnostics) = crate::parser::parse_expr(content, id);
97    if diagnostics.is_empty() {
98        Ok(ast.unwrap())
99    } else {
100        Err(diagnostics)
101    }
102}
103
104pub fn insert_module_at_path(
105    module: &mut pr::ModuleDef,
106    mut path: Vec<String>,
107    to_insert: pr::ModuleDef,
108) -> Vec<diagnostic::Diagnostic> {
109    if path.is_empty() {
110        let mut d = Vec::new();
111        for (name, def) in to_insert.defs {
112            let conflict = module.defs.insert(name, def);
113            if let Some(conflict) = conflict {
114                d.push(
115                    diagnostic::Diagnostic::new_custom("duplicate name").with_span(conflict.span),
116                );
117            }
118        }
119        return d;
120    }
121
122    let step = path.remove(0);
123
124    // find submodule def
125    let submodule = module.defs.entry(step).or_insert_with(|| {
126        pr::Def::new(pr::DefKind::Module(pr::ModuleDef {
127            defs: Default::default(),
128        }))
129    });
130    let pr::DefKind::Module(submodule) = &mut submodule.kind else {
131        return vec![
132            diagnostic::Diagnostic::new_custom("duplicate name").with_span(submodule.span),
133        ];
134    };
135    insert_module_at_path(submodule, path, to_insert)
136}
137
138fn os_path_to_mod_path(path: &Path) -> Result<Vec<String>, error::Error> {
139    let path = if path.ends_with("module.lt") {
140        // remove module.lt suffix
141        path.parent().unwrap().to_path_buf()
142    } else {
143        // remove file format extension
144        path.with_extension("")
145    };
146
147    // split by /
148    path.components()
149        .map(|x| {
150            x.as_os_str()
151                .to_str()
152                .map(str::to_string)
153                .ok_or_else(|| error::Error::InvalidPath { path: path.clone() })
154        })
155        .try_collect()
156}
157
158fn compile_std_lib() -> Result<crate::Project, error::Error> {
159    let source = SourceTree::single(
160        std::path::PathBuf::new(),
161        include_str!("std.lt").to_string(),
162    );
163
164    let mut project = parse(&source)?
165        .and_then(|root| crate::resolver::resolve(root, vec![]))
166        .map_err(|e| Error::from_diagnostics(e, &source))?;
167    project.source = source;
168    Ok(project)
169}