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#[cfg(feature = "duckdb")]
14pub mod backend;
15#[cfg(feature = "duckdb")]
16use backend::Backend;
17
18#[derive(Debug, PartialEq)]
20pub enum ExecuteResult {
21 Success(String), Error(FCError), NoOutput, }
25
26#[allow(dead_code)]
27pub struct Executor {
28 #[cfg(feature = "duckdb")]
29 backend: Option<Box<dyn Backend + Send + Sync + 'static>>,
30 #[cfg(feature = "duckdb")]
31 state: std::collections::HashMap<String, String>,
32}
33
34impl Executor {
35 pub fn new() -> Self {
36 Executor {
37 #[cfg(feature = "duckdb")]
38 backend: None,
39 #[cfg(feature = "duckdb")]
40 state: std::collections::HashMap::new(),
41 }
42 }
43
44 #[cfg(feature = "duckdb")]
46 pub fn new_with_backend<T>(backend: Box<T>) -> Self
47 where
48 T: Backend + Send + Sync + 'static,
49 {
50 Executor {
51 backend: Some(backend),
52 ..Executor::new()
53 }
54 }
55
56 pub fn execute_command(&self, parsed_command: &ParsedCommand, logger: &mut Logger) -> ExecuteResult {
58 logger.log_action(format!("Executing {}", parsed_command.name.to_uppercase()));
59 let mut _log_output = true; let _log_exempt; #[cfg(feature = "duckdb")]
64 {
65 if let Some(ref backend) = self.backend {
66 logger.log_action(format!("Using DuckDB backend for {}", parsed_command.name));
67 return match backend.execute(parsed_command) {
68 Ok(result) => ExecuteResult::Success(result),
69 Err(e) => ExecuteResult::Error(FCError::BackendError(format!("DuckDB backend error: {}", e))),
70 };
71 }
72 }
73
74 let result = match parsed_command.name.as_str() {
75 "combine" => {
76 _log_output = false;
77 _log_exempt = true; let mut typed_args: Vec<TypedValue> = Vec::new();
81 for arg in &parsed_command.args {
82 match arg {
83 ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
84 ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
85 ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
86 ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
87 ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
88 }
89 }
90
91 if !typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
93 logger.log_action(format!("Attempted COMBINE with invalid args: {:?}", typed_args));
94 return ExecuteResult::Error(FCError::InvalidArgument("combine command requires numeric arguments".to_string()));
95 }
96
97 let sum: f64 = typed_args.iter().map(|arg| match arg.value {
99 ArgValue::Number(n) => n,
100 _ => 0.0, }).sum();
102
103 let sum_string = if typed_args.is_empty() {
104 "-0".to_string()
105 } else {
106 let s = sum.to_string();
108 if s.ends_with(".0") {
109 s.trim_end_matches(".0").to_string()
110 } else {
111 s
112 }
113 };
114
115 logger.log_action(format!("Executed COMBINE with args {:?} -> {}", typed_args, sum_string));
116 ExecuteResult::Success(sum_string)
117 }
118 "predict" => {
119 let mut typed_args: Vec<TypedValue> = Vec::new();
120 for arg in &parsed_command.args {
121 match arg {
122 ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
123 ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
124 ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
125 ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
126 ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
127 }
128 }
129 if typed_args.len() < 2 {
130 logger.log_action(format!("Attempted PREDICT with insufficient args: {:?}", typed_args));
131 _log_exempt = true; return ExecuteResult::Error(FCError::InsufficientData("predict command requires at least two numeric arguments".to_string()));
133 }
134 if typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
135 let last_two: Vec<f64> = typed_args.iter().rev().take(2).map(|arg| match arg.value {
136 ArgValue::Number(n) => n,
137 _ => unreachable!(),
138 }).collect();
139 let prediction = 2.0 * last_two[0] - last_two[1];
140 logger.log_action(format!("Executed PREDICT with args {:?} -> Prediction: {}", typed_args, prediction));
141 _log_exempt = true; ExecuteResult::Success(format!("Prediction: {}", prediction))
143 } else {
144 logger.log_action(format!("Attempted PREDICT with invalid args: {:?}", typed_args));
145 _log_exempt = true; ExecuteResult::Error(FCError::InvalidArgument("predict command requires numeric arguments".to_string()))
147 }
148 }
149 "show" => {
150 _log_output = false; _log_exempt = true; if !parsed_command.args.is_empty() {
153 return ExecuteResult::Error(FCError::InvalidArgument("show command does not take arguments".to_string()));
154 }
155 let logs = logger.get_logs();
156 if logs.is_empty() {
157 ExecuteResult::Success("No logs available.".to_string())
158 } else {
159 ExecuteResult::Success(logs.join("\n"))
160 }
161 }
162 "help" => {
163 _log_output = false; _log_exempt = true; self.execute_help(parsed_command)
166 }
167 _ => {
168 _log_exempt = true; ExecuteResult::Error(FCError::UnknownCommand(format!("Command '{}' is recognized but not implemented.", parsed_command.name))) }
172 };
173
174 if matches!(result, ExecuteResult::Success(_)) {
175 _log_output = false; }
177
178 if _log_exempt {
179 return result;
180 }
181 match &result {
183 ExecuteResult::Error(err) => {
184 logger.log_action(format!("ERROR: Command '{}' failed: {}", parsed_command.name, err));
185 }
186 _ => {}
187 }
188 result
189 }
190
191 fn execute_help(&self, command: &ParsedCommand) -> ExecuteResult {
193 let mut target_command_name: Option<&str> = None;
194 let mut wants_json = false;
195
196 for arg in &command.args {
197 match arg {
198 ArgValue::String(s) if s == "--json" || s == "-j" => {
199 wants_json = true;
200 }
201 ArgValue::String(s) if !s.starts_with('-') && target_command_name.is_none() => {
202 target_command_name = Some(s);
203 }
204 _ => {
205 return ExecuteResult::Error(FCError::InvalidArgument(format!("Invalid argument for help: '{}'. Syntax: help [command_name] [--json | -j]", arg)));
206 }
207 }
208 }
209
210 if wants_json {
211 #[cfg(feature = "json")]
212 {
213 let result_json = if let Some(cmd_name) = target_command_name {
214 if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
215 serde_json::to_string_pretty(spec)
216 } else {
217 return ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)));
218 }
219 } else {
220 spec::get_specs_json()
221 };
222
223 match result_json {
224 Ok(json_string) => ExecuteResult::Success(json_string),
225 Err(e) => ExecuteResult::Error(FCError::InternalError(format!("Failed to serialize help to JSON: {}", e))),
226 }
227 }
228 #[cfg(not(feature = "json"))]
229 {
230 ExecuteResult::Error(FCError::InternalError("JSON output for help is not available because the 'json' feature was not enabled at compile time.".to_string()))
231 }
232 } else {
233 if let Some(cmd_name) = target_command_name {
235 if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
236 ExecuteResult::Success(self.format_single_command_help(spec))
237 } else {
238 ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)))
239 }
240 } else {
241 ExecuteResult::Success(self.format_all_commands_help())
242 }
243 }
244 }
245
246 fn format_single_command_help(&self, spec: &CommandSpec) -> String {
247 let mut help_text = format!(
248 "Command: {}\nDescription: {}\nSyntax: {}\n",
249 spec.name, spec.description, spec.syntax
250 );
251 if !spec.arguments.is_empty() {
252 help_text.push_str("\nArguments:\n");
253 for arg_spec in spec.arguments {
254 let required_str = if arg_spec.required { "(required)" } else { "(optional)" };
255 help_text.push_str(&format!(
256 " {} - {} {}\n",
257 arg_spec.name, arg_spec.description, required_str
258 ));
259 }
260 }
261 help_text
262 }
263
264 fn format_all_commands_help(&self) -> String {
265 let mut help_text = "Available commands:\n\n".to_string();
266 for spec in spec::SPECS.iter() {
267 help_text.push_str(&format!(
268 "{:<15} - {}\n{:<15} Syntax: {}\n\n",
269 spec.name,
270 spec.description,
271 "", spec.syntax
273 ));
274 }
275 help_text.push_str("Type 'help <command_name>' for more details on a specific command.\n");
276 help_text.push_str("Type 'help --json' or 'help <command_name> --json' for JSON output (if enabled).");
277 help_text
278 }
279}
280
281pub struct ExecutionContext {
283 pub current_values: Vec<TypedValue>,
284}
285
286impl ExecutionContext {
287 pub fn new() -> Self {
288 ExecutionContext {
289 current_values: Vec::new(),
290 }
291 }
292
293 pub fn add_value(&mut self, value: TypedValue) {
294 self.current_values.push(value);
295 }
296
297 pub fn get_value(&self, index: usize) -> Option<&TypedValue> {
298 self.current_values.get(index)
299 }
300}