use async_trait::async_trait;
use std::path::Path;
use thread_utilities::RapidMap;
use crate::error::{ParseError, ServiceResult};
use crate::types::{AnalysisContext, ParsedDocument};
cfg_if::cfg_if!(
if #[cfg(feature = "ast-grep-backend")] {
use thread_ast_engine::source::Doc;
use thread_ast_engine::Language;
use thread_language::SupportLang;
} else {
use crate::types::{Doc, SupportLang};
}
);
#[async_trait]
pub trait CodeParser<D: Doc + Send + Sync>: Send + Sync {
async fn parse_content(
&self,
content: &str,
language: SupportLang,
context: &AnalysisContext,
) -> ServiceResult<ParsedDocument<D>>;
async fn parse_file(
&self,
file_path: &Path,
context: &AnalysisContext,
) -> ServiceResult<ParsedDocument<D>>;
async fn parse_multiple_files(
&self,
file_paths: &[&Path],
context: &AnalysisContext,
) -> ServiceResult<Vec<ParsedDocument<D>>>;
fn capabilities(&self) -> ParserCapabilities;
fn supported_languages(&self) -> &[SupportLang];
fn detect_language(&self, file_path: &Path) -> ServiceResult<SupportLang> {
SupportLang::from_path(file_path).ok_or_else(|| {
ParseError::LanguageDetectionFailed {
file_path: file_path.to_path_buf(),
}
.into()
})
}
fn validate_content(&self, content: &str, _language: SupportLang) -> ServiceResult<()> {
if content.is_empty() {
return Err(ParseError::InvalidSource {
message: "Content is empty".into(),
}
.into());
}
let capabilities = self.capabilities();
if let Some(max_size) = capabilities.max_content_size
&& content.len() > max_size
{
return Err(ParseError::ContentTooLarge {
size: content.len(),
max_size,
}
.into());
}
Ok(())
}
fn preprocess_content(&self, content: &str, _language: SupportLang) -> String {
content.to_string()
}
async fn postprocess_document(
&self,
mut document: ParsedDocument<D>,
context: &AnalysisContext,
) -> ServiceResult<ParsedDocument<D>> {
self.collect_basic_metadata(&mut document, context).await?;
Ok(document)
}
async fn collect_basic_metadata(
&self,
_document: &mut ParsedDocument<D>,
_context: &AnalysisContext,
) -> ServiceResult<()> {
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct ParserCapabilities {
pub max_content_size: Option<usize>,
pub max_concurrent_files: Option<usize>,
pub execution_strategies: Vec<ExecutionStrategy>,
pub supports_incremental: bool,
pub supports_error_recovery: bool,
pub supports_metadata_collection: bool,
pub supports_cross_file_analysis: bool,
pub performance_profile: PerformanceProfile,
pub capability_flags: RapidMap<String, bool>,
}
impl Default for ParserCapabilities {
fn default() -> Self {
Self {
max_content_size: Some(10 * 1024 * 1024), max_concurrent_files: Some(100),
execution_strategies: vec![ExecutionStrategy::Sequential, ExecutionStrategy::Rayon],
supports_incremental: false,
supports_error_recovery: true,
supports_metadata_collection: true,
supports_cross_file_analysis: false,
performance_profile: PerformanceProfile::Balanced,
capability_flags: thread_utilities::get_map(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionStrategy {
Sequential,
Rayon,
Chunked { chunk_size: usize },
Custom(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum PerformanceProfile {
LowMemory,
FastParsing,
Balanced,
HighThroughput,
}
#[derive(Debug, Clone)]
pub struct ParserConfig {
pub collect_metadata: bool,
pub enable_error_recovery: bool,
pub execution_strategy: Option<ExecutionStrategy>,
pub custom_options: RapidMap<String, String>,
}
impl Default for ParserConfig {
fn default() -> Self {
Self {
collect_metadata: true,
enable_error_recovery: true,
execution_strategy: None, custom_options: thread_utilities::get_map(),
}
}
}
pub trait ParserFactory<D: Doc + Send + Sync>: Send + Sync {
fn create_parser(&self) -> Box<dyn CodeParser<D>>;
fn create_configured_parser(&self, config: ParserConfig) -> Box<dyn CodeParser<D>>;
fn available_parsers(&self) -> Vec<String>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parser_capabilities_default() {
let caps = ParserCapabilities::default();
assert!(caps.supports_metadata_collection);
assert!(caps.supports_error_recovery);
assert!(!caps.supports_cross_file_analysis);
assert_eq!(caps.performance_profile, PerformanceProfile::Balanced);
}
#[test]
fn test_parser_config_default() {
let config = ParserConfig::default();
assert!(config.collect_metadata);
assert!(config.enable_error_recovery);
assert!(config.execution_strategy.is_none());
}
}