1use std::path::{Path, PathBuf};
2use std::sync::{Arc, OnceLock};
3
4use itertools::Itertools;
5
6use crate::diagnostic::{Diagnostic, WithErrorInfo};
7use crate::error::Error;
8use crate::pr;
9use crate::project::SourceOverlay;
10use crate::project::SourceProvider;
11use crate::project::{self, Dependency};
12use crate::resolver::NS_STD;
13use crate::{SourceTree, diagnostic};
14use crate::{Span, error};
15
16#[cfg_attr(feature = "clap", derive(clap::Parser))]
17#[derive(Default, Clone)]
18pub struct CheckParams {
19 #[cfg_attr(feature = "clap", arg(skip))]
20 pub dependencies: Vec<Dependency>,
21}
22
23pub fn check(
24 mut source: project::SourceTree,
25 params: CheckParams,
26) -> Result<project::Project, error::Error> {
27 if source.is_empty() {
29 source.insert(PathBuf::from(""), "".into());
30 }
31 let root_mod = parse(&source)?.map_err(|e| Error::from_diagnostics(e, &source))?;
32
33 let metadata = root_mod.get_anno_at(&pr::Path::empty(), pr::Anno::as_std_metadata);
35 let is_std = metadata.is_some_and(|m| m == NS_STD);
36 let mut dependencies = params.dependencies;
37 if !is_std {
38 dependencies.push(Dependency {
39 name: NS_STD.into(),
40 inner: std_project()?,
41 });
42 }
43 let mut project = crate::resolver::resolve(root_mod, dependencies, is_std)
44 .map_err(|e| Error::from_diagnostics(e, &source))?;
45 project.source = source;
46 Ok(project)
47}
48
49pub fn check_overlay(
50 project: &project::Project,
51 overlay: &str,
52 overlay_name: Option<&str>,
53) -> Result<pr::Expr, error::Error> {
54 let source = crate::project::SourceOverlay::new(&project.source, overlay, overlay_name);
55
56 parse_overlay(&source)
57 .and_then(|expr| crate::resolver::resolve_overlay_expr(&project.root_module, expr))
58 .map_err(|e| Error::from_diagnostics(e, &source))
59}
60
61fn parse(tree: &SourceTree) -> Result<Result<pr::ModuleDef, Vec<Diagnostic>>, error::Error> {
62 let mut root = pr::ModuleDef::default();
64
65 let mut diags = Vec::new();
67 for source_id in tree.get_ids() {
68 let (path, content) = tree.get_by_id(source_id).unwrap();
69
70 let module_path = os_path_to_mod_path(path)?;
71
72 let (parsed, errs, _) = crate::parser::parse_source(content, source_id);
73 diags.extend(errs);
74 if let Some(parsed) = parsed {
75 if module_path.is_empty() && parsed.is_submodule {
78 diags.push(
79 Diagnostic::new_custom("cannot load the project root")
80 .with_span(Some(Span {
81 start: 0,
82 len: 1,
83 source_id,
84 }))
85 .push_hint(format!("file {} is a submodule", path.display())),
86 );
87 }
88 let included = module_path.is_empty() || parsed.is_submodule;
89 if included {
90 diags.extend(insert_module_at_path(&mut root, module_path, parsed.root));
91 }
92 }
93 }
94 Ok(if diags.is_empty() {
95 Ok(root)
96 } else {
97 Err(diags)
98 })
99}
100
101fn parse_overlay(overlay: &SourceOverlay) -> Result<pr::Expr, Vec<Diagnostic>> {
102 let id = SourceOverlay::overlay_id();
103 let (_path, content) = overlay.get_by_id(id).unwrap();
104 let (ast, diagnostics) = crate::parser::parse_expr(content, id);
105 if diagnostics.is_empty() {
106 Ok(ast.unwrap())
107 } else {
108 Err(diagnostics)
109 }
110}
111
112pub fn insert_module_at_path(
113 module: &mut pr::ModuleDef,
114 mut path: Vec<String>,
115 to_insert: pr::ModuleDef,
116) -> Vec<diagnostic::Diagnostic> {
117 if path.is_empty() {
118 let mut d = Vec::new();
119
120 module.annotations.extend(to_insert.annotations);
121 module.span_content = to_insert.span_content;
122 module.imports.extend(to_insert.imports);
123 for (name, def) in to_insert.defs {
124 let conflict = module.defs.insert(name, def);
125 if let Some(conflict) = conflict {
126 d.push(
127 diagnostic::Diagnostic::new_custom("duplicate name").with_span(conflict.span),
128 );
129 }
130 }
131 return d;
132 }
133
134 let step = path.remove(0);
135
136 let submodule = module
138 .defs
139 .entry(step)
140 .or_insert_with(|| pr::Def::new(pr::ModuleDef::default()));
141 let pr::DefKind::Module(submodule) = &mut submodule.kind else {
142 return vec![
143 diagnostic::Diagnostic::new_custom("duplicate name").with_span(submodule.span),
144 ];
145 };
146 insert_module_at_path(submodule, path, to_insert)
147}
148
149fn os_path_to_mod_path(path: &Path) -> Result<Vec<String>, error::Error> {
150 let path = if path.ends_with("module.lt") {
151 path.parent().unwrap().to_path_buf()
153 } else {
154 path.with_extension("")
156 };
157
158 path.components()
160 .map(|x| {
161 x.as_os_str()
162 .to_str()
163 .map(str::to_string)
164 .ok_or_else(|| error::Error::InvalidPath { path: path.clone() })
165 })
166 .try_collect()
167}
168
169pub fn std_source() -> SourceTree {
170 SourceTree::single(
171 std::path::PathBuf::new(),
172 include_str!("std.lt").to_string(),
173 )
174}
175
176static STD_PROJECT: OnceLock<Arc<crate::Project>> = OnceLock::new();
178
179fn std_project() -> Result<Arc<crate::Project>, error::Error> {
180 if let Some(cached) = STD_PROJECT.get() {
181 return Ok(Arc::clone(cached));
182 }
183
184 let project = Arc::new(check(std_source(), CheckParams::new())?);
185 let _ = STD_PROJECT.set(project);
186 Ok(Arc::clone(STD_PROJECT.get().unwrap()))
187}
188
189impl CheckParams {
190 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn with_dep(mut self, name: impl Into<String>, project: Arc<crate::Project>) -> Self {
195 self.dependencies.push(crate::project::Dependency {
196 name: name.into(),
197 inner: project,
198 });
199 self
200 }
201}