summaryrefslogtreecommitdiff
path: root/src/source_unit.rs
blob: 28cc85495ad10367eaf31ab2eba308a04d3d6711 (plain) (blame)
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
use crate::*;

use log::{info, warn};
use vagabond::*;


type ParseFn = fn(&str, Option<&Path>) -> Option<Vec<Symbol>>;


/// Gather all source units with a given extension using a PATH-style environment variable.
pub fn gather_from_path_variable(variable: &str, extension: Option<&str>, parse: ParseFn) -> Vec<SourceUnit> {
    let mut source_units = Vec::new();
    if let Ok(string) = std::env::var(variable) {
        for path in string.split(":").map(PathBuf::from) {
            info!("Found path {path:?} in environment variable {variable:?}");
            source_units.extend(gather_from_path(&path, extension, parse));
        }
    };
    return source_units;
}

/// Gather source units with a given extension at or descending from a path.
pub fn gather_from_path(path: &Path, extension: Option<&str>, parse: ParseFn) -> Vec<SourceUnit> {
    let mut source_units = Vec::new();
    let check_optional_file = |file: &Option<SourceFile>| -> bool {
        match file {
            Some(file) => match file.symbols {
                Some(_) => { info!("Found source file at {:?}", file.path); true }
                None => { warn!("Could not parse source file at {:?}", file.path); false }
            }
            None => true,
        }
    };
    let mut gather_source_unit = |path: &Path| {
        if let Ok(unit) = SourceUnit::from_path(&path, extension, parse) {
            if unit.main.symbols.is_some() {
                info!("Found source file at {:?}", unit.main.path);
                let head_good = check_optional_file(&unit.head);
                let tail_good = check_optional_file(&unit.tail);
                if head_good && tail_good {
                    source_units.push(unit);
                }
            } else {
                warn!("Could not parse source file at {path:?}");
                check_optional_file(&unit.head);
                check_optional_file(&unit.tail);
            }
        }
    };
    if let Ok(entry) = Entry::from_path(path) {
        if EntryType::File == entry.entry_type {
            gather_source_unit(&entry.path)
        } else if EntryType::Directory == entry.entry_type {
            info!("Traversing directory {path:?} for source files");
            if let Ok(entries) = traverse_directory(entry.path) {
                for entry in entries {
                    gather_source_unit(&entry.path)
                }
            }
        }
    };
    return source_units;
}


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,
}