use crate::server::brp_messages::{BrpRequest, QueryFilter};
use crate::server::error::{Error, Result};
use crate::server::semantic_analyzer::{SemanticAnalyzer, SemanticQueryResult};
use regex::Regex;
use std::collections::HashMap;
pub trait QueryParser {
fn parse(&self, query: &str) -> Result<BrpRequest>;
fn parse_semantic(&self, query: &str) -> Result<SemanticQueryResult>;
fn help(&self) -> &str;
}
pub struct RegexQueryParser {
patterns: Vec<QueryPattern>,
semantic_analyzer: SemanticAnalyzer,
}
struct QueryPattern {
pattern: Regex,
builder: fn(®ex::Captures) -> Result<BrpRequest>,
description: &'static str,
}
fn make_query_request(filter: Option<QueryFilter>) -> BrpRequest {
BrpRequest {
method: crate::server::brp::builtin_methods::BRP_QUERY_METHOD.to_string(),
id: None,
params: Some(crate::server::brp::query_params(filter.as_ref(), true)),
}
}
fn make_get_request(entity: u64, components: Option<Vec<String>>) -> BrpRequest {
BrpRequest {
method: crate::server::brp::builtin_methods::BRP_GET_COMPONENTS_METHOD.to_string(),
id: None,
params: Some(serde_json::json!({
"entity": entity,
"components": components.unwrap_or_default(),
"strict": false
})),
}
}
fn make_list_components_request() -> BrpRequest {
BrpRequest {
method: crate::server::brp::builtin_methods::BRP_LIST_COMPONENTS_METHOD.to_string(),
id: None,
params: None,
}
}
impl RegexQueryParser {
pub fn new() -> Result<Self> {
let patterns = vec![
QueryPattern {
pattern: Regex::new(r"^(?i)list\s+all\s+entities?$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |_| Ok(make_query_request(None)),
description: "list all entities - List all entities in the game",
},
QueryPattern {
pattern: Regex::new(r"^(?i)show\s+entity\s+(\d+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let entity_id = caps[1].parse::<u64>()
.map_err(|_| Error::Brp("Invalid entity ID".to_string()))?;
Ok(make_get_request(entity_id, None))
},
description: "show entity X - Show details for entity with ID X",
},
QueryPattern {
pattern: Regex::new(r"^(?i)find\s+entities\s+with\s+component\s+([a-zA-Z_:]+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let component_type = caps[1].to_string();
Ok(make_query_request(Some(QueryFilter {
with: Some(vec![component_type]),
without: None,
where_clause: None,
})))
},
description: "find entities with component Y - Find all entities that have component Y",
},
QueryPattern {
pattern: Regex::new(r"^(?i)find\s+entities\s+without\s+component\s+([a-zA-Z_:]+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let component_type = caps[1].to_string();
Ok(make_query_request(Some(QueryFilter {
with: None,
without: Some(vec![component_type]),
where_clause: None,
})))
},
description: "find entities without component Y - Find all entities that don't have component Y",
},
QueryPattern {
pattern: Regex::new(r"^(?i)list\s+components?$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |_| Ok(make_list_components_request()),
description: "list components - List all available component types",
},
QueryPattern {
pattern: Regex::new(r"^(?i)find\s+entities\s+with\s+(?:components?\s+)?([a-zA-Z_:,\s]+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let components_str = &caps[1];
let components: Vec<String> = components_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
if components.is_empty() {
return Err(Error::Brp("No components specified".to_string()));
}
Ok(make_query_request(Some(QueryFilter {
with: Some(components),
without: None,
where_clause: None,
})))
},
description: "find entities with A, B, C - Find entities that have all specified components",
},
QueryPattern {
pattern: Regex::new(r"^(?i)show\s+entity\s+(\d+)\s+components?\s+([a-zA-Z_:,\s]+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let entity_id = caps[1].parse::<u64>()
.map_err(|_| Error::Brp("Invalid entity ID".to_string()))?;
let components_str = &caps[2];
let components: Vec<String> = components_str
.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect();
Ok(make_get_request(
entity_id,
if components.is_empty() { None } else { Some(components) },
))
},
description: "show entity X components A, B - Show specific components of entity X",
},
QueryPattern {
pattern: Regex::new(r"^(?i)find\s+(\d+)\s+entities\s+with\s+component\s+([a-zA-Z_:]+)$")
.map_err(|e| Error::Validation(format!("Invalid regex pattern: {}", e)))?,
builder: |caps| {
let _limit = caps[1].parse::<usize>()
.map_err(|_| Error::Brp("Invalid limit".to_string()))?;
let component_type = caps[2].to_string();
Ok(make_query_request(Some(QueryFilter {
with: Some(vec![component_type]),
without: None,
where_clause: None,
})))
},
description: "find N entities with component Y - Find up to N entities with component Y",
},
];
Ok(Self {
patterns,
semantic_analyzer: SemanticAnalyzer::new()?,
})
}
}
impl Default for RegexQueryParser {
fn default() -> Self {
Self::new().expect("Default regex patterns should be valid")
}
}
impl QueryParser for RegexQueryParser {
fn parse(&self, query: &str) -> Result<BrpRequest> {
let query = query.trim();
if query.is_empty() {
return Err(Error::Brp("Empty query".to_string()));
}
if let Ok(semantic_result) = self.semantic_analyzer.analyze(query) {
return Ok(semantic_result.request);
}
for pattern in &self.patterns {
if let Some(captures) = pattern.pattern.captures(query) {
return (pattern.builder)(&captures);
}
}
Err(Error::Brp(format!(
"Unrecognized query pattern: '{}'. {}",
query,
self.help()
)))
}
fn parse_semantic(&self, query: &str) -> Result<SemanticQueryResult> {
self.semantic_analyzer.analyze(query)
}
fn help(&self) -> &str {
"Supported query patterns:\n\
Basic queries:\n\
- list all entities\n\
- show entity X\n\
- find entities with component Y\n\
- find entities without component Y\n\
- find entities with A, B, C\n\
- show entity X components A, B\n\
- find N entities with component Y\n\
- list components\n\
\n\
Semantic queries:\n\
- find stuck entities\n\
- show fast moving objects\n\
- find overlapping colliders\n\
- find memory leaks\n\
- find inconsistent state\n\
- find physics violations\n\
- find stuck and fast entities (compound)"
}
}
#[derive(Debug, Clone)]
pub struct QueryMetrics {
pub query: String,
pub execution_time_ms: u64,
pub entity_count: usize,
pub cache_hit: bool,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone)]
pub struct QueryResult {
pub result: serde_json::Value,
pub metrics: QueryMetrics,
}
#[derive(Debug, Clone)]
struct CachedResult {
result: serde_json::Value,
timestamp: std::time::Instant,
entity_count: usize,
}
pub struct QueryCache {
cache: dashmap::DashMap<String, CachedResult>,
ttl_seconds: u64,
}
impl QueryCache {
#[must_use]
pub fn new(ttl_seconds: u64) -> Self {
Self {
cache: dashmap::DashMap::new(),
ttl_seconds,
}
}
pub fn get(&self, query: &str) -> Option<(serde_json::Value, usize)> {
let entry = self.cache.get(query)?;
if entry.timestamp.elapsed().as_secs() > self.ttl_seconds {
drop(entry);
self.cache.remove(query);
return None;
}
Some((entry.result.clone(), entry.entity_count))
}
pub fn set(&self, query: String, result: serde_json::Value, entity_count: usize) {
self.cache.insert(
query,
CachedResult {
result,
timestamp: std::time::Instant::now(),
entity_count,
},
);
}
pub fn cleanup(&self) {
let cutoff = std::time::Instant::now() - std::time::Duration::from_secs(self.ttl_seconds);
self.cache.retain(|_, entry| entry.timestamp > cutoff);
}
pub fn stats(&self) -> HashMap<String, usize> {
let mut stats = HashMap::new();
stats.insert("total_entries".to_string(), self.cache.len());
let expired_count = self
.cache
.iter()
.filter(|entry| entry.timestamp.elapsed().as_secs() > self.ttl_seconds)
.count();
stats.insert("expired_entries".to_string(), expired_count);
stats
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_query_parsing() {
let parser = RegexQueryParser::new().unwrap();
let result = parser.parse("list all entities").unwrap();
assert_eq!(result.method, crate::server::brp::builtin_methods::BRP_QUERY_METHOD);
let result = parser.parse("show entity 123").unwrap();
assert_eq!(result.method, crate::server::brp::builtin_methods::BRP_GET_COMPONENTS_METHOD);
let entity = result
.params
.as_ref()
.and_then(|p| p.get("entity"))
.and_then(|e| e.as_u64())
.unwrap();
assert_eq!(entity, 123);
let result = parser
.parse("find entities with component Transform")
.unwrap();
assert_eq!(result.method, crate::server::brp::builtin_methods::BRP_QUERY_METHOD);
let with = result
.params
.as_ref()
.and_then(|p| p.get("filter"))
.and_then(|f| f.get("with"))
.and_then(|w| w.as_array())
.unwrap();
assert_eq!(with, &vec![serde_json::json!("Transform")]);
}
#[test]
fn test_case_insensitive_parsing() {
let parser = RegexQueryParser::new().unwrap();
let result1 = parser.parse("LIST ALL ENTITIES").unwrap();
let result2 = parser.parse("list all entities").unwrap();
assert_eq!(result1.method, crate::server::brp::builtin_methods::BRP_QUERY_METHOD);
assert_eq!(result2.method, crate::server::brp::builtin_methods::BRP_QUERY_METHOD);
}
#[test]
fn test_invalid_queries() {
let parser = RegexQueryParser::new().unwrap();
assert!(parser.parse("").is_err());
assert!(parser.parse("invalid query").is_err());
assert!(parser.parse("show entity abc").is_err()); }
#[test]
fn test_query_cache() {
let cache = QueryCache::new(300);
let query = "list all entities";
let result = serde_json::json!({"entities": []});
assert!(cache.get(query).is_none());
cache.set(query.to_string(), result.clone(), 0);
let cached = cache.get(query).unwrap();
assert_eq!(cached.0, result);
assert_eq!(cached.1, 0);
}
#[test]
fn test_multiple_components_query() {
let parser = RegexQueryParser::new().unwrap();
let result = parser
.parse("find entities with Transform, Velocity, Health")
.unwrap();
assert_eq!(result.method, crate::server::brp::builtin_methods::BRP_QUERY_METHOD);
let with = result
.params
.as_ref()
.and_then(|p| p.get("filter"))
.and_then(|f| f.get("with"))
.and_then(|w| w.as_array())
.unwrap();
assert_eq!(
with,
&vec![
serde_json::json!("Transform"),
serde_json::json!("Velocity"),
serde_json::json!("Health"),
]
);
}
}