use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fmt;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Read};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use super::ast::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleMapTokenKind {
Module,
Extern,
Framework,
Explicit,
Export,
Wildcard,
Umbrella,
Header,
Textual,
Private,
Exclude,
Requires,
Link,
FrameworkLink,
Library,
ConfigMacros,
Conflict,
ExternModule,
Use,
LCurly, RCurly, LSquare, RSquare, Comma,
Dot,
Star,
StringLit,
Identifier,
Eof,
Unknown,
}
#[derive(Debug, Clone)]
pub struct ModuleMapToken {
pub kind: ModuleMapTokenKind,
pub text: String,
pub line: usize,
pub column: usize,
}
impl ModuleMapToken {
fn new(kind: ModuleMapTokenKind, text: &str, line: usize, col: usize) -> Self {
Self {
kind,
text: text.to_string(),
line,
column: col,
}
}
}
pub struct ModuleMapLexer {
source: Vec<char>,
pos: usize,
line: usize,
column: usize,
tokens: Vec<ModuleMapToken>,
token_pos: usize,
}
impl ModuleMapLexer {
pub fn new(source: &str) -> Self {
Self {
source: source.chars().collect(),
pos: 0,
line: 1,
column: 1,
tokens: Vec::new(),
token_pos: 0,
}
}
pub fn tokenize(&mut self) -> Vec<ModuleMapToken> {
self.tokens.clear();
self.pos = 0;
self.line = 1;
self.column = 1;
while self.pos < self.source.len() {
let ch = self.source[self.pos];
if ch.is_whitespace() {
if ch == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
self.pos += 1;
continue;
}
if ch == '/' && self.peek() == Some('/') {
self.skip_line_comment();
continue;
}
if ch == '/' && self.peek() == Some('*') {
self.skip_block_comment();
continue;
}
if ch == '"' {
self.lex_string();
continue;
}
let punct = match ch {
'{' => Some(ModuleMapTokenKind::LCurly),
'}' => Some(ModuleMapTokenKind::RCurly),
'[' => Some(ModuleMapTokenKind::LSquare),
']' => Some(ModuleMapTokenKind::RSquare),
',' => Some(ModuleMapTokenKind::Comma),
'.' => Some(ModuleMapTokenKind::Dot),
'*' => Some(ModuleMapTokenKind::Star),
_ => None,
};
if let Some(kind) = punct {
self.tokens.push(ModuleMapToken::new(
kind,
&ch.to_string(),
self.line,
self.column,
));
self.pos += 1;
self.column += 1;
continue;
}
if ch.is_ascii_alphabetic() || ch == '_' || ch == '$' {
self.lex_identifier();
continue;
}
self.tokens.push(ModuleMapToken::new(
ModuleMapTokenKind::Unknown,
&ch.to_string(),
self.line,
self.column,
));
self.pos += 1;
self.column += 1;
}
self.tokens.push(ModuleMapToken::new(
ModuleMapTokenKind::Eof,
"",
self.line,
self.column,
));
self.tokens.clone()
}
fn peek(&self) -> Option<char> {
if self.pos + 1 < self.source.len() {
Some(self.source[self.pos + 1])
} else {
None
}
}
fn skip_line_comment(&mut self) {
while self.pos < self.source.len() && self.source[self.pos] != '\n' {
self.pos += 1;
}
}
fn skip_block_comment(&mut self) {
self.pos += 2; while self.pos + 1 < self.source.len() {
if self.source[self.pos] == '*' && self.source[self.pos + 1] == '/' {
self.pos += 2; return;
}
if self.source[self.pos] == '\n' {
self.line += 1;
self.column = 1;
}
self.pos += 1;
}
}
fn lex_string(&mut self) {
let start_col = self.column;
self.pos += 1; let mut text = String::new();
while self.pos < self.source.len() && self.source[self.pos] != '"' {
if self.source[self.pos] == '\\' && self.pos + 1 < self.source.len() {
self.pos += 1;
text.push(self.source[self.pos]);
} else {
text.push(self.source[self.pos]);
}
self.pos += 1;
}
if self.pos < self.source.len() {
self.pos += 1; }
self.tokens.push(ModuleMapToken::new(
ModuleMapTokenKind::StringLit,
&text,
self.line,
start_col,
));
self.column += text.len() + 2;
}
fn lex_identifier(&mut self) {
let start = self.pos;
while self.pos < self.source.len()
&& (self.source[self.pos].is_ascii_alphanumeric()
|| self.source[self.pos] == '_'
|| self.source[self.pos] == '$'
|| self.source[self.pos] == '-'
|| self.source[self.pos] == '.'
|| self.source[self.pos] == '/')
{
self.pos += 1;
}
let text: String = self.source[start..self.pos].iter().collect();
let kind = match text.as_str() {
"module" => ModuleMapTokenKind::Module,
"extern" => ModuleMapTokenKind::Extern,
"framework" => ModuleMapTokenKind::Framework,
"explicit" => ModuleMapTokenKind::Explicit,
"export" => ModuleMapTokenKind::Export,
"umbrella" => ModuleMapTokenKind::Umbrella,
"header" => ModuleMapTokenKind::Header,
"textual" => ModuleMapTokenKind::Textual,
"private" => ModuleMapTokenKind::Private,
"exclude" => ModuleMapTokenKind::Exclude,
"requires" => ModuleMapTokenKind::Requires,
"link" => ModuleMapTokenKind::Link,
"framework_link" | "framework-link" => ModuleMapTokenKind::FrameworkLink,
"library" => ModuleMapTokenKind::Library,
"config_macros" | "config-macros" => ModuleMapTokenKind::ConfigMacros,
"conflict" => ModuleMapTokenKind::Conflict,
"extern_module" | "extern-module" => ModuleMapTokenKind::ExternModule,
"use" => ModuleMapTokenKind::Use,
_ => ModuleMapTokenKind::Identifier,
};
self.tokens
.push(ModuleMapToken::new(kind, &text, self.line, self.column));
self.column += text.len();
}
}
#[derive(Debug, Clone, Default)]
pub struct ModuleMap {
pub modules: Vec<ModuleDefinition>,
pub file_path: PathBuf,
}
#[derive(Debug, Clone)]
pub struct ModuleDefinition {
pub name: String,
pub is_framework: bool,
pub is_explicit: bool,
pub export_wildcard: bool,
pub wildcard_exports: Vec<String>,
pub umbrella_header: Option<String>,
pub umbrella_directory: Option<String>,
pub headers: Vec<HeaderDeclaration>,
pub submodules: Vec<ModuleDefinition>,
pub requires: Vec<RequiresDecl>,
pub link_frameworks: Vec<String>,
pub link_libraries: Vec<String>,
pub config_macros: Vec<String>,
pub conflicts: Vec<String>,
pub use_modules: Vec<String>,
pub source_line: usize,
}
#[derive(Debug, Clone)]
pub struct HeaderDeclaration {
pub path: String,
pub kind: HeaderKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HeaderKind {
Normal,
Textual,
Private,
Excluded,
}
impl fmt::Display for HeaderKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HeaderKind::Normal => write!(f, "header"),
HeaderKind::Textual => write!(f, "textual header"),
HeaderKind::Private => write!(f, "private header"),
HeaderKind::Excluded => write!(f, "excluded header"),
}
}
}
#[derive(Debug, Clone)]
pub struct RequiresDecl {
pub feature: String,
pub line: usize,
}
impl ModuleDefinition {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
is_framework: false,
is_explicit: false,
export_wildcard: false,
wildcard_exports: Vec::new(),
umbrella_header: None,
umbrella_directory: None,
headers: Vec::new(),
submodules: Vec::new(),
requires: Vec::new(),
link_frameworks: Vec::new(),
link_libraries: Vec::new(),
config_macros: Vec::new(),
conflicts: Vec::new(),
use_modules: Vec::new(),
source_line: 0,
}
}
pub fn qualified_name(&self, parent: Option<&str>) -> String {
match parent {
Some(p) => format!("{}.{}", p, self.name),
None => self.name.clone(),
}
}
pub fn collect_all_headers(&self) -> Vec<HeaderDeclaration> {
let mut result = self.headers.clone();
for sub in &self.submodules {
result.extend(sub.collect_all_headers());
}
result
}
pub fn has_header(&self, path: &str) -> bool {
self.headers.iter().any(|h| h.path == path)
|| self.submodules.iter().any(|s| s.has_header(path))
}
pub fn effective_umbrella_header(&self) -> Option<&str> {
if self.umbrella_header.is_some() {
return self.umbrella_header.as_deref();
}
for sub in &self.submodules {
if let Some(uh) = sub.effective_umbrella_header() {
return Some(uh);
}
}
None
}
}
pub struct ModuleMapParser {
tokens: Vec<ModuleMapToken>,
pos: usize,
diagnostics: Vec<String>,
}
impl ModuleMapParser {
pub fn new(tokens: Vec<ModuleMapToken>) -> Self {
Self {
tokens,
pos: 0,
diagnostics: Vec::new(),
}
}
pub fn parse_module_map(&mut self) -> Result<ModuleMap, Vec<String>> {
let mut map = ModuleMap::default();
while !self.is_eof() {
if self.match_kw("extern") {
self.advance(); if self.match_kw("module") {
self.advance(); let name = self.expect_identifier()?;
let path = self.expect_string()?;
let mut def = ModuleDefinition::new(&name);
def.source_line = self.current().line;
map.modules.push(def);
}
} else if self.match_kw("module") {
self.advance(); let def = self.parse_module_definition(None)?;
map.modules.push(def);
} else {
self.diagnostics.push(format!(
"unexpected token at line {}: '{}'",
self.current().line,
self.current().text
));
self.advance();
}
if !self.diagnostics.is_empty() {
break;
}
}
if !self.diagnostics.is_empty() {
return Err(self.diagnostics.clone());
}
Ok(map)
}
fn parse_module_definition(
&mut self,
parent: Option<&str>,
) -> Result<ModuleDefinition, Vec<String>> {
let name = self.expect_identifier()?;
let mut def = ModuleDefinition::new(&name);
def.source_line = self.current().line;
if self.match_token(ModuleMapTokenKind::LSquare) {
self.advance(); let attr = self.expect_identifier()?;
if attr == "system" {
} else if attr == "extern_c" || attr == "extern-c" {
}
self.expect_token(ModuleMapTokenKind::RSquare)?;
}
self.expect_token(ModuleMapTokenKind::LCurly)?;
while !self.match_token(ModuleMapTokenKind::RCurly) && !self.is_eof() {
self.parse_module_body_item(&mut def, &name)?;
}
self.expect_token(ModuleMapTokenKind::RCurly)?;
Ok(def)
}
fn parse_module_body_item(
&mut self,
def: &mut ModuleDefinition,
parent_name: &str,
) -> Result<(), Vec<String>> {
match self.current().kind {
ModuleMapTokenKind::Umbrella => {
self.advance(); if self.match_kw("header") {
self.advance(); def.umbrella_header = Some(self.expect_string()?);
} else if self.match_kw("directory") {
self.advance(); def.umbrella_directory = Some(self.expect_string()?);
} else {
def.umbrella_header = Some(self.expect_string()?);
}
}
ModuleMapTokenKind::Header => {
self.advance(); let path = self.expect_string()?;
def.headers.push(HeaderDeclaration {
path,
kind: HeaderKind::Normal,
});
}
ModuleMapTokenKind::Textual => {
self.advance(); self.expect_kw("header")?;
let path = self.expect_string()?;
def.headers.push(HeaderDeclaration {
path,
kind: HeaderKind::Textual,
});
}
ModuleMapTokenKind::Private => {
self.advance(); if self.match_kw("header") {
self.advance(); let path = self.expect_string()?;
def.headers.push(HeaderDeclaration {
path,
kind: HeaderKind::Private,
});
} else if self.match_kw("textual") {
self.advance(); self.expect_kw("header")?;
let path = self.expect_string()?;
def.headers.push(HeaderDeclaration {
path,
kind: HeaderKind::Private,
});
} else {
self.expect_kw("module")?;
let sub = self.parse_module_definition(Some(parent_name))?;
def.submodules.push(sub);
}
}
ModuleMapTokenKind::Exclude => {
self.advance(); self.expect_kw("header")?;
let path = self.expect_string()?;
def.headers.push(HeaderDeclaration {
path,
kind: HeaderKind::Excluded,
});
}
ModuleMapTokenKind::Module => {
self.advance(); let sub = self.parse_module_definition(Some(parent_name))?;
def.submodules.push(sub);
}
ModuleMapTokenKind::Explicit => {
self.advance(); if self.match_kw("module") {
self.advance(); let mut sub = self.parse_module_definition(Some(parent_name))?;
sub.is_explicit = true;
def.submodules.push(sub);
}
}
ModuleMapTokenKind::Export => {
self.advance(); if self.match_token(ModuleMapTokenKind::Star) {
self.advance(); def.export_wildcard = true;
} else {
let export_name = self.expect_identifier()?;
def.wildcard_exports.push(export_name);
}
}
ModuleMapTokenKind::Requires => {
self.advance(); let feature = self.expect_identifier()?;
def.requires.push(RequiresDecl {
feature,
line: self.current().line,
});
}
ModuleMapTokenKind::Link => {
self.advance(); if self.match_kw("framework") || self.match_token(ModuleMapTokenKind::Framework) {
self.advance(); let fw = self.expect_string()?;
def.link_frameworks.push(fw);
} else {
let lib = self.expect_string()?;
def.link_libraries.push(lib);
}
}
ModuleMapTokenKind::FrameworkLink => {
self.advance(); let fw = self.expect_string()?;
def.link_frameworks.push(fw);
}
ModuleMapTokenKind::Library => {
self.advance(); let lib = self.expect_string()?;
def.link_libraries.push(lib);
}
ModuleMapTokenKind::ConfigMacros => {
self.advance(); self.parse_ident_list(&mut def.config_macros)?;
}
ModuleMapTokenKind::Conflict => {
self.advance(); let module_name = self.expect_identifier()?;
def.conflicts.push(module_name);
}
ModuleMapTokenKind::Use => {
self.advance(); let module_name = self.expect_identifier()?;
def.use_modules.push(module_name);
}
_ => {
self.diagnostics.push(format!(
"unexpected token in module body at line {}: '{}'",
self.current().line,
self.current().text
));
self.advance();
}
}
Ok(())
}
fn parse_ident_list(&mut self, dest: &mut Vec<String>) -> Result<(), Vec<String>> {
loop {
let ident = self.expect_identifier()?;
dest.push(ident);
if self.match_token(ModuleMapTokenKind::Comma) {
self.advance();
} else {
break;
}
}
Ok(())
}
fn current(&self) -> &ModuleMapToken {
if self.pos < self.tokens.len() {
&self.tokens[self.pos]
} else {
&self.tokens[self.tokens.len() - 1]
}
}
fn advance(&mut self) {
if self.pos < self.tokens.len() {
self.pos += 1;
}
}
fn is_eof(&self) -> bool {
self.pos >= self.tokens.len() || self.tokens[self.pos].kind == ModuleMapTokenKind::Eof
}
fn match_token(&self, kind: ModuleMapTokenKind) -> bool {
!self.is_eof() && self.current().kind == kind
}
fn match_kw(&self, kw: &str) -> bool {
!self.is_eof()
&& self.current().kind == ModuleMapTokenKind::Identifier
&& self.current().text == kw
}
fn expect_token(&mut self, kind: ModuleMapTokenKind) -> Result<(), Vec<String>> {
if self.is_eof() {
self.diagnostics
.push(format!("unexpected end of file, expected {:?}", kind));
return Err(self.diagnostics.clone());
}
if self.current().kind != kind {
self.diagnostics.push(format!(
"expected {:?} at line {}, got {:?} ('{}')",
kind,
self.current().line,
self.current().kind,
self.current().text
));
return Err(self.diagnostics.clone());
}
self.advance();
Ok(())
}
fn expect_kw(&mut self, kw: &str) -> Result<(), Vec<String>> {
if self.is_eof() {
self.diagnostics
.push(format!("unexpected end of file, expected '{}'", kw));
return Err(self.diagnostics.clone());
}
if !self.match_kw(kw) {
self.diagnostics.push(format!(
"expected '{}' at line {}, got '{}'",
kw,
self.current().line,
self.current().text
));
return Err(self.diagnostics.clone());
}
self.advance();
Ok(())
}
fn expect_identifier(&mut self) -> Result<String, Vec<String>> {
if self.is_eof() {
self.diagnostics
.push("unexpected end of file, expected identifier".into());
return Err(self.diagnostics.clone());
}
if self.current().kind != ModuleMapTokenKind::Identifier {
self.diagnostics.push(format!(
"expected identifier at line {}, got {:?} ('{}')",
self.current().line,
self.current().kind,
self.current().text
));
return Err(self.diagnostics.clone());
}
let text = self.current().text.clone();
self.advance();
Ok(text)
}
fn expect_string(&mut self) -> Result<String, Vec<String>> {
if self.is_eof() {
self.diagnostics
.push("unexpected end of file, expected string".into());
return Err(self.diagnostics.clone());
}
if self.current().kind != ModuleMapTokenKind::StringLit {
self.diagnostics.push(format!(
"expected string literal at line {}, got {:?} ('{}')",
self.current().line,
self.current().kind,
self.current().text
));
return Err(self.diagnostics.clone());
}
let text = self.current().text.clone();
self.advance();
Ok(text)
}
}
#[derive(Debug, Clone)]
pub struct ModuleCacheEntry {
pub module_name: String,
pub bmi_path: PathBuf,
pub module_map_path: PathBuf,
pub build_timestamp: SystemTime,
pub content_hash: u64,
pub is_valid: bool,
pub is_loaded: bool,
pub dependencies: Vec<String>,
}
impl ModuleCacheEntry {
pub fn new(module_name: &str, bmi_path: PathBuf) -> Self {
Self {
module_name: module_name.to_string(),
bmi_path,
module_map_path: PathBuf::new(),
build_timestamp: SystemTime::UNIX_EPOCH,
content_hash: 0,
is_valid: false,
is_loaded: false,
dependencies: Vec::new(),
}
}
}
pub struct ModuleCacheManager {
pub cache_root: PathBuf,
entries: HashMap<String, ModuleCacheEntry>,
validate_on_lookup: bool,
module_map_search_paths: Vec<PathBuf>,
}
impl ModuleCacheManager {
pub fn new(cache_root: &str) -> Self {
Self {
cache_root: PathBuf::from(cache_root),
entries: HashMap::new(),
validate_on_lookup: true,
module_map_search_paths: Vec::new(),
}
}
pub fn set_search_paths(&mut self, paths: Vec<PathBuf>) {
self.module_map_search_paths = paths;
}
pub fn add_search_path(&mut self, path: PathBuf) {
self.module_map_search_paths.push(path);
}
pub fn bmi_path_for(&self, module_name: &str) -> PathBuf {
self.cache_root
.join(module_name.replace('.', "/"))
.with_extension("pcm")
}
pub fn lookup(&mut self, module_name: &str) -> Option<&ModuleCacheEntry> {
if self.entries.contains_key(module_name) {
let valid = if self.validate_on_lookup {
self.validate_entry(module_name)
} else {
true
};
if valid {
return self.entries.get(module_name);
} else {
self.entries.remove(module_name);
}
}
let bmi_path = self.bmi_path_for(module_name);
if bmi_path.exists() {
let entry = ModuleCacheEntry::new(module_name, bmi_path);
self.entries.insert(module_name.to_string(), entry);
return self.entries.get(module_name);
}
None
}
pub fn load(&mut self, module_name: &str) -> Result<Vec<u8>, String> {
let bmi_path = self.bmi_path_for(module_name);
if !bmi_path.exists() {
return Err(format!(
"BMI file not found for module '{}' at {}",
module_name,
bmi_path.display()
));
}
let data = std::fs::read(&bmi_path).map_err(|e| {
format!(
"failed to read BMI file for module '{}': {}",
module_name, e
)
})?;
if let Some(entry) = self.entries.get_mut(module_name) {
entry.is_loaded = true;
entry.build_timestamp = bmi_path
.metadata()
.ok()
.and_then(|m| m.modified().ok())
.unwrap_or(SystemTime::UNIX_EPOCH);
} else {
let mut entry = ModuleCacheEntry::new(module_name, bmi_path.clone());
entry.is_loaded = true;
entry.build_timestamp = bmi_path
.metadata()
.ok()
.and_then(|m| m.modified().ok())
.unwrap_or(SystemTime::UNIX_EPOCH);
self.entries.insert(module_name.to_string(), entry);
}
Ok(data)
}
pub fn store(
&mut self,
module_name: &str,
data: &[u8],
deps: Vec<String>,
) -> Result<(), String> {
let bmi_path = self.bmi_path_for(module_name);
if let Some(parent) = bmi_path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| format!("failed to create cache directory: {}", e))?;
}
std::fs::write(&bmi_path, data).map_err(|e| {
format!(
"failed to write BMI file for module '{}': {}",
module_name, e
)
})?;
let mut entry = ModuleCacheEntry::new(module_name, bmi_path);
entry.build_timestamp = SystemTime::now();
entry.content_hash = Self::compute_hash(data);
entry.is_valid = true;
entry.is_loaded = true;
entry.dependencies = deps;
self.entries.insert(module_name.to_string(), entry);
Ok(())
}
pub fn validate_entry(&self, module_name: &str) -> bool {
if let Some(entry) = self.entries.get(module_name) {
if !entry.bmi_path.exists() {
return false;
}
if let Ok(meta) = entry.bmi_path.metadata() {
if let Ok(mtime) = meta.modified() {
if mtime < entry.build_timestamp {
return false;
}
}
}
true
} else {
false
}
}
pub fn invalidate(&mut self, module_name: &str) {
if let Some(entry) = self.entries.get(module_name) {
let bmi_path = entry.bmi_path.clone();
self.entries.remove(module_name);
let _ = std::fs::remove_file(&bmi_path);
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn entry_names(&self) -> Vec<&str> {
self.entries.keys().map(|s| s.as_str()).collect()
}
pub fn find_module_map(&self, module_name: &str) -> Option<PathBuf> {
for dir in &self.module_map_search_paths {
let candidate = dir.join("module.modulemap");
if candidate.exists() {
if let Ok(contents) = std::fs::read_to_string(&candidate) {
let mut lexer = ModuleMapLexer::new(&contents);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
if let Ok(map) = parser.parse_module_map() {
if map.modules.iter().any(|m| {
m.name == module_name
|| m.submodules
.iter()
.any(|s| s.qualified_name(Some(&m.name)) == module_name)
}) {
return Some(candidate);
}
}
}
}
}
None
}
fn compute_hash(data: &[u8]) -> u64 {
let mut hash: u64 = 0x1505;
for &byte in data {
hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
}
hash
}
}
pub const BMI_MAGIC: [u8; 4] = *b"BMOD";
pub const BMI_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum BMISection {
Metadata = 1,
Declarations = 2,
Types = 3,
Identifiers = 4,
SourceLocations = 5,
Dependencies = 6,
Macros = 7,
Control = 8,
}
pub struct BMILoader {
data: Vec<u8>,
pos: usize,
pub metadata: BMIMetadata,
pub declarations: BTreeMap<u64, Decl>,
pub types: BTreeMap<u64, QualType>,
pub identifiers: BTreeMap<u64, String>,
pub dependencies: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct BMIMetadata {
pub module_name: String,
pub target_triple: String,
pub language_standard: String,
pub version: u32,
pub build_timestamp: u64,
pub content_hash: u64,
}
impl BMILoader {
pub fn new(data: Vec<u8>) -> Self {
Self {
data,
pos: 0,
metadata: BMIMetadata::default(),
declarations: BTreeMap::new(),
types: BTreeMap::new(),
identifiers: BTreeMap::new(),
dependencies: Vec::new(),
}
}
pub fn from_file(path: &Path) -> io::Result<Self> {
let data = std::fs::read(path)?;
Ok(Self::new(data))
}
pub fn read_header(&mut self) -> io::Result<()> {
let mut magic = [0u8; 4];
self.read_exact(&mut magic)?;
if magic != BMI_MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"invalid BMI file magic",
));
}
let version = self.read_u32()?;
if version != BMI_VERSION {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"BMI version mismatch: expected {}, got {}",
BMI_VERSION, version
),
));
}
self.metadata.version = version;
Ok(())
}
pub fn parse(&mut self) -> io::Result<()> {
self.read_header()?;
while self.pos < self.data.len() {
let section_id = self.read_u32()?;
let section_size = self.read_u32()? as usize;
let section_start = self.pos;
match section_id {
1 => self.parse_metadata_section()?,
2 => self.parse_declarations_section()?,
3 => self.parse_types_section()?,
4 => self.parse_identifiers_section()?,
5 => self.parse_sloc_section()?,
6 => self.parse_dependencies_section()?,
7 => {
self.pos = section_start + section_size;
}
8 => {
self.pos = section_start + section_size;
}
_ => {
self.pos = section_start + section_size;
}
}
}
Ok(())
}
fn parse_metadata_section(&mut self) -> io::Result<()> {
let name_len = self.read_u32()? as usize;
let mut name = vec![0u8; name_len];
self.read_exact(&mut name)?;
self.metadata.module_name = String::from_utf8_lossy(&name).to_string();
let triple_len = self.read_u32()? as usize;
let mut triple = vec![0u8; triple_len];
self.read_exact(&mut triple)?;
self.metadata.target_triple = String::from_utf8_lossy(&triple).to_string();
let lang_len = self.read_u32()? as usize;
let mut lang = vec![0u8; lang_len];
self.read_exact(&mut lang)?;
self.metadata.language_standard = String::from_utf8_lossy(&lang).to_string();
self.metadata.build_timestamp = self.read_u64()?;
self.metadata.content_hash = self.read_u64()?;
Ok(())
}
fn parse_declarations_section(&mut self) -> io::Result<()> {
let count = self.read_u32()? as usize;
for _ in 0..count {
let offset = self.read_u64()?;
let decl_size = self.read_u32()? as usize;
self.pos += decl_size;
}
Ok(())
}
fn parse_types_section(&mut self) -> io::Result<()> {
let count = self.read_u32()? as usize;
for _ in 0..count {
let offset = self.read_u64()?;
let type_size = self.read_u32()? as usize;
self.pos += type_size;
}
Ok(())
}
fn parse_identifiers_section(&mut self) -> io::Result<()> {
let count = self.read_u32()? as usize;
for _ in 0..count {
let offset = self.read_u64()?;
let len = self.read_u32()? as usize;
let mut bytes = vec![0u8; len];
self.read_exact(&mut bytes)?;
let ident = String::from_utf8_lossy(&bytes).to_string();
self.identifiers.insert(offset, ident);
}
Ok(())
}
fn parse_sloc_section(&mut self) -> io::Result<()> {
let count = self.read_u32()? as usize;
self.pos += count * 24; Ok(())
}
fn parse_dependencies_section(&mut self) -> io::Result<()> {
let count = self.read_u32()? as usize;
for _ in 0..count {
let len = self.read_u32()? as usize;
let mut bytes = vec![0u8; len];
self.read_exact(&mut bytes)?;
let dep = String::from_utf8_lossy(&bytes).to_string();
self.dependencies.push(dep);
}
Ok(())
}
fn read_u8(&mut self) -> io::Result<u8> {
if self.pos >= self.data.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected end of BMI data",
));
}
let v = self.data[self.pos];
self.pos += 1;
Ok(v)
}
fn read_u32(&mut self) -> io::Result<u32> {
let mut buf = [0u8; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u64(&mut self) -> io::Result<u64> {
let mut buf = [0u8; 8];
self.read_exact(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
let end = self.pos + buf.len();
if end > self.data.len() {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected end of BMI data",
));
}
buf.copy_from_slice(&self.data[self.pos..end]);
self.pos = end;
Ok(())
}
}
pub struct BMISerializer {
buffer: Vec<u8>,
pos: usize,
}
impl BMISerializer {
pub fn new() -> Self {
Self {
buffer: Vec::with_capacity(64 * 1024),
pos: 0,
}
}
pub fn serialize(
&mut self,
metadata: &BMIMetadata,
dependencies: &[String],
) -> io::Result<Vec<u8>> {
self.buffer.clear();
self.pos = 0;
self.write_bytes(&BMI_MAGIC)?;
self.write_u32(BMI_VERSION)?;
self.write_u32(BMISection::Metadata as u32)?;
let size_pos = self.buffer.len();
self.write_u32(0)?;
let start = self.buffer.len();
self.write_string(&metadata.module_name)?;
self.write_string(&metadata.target_triple)?;
self.write_string(&metadata.language_standard)?;
self.write_u64(metadata.build_timestamp)?;
self.write_u64(metadata.content_hash)?;
let end = self.buffer.len();
let size = (end - start) as u32;
self.patch_u32(size_pos, size)?;
self.write_u32(BMISection::Dependencies as u32)?;
let size_pos = self.buffer.len();
self.write_u32(0)?;
let start = self.buffer.len();
self.write_u32(dependencies.len() as u32)?;
for dep in dependencies {
self.write_string(dep)?;
}
let end = self.buffer.len();
let size = (end - start) as u32;
self.patch_u32(size_pos, size)?;
Ok(self.buffer.clone())
}
fn write_section<F>(&mut self, section: BMISection, f: F) -> io::Result<()>
where
F: FnOnce() -> io::Result<()>,
{
self.write_u32(section as u32)?;
let size_pos = self.buffer.len();
self.write_u32(0)?;
let start = self.buffer.len();
f()?;
let end = self.buffer.len();
let size = (end - start) as u32;
self.patch_u32(size_pos, size)?;
Ok(())
}
fn write_u8(&mut self, v: u8) -> io::Result<()> {
self.buffer.push(v);
self.pos += 1;
Ok(())
}
fn write_u32(&mut self, v: u32) -> io::Result<()> {
self.buffer.extend_from_slice(&v.to_le_bytes());
self.pos += 4;
Ok(())
}
fn write_u64(&mut self, v: u64) -> io::Result<()> {
self.buffer.extend_from_slice(&v.to_le_bytes());
self.pos += 8;
Ok(())
}
fn write_bytes(&mut self, bytes: &[u8]) -> io::Result<()> {
self.buffer.extend_from_slice(bytes);
self.pos += bytes.len();
Ok(())
}
fn write_string(&mut self, s: &str) -> io::Result<()> {
let bytes = s.as_bytes();
self.write_u32(bytes.len() as u32)?;
self.write_bytes(bytes)
}
fn patch_u32(&mut self, pos: usize, val: u32) -> io::Result<()> {
if pos + 4 <= self.buffer.len() {
self.buffer[pos..pos + 4].copy_from_slice(&val.to_le_bytes());
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct FrameworkModule {
pub name: String,
pub framework_path: PathBuf,
pub umbrella_header: Option<PathBuf>,
pub module_map_path: Option<PathBuf>,
pub is_system: bool,
}
impl FrameworkModule {
pub fn new(name: &str, framework_path: PathBuf) -> Self {
let umbrella = framework_path.join("Headers").join(format!("{}.h", name));
let module_map = framework_path.join("Modules").join("module.modulemap");
Self {
name: name.to_string(),
framework_path,
umbrella_header: if umbrella.exists() {
Some(umbrella)
} else {
None
},
module_map_path: if module_map.exists() {
Some(module_map)
} else {
None
},
is_system: true,
}
}
pub fn discover_headers(&self) -> Vec<PathBuf> {
let headers_dir = self.framework_path.join("Headers");
if !headers_dir.is_dir() {
return Vec::new();
}
let mut headers = Vec::new();
if let Ok(entries) = std::fs::read_dir(&headers_dir) {
for entry in entries.flatten() {
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "h" || ext == "hpp" || ext == "pch" {
headers.push(path);
}
}
}
}
let private_dir = self.framework_path.join("PrivateHeaders");
if private_dir.is_dir() {
if let Ok(entries) = std::fs::read_dir(&private_dir) {
for entry in entries.flatten() {
let path = entry.path();
if let Some(ext) = path.extension() {
if ext == "h" || ext == "hpp" {
headers.push(path);
}
}
}
}
}
headers.sort();
headers
}
pub fn has_module_map(&self) -> bool {
self.module_map_path
.as_ref()
.map(|p| p.exists())
.unwrap_or(false)
}
pub fn load_module_map(&self) -> Result<ModuleMap, String> {
let path = self
.module_map_path
.as_ref()
.ok_or_else(|| format!("framework '{}' has no module map", self.name))?;
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("failed to read module map: {}", e))?;
let mut lexer = ModuleMapLexer::new(&contents);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let mut map = parser.parse_module_map().map_err(|e| e.join("; "))?;
map.file_path = path.clone();
Ok(map)
}
pub fn to_module_definition(&self) -> ModuleDefinition {
let mut def = ModuleDefinition::new(&self.name);
def.is_framework = true;
if let Some(ref umbrella) = self.umbrella_header {
def.umbrella_header = Some(umbrella.to_string_lossy().to_string());
}
for header in self.discover_headers() {
def.headers.push(HeaderDeclaration {
path: header.to_string_lossy().to_string(),
kind: HeaderKind::Normal,
});
}
def
}
}
pub struct X86ModuleLoader {
pub module_maps: HashMap<PathBuf, ModuleMap>,
pub frameworks: HashMap<String, FrameworkModule>,
pub cache: ModuleCacheManager,
pub framework_search_paths: Vec<PathBuf>,
pub use_cache: bool,
pub precompile_on_demand: bool,
pub diagnostics: Vec<ModuleLoadDiagnostic>,
}
#[derive(Debug, Clone)]
pub struct ModuleLoadDiagnostic {
pub level: ModuleLoadDiagLevel,
pub message: String,
pub module_name: Option<String>,
pub source_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModuleLoadDiagLevel {
Note,
Warning,
Error,
}
impl X86ModuleLoader {
pub fn new(cache_root: &str) -> Self {
Self {
module_maps: HashMap::new(),
frameworks: HashMap::new(),
cache: ModuleCacheManager::new(cache_root),
framework_search_paths: Vec::new(),
use_cache: true,
precompile_on_demand: false,
diagnostics: Vec::new(),
}
}
pub fn add_framework_path(&mut self, path: &str) {
self.framework_search_paths.push(PathBuf::from(path));
}
pub fn load_module_map(&mut self, path: &Path) -> Result<&ModuleMap, String> {
if self.module_maps.contains_key(path) {
return Ok(&self.module_maps[path]);
}
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("failed to read module map '{}': {}", path.display(), e))?;
let mut lexer = ModuleMapLexer::new(&contents);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let mut map = parser.parse_module_map().map_err(|errors| {
format!(
"failed to parse module map '{}': {}",
path.display(),
errors.join("; ")
)
})?;
map.file_path = path.to_path_buf();
self.module_maps.insert(path.to_path_buf(), map);
Ok(&self.module_maps[path])
}
pub fn lookup_module(&self, module_name: &str) -> Option<&ModuleDefinition> {
for map in self.module_maps.values() {
for m in &map.modules {
if m.name == module_name {
return Some(m);
}
if let Some(sub) = self.lookup_submodule(m, module_name) {
return Some(sub);
}
}
}
None
}
fn lookup_submodule<'a>(
&'a self,
parent: &'a ModuleDefinition,
name: &str,
) -> Option<&'a ModuleDefinition> {
for sub in &parent.submodules {
let qname = sub.qualified_name(Some(&parent.name));
if qname == name {
return Some(sub);
}
if let Some(found) = self.lookup_submodule(sub, name) {
return Some(found);
}
}
None
}
pub fn load_module_bmi(&mut self, module_name: &str) -> Result<Vec<u8>, String> {
if self.use_cache {
if let Some(_entry) = self.cache.lookup(module_name) {
return self.cache.load(module_name);
}
}
if !self.precompile_on_demand {
return Err(format!(
"module '{}' not found in cache and precompilation is disabled",
module_name
));
}
Err(format!(
"module '{}' compilation not yet implemented",
module_name
))
}
pub fn discover_frameworks(&mut self) -> usize {
let mut count = 0;
for search_path in &self.framework_search_paths.clone() {
if let Ok(entries) = std::fs::read_dir(search_path) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
if let Some(ext) = path.extension() {
if ext == "framework" {
if let Some(stem) = path.file_stem() {
let name = stem.to_string_lossy().to_string();
let fw = FrameworkModule::new(&name, path.clone());
self.frameworks.insert(name.clone(), fw);
count += 1;
}
}
}
}
}
}
}
count
}
pub fn get_framework(&self, name: &str) -> Option<&FrameworkModule> {
self.frameworks.get(name)
}
pub fn check_requires(
&self,
module: &ModuleDefinition,
available_features: &HashSet<String>,
) -> Result<(), Vec<String>> {
let mut missing = Vec::new();
for req in &module.requires {
if !available_features.contains(&req.feature) {
missing.push(format!(
"module '{}' requires feature '{}' which is not available",
module.name, req.feature
));
}
}
for sub in &module.submodules {
for req in &sub.requires {
if !available_features.contains(&req.feature) {
missing.push(format!(
"submodule '{}' requires feature '{}' which is not available",
sub.name, req.feature
));
}
}
}
if missing.is_empty() {
Ok(())
} else {
Err(missing)
}
}
pub fn resolve_dependencies(&self, module_name: &str) -> Vec<String> {
let mut resolved = Vec::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(module_name.to_string());
while let Some(name) = queue.pop_front() {
if !visited.insert(name.clone()) {
continue;
}
if name != module_name {
resolved.push(name.clone());
}
if let Some(def) = self.lookup_module(&name) {
for dep in &def.use_modules {
if !visited.contains(dep.as_str()) {
queue.push_back(dep.clone());
}
}
}
}
resolved
}
pub fn summary(&self) -> String {
let mut s = String::new();
s.push_str("Module Loader Summary\n");
s.push_str("=====================\n");
s.push_str(&format!("Module maps loaded: {}\n", self.module_maps.len()));
s.push_str(&format!(
"Frameworks discovered: {}\n",
self.frameworks.len()
));
s.push_str(&format!(
"Cache entries: {}\n",
self.cache.entry_names().len()
));
s.push_str(&format!("Diagnostics: {}\n", self.diagnostics.len()));
let mut total_modules = 0usize;
for map in self.module_maps.values() {
total_modules += map.modules.len();
for m in &map.modules {
total_modules += count_submodules(m);
}
}
s.push_str(&format!("Total modules defined: {}\n", total_modules));
for diag in &self.diagnostics {
s.push_str(&format!(
" [{}] {}\n",
match diag.level {
ModuleLoadDiagLevel::Note => "note",
ModuleLoadDiagLevel::Warning => "warning",
ModuleLoadDiagLevel::Error => "error",
},
diag.message
));
}
s
}
}
fn count_submodules(def: &ModuleDefinition) -> usize {
let mut count = def.submodules.len();
for sub in &def.submodules {
count += count_submodules(sub);
}
count
}
pub struct ConfigMacroEvaluator {
pub defined_macros: HashSet<String>,
}
impl ConfigMacroEvaluator {
pub fn new() -> Self {
Self {
defined_macros: HashSet::new(),
}
}
pub fn define(&mut self, name: &str) {
self.defined_macros.insert(name.to_string());
}
pub fn is_available(&self, module: &ModuleDefinition) -> bool {
if module.config_macros.is_empty() {
return true;
}
module
.config_macros
.iter()
.any(|m| self.defined_macros.contains(m.as_str()))
}
pub fn filter_module_map(&self, map: &ModuleMap) -> ModuleMap {
let mut filtered = map.clone();
filtered.modules.retain(|m| self.is_available(m));
for m in &mut filtered.modules {
m.submodules.retain(|s| self.is_available(s));
}
filtered
}
}
pub fn generate_module_map_for_directory(
module_name: &str,
headers_dir: &Path,
include_subdirs: bool,
) -> ModuleMap {
let mut map = ModuleMap::default();
let mut def = ModuleDefinition::new(module_name);
if headers_dir.is_dir() {
def.umbrella_directory = Some(headers_dir.to_string_lossy().to_string());
if let Ok(entries) = std::fs::read_dir(headers_dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "h" || ext == "hpp" || ext == "hh" {
def.headers.push(HeaderDeclaration {
path: path.to_string_lossy().to_string(),
kind: HeaderKind::Normal,
});
}
}
} else if path.is_dir() && include_subdirs {
if let Some(sub_name) = path.file_name() {
let sub =
generate_module_definition_for_dir(&sub_name.to_string_lossy(), &path);
def.submodules.push(sub);
}
}
}
}
}
map.modules.push(def);
map
}
fn generate_module_definition_for_dir(name: &str, dir: &Path) -> ModuleDefinition {
let mut def = ModuleDefinition::new(name);
def.umbrella_directory = Some(dir.to_string_lossy().to_string());
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "h" || ext == "hpp" {
def.headers.push(HeaderDeclaration {
path: path.to_string_lossy().to_string(),
kind: HeaderKind::Normal,
});
}
}
}
}
}
def
}
pub fn parse_module_map_file(path: &Path) -> Result<ModuleMap, String> {
let contents = std::fs::read_to_string(path)
.map_err(|e| format!("failed to read module map '{}': {}", path.display(), e))?;
let mut lexer = ModuleMapLexer::new(&contents);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let mut map = parser.parse_module_map().map_err(|e| e.join("; "))?;
map.file_path = path.to_path_buf();
Ok(map)
}
pub fn parse_module_map_string(source: &str) -> Result<ModuleMap, String> {
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
parser.parse_module_map().map_err(|e| e.join("; "))
}
pub fn serialize_bmi(
module_name: &str,
target_triple: &str,
lang_standard: &str,
dependencies: &[String],
) -> io::Result<Vec<u8>> {
let metadata = BMIMetadata {
module_name: module_name.to_string(),
target_triple: target_triple.to_string(),
language_standard: lang_standard.to_string(),
version: BMI_VERSION,
build_timestamp: 0,
content_hash: 0,
};
let mut serializer = BMISerializer::new();
serializer.serialize(&metadata, dependencies)
}
pub fn write_bmi_to_file(
path: &Path,
module_name: &str,
target_triple: &str,
lang_standard: &str,
dependencies: &[String],
) -> io::Result<()> {
let data = serialize_bmi(module_name, target_triple, lang_standard, dependencies)?;
std::fs::write(path, &data)
}
pub fn load_bmi_metadata(path: &Path) -> io::Result<BMIMetadata> {
let mut loader = BMILoader::from_file(path)?;
loader.read_header()?;
Ok(loader.metadata)
}
#[derive(Debug, Clone, Default)]
pub struct ModuleImportGraph {
edges: HashMap<String, Vec<String>>,
reverse_edges: HashMap<String, Vec<String>>,
cycle_checked: bool,
cycles: Vec<Vec<String>>,
}
impl ModuleImportGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add_import(&mut self, importer: &str, importee: &str) {
self.edges
.entry(importer.to_string())
.or_default()
.push(importee.to_string());
self.reverse_edges
.entry(importee.to_string())
.or_default()
.push(importer.to_string());
self.cycle_checked = false;
}
pub fn get_imports(&self, module_name: &str) -> Vec<&str> {
self.edges
.get(module_name)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn get_importers(&self, module_name: &str) -> Vec<&str> {
self.reverse_edges
.get(module_name)
.map(|v| v.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
pub fn transitive_imports(&self, module_name: &str) -> HashSet<String> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(module_name.to_string());
while let Some(current) = queue.pop_front() {
if !visited.insert(current.clone()) {
continue;
}
if let Some(imports) = self.edges.get(¤t) {
for imp in imports {
if !visited.contains(imp.as_str()) {
queue.push_back(imp.clone());
}
}
}
}
visited.remove(module_name);
visited
}
pub fn detect_cycles(&mut self) -> &[Vec<String>] {
if self.cycle_checked {
return &self.cycles;
}
self.cycles.clear();
let mut visited = HashSet::new();
let mut rec_stack = Vec::new();
let mut rec_set = HashSet::new();
let all_modules: Vec<String> = {
let mut names: HashSet<String> = HashSet::new();
for k in self.edges.keys() {
names.insert(k.clone());
}
for k in self.reverse_edges.keys() {
names.insert(k.clone());
}
names.into_iter().collect()
};
for module in &all_modules {
if !visited.contains(module.as_str()) {
self.cycle_dfs(module, &mut visited, &mut rec_stack, &mut rec_set);
}
}
self.cycle_checked = true;
&self.cycles
}
fn cycle_dfs(
&mut self,
node: &str,
visited: &mut HashSet<String>,
rec_stack: &mut Vec<String>,
rec_set: &mut HashSet<String>,
) {
visited.insert(node.to_string());
rec_stack.push(node.to_string());
rec_set.insert(node.to_string());
if let Some(neighbors) = self.edges.get(node) {
let neighbors: Vec<String> = neighbors.clone();
for neighbor in &neighbors {
if !visited.contains(neighbor.as_str()) {
self.cycle_dfs(neighbor, visited, rec_stack, rec_set);
} else if rec_set.contains(neighbor.as_str()) {
let mut cycle = Vec::new();
for item in rec_stack.iter().rev() {
cycle.push(item.clone());
if item == neighbor {
break;
}
}
cycle.reverse();
self.cycles.push(cycle);
}
}
}
rec_stack.pop();
rec_set.remove(node);
}
pub fn topological_sort(&mut self) -> Result<Vec<String>, Vec<Vec<String>>> {
let cycles = self.detect_cycles().to_vec();
if !cycles.is_empty() {
return Err(cycles);
}
let all_modules: Vec<String> = {
let mut names: HashSet<String> = HashSet::new();
for k in self.edges.keys() {
names.insert(k.clone());
}
for k in self.reverse_edges.keys() {
names.insert(k.clone());
}
names.into_iter().collect()
};
let mut in_degree: HashMap<String, usize> = HashMap::new();
for module in &all_modules {
in_degree.entry(module.clone()).or_insert(0);
}
for (_, imports) in &self.edges {
for imp in imports {
*in_degree.entry(imp.clone()).or_insert(0) += 1;
}
}
let mut queue: VecDeque<String> = VecDeque::new();
for module in &all_modules {
if *in_degree.get(module).unwrap_or(&0) == 0 {
queue.push_back(module.clone());
}
}
let mut sorted = Vec::new();
while let Some(module) = queue.pop_front() {
sorted.push(module.clone());
if let Some(imports) = self.edges.get(&module) {
for imp in imports {
let entry = in_degree.entry(imp.clone()).or_insert(0);
*entry = entry.saturating_sub(1);
if *entry == 0 {
queue.push_back(imp.clone());
}
}
}
}
if sorted.len() != all_modules.len() {
self.detect_cycles();
return Err(self.cycles.clone());
}
Ok(sorted)
}
}
#[derive(Debug, Clone)]
pub struct ModuleVisibility {
module_name: String,
pub exported_decls: HashSet<String>,
pub all_decls: HashSet<String>,
pub is_re_exported: bool,
pub re_exports: Vec<String>,
pub is_implementation: bool,
}
impl ModuleVisibility {
pub fn new(module_name: &str) -> Self {
Self {
module_name: module_name.to_string(),
exported_decls: HashSet::new(),
all_decls: HashSet::new(),
is_re_exported: false,
re_exports: Vec::new(),
is_implementation: false,
}
}
pub fn is_decl_visible(&self, decl_name: &str) -> bool {
self.exported_decls.contains(decl_name)
|| (!self.is_implementation && self.all_decls.contains(decl_name))
}
pub fn export_decl(&mut self, decl_name: &str) {
self.exported_decls.insert(decl_name.to_string());
self.all_decls.insert(decl_name.to_string());
}
pub fn internal_decl(&mut self, decl_name: &str) {
self.all_decls.insert(decl_name.to_string());
}
pub fn re_export_module(&mut self, module_name: &str) {
self.re_exports.push(module_name.to_string());
}
}
#[derive(Debug, Clone, Default)]
pub struct ModuleVisibilityManager {
modules: HashMap<String, ModuleVisibility>,
re_exported_modules: HashSet<String>,
}
impl ModuleVisibilityManager {
pub fn new() -> Self {
Self::default()
}
pub fn register_module(&mut self, vis: ModuleVisibility) {
self.modules.insert(vis.module_name.clone(), vis);
}
pub fn import_module(&mut self, importer: &str, importee: &str) {
if let Some(vis) = self.modules.get_mut(importer) {
vis.re_export_module(importee);
}
}
pub fn is_visible(
&self,
module_name: &str,
decl_name: &str,
imported_modules: &HashSet<String>,
) -> bool {
if !imported_modules.contains(module_name) {
return false;
}
if let Some(vis) = self.modules.get(module_name) {
if vis.is_decl_visible(decl_name) {
return true;
}
for re_exp in &vis.re_exports {
if self.is_visible(re_exp, decl_name, imported_modules) {
return true;
}
}
}
false
}
pub fn visible_decls(&self, imported_modules: &HashSet<String>) -> HashSet<String> {
let mut visible = HashSet::new();
for module_name in imported_modules {
if let Some(vis) = self.modules.get(module_name) {
for decl in &vis.exported_decls {
visible.insert(decl.clone());
}
if !vis.is_implementation {
for decl in &vis.all_decls {
visible.insert(decl.clone());
}
}
}
}
visible
}
}
#[derive(Debug, Clone, Default)]
pub struct HeaderDependencyScanner {
module_headers: HashMap<String, Vec<PathBuf>>,
header_modules: HashMap<PathBuf, Vec<String>>,
header_includes: HashMap<PathBuf, Vec<PathBuf>>,
follow_system_headers: bool,
}
impl HeaderDependencyScanner {
pub fn new() -> Self {
Self::default()
}
pub fn register_module_header(&mut self, module_name: &str, header: PathBuf) {
self.module_headers
.entry(module_name.to_string())
.or_default()
.push(header.clone());
self.header_modules
.entry(header)
.or_default()
.push(module_name.to_string());
}
pub fn register_include(&mut self, includer: PathBuf, includee: PathBuf) {
self.header_includes
.entry(includer)
.or_default()
.push(includee);
}
pub fn compute_module_dependencies(&self, header: &Path) -> HashSet<String> {
let mut deps = HashSet::new();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(header.to_path_buf());
while let Some(current) = queue.pop_front() {
if !visited.insert(current.clone()) {
continue;
}
if let Some(modules) = self.header_modules.get(¤t) {
for m in modules {
deps.insert(m.clone());
}
}
if let Some(includes) = self.header_includes.get(¤t) {
for inc in includes {
if !visited.contains(inc) {
queue.push_back(inc.clone());
}
}
}
}
deps
}
pub fn affected_modules(&self, changed_header: &Path) -> Vec<String> {
let mut affected = HashSet::new();
let mut queue = VecDeque::new();
let mut visited = HashSet::new();
queue.push_back(changed_header.to_path_buf());
while let Some(current) = queue.pop_front() {
if !visited.insert(current.clone()) {
continue;
}
if let Some(modules) = self.header_modules.get(¤t) {
for m in modules {
affected.insert(m.clone());
}
}
for (includer, includes) in &self.header_includes {
if includes.contains(¤t) && !visited.contains(includer) {
queue.push_back(includer.clone());
}
}
}
affected.into_iter().collect()
}
}
#[derive(Debug, Clone)]
pub struct BMIValidator {
timestamps: HashMap<PathBuf, (SystemTime, String)>,
content_hashes: HashMap<PathBuf, (u64, String)>,
config_macros: HashMap<String, HashSet<String>>,
}
impl BMIValidator {
pub fn new() -> Self {
Self {
timestamps: HashMap::new(),
content_hashes: HashMap::new(),
config_macros: HashMap::new(),
}
}
pub fn record_timestamp(&mut self, file: PathBuf, module: &str, ts: SystemTime) {
self.timestamps.insert(file, (ts, module.to_string()));
}
pub fn record_hash(&mut self, file: PathBuf, module: &str, hash: u64) {
self.content_hashes.insert(file, (hash, module.to_string()));
}
pub fn record_config_macros(&mut self, module: &str, macros: HashSet<String>) {
self.config_macros.insert(module.to_string(), macros);
}
pub fn validate_module(&self, module_name: &str, file: &Path) -> Result<(), String> {
if let Some((ts, _)) = self.timestamps.get(file) {
if let Ok(meta) = std::fs::metadata(file) {
if let Ok(mtime) = meta.modified() {
if mtime > *ts {
return Err(format!(
"module '{}' is stale: '{}' was modified after build",
module_name,
file.display()
));
}
}
}
}
if let Some((stored_hash, _)) = self.content_hashes.get(file) {
if let Ok(contents) = std::fs::read(file) {
let current_hash = ModuleCacheManager::compute_hash_static(&contents);
if current_hash != *stored_hash {
return Err(format!(
"module '{}' is stale: '{}' content has changed",
module_name,
file.display()
));
}
}
}
Ok(())
}
pub fn validate_all(&self, module_name: &str) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for (file, (_, mod_name)) in &self.timestamps {
if mod_name == module_name {
if let Err(e) = self.validate_module(module_name, file) {
errors.push(e);
}
}
}
for (file, (_, mod_name)) in &self.content_hashes {
if mod_name == module_name {
if let Err(e) = self.validate_module(module_name, file) {
if !errors
.iter()
.any(|err: &String| err.contains(&*file.to_string_lossy()))
{
errors.push(e);
}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
impl ModuleCacheManager {
pub fn compute_hash_static(data: &[u8]) -> u64 {
let mut hash: u64 = 0x1505;
for &byte in data {
hash = hash.wrapping_mul(31).wrapping_add(byte as u64);
}
hash
}
}
#[derive(Debug, Clone, Default)]
pub struct ModuleConflictResolver {
conflicts: HashMap<String, HashSet<String>>,
imported: HashSet<String>,
}
impl ModuleConflictResolver {
pub fn new() -> Self {
Self::default()
}
pub fn register_conflict(&mut self, module_a: &str, module_b: &str) {
self.conflicts
.entry(module_a.to_string())
.or_default()
.insert(module_b.to_string());
self.conflicts
.entry(module_b.to_string())
.or_default()
.insert(module_a.to_string());
}
pub fn try_import(&mut self, module_name: &str) -> Result<(), String> {
if self.imported.contains(module_name) {
return Ok(()); }
if let Some(conflict_set) = self.conflicts.get(module_name) {
for imported in &self.imported {
if conflict_set.contains(imported.as_str()) {
return Err(format!(
"module '{}' conflicts with already-imported module '{}'",
module_name, imported
));
}
}
}
self.imported.insert(module_name.to_string());
Ok(())
}
pub fn are_conflicting(&self, a: &str, b: &str) -> bool {
self.conflicts
.get(a)
.map(|set| set.contains(b))
.unwrap_or(false)
}
pub fn get_conflicts(&self, module_name: &str) -> Vec<&str> {
self.conflicts
.get(module_name)
.map(|set| set.iter().map(|s| s.as_str()).collect())
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lex_simple_module_map() {
let source = r#"
module MyLib {
header "MyLib.h"
export *
}
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
assert!(tokens.len() >= 8);
assert_eq!(tokens[0].kind, ModuleMapTokenKind::Module);
assert_eq!(tokens[1].text, "MyLib");
assert_eq!(tokens[2].kind, ModuleMapTokenKind::LCurly);
assert_eq!(tokens[3].kind, ModuleMapTokenKind::Header);
assert_eq!(tokens[4].text, "MyLib.h");
assert_eq!(tokens[5].kind, ModuleMapTokenKind::Export);
assert_eq!(tokens[6].kind, ModuleMapTokenKind::Star);
assert_eq!(tokens[7].kind, ModuleMapTokenKind::RCurly);
}
#[test]
fn test_parse_simple_module() {
let source = r#"
module MyLib {
header "MyLib.h"
export *
}
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let map = parser.parse_module_map().expect("parse");
assert_eq!(map.modules.len(), 1);
let m = &map.modules[0];
assert_eq!(m.name, "MyLib");
assert!(m.export_wildcard);
assert_eq!(m.headers.len(), 1);
assert_eq!(m.headers[0].path, "MyLib.h");
assert_eq!(m.headers[0].kind, HeaderKind::Normal);
}
#[test]
fn test_parse_framework_module() {
let source = r#"
framework module MyFramework {
umbrella header "MyFramework.h"
module SubMod {
header "SubMod.h"
}
link framework "CoreFoundation"
requires objc
}
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let map = parser.parse_module_map().expect("parse");
assert_eq!(map.modules.len(), 1);
let m = &map.modules[0];
assert!(m.is_framework);
assert_eq!(m.umbrella_header.as_deref(), Some("MyFramework.h"));
assert_eq!(m.submodules.len(), 1);
assert_eq!(m.link_frameworks.len(), 1);
assert_eq!(m.link_frameworks[0], "CoreFoundation");
assert_eq!(m.requires.len(), 1);
assert_eq!(m.requires[0].feature, "objc");
}
#[test]
fn test_parse_with_submodules() {
let source = r#"
module std {
explicit module vector {
header "vector"
}
explicit module string {
header "string"
}
requires cplusplus
}
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let map = parser.parse_module_map().expect("parse");
assert_eq!(map.modules.len(), 1);
let m = &map.modules[0];
assert_eq!(m.name, "std");
assert_eq!(m.submodules.len(), 2);
assert!(m.submodules[0].is_explicit);
assert_eq!(m.submodules[0].name, "vector");
assert_eq!(m.submodules[1].name, "string");
assert_eq!(m.requires[0].feature, "cplusplus");
}
#[test]
fn test_parse_extern_module() {
let source = r#"
extern module ExternalMod "ExternalMod.pcm"
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let map = parser.parse_module_map().expect("parse");
assert_eq!(map.modules.len(), 1);
assert_eq!(map.modules[0].name, "ExternalMod");
}
#[test]
fn test_parse_textual_private_exclude() {
let source = r#"
module TestMod {
header "public.h"
textual header "textual.h"
private header "private.h"
exclude header "excluded.h"
}
"#;
let mut lexer = ModuleMapLexer::new(source);
let tokens = lexer.tokenize();
let mut parser = ModuleMapParser::new(tokens);
let map = parser.parse_module_map().expect("parse");
let m = &map.modules[0];
assert_eq!(m.headers.len(), 4);
assert_eq!(m.headers[0].kind, HeaderKind::Normal);
assert_eq!(m.headers[1].kind, HeaderKind::Textual);
assert_eq!(m.headers[2].kind, HeaderKind::Private);
assert_eq!(m.headers[3].kind, HeaderKind::Excluded);
}
#[test]
fn test_config_macro_evaluator() {
let mut evaluator = ConfigMacroEvaluator::new();
evaluator.define("OBJC");
evaluator.define("__cplusplus");
let mut def = ModuleDefinition::new("Test");
assert!(evaluator.is_available(&def));
def.config_macros.push("OBJC".into());
assert!(evaluator.is_available(&def));
def.config_macros.push("RUST".into());
assert!(evaluator.is_available(&def));
let mut def2 = ModuleDefinition::new("Test2");
def2.config_macros.push("SWIFT".into());
assert!(!evaluator.is_available(&def2));
}
#[test]
fn test_module_cache_lookup_store() {
let temp_dir = std::env::temp_dir().join("test_module_cache");
let _ = std::fs::create_dir_all(&temp_dir);
let mut cache = ModuleCacheManager::new(&temp_dir.to_string_lossy());
let data = b"fake BMI data for testing";
let deps = vec!["std".to_string()];
cache.store("MyModule", data, deps.clone()).expect("store");
let entry = cache.lookup("MyModule");
assert!(entry.is_some());
let loaded = cache.load("MyModule").expect("load");
assert_eq!(loaded, data);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_bmi_serialize_deserialize() {
let metadata = BMIMetadata {
module_name: "TestModule".into(),
target_triple: "x86_64-unknown-linux-gnu".into(),
language_standard: "c++20".into(),
version: BMI_VERSION,
build_timestamp: 1234567890,
content_hash: 0xDEADBEEF,
};
let deps = vec!["std".to_string(), "iostream".to_string()];
let mut serializer = BMISerializer::new();
let data = serializer.serialize(&metadata, &deps).expect("serialize");
let mut loader = BMILoader::new(data);
loader.parse().expect("parse");
assert_eq!(loader.metadata.module_name, "TestModule");
assert_eq!(loader.metadata.target_triple, "x86_64-unknown-linux-gnu");
assert_eq!(loader.metadata.language_standard, "c++20");
assert_eq!(loader.metadata.build_timestamp, 1234567890);
assert_eq!(loader.metadata.content_hash, 0xDEADBEEF);
assert_eq!(loader.dependencies.len(), 2);
assert!(loader.dependencies.contains(&"std".to_string()));
assert!(loader.dependencies.contains(&"iostream".to_string()));
}
#[test]
fn test_module_loader_basic() {
let temp_dir = std::env::temp_dir().join("test_module_loader");
let _ = std::fs::create_dir_all(&temp_dir);
let mut loader = X86ModuleLoader::new(&temp_dir.to_string_lossy());
let map_path = temp_dir.join("module.modulemap");
let map_contents = r#"
module TestMod {
header "TestMod.h"
export *
}
"#;
std::fs::write(&map_path, map_contents).expect("write");
let map = loader.load_module_map(&map_path).expect("load");
assert_eq!(map.modules.len(), 1);
assert_eq!(map.modules[0].name, "TestMod");
let def = loader.lookup_module("TestMod");
assert!(def.is_some());
assert!(def.unwrap().export_wildcard);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_framework_module_discovery() {
let mut fw = FrameworkModule::new("TestFW", PathBuf::from("/tmp/TestFW.framework"));
assert_eq!(fw.name, "TestFW");
assert!(fw.is_system);
let def = fw.to_module_definition();
assert_eq!(def.name, "TestFW");
assert!(def.is_framework);
}
#[test]
fn test_generate_module_map() {
let temp_dir = std::env::temp_dir().join("test_gen_module_map");
let _ = std::fs::create_dir_all(&temp_dir);
std::fs::write(temp_dir.join("header1.h"), "int x;").expect("write");
std::fs::write(temp_dir.join("header2.hpp"), "float y;").expect("write");
let map = generate_module_map_for_directory("GeneratedMod", &temp_dir, false);
assert_eq!(map.modules.len(), 1);
let m = &map.modules[0];
assert_eq!(m.name, "GeneratedMod");
assert!(m.umbrella_directory.is_some());
assert!(m.headers.len() >= 2);
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_resolve_dependencies() {
let temp_dir = std::env::temp_dir().join("test_deps");
let _ = std::fs::create_dir_all(&temp_dir);
let mut loader = X86ModuleLoader::new(&temp_dir.to_string_lossy());
let map_a = r#"
module A {
header "A.h"
use B
use C
}
"#;
let map_path_a = temp_dir.join("a.modulemap");
std::fs::write(&map_path_a, map_a).expect("write");
loader.load_module_map(&map_path_a).expect("load");
let map_b = r#"
module B {
header "B.h"
use C
}
"#;
let map_path_b = temp_dir.join("b.modulemap");
std::fs::write(&map_path_b, map_b).expect("write");
loader.load_module_map(&map_path_b).expect("load");
let map_c = r#"
module C {
header "C.h"
}
"#;
let map_path_c = temp_dir.join("c.modulemap");
std::fs::write(&map_path_c, map_c).expect("write");
loader.load_module_map(&map_path_c).expect("load");
let deps = loader.resolve_dependencies("A");
assert!(deps.contains(&"B".to_string()));
assert!(deps.contains(&"C".to_string()));
let _ = std::fs::remove_dir_all(&temp_dir);
}
#[test]
fn test_check_requires() {
let mut loader = X86ModuleLoader::new("/tmp/test_requires");
let def = {
let mut d = ModuleDefinition::new("Mod");
d.requires.push(RequiresDecl {
feature: "cplusplus".into(),
line: 1,
});
d.requires.push(RequiresDecl {
feature: "tls".into(),
line: 2,
});
d
};
let mut features = HashSet::new();
features.insert("cplusplus".to_string());
let result = loader.check_requires(&def, &features);
assert!(result.is_err());
features.insert("tls".to_string());
let result = loader.check_requires(&def, &features);
assert!(result.is_ok());
}
#[test]
fn test_collect_all_headers() {
let mut parent = ModuleDefinition::new("Parent");
parent.headers.push(HeaderDeclaration {
path: "parent.h".into(),
kind: HeaderKind::Normal,
});
let mut child = ModuleDefinition::new("Child");
child.headers.push(HeaderDeclaration {
path: "child.h".into(),
kind: HeaderKind::Private,
});
parent.submodules.push(child);
let all = parent.collect_all_headers();
assert_eq!(all.len(), 2);
}
}