duckscriptsdk 0.11.1

The duckscript SDK.
Documentation
use crate::utils::eval;
use duckscript::types::command::{CommandResult, Commands};
use duckscript::types::env::Env;
use duckscript::types::instruction::Instruction;
use duckscript::types::runtime::StateValue;
use std::collections::HashMap;

#[cfg(test)]
#[path = "./condition_test.rs"]
mod condition_test;

enum FoundToken {
    None,
    And,
    Or,
    Value,
}

pub(crate) fn is_true(value: Option<String>) -> bool {
    let failed = match value {
        Some(value_str) => {
            let lower_case = value_str.to_lowercase();
            lower_case == "" || lower_case == "0" || lower_case == "false" || lower_case == "no"
        }
        None => true,
    };

    !failed
}

pub(crate) fn eval_condition(
    arguments: &Vec<String>,
    instructions: &Vec<Instruction>,
    state: &mut HashMap<String, StateValue>,
    variables: &mut HashMap<String, String>,
    commands: &mut Commands,
    env: &mut Env,
) -> Result<bool, String> {
    if arguments.is_empty() {
        Ok(is_true(None))
    } else {
        let eval_statement = commands.exists(&arguments[0]);

        if eval_statement {
            match eval::eval_with_instructions(
                &arguments,
                instructions,
                state,
                variables,
                commands,
                env,
            ) {
                CommandResult::Continue(value) => {
                    let passed = is_true(value);

                    Ok(passed)
                }
                CommandResult::Error(error) => Err(error.to_string()),
                _ => Err("Invalid condition evaluation result.".to_string()),
            }
        } else {
            eval_condition_for_slice(&arguments[..])
        }
    }
}

pub(crate) fn eval_condition_for_slice(arguments: &[String]) -> Result<bool, String> {
    if arguments.is_empty() {
        Ok(is_true(None))
    } else {
        let mut searching_block_end = false;
        let mut start_block = 0;
        let mut counter = 0;
        let mut index = 0;
        let mut total_evaluated = None;
        let mut partial_evaluated = None;
        let mut found_token = FoundToken::None;
        for argument in arguments {
            if argument == "(" {
                searching_block_end = true;
                if counter == 0 {
                    start_block = index + 1
                }
                counter = counter + 1;
            } else if argument == ")" {
                counter = counter - 1;

                if counter == 0 {
                    searching_block_end = false;

                    match eval_condition_for_slice(&arguments[start_block..index]) {
                        Ok(evaluated) => {
                            start_block = 0;

                            match found_token {
                                FoundToken::None => {
                                    total_evaluated = Some(evaluated);
                                    found_token = FoundToken::Value;
                                }
                                FoundToken::And => {
                                    partial_evaluated = Some(evaluated);
                                    found_token = FoundToken::Value;
                                }
                                FoundToken::Or => {
                                    partial_evaluated =
                                        Some(evaluated || partial_evaluated.unwrap_or(false));
                                    found_token = FoundToken::Value;
                                }
                                FoundToken::Value => {
                                    return Err(
                                        format!("Unexpected value: {}", argument).to_string()
                                    );
                                }
                            }
                        }
                        Err(error) => return Err(error),
                    };
                } else if counter < 0 {
                    return Err("Unexpected ')'".to_string());
                }
            } else if !searching_block_end {
                if argument == "and" {
                    match found_token {
                        FoundToken::Value => {
                            found_token = FoundToken::And;

                            total_evaluated = Some(
                                total_evaluated.unwrap_or(true)
                                    && partial_evaluated.unwrap_or(true),
                            );
                            partial_evaluated = None;

                            if !total_evaluated.unwrap() {
                                return Ok(false);
                            }
                        }
                        _ => return Err("Unexpected 'and'".to_string()),
                    }
                } else if argument == "or" {
                    match found_token {
                        FoundToken::Value => found_token = FoundToken::Or,
                        _ => return Err("Unexpected 'or'".to_string()),
                    }
                } else {
                    let evaluated = is_true(Some(argument.to_string()));

                    match found_token {
                        FoundToken::None => {
                            partial_evaluated = Some(evaluated);
                            found_token = FoundToken::Value;
                        }
                        FoundToken::And => {
                            partial_evaluated = Some(evaluated);
                            found_token = FoundToken::Value;
                        }
                        FoundToken::Or => {
                            partial_evaluated =
                                Some(evaluated || partial_evaluated.unwrap_or(false));
                            found_token = FoundToken::Value;
                        }
                        FoundToken::Value => {
                            return Err(format!("Unexpected value: {}", argument).to_string());
                        }
                    }
                }
            }

            index = index + 1;
        }

        if searching_block_end {
            Err("Missing ')'".to_string())
        } else {
            let total_bool = if total_evaluated.is_none() && partial_evaluated.is_none() {
                is_true(None)
            } else {
                partial_evaluated.unwrap_or(true) && total_evaluated.unwrap_or(true)
            };

            Ok(total_bool)
        }
    }
}