The if/else and not commands now support complex conditions

This commit is contained in:
sagie gur ari 2020-02-05 19:09:27 +00:00
parent c54af0f297
commit 4cc8f820b9
15 changed files with 581 additions and 47 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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"

View File

@ -343,7 +343,7 @@ goto
<a name="std__If"></a>
## 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.<br>
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
<a name="std__Not"></a>
## std::Not
```sh
output = not command|value
output = not [command|value|condition]
```
Enables to switch falsy to true and truthy to false.<br>
@ -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.<br>
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
```

View File

@ -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);

View File

@ -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.<br>
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
```

View File

@ -1,5 +1,5 @@
```sh
output = not command|value
output = not [command|value|condition]
```
Enables to switch falsy to true and truthy to false.<br>
@ -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.<br>
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
```

View File

@ -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<String>) -> 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<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)
}
}
}

View File

@ -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());
}

View File

@ -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"

View File

@ -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

View File

@ -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