pub mod types;
pub use types::*;
#[cfg(test)]
mod test;
pub fn parse_command(line: &str) -> crate::error::Result<ReplCommand> {
let trimmed = line.trim();
if trimmed.is_empty() {
return Err(parse_err("empty input", 0, 0));
}
let (verb, rest) = split_token(trimmed)?;
match verb.to_lowercase().as_str() {
"help" | "?" => Ok(ReplCommand::Help),
"quit" | "exit" | "q" => Ok(ReplCommand::Quit),
"load" => {
let (path, _) = require_token(rest, "load <path>")?;
Ok(ReplCommand::Load { path })
}
"compile" => {
let (name, _) = require_token(rest, "compile <name>")?;
Ok(ReplCommand::Compile { name })
}
"run" => {
let (graph, rest) = require_token(rest, "run <graph> <input>")?;
let (input, _) = require_token(rest, "run <graph> <input>")?;
Ok(ReplCommand::Run { graph, input })
}
"set" => {
let (key, rest) = require_token(rest, "set <key> <value>")?;
let (value, _) = require_token(rest, "set <key> <value>")?;
Ok(ReplCommand::Set { key, value })
}
"get" => {
let (key, _) = require_token(rest, "get <key>")?;
Ok(ReplCommand::Get { key })
}
"show" => {
let (what, _) = require_token(rest, "show <vars|graphs|status>")?;
Ok(ReplCommand::Show { what })
}
"call" => {
let (capability, json_rest) = require_token(rest, "call <capability> <json>")?;
let json_str = json_rest.trim();
if json_str.is_empty() {
return Err(parse_err(
"call requires a JSON argument: call <capability> <json>",
0,
0,
));
}
let args: serde_json::Value = serde_json::from_str(json_str)
.map_err(|e| parse_err(&format!("invalid JSON argument for `call`: {e}"), 0, 0))?;
Ok(ReplCommand::Call { capability, args })
}
other => Err(parse_err(&format!("unknown command `{other}`"), 0, 0)),
}
}
fn parse_err(message: &str, line: usize, column: usize) -> crate::error::RustAgentsError {
crate::error::RustAgentsError::Parse {
message: message.to_string(),
line,
column,
}
}
fn split_token(s: &str) -> crate::error::Result<(String, &str)> {
let s = s.trim_start();
if s.is_empty() {
return Err(parse_err("unexpected end of input", 0, 0));
}
if s.starts_with('"') {
let inner = s
.strip_prefix('"')
.expect("already checked starts_with('\"')");
let mut token = String::new();
let inner_offset: usize = 'scan: {
let mut chars = inner.char_indices();
loop {
match chars.next() {
None => {
return Err(parse_err("unterminated quoted string", 0, 0));
}
Some((i, '"')) => break 'scan i,
Some((_, '\\')) => match chars.next() {
Some((_, '"')) => token.push('"'),
Some((_, '\\')) => token.push('\\'),
Some((_, 'n')) => token.push('\n'),
Some((_, 't')) => token.push('\t'),
Some((_, c)) => {
token.push('\\');
token.push(c);
}
None => {
return Err(parse_err("unterminated escape sequence", 0, 0));
}
},
Some((_, c)) => token.push(c),
}
}
};
let remainder = &s[1 + inner_offset + 1..];
Ok((token, remainder))
} else {
let end = s
.char_indices()
.find(|(_, c)| c.is_whitespace())
.map(|(i, _)| i)
.unwrap_or(s.len());
let token = s[..end].to_string();
let remainder = &s[end..];
Ok((token, remainder))
}
}
fn require_token<'a>(s: &'a str, usage: &str) -> crate::error::Result<(String, &'a str)> {
let s = s.trim_start();
if s.is_empty() {
return Err(parse_err(
&format!("missing argument — usage: {usage}"),
0,
0,
));
}
split_token(s)
}