use crate::*; use vagabond::*; /// Gather all library units from the given path. pub fn gather_project_libraries(path: &Path, extension: &str) -> Vec { match path.parent() { Some(parent_path) => gather_source_units(parent_path, extension), None => Vec::new(), } } /// Gather all library units from the paths specified in an environment variable. pub fn gather_environment_libraries(extension: &str) -> Vec> { let mut environment_libraries = Vec::new(); if let Ok(lib_var) = std::env::var("BEDROCK_LIBS") { for path_str in lib_var.split(":") { let lib_path = PathBuf::from(path_str); let source_units = gather_source_units(&lib_path, extension); if !source_units.is_empty() { environment_libraries.push(source_units); } } }; return environment_libraries; } /// Gather all source units at or descended from the given entry. fn gather_source_units(path: &Path, extension: &str) -> Vec { let mut source_units = Vec::new(); if let Ok(entry) = Entry::from_path(path) { match entry.entry_type { EntryType::File => { if let Ok(source) = SourceUnit::from_path(entry.path, extension) { source_units.push(source); } } EntryType::Directory => { if let Ok(entries) = traverse_directory(entry.path) { for entry in entries { if let Ok(source) = SourceUnit::from_path(entry.path, extension) { source_units.push(source); } } } } } }; return source_units; } pub struct SourceUnit { pub main: SourceFile, pub head: Option, pub tail: Option, } impl SourceUnit { /// Load from a source file and an associated head and tail file. pub fn from_path>(path: P, extension: &str) -> Result { let main_path = canonicalize_path(path); let main_path_str = main_path.as_os_str().to_string_lossy().to_string(); let head_extension = format!("head.{extension}"); let tail_extension = format!("tail.{extension}"); let is_head = main_path_str.ends_with(&head_extension); let is_tail = main_path_str.ends_with(&tail_extension); let is_not_main = !main_path_str.ends_with(extension); if is_not_main || is_head || is_tail { return Err(ParseError::InvalidExtension); } let symbols = parse_symbols_from_file(&main_path)?; let head_path = main_path.with_extension(head_extension); let tail_path = main_path.with_extension(tail_extension); let main = SourceFile { path: main_path, symbols }; let head = match parse_symbols_from_file(&head_path) { Ok(symbols) => Some(SourceFile { path: head_path, symbols }), Err(_) => None, }; let tail = match parse_symbols_from_file(&tail_path) { Ok(symbols) => Some(SourceFile { path: tail_path, symbols }), Err(_) => None, }; Ok( SourceUnit { main, head, tail } ) } /// Load from a string of source code. pub fn from_source_code>(source_code: String, path: P) -> Self { let path = canonicalize_path(path); let symbols = parse_symbols_from_source(source_code, Some(&path)); Self { main: SourceFile { path, symbols }, head: None, tail: None, } } } /// Read and parse all symbols from a source file. fn parse_symbols_from_file(path: &Path) -> Result { let source = read_source_from_file(path)?; Ok(parse_symbols_from_source(source, Some(path))) } /// Parse all symbols from a source code string. fn parse_symbols_from_source(source_code: String, path: Option<&Path>) -> Symbols { use SyntacticTokenVariant as SynVar; let token_iter = SyntacticParser::from_source_code(&source_code, path); let mut definitions = Vec::new(); let mut references = Vec::new(); for token in token_iter { let source = token.source; match token.variant { SynVar::LabelDefinition(name) => { let variant = SymbolVariant::LabelDefinition; definitions.push(Symbol { name, source, variant }); }, SynVar::MacroDefinition(name) => { let variant = SymbolVariant::MacroDefinition; definitions.push(Symbol { name, source, variant }); } SynVar::Symbol(name) => { let variant = SymbolVariant::Reference; references.push(Symbol { name, source, variant }); }, _ => (), } } Symbols { definitions: Some(definitions), references: Some(references), source_code, } } /// Attempt to read program source from a file. pub fn read_source_from_file(path: &Path) -> Result { match std::fs::read(&path) { Ok(bytes) => match String::from_utf8(bytes) { Ok(source) => Ok(source), Err(_) => return Err(ParseError::InvalidUtf8), } Err(err) => return Err( match err.kind() { std::io::ErrorKind::NotFound => ParseError::NotFound, std::io::ErrorKind::PermissionDenied => ParseError::NotReadable, std::io::ErrorKind::IsADirectory => ParseError::IsADirectory, _ => ParseError::Unknown, } ) } } fn canonicalize_path>(path: P) -> PathBuf { let pathbuf = path.into(); match pathbuf.canonicalize() { Ok(canonical) => canonical, Err(_) => pathbuf, } } pub struct SourceFile { pub path: PathBuf, pub symbols: Symbols, } pub struct Symbols { pub definitions: Option>, pub references: Option>, pub source_code: String, } pub struct Symbol { pub name: String, pub variant: SymbolVariant, pub source: SourceSpan, } #[derive(PartialEq)] pub enum SymbolVariant { LabelDefinition, MacroDefinition, Reference, }