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 let mut root = pr::ModuleDef {
54 defs: Default::default(),
55 };
56
57 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 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 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 path.parent().unwrap().to_path_buf()
142 } else {
143 path.with_extension("")
145 };
146
147 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}