use crate::parser::ParsedCommand;
use crate::logger::Logger; use crate::ast::ArgValue;
use crate::error::FCError; use crate::spec::{self, CommandSpec}; #[allow(unused_imports)]
use crate::types::{ValueKind, TypedValue};
#[cfg(feature = "duckdb")]
pub mod backend;
#[cfg(feature = "duckdb")]
use backend::Backend;
#[derive(Debug, PartialEq)]
pub enum ExecuteResult {
Success(String), Error(FCError), NoOutput, }
#[allow(dead_code)]
pub struct Executor {
#[cfg(feature = "duckdb")]
backend: Option<Box<dyn Backend + Send + Sync + 'static>>,
#[cfg(feature = "duckdb")]
state: std::collections::HashMap<String, String>,
}
impl Executor {
pub fn new() -> Self {
Executor {
#[cfg(feature = "duckdb")]
backend: None,
#[cfg(feature = "duckdb")]
state: std::collections::HashMap::new(),
}
}
#[cfg(feature = "duckdb")]
pub fn new_with_backend<T>(backend: Box<T>) -> Self
where
T: Backend + Send + Sync + 'static,
{
Executor {
backend: Some(backend),
..Executor::new()
}
}
pub fn execute_command(&self, parsed_command: &ParsedCommand, logger: &mut Logger) -> ExecuteResult {
logger.log_action(format!("Executing {}", parsed_command.name.to_uppercase()));
let mut _log_output = true; let _log_exempt;
#[cfg(feature = "duckdb")]
{
if let Some(ref backend) = self.backend {
logger.log_action(format!("Using DuckDB backend for {}", parsed_command.name));
return match backend.execute(parsed_command) {
Ok(result) => ExecuteResult::Success(result),
Err(e) => ExecuteResult::Error(FCError::BackendError(format!("DuckDB backend error: {}", e))),
};
}
}
let result = match parsed_command.name.as_str() {
"combine" => {
_log_output = false;
_log_exempt = true;
let mut typed_args: Vec<TypedValue> = Vec::new();
for arg in &parsed_command.args {
match arg {
ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
}
}
if !typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
logger.log_action(format!("Attempted COMBINE with invalid args: {:?}", typed_args));
return ExecuteResult::Error(FCError::InvalidArgument("combine command requires numeric arguments".to_string()));
}
let sum: f64 = typed_args.iter().map(|arg| match arg.value {
ArgValue::Number(n) => n,
_ => 0.0, }).sum();
let sum_string = if typed_args.is_empty() {
"-0".to_string()
} else {
let s = sum.to_string();
if s.ends_with(".0") {
s.trim_end_matches(".0").to_string()
} else {
s
}
};
logger.log_action(format!("Executed COMBINE with args {:?} -> {}", typed_args, sum_string));
ExecuteResult::Success(sum_string)
}
"predict" => {
let mut typed_args: Vec<TypedValue> = Vec::new();
for arg in &parsed_command.args {
match arg {
ArgValue::Number(n) => typed_args.push(TypedValue { kind: ValueKind::Number, value: ArgValue::Number(*n) }),
ArgValue::String(s) => typed_args.push(TypedValue { kind: ValueKind::String, value: ArgValue::String(s.clone()) }),
ArgValue::Bool(b) => typed_args.push(TypedValue { kind: ValueKind::Bool, value: ArgValue::Bool(*b) }),
ArgValue::Null => typed_args.push(TypedValue { kind: ValueKind::Null, value: ArgValue::Null }),
ArgValue::Table(tbl) => typed_args.push(TypedValue { kind: ValueKind::Table, value: ArgValue::Table(tbl.clone()) }),
}
}
if typed_args.len() < 2 {
logger.log_action(format!("Attempted PREDICT with insufficient args: {:?}", typed_args));
_log_exempt = true; return ExecuteResult::Error(FCError::InsufficientData("predict command requires at least two numeric arguments".to_string()));
}
if typed_args.iter().all(|arg| arg.kind == ValueKind::Number) {
let last_two: Vec<f64> = typed_args.iter().rev().take(2).map(|arg| match arg.value {
ArgValue::Number(n) => n,
_ => unreachable!(),
}).collect();
let prediction = 2.0 * last_two[0] - last_two[1];
logger.log_action(format!("Executed PREDICT with args {:?} -> Prediction: {}", typed_args, prediction));
_log_exempt = true; ExecuteResult::Success(format!("Prediction: {}", prediction))
} else {
logger.log_action(format!("Attempted PREDICT with invalid args: {:?}", typed_args));
_log_exempt = true; ExecuteResult::Error(FCError::InvalidArgument("predict command requires numeric arguments".to_string()))
}
}
"show" => {
_log_output = false; _log_exempt = true; if !parsed_command.args.is_empty() {
return ExecuteResult::Error(FCError::InvalidArgument("show command does not take arguments".to_string()));
}
let logs = logger.get_logs();
if logs.is_empty() {
ExecuteResult::Success("No logs available.".to_string())
} else {
ExecuteResult::Success(logs.join("\n"))
}
}
"help" => {
_log_output = false; _log_exempt = true; self.execute_help(parsed_command)
}
_ => {
_log_exempt = true; ExecuteResult::Error(FCError::UnknownCommand(format!("Command '{}' is recognized but not implemented.", parsed_command.name))) }
};
if matches!(result, ExecuteResult::Success(_)) {
_log_output = false; }
if _log_exempt {
return result;
}
match &result {
ExecuteResult::Error(err) => {
logger.log_action(format!("ERROR: Command '{}' failed: {}", parsed_command.name, err));
}
_ => {}
}
result
}
fn execute_help(&self, command: &ParsedCommand) -> ExecuteResult {
let mut target_command_name: Option<&str> = None;
let mut wants_json = false;
for arg in &command.args {
match arg {
ArgValue::String(s) if s == "--json" || s == "-j" => {
wants_json = true;
}
ArgValue::String(s) if !s.starts_with('-') && target_command_name.is_none() => {
target_command_name = Some(s);
}
_ => {
return ExecuteResult::Error(FCError::InvalidArgument(format!("Invalid argument for help: '{}'. Syntax: help [command_name] [--json | -j]", arg)));
}
}
}
if wants_json {
#[cfg(feature = "json")]
{
let result_json = if let Some(cmd_name) = target_command_name {
if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
serde_json::to_string_pretty(spec)
} else {
return ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)));
}
} else {
spec::get_specs_json()
};
match result_json {
Ok(json_string) => ExecuteResult::Success(json_string),
Err(e) => ExecuteResult::Error(FCError::InternalError(format!("Failed to serialize help to JSON: {}", e))),
}
}
#[cfg(not(feature = "json"))]
{
ExecuteResult::Error(FCError::InternalError("JSON output for help is not available because the 'json' feature was not enabled at compile time.".to_string()))
}
} else {
if let Some(cmd_name) = target_command_name {
if let Some(spec) = spec::SPECS.iter().find(|s| s.name == cmd_name) {
ExecuteResult::Success(self.format_single_command_help(spec))
} else {
ExecuteResult::Error(FCError::UnknownCommand(format!("Cannot provide help for unknown command: '{}'", cmd_name)))
}
} else {
ExecuteResult::Success(self.format_all_commands_help())
}
}
}
fn format_single_command_help(&self, spec: &CommandSpec) -> String {
let mut help_text = format!(
"Command: {}\nDescription: {}\nSyntax: {}\n",
spec.name, spec.description, spec.syntax
);
if !spec.arguments.is_empty() {
help_text.push_str("\nArguments:\n");
for arg_spec in spec.arguments {
let required_str = if arg_spec.required { "(required)" } else { "(optional)" };
help_text.push_str(&format!(
" {} - {} {}\n",
arg_spec.name, arg_spec.description, required_str
));
}
}
help_text
}
fn format_all_commands_help(&self) -> String {
let mut help_text = "Available commands:\n\n".to_string();
for spec in spec::SPECS.iter() {
help_text.push_str(&format!(
"{:<15} - {}\n{:<15} Syntax: {}\n\n",
spec.name,
spec.description,
"", spec.syntax
));
}
help_text.push_str("Type 'help <command_name>' for more details on a specific command.\n");
help_text.push_str("Type 'help --json' or 'help <command_name> --json' for JSON output (if enabled).");
help_text
}
}
pub struct ExecutionContext {
pub current_values: Vec<TypedValue>,
}
impl ExecutionContext {
pub fn new() -> Self {
ExecutionContext {
current_values: Vec::new(),
}
}
pub fn add_value(&mut self, value: TypedValue) {
self.current_values.push(value);
}
pub fn get_value(&self, index: usize) -> Option<&TypedValue> {
self.current_values.get(index)
}
}