summaryrefslogtreecommitdiff
path: root/src/source_unit.rs
blob: bc6d4ab261c7865aa64558ddab077b25a8c5762f (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
use crate::*;

use log::info;
use vagabond::*;


type ParseFn = fn(&str, Option<&Path>) -> 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();
    if let Ok(entry) = Entry::from_path(path) {
        if EntryType::File == entry.entry_type {
            if let Ok(unit) = SourceUnit::from_path(&entry.path, extension, parse) {
                info!("Found source file at {path:?}");
                source_units.push(unit);
            }
        } 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 {
                    if let Ok(unit) = SourceUnit::from_path(&entry.path, extension, parse) {
                        info!("Found source file at {:?}", entry.path);
                        source_units.push(unit);
                    }
                }
            }
        }
    };
    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 source_code = read_file(path.as_ref())?;
        let symbols = parse(&source_code, Some(path.as_ref()));
        macro_rules! parse_file {
            ($path:expr) => {
                read_file(&$path).ok().map(|source_code| {
                    let symbols = parse(&source_code, Some(&$path));
                    let path = $path;
                    SourceFile { symbols, source_code, path }
                })
            };
        }

        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 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));
        Self { main: SourceFile { path, source_code, symbols }, 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()

    }
}


pub struct SourceFile {
    pub path: PathBuf,
    pub source_code: String,
    pub symbols: 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 &&
        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,
}