diff --git a/CHANGELOG.md b/CHANGELOG.md
index cf8104c..2a2375c 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### v0.1.9
+* The if/else and not commands now support complex conditions.
* Release command now support recursive option.
* New map_clear command.
* New map_to_properties command.
diff --git a/README.md b/README.md
index 9da5064..8aed200 100644
--- a/README.md
+++ b/README.md
@@ -348,12 +348,12 @@ end
value = set false
if ${value}
echo should not be here
-elseif true
+elseif true or false
echo in else if but not done yet
value = set true
- if not false
+ if not true and false
echo nested if
value = set "some text"
diff --git a/docs/_includes/content.md b/docs/_includes/content.md
index 9af612d..6e178da 100755
--- a/docs/_includes/content.md
+++ b/docs/_includes/content.md
@@ -304,12 +304,12 @@ end
value = set false
if ${value}
echo should not be here
-elseif true
+elseif true or false
echo in else if but not done yet
value = set true
- if not false
+ if not true and false
echo nested if
value = set "some text"
diff --git a/docs/sdk.md b/docs/sdk.md
index 5c9de65..b7063a0 100644
--- a/docs/sdk.md
+++ b/docs/sdk.md
@@ -343,7 +343,7 @@ goto
## std::If
```sh
-if command|value
+if [command|value|condition]
# commands
elseif command|value
# commands
@@ -363,8 +363,9 @@ if and elseif commands accept either:
* A command with optional arguments and invokes it
* A single value which doesn't match any known command
+* A condition statement
-If the value or the result of the command is one of the following:
+If the result is one of the following:
* No output
* false (case insensitive)
@@ -378,6 +379,9 @@ If a truthy (non falsy) output is found, it will invoke the commands of that cod
if blocks can be nested in other if blocks (see examples).
+A condition statement is made up of values, or/and keywords and '('/')' groups.
+Each must be separated with a space character.
+
#### Parameters
* if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@@ -433,6 +437,16 @@ elseif set true
else
echo should not be here
end
+
+valid = set false
+if true and false or true and false or ( true and true or false )
+ valid = set true
+end
+assert ${valid}
+
+if true and false or true and false or ( true and true or false ) and false
+ assert_fail
+end
```
@@ -469,7 +483,7 @@ is_defined
## std::Not
```sh
-output = not command|value
+output = not [command|value|condition]
```
Enables to switch falsy to true and truthy to false.
@@ -477,8 +491,9 @@ The **not** commands accept either:
* A command with optional arguments and invokes it
* A single value which doesn't match any known command
+* A condition statement
-If the value or the result of the command is one of the following:
+If the result is one of the following:
* No output
* false (case insensitive)
@@ -488,6 +503,9 @@ If the value or the result of the command is one of the following:
It will return true, otherwise it will return false.
+A condition statement is made up of values, or/and keywords and '('/')' groups.
+Each must be separated with a space character.
+
#### Parameters
A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@@ -499,19 +517,41 @@ The switched value of the input.
#### Examples
```sh
-# Simple example of converting true/false values
-is_false = not true
-echo is false: ${is_false}
+fn test_not_true
+ value = not true
-is_true = not false
-echo is true: ${is_true}
+ assert_false ${value}
+end
-# Example of converting command output value
-is_false = not set true
-echo is false: ${is_false}
+fn test_not_false
+ value = not false
-is_true = not set false
-echo is true: ${is_true}
+ assert ${value}
+end
+
+fn test_not_command_true
+ value = not set true
+
+ assert_false ${value}
+end
+
+fn test_not_command_false
+ value = not set false
+
+ assert ${value}
+end
+
+fn test_not_condition_true
+ value = not true and false or true and false or ( true and true or false )
+
+ assert_false ${value}
+end
+
+fn test_not_condition_false
+ value = not true and false or true and false or ( true and true or false ) and false
+
+ assert ${value}
+end
```
diff --git a/duckscript_sdk/src/lib_test.rs b/duckscript_sdk/src/lib_test.rs
index e5288bf..1c284cf 100644
--- a/duckscript_sdk/src/lib_test.rs
+++ b/duckscript_sdk/src/lib_test.rs
@@ -14,11 +14,18 @@ fn load_valid() {
let mut context = Context::new();
let result = load(&mut context.commands);
- match result {
- Ok(_) => (),
- Err(e) => panic!("{:#?}", e),
- };
- // assert!(result.is_ok());
+ assert!(result.is_ok());
+
+ assert!(!context.commands.get_all_command_names().is_empty());
+}
+
+#[test]
+fn test_scripts() {
+ let mut context = Context::new();
+ let result = load(&mut context.commands);
+
+ assert!(result.is_ok());
+
assert!(!context.commands.get_all_command_names().is_empty());
let result = runner::run_script("test_directory ../test", context);
diff --git a/duckscript_sdk/src/sdk/std/flowcontrol/ifelse/help.md b/duckscript_sdk/src/sdk/std/flowcontrol/ifelse/help.md
index 1d319f4..ecb56ba 100644
--- a/duckscript_sdk/src/sdk/std/flowcontrol/ifelse/help.md
+++ b/duckscript_sdk/src/sdk/std/flowcontrol/ifelse/help.md
@@ -1,5 +1,5 @@
```sh
-if command|value
+if [command|value|condition]
# commands
elseif command|value
# commands
@@ -19,8 +19,9 @@ if and elseif commands accept either:
* A command with optional arguments and invokes it
* A single value which doesn't match any known command
+* A condition statement
-If the value or the result of the command is one of the following:
+If the result is one of the following:
* No output
* false (case insensitive)
@@ -34,6 +35,9 @@ If a truthy (non falsy) output is found, it will invoke the commands of that cod
if blocks can be nested in other if blocks (see examples).
+A condition statement is made up of values, or/and keywords and '('/')' groups.
+Each must be separated with a space character.
+
#### Parameters
* if/elseif - A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@@ -89,4 +93,14 @@ elseif set true
else
echo should not be here
end
+
+valid = set false
+if true and false or true and false or ( true and true or false )
+ valid = set true
+end
+assert ${valid}
+
+if true and false or true and false or ( true and true or false ) and false
+ assert_fail
+end
```
diff --git a/duckscript_sdk/src/sdk/std/not/help.md b/duckscript_sdk/src/sdk/std/not/help.md
index 10cde40..792b937 100644
--- a/duckscript_sdk/src/sdk/std/not/help.md
+++ b/duckscript_sdk/src/sdk/std/not/help.md
@@ -1,5 +1,5 @@
```sh
-output = not command|value
+output = not [command|value|condition]
```
Enables to switch falsy to true and truthy to false.
@@ -7,8 +7,9 @@ The **not** commands accept either:
* A command with optional arguments and invokes it
* A single value which doesn't match any known command
+* A condition statement
-If the value or the result of the command is one of the following:
+If the result is one of the following:
* No output
* false (case insensitive)
@@ -18,6 +19,9 @@ If the value or the result of the command is one of the following:
It will return true, otherwise it will return false.
+A condition statement is made up of values, or/and keywords and '('/')' groups.
+Each must be separated with a space character.
+
#### Parameters
A command and its arguments to invoke and evaluate its output, if a single value is provided an no such command exists, it is evaluated as a value.
@@ -29,17 +33,39 @@ The switched value of the input.
#### Examples
```sh
-# Simple example of converting true/false values
-is_false = not true
-echo is false: ${is_false}
+fn test_not_true
+ value = not true
-is_true = not false
-echo is true: ${is_true}
+ assert_false ${value}
+end
-# Example of converting command output value
-is_false = not set true
-echo is false: ${is_false}
+fn test_not_false
+ value = not false
-is_true = not set false
-echo is true: ${is_true}
+ assert ${value}
+end
+
+fn test_not_command_true
+ value = not set true
+
+ assert_false ${value}
+end
+
+fn test_not_command_false
+ value = not set false
+
+ assert ${value}
+end
+
+fn test_not_condition_true
+ value = not true and false or true and false or ( true and true or false )
+
+ assert_false ${value}
+end
+
+fn test_not_condition_false
+ value = not true and false or true and false or ( true and true or false ) and false
+
+ assert ${value}
+end
```
diff --git a/duckscript_sdk/src/utils/condition.rs b/duckscript_sdk/src/utils/condition.rs
index 8b46378..6dac475 100644
--- a/duckscript_sdk/src/utils/condition.rs
+++ b/duckscript_sdk/src/utils/condition.rs
@@ -7,6 +7,13 @@ use std::collections::HashMap;
#[path = "./condition_test.rs"]
mod condition_test;
+enum FoundToken {
+ None,
+ And,
+ Or,
+ Value,
+}
+
pub(crate) fn is_true(value: Option) -> bool {
let failed = match value {
Some(value_str) => {
@@ -28,11 +35,7 @@ pub(crate) fn eval_condition(
if arguments.is_empty() {
Ok(is_true(None))
} else {
- let eval_statement = if arguments.len() == 1 {
- commands.exists(&arguments[0])
- } else {
- true
- };
+ let eval_statement = commands.exists(&arguments[0]);
if eval_statement {
match eval::eval_with_error(&arguments, state, variables, commands) {
@@ -45,9 +48,125 @@ pub(crate) fn eval_condition(
_ => Err("Invalid condition evaluation result.".to_string()),
}
} else {
- let passed = is_true(Some(arguments[0].to_string()));
-
- Ok(passed)
+ eval_condition_for_slice(&arguments[..])
+ }
+ }
+}
+
+pub(crate) fn eval_condition_for_slice(arguments: &[String]) -> Result {
+ 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)
}
}
}
diff --git a/duckscript_sdk/src/utils/condition_test.rs b/duckscript_sdk/src/utils/condition_test.rs
index f61ff7f..8b69835 100644
--- a/duckscript_sdk/src/utils/condition_test.rs
+++ b/duckscript_sdk/src/utils/condition_test.rs
@@ -166,3 +166,290 @@ fn eval_condition_command_error() {
assert!(result.is_err());
}
+
+#[test]
+fn eval_condition_for_slice_empty() {
+ let result = eval_condition_for_slice(&vec![]);
+
+ let output = result.unwrap();
+ assert!(!output);
+}
+
+#[test]
+fn eval_condition_for_slice_true() {
+ let result = eval_condition_for_slice(&vec!["true".to_string()]);
+
+ let output = result.unwrap();
+ assert!(output);
+}
+
+#[test]
+fn eval_condition_for_slice_false() {
+ let result = eval_condition_for_slice(&vec!["false".to_string()]);
+
+ let output = result.unwrap();
+ assert!(!output);
+}
+
+#[test]
+fn eval_condition_for_slice_true_and_false() {
+ let result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ ]);
+
+ let output = result.unwrap();
+ assert!(!output);
+}
+
+#[test]
+fn eval_condition_for_slice_false_and_true() {
+ let result = eval_condition_for_slice(&vec![
+ "false".to_string(),
+ "and".to_string(),
+ "true".to_string(),
+ ]);
+
+ let output = result.unwrap();
+ assert!(!output);
+}
+
+#[test]
+fn eval_condition_for_slice_true_or_false() {
+ let result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ]);
+
+ let output = result.unwrap();
+ assert!(output);
+}
+
+#[test]
+fn eval_condition_for_slice_false_or_true() {
+ let result = eval_condition_for_slice(&vec![
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+
+ let output = result.unwrap();
+ assert!(output);
+}
+
+#[test]
+fn eval_condition_for_slice_complex_no_parts() {
+ let mut result = eval_condition_for_slice(&vec![
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ]);
+ assert!(result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "false".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(!result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ]);
+ assert!(!result.unwrap());
+}
+
+#[test]
+fn eval_condition_for_slice_complex_with_parts() {
+ let mut result = eval_condition_for_slice(&vec![
+ "(".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ")".to_string(),
+ ]);
+ assert!(result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "(".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "(".to_string(),
+ "true".to_string(),
+ ")".to_string(),
+ ")".to_string(),
+ ]);
+ assert!(result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "(".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ")".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ]);
+ assert!(!result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "(".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ ")".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "(".to_string(),
+ "(".to_string(),
+ "(".to_string(),
+ ")".to_string(),
+ ")".to_string(),
+ ")".to_string(),
+ ]);
+ assert!(!result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "(".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "true".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ")".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ ]);
+ assert!(!result.unwrap());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "false".to_string(),
+ "or".to_string(),
+ "(".to_string(),
+ "true".to_string(),
+ "and".to_string(),
+ "true".to_string(),
+ "or".to_string(),
+ "false".to_string(),
+ ")".to_string(),
+ ]);
+ assert!(result.unwrap());
+}
+
+#[test]
+fn eval_condition_for_slice_parse_errors() {
+ let mut result = eval_condition_for_slice(&vec!["or".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec!["and".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec!["(".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![")".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec!["(".to_string(), "true".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![
+ "false".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ")".to_string(),
+ ]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec!["false".to_string(), "true".to_string()]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "or".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "and".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "and".to_string(),
+ "or".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.is_err());
+
+ result = eval_condition_for_slice(&vec![
+ "true".to_string(),
+ "or".to_string(),
+ "and".to_string(),
+ "true".to_string(),
+ ]);
+ assert!(result.is_err());
+}
diff --git a/examples/if_else.ds b/examples/if_else.ds
index 75ec8fc..245a4ff 100644
--- a/examples/if_else.ds
+++ b/examples/if_else.ds
@@ -31,7 +31,7 @@ end
if set false
echo should not be here
-elseif set true
+elseif true or false
echo in else if but not done yet
if set true
@@ -49,7 +49,7 @@ elseif true
value = set true
- if not false
+ if not true and false
echo nested if
value = set "some text"
diff --git a/test/std/flow_control_test.ds b/test/std/flowcontrol/flow_control_test.ds
similarity index 100%
rename from test/std/flow_control_test.ds
rename to test/std/flowcontrol/flow_control_test.ds
diff --git a/test/std/for_in_test.ds b/test/std/flowcontrol/for_in_test.ds
similarity index 100%
rename from test/std/for_in_test.ds
rename to test/std/flowcontrol/for_in_test.ds
diff --git a/test/std/goto_test.ds b/test/std/flowcontrol/goto_test.ds
similarity index 100%
rename from test/std/goto_test.ds
rename to test/std/flowcontrol/goto_test.ds
diff --git a/test/std/if_else_test.ds b/test/std/flowcontrol/if_else_test.ds
similarity index 81%
rename from test/std/if_else_test.ds
rename to test/std/flowcontrol/if_else_test.ds
index 8509da5..ce7e339 100644
--- a/test/std/if_else_test.ds
+++ b/test/std/flowcontrol/if_else_test.ds
@@ -35,6 +35,22 @@ fn test_if_command_returns_true
assert ${valid}
end
+fn test_if_condition_true
+ valid = set false
+
+ if true and false or true and false or ( true and true or false )
+ valid = set true
+ end
+
+ assert ${valid}
+end
+
+fn test_if_condition_false
+ if true and false or true and false or ( true and true or false ) and false
+ assert_fail
+ end
+end
+
fn test_simple_else
valid = set false
diff --git a/test/std/not_test.ds b/test/std/not_test.ds
index adbded9..e9c0c60 100644
--- a/test/std/not_test.ds
+++ b/test/std/not_test.ds
@@ -10,3 +10,27 @@ fn test_not_false
assert ${value}
end
+
+fn test_not_command_true
+ value = not set true
+
+ assert_false ${value}
+end
+
+fn test_not_command_false
+ value = not set false
+
+ assert ${value}
+end
+
+fn test_not_condition_true
+ value = not true and false or true and false or ( true and true or false )
+
+ assert_false ${value}
+end
+
+fn test_not_condition_false
+ value = not true and false or true and false or ( true and true or false ) and false
+
+ assert ${value}
+end