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
|
use crate::*;
pub struct SourceUnit {
pub main: SourceFile,
pub head: Option<SourceFile>,
pub tail: Option<SourceFile>,
}
impl SourceUnit {
/// Load source from a main file and an associated head and tail file.
pub fn from_path<P: AsRef<Path>>(path: P, extension: Option<&str>, parse: ParseFn) -> Result<Self, FileError> {
let main_path = { path.as_ref().canonicalize().unwrap_or_else(|_| path.as_ref().to_path_buf()) };
let main_path_str = main_path.as_os_str().to_string_lossy().to_string();
// Attempt to extract an extension from main path if no extension was provided.
let extension = extension.or_else(|| main_path.extension().and_then(|ext| ext.to_str()));
let head_extension = extension.map(|ext| format!("head.{ext}"));
let tail_extension = extension.map(|ext| format!("tail.{ext}"));
let is_head = head_extension.as_ref().map_or(false, |ext| main_path_str.ends_with(ext.as_str()));
let is_tail = tail_extension.as_ref().map_or(false, |ext| main_path_str.ends_with(ext.as_str()));
let is_main = extension.map_or(true, |ext| main_path_str.ends_with(ext));
// Head and tail files will be picked up later along with the main file.
if !is_main || is_head || is_tail { return Err(FileError::InvalidExtension); }
let parse_file = |path: PathBuf| {
if let Ok(source_code) = read_file(&path) {
let symbols = parse(&source_code, Some(&path));
return Some(SourceFile { symbols, source_code, path: path });
}
return None;
};
let head = head_extension.map_or(None, |ext| parse_file(main_path.with_extension(&ext)));
let tail = tail_extension.map_or(None, |ext| parse_file(main_path.with_extension(&ext)));
let source_code = read_file(path.as_ref())?;
let symbols = parse(&source_code, Some(path.as_ref()));
let main = SourceFile { path: main_path, source_code, symbols };
Ok(SourceUnit { main, head, tail })
}
/// Load from a string of source code.
pub fn from_string<P: AsRef<Path>>(source_code: String, path: P, parse: ParseFn) -> Self {
let path = { path.as_ref().canonicalize().unwrap_or_else(|_| path.as_ref().to_path_buf()) };
let symbols = parse(&source_code, Some(&path));
let main = SourceFile { path, source_code, symbols };
Self { main, head: None, tail: None }
}
pub fn name(&self) -> Option<String> {
self.main.path.file_name().map(|s| s.to_string_lossy().to_string())
}
pub fn path(&self) -> String {
self.main.path.as_os_str().to_string_lossy().to_string()
}
}
impl PartialEq for SourceUnit {
fn eq(&self, other: &SourceUnit) -> bool {
if let Ok(this_path) = self.main.path.canonicalize() {
if let Ok(other_path) = other.main.path.canonicalize() {
return this_path == other_path;
}
}
return false;
}
}
pub struct SourceFile {
pub path: PathBuf,
pub source_code: String,
pub symbols: Option<Vec<Symbol>>,
}
pub struct Symbol {
pub name: String,
pub namespace: Vec<String>,
pub source: SourceSpan,
pub role: SymbolRole,
}
impl Symbol {
/// True if this symbol is a valid definition for a reference symbol
pub fn defines(&self, reference: &Symbol) -> bool {
self.name == reference.name &&
self.namespace.len() <= reference.namespace.len() &&
std::iter::zip(&self.namespace, &reference.namespace).all(|(a, b)| a == b)
}
}
impl PartialEq for Symbol {
fn eq(&self, other: &Symbol) -> bool {
self.name == other.name && self.namespace == other.namespace
}
}
impl std::fmt::Debug for Symbol {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
for name in &self.namespace {
write!(f, "{name}::")?
}
write!(f, "{}", self.name)
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum SymbolRole {
Definition(DefinitionType),
Reference,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DefinitionType {
MustPrecedeReference,
CanFollowReference,
}
|