1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use std::path::Path;
use std::sync::Arc;
/// Powers error reporting by mapping compiler diagnostics to the specific file.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct SourceFile {
/// The path of the source file (e.g., "./src/main.simf").
name: Option<Arc<Path>>,
/// The actual text content of the source file.
content: Arc<str>,
}
impl From<(&Path, &str)> for SourceFile {
fn from((name, content): (&Path, &str)) -> Self {
Self::new(name, Arc::from(content))
}
}
impl From<CanonSourceFile> for SourceFile {
fn from(canon_source: CanonSourceFile) -> Self {
Self::new(canon_source.name().as_path(), canon_source.content())
}
}
impl SourceFile {
/// Creates a standard `SourceFile` from a file path and its content.
pub fn new(name: &Path, content: Arc<str>) -> Self {
Self {
name: Some(Arc::from(name)),
content,
}
}
/// Creates an anonymous `SourceFile` without a file path (e.g., for a single-file programs)
pub fn anonymous(content: Arc<str>) -> Self {
Self {
name: None,
content,
}
}
pub fn name(&self) -> &Option<Arc<Path>> {
&self.name
}
pub fn content(&self) -> Arc<str> {
self.content.clone()
}
}
/// Caches the canonicalized path of a source file to prevent redundant,
/// expensive, and potentially failing filesystem operations.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct CanonSourceFile {
/// The path of the source file (e.g., "./src/main.simf").
name: CanonPath,
/// The actual text content of the source file.
content: Arc<str>,
}
impl TryFrom<SourceFile> for CanonSourceFile {
type Error = String;
fn try_from(source: SourceFile) -> Result<Self, Self::Error> {
let name = if let Some(root_name) = source.name() {
CanonPath::canonicalize(root_name)?
} else {
return Err(
"Cannot canonicalize the SourceFile because it is missing a file name.".to_string(),
);
};
Ok(CanonSourceFile {
name,
content: source.content(),
})
}
}
impl CanonSourceFile {
pub fn new(name: CanonPath, content: Arc<str>) -> Self {
Self { name, content }
}
pub fn name(&self) -> &CanonPath {
&self.name
}
pub fn str_name(&self) -> String {
self.name.as_path().display().to_string()
}
pub fn content(&self) -> Arc<str> {
self.content.clone()
}
}
/// A guaranteed, fully coanonicalized absolute path.
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct CanonPath(Arc<Path>);
impl CanonPath {
/// Safely resolves an absolute path via the OS and wraps it in a `CanonPath`.
///
/// # Errors
///
/// Returns a `String` containing the OS error if the path does not exist or
/// cannot be accessed. The caller is expected to map this into a more specific
/// compiler diagnostic (e.g., `RichError`).
pub fn canonicalize(path: &Path) -> Result<Self, String> {
// We use `map_err` here to intercept the generic OS error and enrich
// it with the specific path that failed
let canon_path = std::fs::canonicalize(path).map_err(|err| {
format!(
"Failed to find library target path '{}' :{}",
path.display(),
err
)
})?;
Ok(Self(Arc::from(canon_path.as_path())))
}
/// Appends a logical module path to this physical root directory and verifies it.
/// It automatically appends the `.simf` extension to the final path *before* asking
/// the OS to verify its existence.
pub fn join(&self, parts: &[&str]) -> Result<Self, String> {
let mut new_path = self.0.to_path_buf();
for part in parts {
new_path.push(part);
}
Self::canonicalize(&new_path.with_extension("simf"))
}
/// Check if the current file is executing inside the context's directory tree.
/// This prevents a file in `/project_a/` from using a dependency meant for `/project_b/`
pub fn starts_with(&self, path: &CanonPath) -> bool {
self.as_path().starts_with(path.as_path())
}
pub fn as_path(&self) -> &Path {
&self.0
}
#[cfg(test)]
pub fn dummy_for_test(path: &Path) -> Self {
Self(Arc::from(path))
}
}