1use crate::error::FCError; use crate::ast::ArgValue; use strsim::jaro_winkler;
5
6#[derive(Debug, PartialEq, Clone)] pub struct ParsedCommand {
8 pub name: String,
9 pub args: Vec<ArgValue>,
10}
11
12const KNOWN_COMMANDS: [&'static str; 4] = ["combine", "predict", "show", "help"]; const SIMILARITY_THRESHOLD: f64 = 0.85;
14
15fn suggest_command(input: &str) -> Option<&'static str> {
16 KNOWN_COMMANDS
17 .iter()
18 .map(|&cmd| (cmd, jaro_winkler(input, cmd)))
19 .filter(|(_, similarity)| *similarity >= SIMILARITY_THRESHOLD)
20 .max_by(|(_, sim1), (_, sim2)| sim1.partial_cmp(sim2).unwrap_or(std::cmp::Ordering::Equal))
21 .map(|(cmd, _)| cmd)
22}
23
24pub struct Parser;
25
26impl Parser {
27 pub fn new() -> Self {
28 Parser
29 }
30
31 pub fn parse(&self, input: &str) -> Result<ParsedCommand, FCError> {
33 let input = input.trim();
34 if input.is_empty() {
35 return Err(FCError::ParseError("empty input".to_string()));
36 }
37
38 let parts: Vec<&str> = input.split_whitespace().collect();
39 let command_name_str = parts[0];
40
41 if !KNOWN_COMMANDS.contains(&command_name_str) {
43 if let Some(s) = suggest_command(command_name_str) {
44 return Err(FCError::Suggestion(command_name_str.to_string(), s.to_string()));
45 } else {
46 return Err(FCError::UnknownCommand(command_name_str.to_string()));
47 }
48 }
49
50 let command_name = command_name_str.to_string();
51 let command_args = parts[1..]
52 .iter()
53 .map(|s| classify_arg(s))
54 .collect();
55
56 Ok(ParsedCommand {
57 name: command_name,
58 args: command_args,
59 })
60 }
61}
62
63pub fn all_command_names() -> Vec<&'static str> {
67 KNOWN_COMMANDS.to_vec()
68}
69
70fn classify_arg(raw: &str) -> ArgValue {
73 if raw.eq_ignore_ascii_case("true") {
75 return ArgValue::Bool(true);
76 }
77 if raw.eq_ignore_ascii_case("false") {
78 return ArgValue::Bool(false);
79 }
80 if raw.eq_ignore_ascii_case("null") {
82 return ArgValue::Null;
83 }
84 if let Ok(n) = raw.parse::<f64>() {
86 return ArgValue::Number(n);
87 }
88 #[cfg(feature = "typed-args")]
89 if let Ok(d) = chrono::NaiveDate::parse_from_str(raw, "%Y-%m-%d") {
90 return ArgValue::Date(d);
91 }
92 ArgValue::String(raw.to_string())
94}