1use std::path::{Path, PathBuf};
2
3use crate::ast::TopLevel;
4use crate::lexer::Lexer;
5use crate::parser::Parser;
6
7pub fn parse_source(source: &str) -> Result<Vec<TopLevel>, String> {
8 let mut lexer = Lexer::new(source);
9 let tokens = lexer.tokenize().map_err(|e| e.to_string())?;
10 let mut parser = Parser::new(tokens);
11 parser.parse().map_err(|e| e.to_string())
12}
13
14pub fn require_module_declaration(items: &[TopLevel], file: &str) -> Result<(), String> {
17 let module_positions: Vec<usize> = items
18 .iter()
19 .enumerate()
20 .filter_map(|(idx, item)| matches!(item, TopLevel::Module(_)).then_some(idx))
21 .collect();
22
23 if module_positions.is_empty() {
24 return Err(format!(
25 "File '{}' must declare `module <Name>` as the first top-level item",
26 file
27 ));
28 }
29
30 if module_positions[0] != 0 {
31 return Err(format!(
32 "File '{}' must place `module <Name>` as the first top-level item",
33 file
34 ));
35 }
36
37 if module_positions.len() > 1 {
38 return Err(format!(
39 "File '{}' must contain exactly one module declaration (found {})",
40 file,
41 module_positions.len()
42 ));
43 }
44
45 Ok(())
46}
47
48pub fn find_module_file(name: &str, module_root: &str) -> Option<PathBuf> {
49 let root = Path::new(module_root);
50 let parts: Vec<&str> = name.split('.').filter(|s| !s.is_empty()).collect();
51 if parts.is_empty() {
52 return None;
53 }
54
55 let lower_rel = format!(
56 "{}.av",
57 parts
58 .iter()
59 .map(|p| p.to_lowercase())
60 .collect::<Vec<_>>()
61 .join("/")
62 );
63 let exact_rel = format!("{}.av", parts.join("/"));
64
65 let lower = root.join(&lower_rel);
66 if lower.exists() {
67 return Some(lower);
68 }
69
70 let exact = root.join(&exact_rel);
71 if exact.exists() {
72 return Some(exact);
73 }
74
75 None
76}
77
78pub fn canonicalize_path(path: &Path) -> PathBuf {
79 std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
80}
81
82#[cfg(test)]
83mod tests {
84 use super::{parse_source, require_module_declaration};
85
86 #[test]
87 fn require_module_accepts_single_first_module() {
88 let src = "module Demo\n intent = \"ok\"\nfn x() -> Int\n = 1\n";
89 let items = parse_source(src).expect("parse");
90 require_module_declaration(&items, "demo.av").expect("module declaration should pass");
91 }
92
93 #[test]
94 fn require_module_rejects_missing_module() {
95 let src = "fn x() -> Int\n = 1\n";
96 let items = parse_source(src).expect("parse");
97 let err = require_module_declaration(&items, "demo.av").expect_err("expected error");
98 assert!(err.contains("must declare `module <Name>`"));
99 }
100
101 #[test]
102 fn require_module_rejects_module_not_first() {
103 let src = "fn x() -> Int\n = 1\nmodule Demo\n";
104 let items = parse_source(src).expect("parse");
105 let err = require_module_declaration(&items, "demo.av").expect_err("expected error");
106 assert!(err.contains("must place `module <Name>` as the first"));
107 }
108
109 #[test]
110 fn require_module_rejects_multiple_modules() {
111 let src = "module A\nmodule B\n";
112 let items = parse_source(src).expect("parse");
113 let err = require_module_declaration(&items, "demo.av").expect_err("expected error");
114 assert!(err.contains("exactly one module declaration"));
115 }
116}