use proc_macro2::TokenStream;
use quote::ToTokens;
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;
use syn::{parse_file, Item};
pub struct Module {
pub name: String,
pub path: PathBuf,
pub ast: syn::File,
pub symbols: HashMap<String, TokenStream>,
}
pub struct Importer {
modules: HashMap<String, Module>,
search_paths: Vec<PathBuf>,
}
impl Importer {
pub fn new() -> Self {
Importer {
modules: HashMap::new(),
search_paths: vec![PathBuf::from(".")],
}
}
pub fn add_search_path(&mut self, path: PathBuf) {
self.search_paths.push(path);
}
fn find_module(&self, module_name: &str) -> Option<PathBuf> {
for search_path in &self.search_paths {
let module_path = search_path.join(format!("{}.rs", module_name));
if module_path.exists() {
return Some(module_path);
}
}
None
}
pub fn import(&mut self, module_name: &str) -> Result<&Module, String> {
if !self.modules.contains_key(module_name) {
let module_path = self
.find_module(module_name)
.ok_or_else(|| format!("Module '{}' not found", module_name))?;
let contents = fs::read_to_string(&module_path)
.map_err(|e| format!("Failed to read module '{}': {}", module_name, e))?;
let ast = parse_file(&contents)
.map_err(|e| format!("Failed to parse module '{}': {}", module_name, e))?;
let mut symbols = HashMap::new();
for item in &ast.items {
match item {
Item::Fn(f) => {
symbols.insert(f.sig.ident.to_string(), f.to_token_stream());
}
Item::Struct(s) => {
symbols.insert(s.ident.to_string(), s.to_token_stream());
}
Item::Enum(e) => {
symbols.insert(e.ident.to_string(), e.to_token_stream());
}
Item::Const(c) => {
symbols.insert(c.ident.to_string(), c.to_token_stream());
}
Item::Static(s) => {
symbols.insert(s.ident.to_string(), s.to_token_stream());
}
Item::Trait(t) => {
symbols.insert(t.ident.to_string(), t.to_token_stream());
}
Item::Impl(i) => {
if let Some((_, trait_path, _)) = &i.trait_ {
symbols.insert(
format!("impl_{}", trait_path.segments.last().unwrap().ident),
i.to_token_stream(),
);
} else {
symbols.insert(
format!("impl_{}", i.self_ty.to_token_stream()),
i.to_token_stream(),
);
}
}
_ => {}
}
}
self.modules.insert(
module_name.to_string(),
Module {
name: module_name.to_string(),
path: module_path,
ast,
symbols,
},
);
}
Ok(self.modules.get(module_name).unwrap())
}
pub fn get_module(&self, module_name: &str) -> Option<&Module> {
self.modules.get(module_name)
}
pub fn get_symbol(&self, module_name: &str, symbol_name: &str) -> Option<&TokenStream> {
self.modules.get(module_name)?.symbols.get(symbol_name)
}
}
#[macro_export]
macro_rules! import {
($importer:expr, $module:expr) => {
$importer.import($module).unwrap()
};
}
#[macro_export]
macro_rules! from_import {
($importer:expr, $module:expr, $($item:ident),+) => {
$(
let $item = $importer.get_symbol($module, stringify!($item))
.unwrap_or_else(|| panic!("Symbol '{}' not found in module '{}'", stringify!($item), $module));
)+
};
}