use crate::error::{McpError, McpResult};
use crate::protocol::ToolCallResult;
use crate::tools::{error_response, text_response};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::sync::Arc;
use tokio::sync::Mutex;
use windjammer::lexer::Lexer;
use windjammer::parser::Parser;
use windjammer_lsp::database::WindjammerDatabase;
#[derive(Debug, Deserialize)]
struct ParseCodeRequest {
code: String,
#[serde(default = "default_true")]
include_diagnostics: bool,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Serialize)]
struct ParseCodeResponse {
success: bool,
#[serde(skip_serializing_if = "Option::is_none")]
ast: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
diagnostics: Option<Vec<Diagnostic>>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
}
#[derive(Debug, Serialize)]
struct Diagnostic {
message: String,
severity: String,
#[serde(skip_serializing_if = "Option::is_none")]
location: Option<DiagnosticLocation>,
}
#[derive(Debug, Serialize)]
struct DiagnosticLocation {
line: usize,
column: usize,
}
pub async fn handle(
_db: Arc<Mutex<WindjammerDatabase>>,
arguments: Value,
) -> McpResult<ToolCallResult> {
let request: ParseCodeRequest =
serde_json::from_value(arguments).map_err(|e| McpError::ValidationError {
field: "arguments".to_string(),
message: e.to_string(),
})?;
if request.code.len() > 1_000_000 {
return Ok(error_response("Code too large (max 1MB)"));
}
let mut lexer = Lexer::new(&request.code);
let tokens = lexer.tokenize_with_locations();
let mut parser = Parser::new(tokens);
let parse_result = parser.parse();
let response = match parse_result {
Ok(program) => {
let ast_json = serde_json::json!({
"type": "Program",
"items_count": program.items.len(),
"summary": format!("{} items", program.items.len()),
"debug": format!("{:#?}", program)
});
ParseCodeResponse {
success: true,
ast: Some(ast_json),
diagnostics: if request.include_diagnostics {
Some(vec![]) } else {
None
},
error: None,
}
}
Err(e) => {
let diagnostics = if request.include_diagnostics {
Some(vec![Diagnostic {
message: e.to_string(),
severity: "error".to_string(),
location: None, }])
} else {
None
};
ParseCodeResponse {
success: false,
ast: None,
diagnostics,
error: Some(e.to_string()),
}
}
};
let response_json =
serde_json::to_string_pretty(&response).map_err(|e| McpError::InternalError {
message: format!("Failed to serialize response: {}", e),
})?;
Ok(text_response(response_json))
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_parse_valid_code() {
let db = Arc::new(Mutex::new(WindjammerDatabase::new()));
let args = serde_json::json!({
"code": "fn main() { println!(\"Hello\") }",
"include_diagnostics": true
});
let result = handle(db, args).await.unwrap();
assert!(!result.is_error);
let text = match &result.content[0] {
crate::protocol::ToolContent::Text { text } => text,
_ => panic!("Expected text content"),
};
assert!(text.contains("success") && text.contains("true"));
assert!(text.contains("items_count"));
}
#[tokio::test]
async fn test_parse_invalid_code() {
let db = Arc::new(Mutex::new(WindjammerDatabase::new()));
let args = serde_json::json!({
"code": "fn main() {{{", "include_diagnostics": true
});
let result = handle(db, args).await.unwrap();
assert!(!result.is_error);
let text = match &result.content[0] {
crate::protocol::ToolContent::Text { text } => text,
_ => panic!("Expected text content"),
};
assert!(text.contains("success") && text.contains("false"));
assert!(text.contains("error") || text.contains("diagnostics"));
}
#[tokio::test]
async fn test_parse_code_too_large() {
let db = Arc::new(Mutex::new(WindjammerDatabase::new()));
let large_code = "x".repeat(2_000_000); let args = serde_json::json!({
"code": large_code,
"include_diagnostics": false
});
let result = handle(db, args).await.unwrap();
assert!(result.is_error);
}
}