264 lines
8.4 KiB
Rust
264 lines
8.4 KiB
Rust
use crate::common::diagnostics::DiagnosticBundle;
|
|
use crate::common::files::FileManager;
|
|
use crate::frontends::pbs::{collector::SymbolCollector, parser::Parser, Symbol, Visibility};
|
|
use crate::common::spans::FileId;
|
|
use crate::manifest::{load_manifest, ManifestKind};
|
|
use prometeu_analysis::NameInterner;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct ProjectSources {
|
|
pub main: Option<PathBuf>,
|
|
pub files: Vec<PathBuf>,
|
|
pub test_files: Vec<PathBuf>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum SourceError {
|
|
Io(std::io::Error),
|
|
Manifest(crate::manifest::ManifestError),
|
|
MissingMain(PathBuf),
|
|
Diagnostics(DiagnosticBundle),
|
|
}
|
|
|
|
impl std::fmt::Display for SourceError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
SourceError::Io(e) => write!(f, "IO error: {}", e),
|
|
SourceError::Manifest(e) => write!(f, "Manifest error: {}", e),
|
|
SourceError::MissingMain(path) => write!(f, "Missing entry point: {}", path.display()),
|
|
SourceError::Diagnostics(d) => write!(f, "Source diagnostics: {:?}", d),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for SourceError {}
|
|
|
|
impl From<std::io::Error> for SourceError {
|
|
fn from(e: std::io::Error) -> Self {
|
|
SourceError::Io(e)
|
|
}
|
|
}
|
|
|
|
impl From<crate::manifest::ManifestError> for SourceError {
|
|
fn from(e: crate::manifest::ManifestError) -> Self {
|
|
SourceError::Manifest(e)
|
|
}
|
|
}
|
|
|
|
impl From<DiagnosticBundle> for SourceError {
|
|
fn from(d: DiagnosticBundle) -> Self {
|
|
SourceError::Diagnostics(d)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ExportTable {
|
|
pub symbols: HashMap<String, Symbol>,
|
|
}
|
|
|
|
pub fn discover(project_dir: &Path) -> Result<ProjectSources, SourceError> {
|
|
let project_dir = project_dir.canonicalize()?;
|
|
let manifest = load_manifest(&project_dir)?;
|
|
|
|
let main_modules_dir = project_dir.join("src/main/modules");
|
|
let test_modules_dir = project_dir.join("src/test/modules");
|
|
|
|
let mut production_files = Vec::new();
|
|
if main_modules_dir.exists() && main_modules_dir.is_dir() {
|
|
discover_recursive(&main_modules_dir, &mut production_files)?;
|
|
}
|
|
|
|
let mut test_files = Vec::new();
|
|
if test_modules_dir.exists() && test_modules_dir.is_dir() {
|
|
discover_recursive(&test_modules_dir, &mut test_files)?;
|
|
}
|
|
|
|
// Sort files for determinism
|
|
production_files.sort();
|
|
test_files.sort();
|
|
|
|
// Recommended main: src/main/modules/main.pbs
|
|
let main_path = main_modules_dir.join("main.pbs");
|
|
let has_main = production_files.iter().any(|p| p == &main_path);
|
|
|
|
let main = if has_main {
|
|
Some(main_path)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if manifest.kind == ManifestKind::App && main.is_none() {
|
|
return Err(SourceError::MissingMain(main_modules_dir.join("main.pbs")));
|
|
}
|
|
|
|
Ok(ProjectSources {
|
|
main,
|
|
files: production_files,
|
|
test_files,
|
|
})
|
|
}
|
|
|
|
fn discover_recursive(dir: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
|
|
for entry in fs::read_dir(dir)? {
|
|
let entry = entry?;
|
|
let path = entry.path();
|
|
if path.is_dir() {
|
|
discover_recursive(&path, files)?;
|
|
} else if let Some(ext) = path.extension() {
|
|
if ext == "pbs" {
|
|
files.push(path);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn build_exports(module_dir: &Path, file_manager: &mut FileManager) -> Result<ExportTable, SourceError> {
|
|
let mut symbols = HashMap::new();
|
|
let mut files = Vec::new();
|
|
let mut interner = NameInterner::new();
|
|
|
|
if module_dir.is_dir() {
|
|
discover_recursive(module_dir, &mut files)?;
|
|
} else if module_dir.extension().map_or(false, |ext| ext == "pbs") {
|
|
files.push(module_dir.to_path_buf());
|
|
}
|
|
|
|
for file_path in files {
|
|
let source = fs::read_to_string(&file_path)?;
|
|
let file_id = file_manager.add(file_path.clone(), source.clone());
|
|
|
|
let mut parser = Parser::new(&source, FileId(file_id as u32), &mut interner);
|
|
let parsed = parser.parse_file()?;
|
|
|
|
let mut collector = SymbolCollector::new(&interner);
|
|
let (type_symbols, value_symbols) =
|
|
collector.collect(&parsed.arena, parsed.root)?;
|
|
|
|
// Merge only public symbols
|
|
for symbol in type_symbols.symbols.into_values() {
|
|
if symbol.visibility == Visibility::Pub {
|
|
symbols.insert(interner.resolve(symbol.name).to_string(), symbol);
|
|
}
|
|
}
|
|
for symbol in value_symbols.symbols.into_values() {
|
|
if symbol.visibility == Visibility::Pub {
|
|
symbols.insert(interner.resolve(symbol.name).to_string(), symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(ExportTable { symbols })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::fs;
|
|
use tempfile::tempdir;
|
|
|
|
#[test]
|
|
fn test_discover_app_with_main() {
|
|
let dir = tempdir().unwrap();
|
|
let project_dir = dir.path().canonicalize().unwrap();
|
|
|
|
fs::write(project_dir.join("prometeu.json"), r#"{
|
|
"name": "app",
|
|
"version": "0.1.0",
|
|
"kind": "app"
|
|
}"#).unwrap();
|
|
|
|
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
|
let main_pbs = project_dir.join("src/main/modules/main.pbs");
|
|
fs::write(&main_pbs, "").unwrap();
|
|
|
|
let other_pbs = project_dir.join("src/main/modules/other.pbs");
|
|
fs::write(&other_pbs, "").unwrap();
|
|
|
|
let sources = discover(&project_dir).unwrap();
|
|
assert_eq!(sources.main, Some(main_pbs));
|
|
assert_eq!(sources.files.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_discover_app_missing_main() {
|
|
let dir = tempdir().unwrap();
|
|
let project_dir = dir.path().canonicalize().unwrap();
|
|
|
|
fs::write(project_dir.join("prometeu.json"), r#"{
|
|
"name": "app",
|
|
"version": "0.1.0",
|
|
"kind": "app"
|
|
}"#).unwrap();
|
|
|
|
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
|
fs::write(project_dir.join("src/main/modules/not_main.pbs"), "").unwrap();
|
|
|
|
let result = discover(&project_dir);
|
|
assert!(matches!(result, Err(SourceError::MissingMain(_))));
|
|
}
|
|
|
|
#[test]
|
|
fn test_discover_lib_without_main() {
|
|
let dir = tempdir().unwrap();
|
|
let project_dir = dir.path().canonicalize().unwrap();
|
|
|
|
fs::write(project_dir.join("prometeu.json"), r#"{
|
|
"name": "lib",
|
|
"version": "0.1.0",
|
|
"kind": "lib"
|
|
}"#).unwrap();
|
|
|
|
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
|
let lib_pbs = project_dir.join("src/main/modules/lib.pbs");
|
|
fs::write(&lib_pbs, "").unwrap();
|
|
|
|
let sources = discover(&project_dir).unwrap();
|
|
assert_eq!(sources.main, None);
|
|
assert_eq!(sources.files, vec![lib_pbs]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_discover_recursive() {
|
|
let dir = tempdir().unwrap();
|
|
let project_dir = dir.path().canonicalize().unwrap();
|
|
|
|
fs::write(project_dir.join("prometeu.json"), r#"{
|
|
"name": "lib",
|
|
"version": "0.1.0",
|
|
"kind": "lib"
|
|
}"#).unwrap();
|
|
|
|
fs::create_dir_all(project_dir.join("src/main/modules/utils")).unwrap();
|
|
let main_pbs = project_dir.join("src/main/modules/main.pbs");
|
|
let util_pbs = project_dir.join("src/main/modules/utils/util.pbs");
|
|
fs::write(&main_pbs, "").unwrap();
|
|
fs::write(&util_pbs, "").unwrap();
|
|
|
|
let sources = discover(&project_dir).unwrap();
|
|
assert_eq!(sources.files.len(), 2);
|
|
assert!(sources.files.contains(&main_pbs));
|
|
assert!(sources.files.contains(&util_pbs));
|
|
}
|
|
|
|
#[test]
|
|
fn test_build_exports() {
|
|
let dir = tempdir().unwrap();
|
|
let module_dir = dir.path().join("math");
|
|
fs::create_dir_all(&module_dir).unwrap();
|
|
|
|
fs::write(module_dir.join("Vector.pbs"), "pub declare struct Vector {}").unwrap();
|
|
fs::write(module_dir.join("Internal.pbs"), "declare struct Hidden {}").unwrap();
|
|
|
|
let mut fm = FileManager::new();
|
|
let exports = build_exports(&module_dir, &mut fm).unwrap();
|
|
|
|
assert!(exports.symbols.contains_key("Vector"));
|
|
assert!(!exports.symbols.contains_key("Hidden"));
|
|
}
|
|
}
|