1use std::collections::HashSet;
4use std::path::PathBuf;
5
6use crate::ast::Program;
7
8pub(crate) type ModulePath = Vec<String>;
10
11pub(crate) fn module_path_to_string(path: &[String]) -> String {
13 path.join("/")
14}
15
16#[derive(Debug)]
18pub struct LoadedModule {
19 pub path: ModulePath,
21 pub source_file: PathBuf,
23 pub exports: HashSet<String>,
25 pub function_exports: HashSet<String>,
27 pub program: Program,
29}
30
31impl LoadedModule {
32 pub fn new(path: ModulePath, source_file: PathBuf, program: Program) -> Self {
34 Self {
35 path,
36 source_file,
37 exports: HashSet::new(),
38 function_exports: HashSet::new(),
39 program,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
46#[non_exhaustive]
47pub enum ModuleError {
48 NotFound {
50 path: ModulePath,
52 searched: Vec<PathBuf>,
54 },
55 CircularImport {
57 cycle: Vec<ModulePath>,
59 },
60 ImportConflict {
62 name: String,
64 module1: ModulePath,
66 module2: ModulePath,
68 },
69 PrivatePredicate {
71 name: String,
73 module: ModulePath,
75 },
76 PredicateNotFound {
78 name: String,
80 module: ModulePath,
82 },
83 ParseError {
85 path: PathBuf,
87 message: String,
89 },
90}
91
92impl std::fmt::Display for ModuleError {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 ModuleError::NotFound { path, searched } => {
96 writeln!(
97 f,
98 "error[E0400]: module not found: `{}`",
99 module_path_to_string(path)
100 )?;
101 writeln!(f, " = note: searched in:")?;
102 for s in searched {
103 writeln!(f, " - {}", s.display())?;
104 }
105 write!(
106 f,
107 " = help: check the module path spelling or add to --module-path"
108 )
109 }
110 ModuleError::CircularImport { cycle } => {
111 writeln!(f, "error[E0401]: circular import detected")?;
112 for (i, path) in cycle.iter().enumerate() {
113 if i < cycle.len() - 1 {
114 writeln!(
115 f,
116 " {} imports {}",
117 module_path_to_string(path),
118 module_path_to_string(&cycle[i + 1])
119 )?;
120 }
121 }
122 write!(f, " = help: extract shared predicates into a third module")
123 }
124 ModuleError::ImportConflict {
125 name,
126 module1,
127 module2,
128 } => {
129 writeln!(f, "error[E0402]: ambiguous import `{}`", name)?;
130 writeln!(
131 f,
132 " `{}` first imported from {}",
133 name,
134 module_path_to_string(module1)
135 )?;
136 writeln!(
137 f,
138 " `{}` also exported by {}",
139 name,
140 module_path_to_string(module2)
141 )?;
142 write!(
143 f,
144 " = help: use selective imports: `use {}::{{...}}.`",
145 module_path_to_string(module1)
146 )
147 }
148 ModuleError::PrivatePredicate { name, module } => {
149 write!(
150 f,
151 "error[E0403]: cannot import private predicate `{}` from {}",
152 name,
153 module_path_to_string(module)
154 )
155 }
156 ModuleError::PredicateNotFound { name, module } => {
157 write!(
158 f,
159 "error[E0404]: predicate `{}` not found in module {}",
160 name,
161 module_path_to_string(module)
162 )
163 }
164 ModuleError::ParseError { path, message } => {
165 write!(f, "error: parse error in {:?}: {}", path, message)
166 }
167 }
168 }
169}
170
171impl std::error::Error for ModuleError {}
172
173impl From<ModuleError> for xlog_core::XlogError {
174 fn from(e: ModuleError) -> Self {
175 xlog_core::XlogError::Compilation(e.to_string())
176 }
177}
178
179#[allow(dead_code)] pub(crate) fn internal_name(module_path: &[String], predicate: &str) -> String {
183 if module_path.is_empty() {
184 predicate.to_string()
185 } else {
186 format!("__{}__{}", module_path.join("_"), predicate)
187 }
188}
189
190#[allow(dead_code)] pub(crate) fn parse_internal_name(internal: &str) -> (Vec<String>, String) {
194 if internal.starts_with("__") {
195 if let Some(pos) = internal.rfind("__") {
196 if pos > 2 {
197 let module_part = &internal[2..pos];
198 let pred_part = &internal[pos + 2..];
199 let modules: Vec<String> = module_part.split('_').map(String::from).collect();
200 return (modules, pred_part.to_string());
201 }
202 }
203 }
204 (vec![], internal.to_string())
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn test_module_path_to_string() {
213 assert_eq!(
214 module_path_to_string(&["utils".into(), "math".into()]),
215 "utils/math"
216 );
217 assert_eq!(module_path_to_string(&["single".into()]), "single");
218 }
219
220 #[test]
221 fn test_loaded_module_new() {
222 let module = LoadedModule::new(
223 vec!["test".to_string()],
224 PathBuf::from("/test.xlog"),
225 Program::default(),
226 );
227 assert_eq!(module.path, vec!["test"]);
228 assert!(module.exports.is_empty());
229 }
230
231 #[test]
232 fn test_module_error_display() {
233 let err = ModuleError::NotFound {
234 path: vec!["missing".to_string()],
235 searched: vec![PathBuf::from("/a/missing.xlog")],
236 };
237 let msg = err.to_string();
238 assert!(msg.contains("module not found"));
239 assert!(msg.contains("missing"));
240 }
241
242 #[test]
243 fn test_internal_name() {
244 assert_eq!(internal_name(&[], "foo"), "foo");
245 assert_eq!(
246 internal_name(&["utils".into(), "math".into()], "abs"),
247 "__utils_math__abs"
248 );
249 assert_eq!(internal_name(&["single".into()], "pred"), "__single__pred");
250 }
251
252 #[test]
253 fn test_parse_internal_name() {
254 assert_eq!(parse_internal_name("foo"), (vec![], "foo".to_string()));
255 assert_eq!(
256 parse_internal_name("__utils_math__abs"),
257 (
258 vec!["utils".to_string(), "math".to_string()],
259 "abs".to_string()
260 )
261 );
262 assert_eq!(
263 parse_internal_name("__single__pred"),
264 (vec!["single".to_string()], "pred".to_string())
265 );
266 }
267
268 #[test]
269 fn test_module_error_into_xlog() {
270 let err = ModuleError::ParseError {
271 path: std::path::PathBuf::from("/test.xlog"),
272 message: "unexpected EOF".to_string(),
273 };
274 let xlog_err: xlog_core::XlogError = err.into();
275 let msg = xlog_err.to_string();
276 assert!(
277 msg.contains("unexpected EOF"),
278 "Expected 'unexpected EOF' in: {msg}"
279 );
280 }
281}