1use std::collections::BTreeMap;
2
3pub(crate) fn build_function_line_map(source_text: &str) -> BTreeMap<String, usize> {
5 let mut lines = BTreeMap::<String, usize>::new();
6 for (line_index, line) in source_text.lines().enumerate() {
7 if let Some(name) = parse_function_name(line) {
8 lines.entry(name).or_insert(line_index + 1);
9 }
10 }
11 lines
12}
13
14fn parse_function_name(line: &str) -> Option<String> {
16 let trimmed = line.trim_start();
17 let after_fn = if let Some(rest) = trimmed.strip_prefix("fn ") {
18 rest
19 } else if let Some(rest) = trimmed.strip_prefix("meta fn ") {
20 rest
21 } else {
22 return None;
23 };
24
25 let mut name = String::new();
26 for ch in after_fn.chars() {
27 if ch == '_' || ch.is_ascii_alphanumeric() {
28 name.push(ch);
29 } else {
30 break;
31 }
32 }
33
34 if name.is_empty() { None } else { Some(name) }
35}
36
37#[cfg(test)]
38mod tests {
39 use super::build_function_line_map;
40
41 #[test]
43 fn maps_runtime_and_meta_function_lines() {
44 let source = r#"
45fn main() -> Unit {}
46meta fn expand() -> Expr[i64] { expr { 0 } }
47"#;
48 let lines = build_function_line_map(source);
49 assert_eq!(lines.get("main"), Some(&2));
50 assert_eq!(lines.get("expand"), Some(&3));
51 }
52
53 #[test]
55 fn keeps_first_line_for_duplicate_names() {
56 let source = r#"
57fn helper() -> Unit {}
58fn helper() -> Unit {}
59"#;
60 let lines = build_function_line_map(source);
61 assert_eq!(lines.get("helper"), Some(&2));
62 }
63}