use nom::{
branch::alt,
bytes::complete::{tag, take_while, take_while1},
character::complete::{char, multispace0, alpha1, alphanumeric1},
combinator::{map, recognize},
multi::{many0, separated_list0},
sequence::{delimited, pair},
IResult,
};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
String(String),
Identifier(String),
Array(Vec<Value>),
Object(HashMap<String, Value>),
Expression(String),
}
#[derive(Debug, Clone)]
pub struct AgentDef {
pub name: String,
pub instructions: String,
pub provider: String,
pub tools: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ToolDef {
pub name: String,
pub description: String,
pub parameters: Vec<ParameterDef>,
pub handler: String,
}
#[derive(Debug, Clone)]
pub struct ParameterDef {
pub name: String,
pub param_type: String,
pub description: String,
pub required: bool,
}
fn identifier(input: &str) -> IResult<&str, &str> {
recognize(pair(
alt((tag("_"), alpha1)),
many0(alt((
alphanumeric1,
tag("_"),
))),
))(input)
}
fn string_literal(input: &str) -> IResult<&str, String> {
delimited(
char('"'),
map(take_while(|c| c != '"'), |s: &str| s.to_string()),
char('"'),
)(input)
}
fn expression(input: &str) -> IResult<&str, String> {
let (input, expr) = take_while1(|c: char| {
c != ',' && c != '}' && c != ']' && c != '\n'
})(input)?;
Ok((input, expr.trim().to_string()))
}
fn array(input: &str) -> IResult<&str, Vec<Value>> {
delimited(
char('['),
separated_list0(
delimited(multispace0, char(','), multispace0),
delimited(multispace0, value, multispace0),
),
char(']'),
)(input)
}
fn value(input: &str) -> IResult<&str, Value> {
alt((
map(string_literal, Value::String),
map(array, Value::Array),
map(expression, |expr| {
if expr.chars().all(|c| c.is_alphanumeric() || c == '_') {
Value::Identifier(expr)
} else {
Value::Expression(expr)
}
}),
))(input)
}
fn key_value_pair(input: &str) -> IResult<&str, (String, Value)> {
let (input, key) = identifier(input)?;
let (input, _) = delimited(multispace0, char(':'), multispace0)(input)?;
let (input, val) = value(input)?;
Ok((input, (key.to_string(), val)))
}
fn object(input: &str) -> IResult<&str, HashMap<String, Value>> {
let (input, pairs) = delimited(
char('{'),
separated_list0(
delimited(multispace0, char(','), multispace0),
delimited(multispace0, key_value_pair, multispace0),
),
char('}'),
)(input)?;
let mut map = HashMap::new();
for (key, value) in pairs {
map.insert(key, value);
}
Ok((input, map))
}
pub fn parse_agent_macro(input: &str) -> Result<AgentDef, String> {
let result = delimited(multispace0, object, multispace0)(input);
match result {
Ok((remaining, obj)) => {
if !remaining.trim().is_empty() {
return Err(format!("Unexpected content after agent definition: {}", remaining));
}
let name = extract_string(&obj, "name")?;
let instructions = extract_string(&obj, "instructions")?;
let provider = extract_expression(&obj, "provider")?;
let tools = extract_tool_list(&obj, "tools")?;
Ok(AgentDef {
name,
instructions,
provider,
tools,
})
}
Err(e) => Err(format!("Failed to parse agent macro: {:?}", e)),
}
}
pub fn parse_tool_macro(input: &str) -> Result<ToolDef, String> {
let result = delimited(multispace0, object, multispace0)(input);
match result {
Ok((remaining, obj)) => {
if !remaining.trim().is_empty() {
return Err(format!("Unexpected content after tool definition: {}", remaining));
}
let name = extract_string(&obj, "name")?;
let description = extract_string(&obj, "description")?;
let handler = extract_expression(&obj, "handler")?;
let parameters = if let Some(Value::Array(params)) = obj.get("parameters") {
parse_parameters(params)?
} else {
Vec::new()
};
Ok(ToolDef {
name,
description,
parameters,
handler,
})
}
Err(e) => Err(format!("Failed to parse tool macro: {:?}", e)),
}
}
fn extract_string(obj: &HashMap<String, Value>, key: &str) -> Result<String, String> {
match obj.get(key) {
Some(Value::String(s)) => Ok(s.clone()),
Some(_) => Err(format!("Field '{}' must be a string", key)),
None => Err(format!("Missing required field '{}'", key)),
}
}
fn extract_expression(obj: &HashMap<String, Value>, key: &str) -> Result<String, String> {
match obj.get(key) {
Some(Value::Expression(e)) => Ok(e.clone()),
Some(Value::Identifier(i)) => Ok(i.clone()),
Some(_) => Err(format!("Field '{}' must be an expression or identifier", key)),
None => Err(format!("Missing required field '{}'", key)),
}
}
fn extract_tool_list(obj: &HashMap<String, Value>, key: &str) -> Result<Vec<String>, String> {
match obj.get(key) {
Some(Value::Array(arr)) => {
let mut tools = Vec::new();
for item in arr {
match item {
Value::Identifier(name) => tools.push(name.clone()),
_ => return Err("Tool list must contain only identifiers".to_string()),
}
}
Ok(tools)
}
Some(_) => Err(format!("Field '{}' must be an array", key)),
None => Ok(Vec::new()), }
}
fn parse_parameters(params: &[Value]) -> Result<Vec<ParameterDef>, String> {
let mut parameters = Vec::new();
for param in params {
match param {
Value::Object(param_obj) => {
let name = extract_string(param_obj, "name")?;
let param_type = extract_string(param_obj, "type")?;
let description = extract_string(param_obj, "description")?;
let required = param_obj.get("required")
.map(|v| match v {
Value::Identifier(s) => s == "true",
_ => false,
})
.unwrap_or(true);
parameters.push(ParameterDef {
name,
param_type,
description,
required,
});
}
_ => return Err("Parameters must be objects".to_string()),
}
}
Ok(parameters)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_agent() {
let input = r#"{
name: "test_agent",
instructions: "Test instructions",
provider: create_provider(),
tools: [tool1, tool2]
}"#;
let result = parse_agent_macro(input);
assert!(result.is_ok());
let agent = result.unwrap();
assert_eq!(agent.name, "test_agent");
assert_eq!(agent.instructions, "Test instructions");
assert_eq!(agent.provider, "create_provider()");
assert_eq!(agent.tools, vec!["tool1", "tool2"]);
}
#[test]
fn test_parse_simple_tool() {
let input = r#"{
name: "test_tool",
description: "Test tool description",
handler: handle_test
}"#;
let result = parse_tool_macro(input);
assert!(result.is_ok());
let tool = result.unwrap();
assert_eq!(tool.name, "test_tool");
assert_eq!(tool.description, "Test tool description");
assert_eq!(tool.handler, "handle_test");
}
}