flowcode_core/executor/
mod.rs1use crate::parser::ParsedCommand;
3use crate::logger::Logger; use crate::ast::ArgValue;
6use crate::error::FCError; use crate::spec::{self, CommandSpec}; #[allow(unused_imports)]
11use crate::types::{ValueKind, TypedValue};
12
13#[derive(Debug, PartialEq)]
15pub enum ExecuteResult {
16 Success(String), Error(FCError), NoOutput, }
20
21pub struct Executor;
22
23impl Executor {
24 pub fn new() -> Self {
25 Executor
26 }
27
28 pub fn execute_command(&self, parsed_command: &ParsedCommand, logger: &mut Logger) -> ExecuteResult {
30 logger.log_action(format!("Executing {}", parsed_command.name.to_uppercase()));
31 let mut _log_output = true; let _log_exempt; let result = match parsed_command.name.as_str() {
35 "combine" => {
36 _log_output = false;
37 _log_exempt = true; let mut typed_args: Vec<TypedValue> = Vec::new();
41 for arg in &parsed_command.args {
42 match arg {
43 ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
44 ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
45 ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
46 ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
47 ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
48 }
49 }
50
51 if !typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
53 logger.log_action(format!("Attempted COMBINE with invalid args: {:?}", typed_args));
54 return ExecuteResult::Error(FCError::InvalidArgument("combine command requires numeric arguments".to_string()));
55 }
56
57 let sum: f64 = typed_args.iter().map(|arg| match arg.value {
59 ArgValue::Number(n) => n,
60 _ => 0.0, }).sum();
62
63 let sum_string = if typed_args.is_empty() {
64 "-0".to_string()
65 } else {
66 let s = sum.to_string();
68 if s.ends_with(".0") {
69 s.trim_end_matches(".0").to_string()
70 } else {
71 s
72 }
73 };
74
75 logger.log_action(format!("Executed COMBINE with args {:?} -> {}", typed_args, sum_string));
76 ExecuteResult::Success(sum_string)
77 }
78 "predict" => {
79 let mut typed_args: Vec<TypedValue> = Vec::new();
80 for arg in &parsed_command.args {
81 match arg {
82 ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
83 ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
84 ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
85 ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
86 ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
87 }
88 }
89 if typed_args.len() < 2 {
90 logger.log_action(format!("Attempted PREDICT with insufficient args: {:?}", typed_args));
91 _log_exempt = true; return ExecuteResult::Error(FCError::InsufficientData("predict command requires at least two numeric arguments".to_string()));
93 }
94 if typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
95 let last_two: Vec<f64> = typed_args.iter().rev().take(2).map(|arg| match arg.value {
96 ArgValue::Number(n) => n,
97 _ => unreachable!(),
98 }).collect();
99 let prediction = 2.0 * last_two[0] - last_two[1];
100 logger.log_action(format!("Executed PREDICT with args {:?} -> Prediction: {}", typed_args, prediction));
101 _log_exempt = true; ExecuteResult::Success(format!("Prediction: {}", prediction))
103 } else {
104 logger.log_action(format!("Attempted PREDICT with invalid args: {:?}", typed_args));
105 _log_exempt = true; ExecuteResult::Error(FCError::InvalidArgument("predict command requires numeric arguments".to_string()))
107 }
108 }
109 "show" => {
110 _log_output = false; _log_exempt = true; if !parsed_command.args.is_empty() {
113 return ExecuteResult::Error(FCError::InvalidArgument("show command does not take arguments".to_string()));
114 }
115 let logs = logger.get_logs();
116 if logs.is_empty() {
117 ExecuteResult::Success("No logs available.".to_string())
118 } else {
119 ExecuteResult::Success(logs.join("\n"))
120 }
121 }
122 "help" => {
123 _log_output = false; _log_exempt = true; self.execute_help(parsed_command)
126 }
127 _ => {
128 _log_exempt = true; ExecuteResult::Error(FCError::UnknownCommand(format!("Command '{}' is recognized but not implemented.", parsed_command.name))) }
132 };
133
134 if matches!(result, ExecuteResult::Success(_)) {
135 _log_output = false; }
137
138 if _log_exempt {
139 return result;
140 }
141 match &result {
143 ExecuteResult::Error(err) => {
144 logger.log_action(format!("ERROR: Command '{}' failed: {}", parsed_command.name, err));
145 }
146 _ => {}
147 }
148 result
149 }
150
151 fn execute_help(&self, command: &ParsedCommand) -> ExecuteResult {
153 let mut target_command_name: Option<&str> = None;
154 let mut wants_json = false;
155
156 for arg in &command.args {
157 match arg {
158 ArgValue::String(s) if s == "--json" || s == "-j" => {
159 wants_json = true;
160 }
161 ArgValue::String(s) if !s.starts_with('-') && target_command_name.is_none() => {
162 target_command_name = Some(s);
163 }
164 _ => {
165 return ExecuteResult::Error(FCError::InvalidArgument(format!("Invalid argument for help: '{}'. Syntax: help [command_name] [--json | -j]", arg)));
166 }
167 }
168 }
169
170 if wants_json {
171 #[cfg(feature = "json")]
172 {
173 let result_json = if let Some(cmd_name) = target_command_name {
174 if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
175 serde_json::to_string_pretty(spec)
176 } else {
177 return ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)));
178 }
179 } else {
180 spec::get_specs_json()
181 };
182
183 match result_json {
184 Ok(json_string) => ExecuteResult::Success(json_string),
185 Err(e) => ExecuteResult::Error(FCError::InternalError(format!("Failed to serialize help to JSON: {}", e))),
186 }
187 }
188 #[cfg(not(feature = "json"))]
189 {
190 ExecuteResult::Error(FCError::InternalError("JSON output for help is not available because the 'json' feature was not enabled at compile time.".to_string()))
191 }
192 } else {
193 if let Some(cmd_name) = target_command_name {
195 if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
196 ExecuteResult::Success(self.format_single_command_help(spec))
197 } else {
198 ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)))
199 }
200 } else {
201 ExecuteResult::Success(self.format_all_commands_help())
202 }
203 }
204 }
205
206 fn format_single_command_help(&self, spec: &CommandSpec) -> String {
207 let mut help_text = format!(
208 "Command: {}\nDescription: {}\nSyntax: {}\n",
209 spec.name, spec.description, spec.syntax
210 );
211 if !spec.arguments.is_empty() {
212 help_text.push_str("\nArguments:\n");
213 for arg_spec in spec.arguments {
214 let required_str = if arg_spec.required { "(required)" } else { "(optional)" };
215 help_text.push_str(&format!(
216 " {} - {} {}\n",
217 arg_spec.name, arg_spec.description, required_str
218 ));
219 }
220 }
221 help_text
222 }
223
224 fn format_all_commands_help(&self) -> String {
225 let mut help_text = "Available commands:\n\n".to_string();
226 for spec in spec::SPECS.iter() {
227 help_text.push_str(&format!(
228 "{:<15} - {}\n{:<15} Syntax: {}\n\n",
229 spec.name,
230 spec.description,
231 "", spec.syntax
233 ));
234 }
235 help_text.push_str("Type 'help <command_name>' for more details on a specific command.\n");
236 help_text.push_str("Type 'help --json' or 'help <command_name> --json' for JSON output (if enabled).");
237 help_text
238 }
239}
240
241pub struct ExecutionContext {
243 pub current_values: Vec<TypedValue>,
244}
245
246impl ExecutionContext {
247 pub fn new() -> Self {
248 ExecutionContext {
249 current_values: Vec::new(),
250 }
251 }
252
253 pub fn add_value(&mut self, value: TypedValue) {
254 self.current_values.push(value);
255 }
256
257 pub fn get_value(&self, index: usize) -> Option<&TypedValue> {
258 self.current_values.get(index)
259 }
260}