Skip to main content

seqc/resolver/
helpers.rs

1//! Free-standing helpers used alongside the `Resolver`: cross-file
2//! collision detection for word and union names, plus the stdlib
3//! discovery routine.
4
5use std::collections::HashMap;
6use std::path::PathBuf;
7
8use crate::ast::{SourceLocation, UnionDef, WordDef};
9
10pub fn check_collisions(words: &[WordDef]) -> Result<(), String> {
11    let mut definitions: HashMap<&str, Vec<&SourceLocation>> = HashMap::new();
12
13    for word in words {
14        if let Some(ref source) = word.source {
15            definitions.entry(&word.name).or_default().push(source);
16        }
17    }
18
19    // Find collisions (words defined in multiple places)
20    let mut errors = Vec::new();
21    for (name, locations) in definitions {
22        if locations.len() > 1 {
23            let mut msg = format!("Word '{}' is defined multiple times:\n", name);
24            for loc in &locations {
25                msg.push_str(&format!("  - {}\n", loc));
26            }
27            msg.push_str("\nHint: Rename one of the definitions to avoid collision.");
28            errors.push(msg);
29        }
30    }
31
32    if errors.is_empty() {
33        Ok(())
34    } else {
35        Err(errors.join("\n\n"))
36    }
37}
38
39/// Check for union name collisions across all definitions
40///
41/// Returns an error with helpful message if any union is defined multiple times.
42pub fn check_union_collisions(unions: &[UnionDef]) -> Result<(), String> {
43    let mut definitions: HashMap<&str, Vec<&SourceLocation>> = HashMap::new();
44
45    for union_def in unions {
46        if let Some(ref source) = union_def.source {
47            definitions.entry(&union_def.name).or_default().push(source);
48        }
49    }
50
51    // Find collisions (unions defined in multiple places)
52    let mut errors = Vec::new();
53    for (name, locations) in definitions {
54        if locations.len() > 1 {
55            let mut msg = format!("Union '{}' is defined multiple times:\n", name);
56            for loc in &locations {
57                msg.push_str(&format!("  - {}\n", loc));
58            }
59            msg.push_str("\nHint: Rename one of the definitions to avoid collision.");
60            errors.push(msg);
61        }
62    }
63
64    if errors.is_empty() {
65        Ok(())
66    } else {
67        Err(errors.join("\n\n"))
68    }
69}
70
71/// Find the stdlib directory for filesystem fallback
72///
73/// Searches in order:
74/// 1. SEQ_STDLIB environment variable
75/// 2. Relative to the current executable (for installed compilers)
76/// 3. Relative to current directory (for development)
77///
78/// Returns None if no stdlib directory is found (embedded stdlib will be used).
79pub fn find_stdlib() -> Option<PathBuf> {
80    // Check environment variable first
81    if let Ok(path) = std::env::var("SEQ_STDLIB") {
82        let path = PathBuf::from(path);
83        if path.is_dir() {
84            return Some(path);
85        }
86        // If SEQ_STDLIB is set but invalid, log warning but continue
87        eprintln!(
88            "Warning: SEQ_STDLIB is set to '{}' but that directory doesn't exist",
89            path.display()
90        );
91    }
92
93    // Check relative to executable
94    if let Ok(exe_path) = std::env::current_exe()
95        && let Some(exe_dir) = exe_path.parent()
96    {
97        let stdlib_path = exe_dir.join("stdlib");
98        if stdlib_path.is_dir() {
99            return Some(stdlib_path);
100        }
101        // Also check one level up (for development builds)
102        if let Some(parent) = exe_dir.parent() {
103            let stdlib_path = parent.join("stdlib");
104            if stdlib_path.is_dir() {
105                return Some(stdlib_path);
106            }
107        }
108    }
109
110    // Check relative to current directory (development)
111    let local_stdlib = PathBuf::from("stdlib");
112    if local_stdlib.is_dir() {
113        return Some(local_stdlib.canonicalize().unwrap_or(local_stdlib));
114    }
115
116    // No filesystem stdlib found - that's OK, we have embedded stdlib
117    None
118}