mirror of
https://github.com/sagiegurari/duckscript
synced 2024-10-14 11:53:05 +00:00
commit
5eafcc4ae3
|
@ -1,5 +1,11 @@
|
|||
## CHANGELOG
|
||||
|
||||
### v0.6.2
|
||||
|
||||
* New function <scope> annotation #121
|
||||
* New scope_pop_stack command #121
|
||||
* New scope_push_stack command #121
|
||||
|
||||
### v0.6.1 (2020-07-08)
|
||||
|
||||
* New is_path_newer command.
|
||||
|
|
|
@ -83,7 +83,7 @@ The following sections will teach you how to write and run duck scripts.
|
|||
|
||||
<a name="tutorial-hello-world"></a>
|
||||
### Hello World Example
|
||||
Let's take a really simple example (all examples are located in the [examples](https://github.com/sagiegurari/duckscript/tree/master/examples) directory):
|
||||
Let's take a really simple example (all examples are located in the [examples](https://github.com/sagiegurari/duckscript/tree/master/examples) directory:
|
||||
|
||||
```sh
|
||||
# print the text "Hello World"
|
||||
|
|
109
docs/sdk.md
109
docs/sdk.md
|
@ -126,6 +126,8 @@
|
|||
* [std::process::Spawn (spawn)](#std__process__Spawn)
|
||||
* [std::process::Watchdog (watchdog)](#std__process__Watchdog)
|
||||
* [std::scope::Clear (clear_scope)](#std__scope__Clear)
|
||||
* [std::scope::PopStack (scope_pop_stack)](#std__scope__PopStack)
|
||||
* [std::scope::PushStack (scope_push_stack)](#std__scope__PushStack)
|
||||
* [std::string::Base64 (base64)](#std__string__Base64)
|
||||
* [std::string::Base64Decode (base64_decode)](#std__string__Base64Decode)
|
||||
* [std::string::Base64Encode (base64_encode)](#std__string__Base64Encode)
|
||||
|
@ -2813,17 +2815,22 @@ for
|
|||
<a name="std__flowcontrol__Function"></a>
|
||||
## std::flowcontrol::Function
|
||||
```sh
|
||||
function my_function
|
||||
fn my_function
|
||||
# function content
|
||||
return output
|
||||
end
|
||||
|
||||
fn <scope> another_function
|
||||
# function content
|
||||
end
|
||||
```
|
||||
|
||||
This command provides the function language feature as a set of commands:
|
||||
|
||||
* function - Defines a function start block
|
||||
* function/fn - Defines a function start block
|
||||
* end - Defines the end of the function block
|
||||
* return - Allows to exist a function at any point and return an output
|
||||
* *<scope>* - Optional annotation which enables to use a new scope during the function invocation.
|
||||
* *function name* - Dynamically created commands based on the function name which are used to invoke the function code.
|
||||
|
||||
When a function command is detected, it will search for the end command that comes after.<br>
|
||||
|
@ -2836,13 +2843,19 @@ Since variables are global, it will overwrite any older values stored in those v
|
|||
To exist a function and return a value, simply use the **return** command with the value you want to return.<br>
|
||||
The variable that was used when the function was originally called, will now store that value.<br>
|
||||
The return command can be used to exist early without any value.<br>
|
||||
In case the code reached the **end** call, the function will exist but will return not value.
|
||||
In case the code reached the **end** call, the function will exist but will return not value.<br>
|
||||
|
||||
The *<scope>* annotation enables to start a new scope when running the function.<br>
|
||||
All variables defined will not be available except the variables provided to the function as arguments.<br>
|
||||
All variables created during the function invocation will be deleted once the function ends, except the return value.<br>
|
||||
This enables a clean function invocation without impacting the global variables.
|
||||
|
||||
#### Parameters
|
||||
|
||||
* function - The function name used later on to invoke the function
|
||||
* end - no parameters
|
||||
* return - optional single paramter to return as an output of the function call
|
||||
* *<scope>* - Optional annotation which enables to use a new scope during the function invocation.
|
||||
* *function name* - Any number of arguments which will automatically be set as global variables: ${1}, ${2}, ... as so on.
|
||||
|
||||
#### Return Value
|
||||
|
@ -2874,7 +2887,9 @@ text = get_hello_world
|
|||
echo ${text}
|
||||
|
||||
# Example of passing arguments
|
||||
fn print_input
|
||||
# Also the function is with scope annotation so it has no access
|
||||
# to any variable except those provided during the function invocation.
|
||||
fn <scope> print_input
|
||||
# ${1} is set with the value 'hello'
|
||||
# ${2} is set with the value 'world'
|
||||
echo ${1} ${2}
|
||||
|
@ -4651,6 +4666,92 @@ assert_false ${defined}
|
|||
#### Aliases:
|
||||
clear_scope
|
||||
|
||||
<a name="std__scope__PopStack"></a>
|
||||
## std::scope::PopStack
|
||||
```sh
|
||||
scope_pop_stack [--copy name1 name2 ...]
|
||||
```
|
||||
|
||||
Removes all known variables except for the variables provided by the optional --copy argument and than restores the
|
||||
previously pushed stack.<br>
|
||||
Functions with the **<scope>** annotation will automatically invoke this command when they end or return a value.
|
||||
|
||||
#### Parameters
|
||||
|
||||
Optional variable names to keep.
|
||||
|
||||
#### Return Value
|
||||
|
||||
None.
|
||||
|
||||
#### Examples
|
||||
|
||||
```sh
|
||||
var1 = set 1
|
||||
var2 = set 2
|
||||
|
||||
scope_push_stack --copy var2
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
|
||||
var3 = set 3
|
||||
var4 = set 4
|
||||
|
||||
scope_pop_stack --copy var4
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
defined = is_defined var3
|
||||
echo ${defined}
|
||||
defined = is_defined var4
|
||||
echo ${defined}
|
||||
```
|
||||
|
||||
|
||||
#### Aliases:
|
||||
scope_pop_stack
|
||||
|
||||
<a name="std__scope__PushStack"></a>
|
||||
## std::scope::PushStack
|
||||
```sh
|
||||
scope_push_stack [--copy name1 name2 ...]
|
||||
```
|
||||
|
||||
Removes all known variables except for the variables provided by the optional --copy argument.<br>
|
||||
Functions with the **<scope>** annotation will automatically invoke this command and keep only the relevant
|
||||
function arguments in the new scope.
|
||||
|
||||
#### Parameters
|
||||
|
||||
Optional variable names to keep.
|
||||
|
||||
#### Return Value
|
||||
|
||||
None.
|
||||
|
||||
#### Examples
|
||||
|
||||
```sh
|
||||
var1 = set 1
|
||||
var2 = set 2
|
||||
|
||||
scope_push_stack --copy var2
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
```
|
||||
|
||||
|
||||
#### Aliases:
|
||||
scope_push_stack
|
||||
|
||||
<a name="std__string__Base64"></a>
|
||||
## std::string::Base64
|
||||
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
```sh
|
||||
function my_function
|
||||
fn my_function
|
||||
# function content
|
||||
return output
|
||||
end
|
||||
|
||||
fn <scope> another_function
|
||||
# function content
|
||||
end
|
||||
```
|
||||
|
||||
This command provides the function language feature as a set of commands:
|
||||
|
||||
* function - Defines a function start block
|
||||
* function/fn - Defines a function start block
|
||||
* end - Defines the end of the function block
|
||||
* return - Allows to exist a function at any point and return an output
|
||||
* *<scope>* - Optional annotation which enables to use a new scope during the function invocation.
|
||||
* *function name* - Dynamically created commands based on the function name which are used to invoke the function code.
|
||||
|
||||
When a function command is detected, it will search for the end command that comes after.<br>
|
||||
|
@ -22,13 +27,19 @@ Since variables are global, it will overwrite any older values stored in those v
|
|||
To exist a function and return a value, simply use the **return** command with the value you want to return.<br>
|
||||
The variable that was used when the function was originally called, will now store that value.<br>
|
||||
The return command can be used to exist early without any value.<br>
|
||||
In case the code reached the **end** call, the function will exist but will return not value.
|
||||
In case the code reached the **end** call, the function will exist but will return not value.<br>
|
||||
|
||||
The *<scope>* annotation enables to start a new scope when running the function.<br>
|
||||
All variables defined will not be available except the variables provided to the function as arguments.<br>
|
||||
All variables created during the function invocation will be deleted once the function ends, except the return value.<br>
|
||||
This enables a clean function invocation without impacting the global variables.
|
||||
|
||||
#### Parameters
|
||||
|
||||
* function - The function name used later on to invoke the function
|
||||
* end - no parameters
|
||||
* return - optional single paramter to return as an output of the function call
|
||||
* *<scope>* - Optional annotation which enables to use a new scope during the function invocation.
|
||||
* *function name* - Any number of arguments which will automatically be set as global variables: ${1}, ${2}, ... as so on.
|
||||
|
||||
#### Return Value
|
||||
|
@ -60,7 +71,9 @@ text = get_hello_world
|
|||
echo ${text}
|
||||
|
||||
# Example of passing arguments
|
||||
fn print_input
|
||||
# Also the function is with scope annotation so it has no access
|
||||
# to any variable except those provided during the function invocation.
|
||||
fn <scope> print_input
|
||||
# ${1} is set with the value 'hello'
|
||||
# ${2} is set with the value 'world'
|
||||
echo ${1} ${2}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::sdk::std::flowcontrol::{end, forin, ifelse};
|
||||
use crate::types::scope::get_line_context_name;
|
||||
use crate::utils::state::{get_core_sub_state_for_command, get_list, get_sub_state};
|
||||
use crate::utils::{instruction_query, pckg};
|
||||
use crate::utils::{annotation, instruction_query, pckg, scope};
|
||||
use duckscript::types::command::{Command, CommandResult, Commands, GoToValue};
|
||||
use duckscript::types::error::ScriptError;
|
||||
use duckscript::types::instruction::Instruction;
|
||||
|
@ -21,6 +21,7 @@ struct FunctionMetaInfo {
|
|||
pub(crate) name: String,
|
||||
pub(crate) start: usize,
|
||||
pub(crate) end: usize,
|
||||
pub(crate) scoped: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -30,6 +31,7 @@ struct CallInfo {
|
|||
pub(crate) end_line: usize,
|
||||
pub(crate) line_context_name: String,
|
||||
pub(crate) output_variable: Option<String>,
|
||||
pub(crate) scoped: bool,
|
||||
}
|
||||
|
||||
fn store_fn_info_in_state(
|
||||
|
@ -57,6 +59,7 @@ fn store_fn_info_in_state(
|
|||
StateValue::UnsignedNumber(meta_info.start),
|
||||
);
|
||||
fn_info_state.insert("end".to_string(), StateValue::UnsignedNumber(meta_info.end));
|
||||
fn_info_state.insert("scoped".to_string(), StateValue::Boolean(meta_info.scoped));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -85,10 +88,19 @@ fn get_fn_info_from_state(
|
|||
_ => return None,
|
||||
};
|
||||
|
||||
let scoped = match fn_info_state.get("scoped") {
|
||||
Some(state_value) => match state_value {
|
||||
StateValue::Boolean(value) => *value,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
Some(FunctionMetaInfo {
|
||||
name: name.to_string(),
|
||||
start,
|
||||
end,
|
||||
scoped,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
@ -122,6 +134,7 @@ fn push_to_call_stack(state: &mut HashMap<String, StateValue>, call_info: &CallI
|
|||
StateValue::String(output_variable.to_string()),
|
||||
);
|
||||
}
|
||||
sub_state.insert("scoped".to_string(), StateValue::Boolean(call_info.scoped));
|
||||
|
||||
call_stack.push(StateValue::SubState(sub_state));
|
||||
}
|
||||
|
@ -173,12 +186,21 @@ fn pop_from_call_stack(state: &mut HashMap<String, StateValue>) -> Option<CallIn
|
|||
None => None,
|
||||
};
|
||||
|
||||
let scoped = match sub_state.get("scoped") {
|
||||
Some(value) => match value {
|
||||
StateValue::Boolean(scoped) => *scoped,
|
||||
_ => false,
|
||||
},
|
||||
None => false,
|
||||
};
|
||||
|
||||
Some(CallInfo {
|
||||
call_line,
|
||||
start_line,
|
||||
end_line,
|
||||
line_context_name,
|
||||
output_variable,
|
||||
scoped,
|
||||
})
|
||||
}
|
||||
_ => return pop_from_call_stack(state),
|
||||
|
@ -197,6 +219,13 @@ fn run_call(
|
|||
) -> CommandResult {
|
||||
match get_fn_info_from_state(state, &function_name) {
|
||||
Some(fn_info) => {
|
||||
if fn_info.scoped {
|
||||
match scope::push(variables, state, &vec![]) {
|
||||
Err(error) => return CommandResult::Error(error),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// define function arguments
|
||||
let mut index = 0;
|
||||
for argument in arguments {
|
||||
|
@ -214,6 +243,7 @@ fn run_call(
|
|||
end_line: fn_info.end,
|
||||
line_context_name,
|
||||
output_variable,
|
||||
scoped: fn_info.scoped,
|
||||
};
|
||||
push_to_call_stack(state, &call_info);
|
||||
|
||||
|
@ -273,7 +303,18 @@ impl Command for FunctionCommand {
|
|||
if arguments.is_empty() {
|
||||
CommandResult::Error("Missing function name.".to_string())
|
||||
} else {
|
||||
let function_name = arguments[0].clone();
|
||||
let (function_name, scoped) = if arguments.len() == 1 {
|
||||
(arguments[0].clone(), false)
|
||||
} else {
|
||||
match annotation::parse(&arguments[0]) {
|
||||
Some(annotations) => (
|
||||
arguments[1].clone(),
|
||||
annotations.contains(&"scope".to_string()),
|
||||
),
|
||||
None => (arguments[0].clone(), false),
|
||||
}
|
||||
};
|
||||
|
||||
match get_fn_info_from_state(state, &function_name) {
|
||||
Some(fn_info) => {
|
||||
if fn_info.start != line {
|
||||
|
@ -332,6 +373,7 @@ impl Command for FunctionCommand {
|
|||
name: function_name.clone(),
|
||||
start: line,
|
||||
end: fn_end_line,
|
||||
scoped,
|
||||
};
|
||||
|
||||
end::set_command(fn_end_line, state, end_command.name());
|
||||
|
@ -446,7 +488,7 @@ impl Command for EndFunctionCommand {
|
|||
&self,
|
||||
_arguments: Vec<String>,
|
||||
state: &mut HashMap<String, StateValue>,
|
||||
_variables: &mut HashMap<String, String>,
|
||||
variables: &mut HashMap<String, String>,
|
||||
_output_variable: Option<String>,
|
||||
_instructions: &Vec<Instruction>,
|
||||
_commands: &mut Commands,
|
||||
|
@ -458,6 +500,14 @@ impl Command for EndFunctionCommand {
|
|||
Some(call_info) => {
|
||||
if call_info.end_line == line && call_info.line_context_name == line_context_name {
|
||||
let next_line = call_info.call_line + 1;
|
||||
|
||||
if call_info.scoped {
|
||||
match scope::pop(variables, state, &vec![]) {
|
||||
Err(error) => return CommandResult::Error(error),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
CommandResult::GoTo(None, GoToValue::Line(next_line))
|
||||
} else {
|
||||
push_to_call_stack(state, &call_info);
|
||||
|
@ -514,11 +564,11 @@ impl Command for ReturnCommand {
|
|||
&& call_info.line_context_name == line_context_name
|
||||
{
|
||||
match call_info.output_variable {
|
||||
Some(name) => {
|
||||
Some(ref name) => {
|
||||
if arguments.is_empty() {
|
||||
variables.remove(&name);
|
||||
variables.remove(name);
|
||||
} else {
|
||||
variables.insert(name, arguments[0].clone());
|
||||
variables.insert(name.to_string(), arguments[0].clone());
|
||||
}
|
||||
}
|
||||
None => (),
|
||||
|
@ -530,6 +580,18 @@ impl Command for ReturnCommand {
|
|||
Some(arguments[0].clone())
|
||||
};
|
||||
|
||||
if call_info.scoped {
|
||||
let copy = match call_info.output_variable {
|
||||
Some(ref name) => vec![name.to_string()],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
match scope::pop(variables, state, ©) {
|
||||
Err(error) => return CommandResult::Error(error),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let next_line = call_info.call_line + 1;
|
||||
CommandResult::GoTo(output, GoToValue::Line(next_line))
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
pub(crate) mod clear;
|
||||
mod pop_stack;
|
||||
mod push_stack;
|
||||
|
||||
use crate::utils::pckg;
|
||||
use duckscript::types::command::Commands;
|
||||
|
@ -10,6 +12,8 @@ pub(crate) fn load(commands: &mut Commands, parent: &str) -> Result<(), ScriptEr
|
|||
let package = pckg::concat(parent, PACKAGE);
|
||||
|
||||
commands.set(clear::create(&package))?;
|
||||
commands.set(pop_stack::create(&package))?;
|
||||
commands.set(push_stack::create(&package))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
43
duckscript_sdk/src/sdk/std/scope/pop_stack/help.md
Normal file
43
duckscript_sdk/src/sdk/std/scope/pop_stack/help.md
Normal file
|
@ -0,0 +1,43 @@
|
|||
```sh
|
||||
scope_pop_stack [--copy name1 name2 ...]
|
||||
```
|
||||
|
||||
Removes all known variables except for the variables provided by the optional --copy argument and than restores the
|
||||
previously pushed stack.<br>
|
||||
Functions with the **<scope>** annotation will automatically invoke this command when they end or return a value.
|
||||
|
||||
#### Parameters
|
||||
|
||||
Optional variable names to keep.
|
||||
|
||||
#### Return Value
|
||||
|
||||
None.
|
||||
|
||||
#### Examples
|
||||
|
||||
```sh
|
||||
var1 = set 1
|
||||
var2 = set 2
|
||||
|
||||
scope_push_stack --copy var2
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
|
||||
var3 = set 3
|
||||
var4 = set 4
|
||||
|
||||
scope_pop_stack --copy var4
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
defined = is_defined var3
|
||||
echo ${defined}
|
||||
defined = is_defined var4
|
||||
echo ${defined}
|
||||
```
|
66
duckscript_sdk/src/sdk/std/scope/pop_stack/mod.rs
Executable file
66
duckscript_sdk/src/sdk/std/scope/pop_stack/mod.rs
Executable file
|
@ -0,0 +1,66 @@
|
|||
use crate::utils::{pckg, scope};
|
||||
use duckscript::types::command::{Command, CommandResult, Commands};
|
||||
use duckscript::types::instruction::Instruction;
|
||||
use duckscript::types::runtime::StateValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./mod_test.rs"]
|
||||
mod mod_test;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommandImpl {
|
||||
package: String,
|
||||
}
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> String {
|
||||
pckg::concat(&self.package, "PopStack")
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<String> {
|
||||
vec!["scope_pop_stack".to_string()]
|
||||
}
|
||||
|
||||
fn help(&self) -> String {
|
||||
include_str!("help.md").to_string()
|
||||
}
|
||||
|
||||
fn clone_and_box(&self) -> Box<dyn Command> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn requires_context(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run_with_context(
|
||||
&self,
|
||||
arguments: Vec<String>,
|
||||
state: &mut HashMap<String, StateValue>,
|
||||
variables: &mut HashMap<String, String>,
|
||||
_output_variable: Option<String>,
|
||||
_instructions: &Vec<Instruction>,
|
||||
_commands: &mut Commands,
|
||||
_line: usize,
|
||||
) -> CommandResult {
|
||||
let copy = if arguments.is_empty() {
|
||||
&[]
|
||||
} else if arguments[0] == "--copy" {
|
||||
&arguments[1..]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
match scope::pop(variables, state, ©) {
|
||||
Ok(_) => CommandResult::Continue(Some("true".to_string())),
|
||||
Err(error) => CommandResult::Error(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create(package: &str) -> Box<dyn Command> {
|
||||
Box::new(CommandImpl {
|
||||
package: package.to_string(),
|
||||
})
|
||||
}
|
55
duckscript_sdk/src/sdk/std/scope/pop_stack/mod_test.rs
Normal file
55
duckscript_sdk/src/sdk/std/scope/pop_stack/mod_test.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use super::*;
|
||||
use crate::sdk::std::scope::push_stack;
|
||||
use crate::test;
|
||||
use crate::test::{CommandValidation, SetCommand};
|
||||
|
||||
#[test]
|
||||
fn common_functions() {
|
||||
test::test_common_command_functions(create(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_no_args() {
|
||||
let context = test::run_script_and_validate(
|
||||
vec![create(""), push_stack::create(""), Box::new(SetCommand {})],
|
||||
r#"
|
||||
a = test_set 1
|
||||
b = test_set 2
|
||||
scope_push_stack
|
||||
|
||||
c = test_set 3
|
||||
scope_pop_stack
|
||||
"#,
|
||||
CommandValidation::Ignore,
|
||||
);
|
||||
|
||||
let variables = context.variables;
|
||||
assert_eq!(variables.len(), 2);
|
||||
assert_eq!(variables.get("a").unwrap(), "1");
|
||||
assert_eq!(variables.get("b").unwrap(), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_keep_variables() {
|
||||
let context = test::run_script_and_validate(
|
||||
vec![create(""), push_stack::create(""), Box::new(SetCommand {})],
|
||||
r#"
|
||||
a = test_set 1
|
||||
b = test_set 2
|
||||
c = test_set 3
|
||||
scope_push_stack --copy b c
|
||||
|
||||
d = test_set 4
|
||||
e = test_set 5
|
||||
scope_pop_stack --copy e
|
||||
"#,
|
||||
CommandValidation::Ignore,
|
||||
);
|
||||
|
||||
let variables = context.variables;
|
||||
assert_eq!(variables.len(), 4);
|
||||
assert_eq!(variables.get("a").unwrap(), "1");
|
||||
assert_eq!(variables.get("b").unwrap(), "2");
|
||||
assert_eq!(variables.get("c").unwrap(), "3");
|
||||
assert_eq!(variables.get("e").unwrap(), "5");
|
||||
}
|
29
duckscript_sdk/src/sdk/std/scope/push_stack/help.md
Normal file
29
duckscript_sdk/src/sdk/std/scope/push_stack/help.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
```sh
|
||||
scope_push_stack [--copy name1 name2 ...]
|
||||
```
|
||||
|
||||
Removes all known variables except for the variables provided by the optional --copy argument.<br>
|
||||
Functions with the **<scope>** annotation will automatically invoke this command and keep only the relevant
|
||||
function arguments in the new scope.
|
||||
|
||||
#### Parameters
|
||||
|
||||
Optional variable names to keep.
|
||||
|
||||
#### Return Value
|
||||
|
||||
None.
|
||||
|
||||
#### Examples
|
||||
|
||||
```sh
|
||||
var1 = set 1
|
||||
var2 = set 2
|
||||
|
||||
scope_push_stack --copy var2
|
||||
|
||||
defined = is_defined var1
|
||||
echo ${defined}
|
||||
defined = is_defined var2
|
||||
echo ${defined}
|
||||
```
|
66
duckscript_sdk/src/sdk/std/scope/push_stack/mod.rs
Executable file
66
duckscript_sdk/src/sdk/std/scope/push_stack/mod.rs
Executable file
|
@ -0,0 +1,66 @@
|
|||
use crate::utils::{pckg, scope};
|
||||
use duckscript::types::command::{Command, CommandResult, Commands};
|
||||
use duckscript::types::instruction::Instruction;
|
||||
use duckscript::types::runtime::StateValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./mod_test.rs"]
|
||||
mod mod_test;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommandImpl {
|
||||
package: String,
|
||||
}
|
||||
|
||||
impl Command for CommandImpl {
|
||||
fn name(&self) -> String {
|
||||
pckg::concat(&self.package, "PushStack")
|
||||
}
|
||||
|
||||
fn aliases(&self) -> Vec<String> {
|
||||
vec!["scope_push_stack".to_string()]
|
||||
}
|
||||
|
||||
fn help(&self) -> String {
|
||||
include_str!("help.md").to_string()
|
||||
}
|
||||
|
||||
fn clone_and_box(&self) -> Box<dyn Command> {
|
||||
Box::new((*self).clone())
|
||||
}
|
||||
|
||||
fn requires_context(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn run_with_context(
|
||||
&self,
|
||||
arguments: Vec<String>,
|
||||
state: &mut HashMap<String, StateValue>,
|
||||
variables: &mut HashMap<String, String>,
|
||||
_output_variable: Option<String>,
|
||||
_instructions: &Vec<Instruction>,
|
||||
_commands: &mut Commands,
|
||||
_line: usize,
|
||||
) -> CommandResult {
|
||||
let copy = if arguments.is_empty() {
|
||||
&[]
|
||||
} else if arguments[0] == "--copy" {
|
||||
&arguments[1..]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
|
||||
match scope::push(variables, state, ©) {
|
||||
Ok(_) => CommandResult::Continue(Some("true".to_string())),
|
||||
Err(error) => CommandResult::Error(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create(package: &str) -> Box<dyn Command> {
|
||||
Box::new(CommandImpl {
|
||||
package: package.to_string(),
|
||||
})
|
||||
}
|
43
duckscript_sdk/src/sdk/std/scope/push_stack/mod_test.rs
Normal file
43
duckscript_sdk/src/sdk/std/scope/push_stack/mod_test.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use super::*;
|
||||
use crate::test;
|
||||
use crate::test::{CommandValidation, SetCommand};
|
||||
|
||||
#[test]
|
||||
fn common_functions() {
|
||||
test::test_common_command_functions(create(""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_no_args() {
|
||||
let context = test::run_script_and_validate(
|
||||
vec![create(""), Box::new(SetCommand {})],
|
||||
r#"
|
||||
a = test_set 1
|
||||
b = test_set 2
|
||||
scope_push_stack
|
||||
"#,
|
||||
CommandValidation::None,
|
||||
);
|
||||
|
||||
let variables = context.variables;
|
||||
assert!(variables.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_keep_variables() {
|
||||
let context = test::run_script_and_validate(
|
||||
vec![create(""), Box::new(SetCommand {})],
|
||||
r#"
|
||||
a = test_set 1
|
||||
b = test_set 2
|
||||
c = test_set 3
|
||||
scope_push_stack --copy b c
|
||||
"#,
|
||||
CommandValidation::Ignore,
|
||||
);
|
||||
|
||||
let variables = context.variables;
|
||||
assert_eq!(variables.len(), 2);
|
||||
assert_eq!(variables.get("b").unwrap(), "2");
|
||||
assert_eq!(variables.get("c").unwrap(), "3");
|
||||
}
|
19
duckscript_sdk/src/utils/annotation.rs
Normal file
19
duckscript_sdk/src/utils/annotation.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
pub(crate) fn parse(string: &str) -> Option<Vec<String>> {
|
||||
let mut modified_string = string.trim();
|
||||
if modified_string.starts_with("<") && modified_string.ends_with(">") {
|
||||
modified_string = modified_string
|
||||
.strip_prefix("<")
|
||||
.unwrap()
|
||||
.strip_suffix(">")
|
||||
.unwrap();
|
||||
|
||||
let values: Vec<String> = modified_string
|
||||
.split(',')
|
||||
.map(|item| item.to_string())
|
||||
.collect();
|
||||
|
||||
Some(values)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub(crate) mod annotation;
|
||||
pub(crate) mod condition;
|
||||
pub(crate) mod eval;
|
||||
pub(crate) mod exec;
|
||||
|
@ -5,4 +6,5 @@ pub(crate) mod flags;
|
|||
pub(crate) mod instruction_query;
|
||||
pub(crate) mod io;
|
||||
pub(crate) mod pckg;
|
||||
pub(crate) mod scope;
|
||||
pub(crate) mod state;
|
||||
|
|
85
duckscript_sdk/src/utils/scope.rs
Normal file
85
duckscript_sdk/src/utils/scope.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use crate::utils::state::{ensure_list, mutate_list};
|
||||
use duckscript::types::runtime::StateValue;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
static SCOPE_STACK_STATE_KEY: &str = "scope_stack";
|
||||
|
||||
pub(crate) fn push(
|
||||
variables: &mut HashMap<String, String>,
|
||||
state: &mut HashMap<String, StateValue>,
|
||||
copy: &[String],
|
||||
) -> Result<(), String> {
|
||||
ensure_list(SCOPE_STACK_STATE_KEY, state);
|
||||
|
||||
match mutate_list(SCOPE_STACK_STATE_KEY.to_string(), state, |list| {
|
||||
list.push(StateValue::Any(Rc::new(RefCell::new(variables.clone()))));
|
||||
Ok(None)
|
||||
}) {
|
||||
Ok(_) => {
|
||||
let mut new_variables = HashMap::new();
|
||||
for key in copy {
|
||||
let value = variables.remove(key);
|
||||
new_variables.insert(key, value);
|
||||
}
|
||||
|
||||
variables.clear();
|
||||
|
||||
for (key, value) in new_variables {
|
||||
variables.insert(key.to_string(), value.unwrap());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn pop(
|
||||
variables: &mut HashMap<String, String>,
|
||||
state: &mut HashMap<String, StateValue>,
|
||||
copy: &[String],
|
||||
) -> Result<(), String> {
|
||||
ensure_list(SCOPE_STACK_STATE_KEY, state);
|
||||
|
||||
match mutate_list(
|
||||
SCOPE_STACK_STATE_KEY.to_string(),
|
||||
state,
|
||||
|list| match list.pop() {
|
||||
Some(state_value) => match state_value {
|
||||
StateValue::Any(rc_value) => {
|
||||
let value_any = rc_value.borrow();
|
||||
|
||||
match value_any.downcast_ref::<HashMap<String, String>>() {
|
||||
Some(old_variables) => {
|
||||
let mut new_variables = HashMap::new();
|
||||
for key in copy {
|
||||
let value = variables.remove(key);
|
||||
new_variables.insert(key, value);
|
||||
}
|
||||
|
||||
variables.clear();
|
||||
|
||||
for (key, value) in old_variables {
|
||||
variables.insert(key.to_string(), value.clone());
|
||||
}
|
||||
|
||||
for (key, value) in new_variables {
|
||||
variables.insert(key.to_string(), value.unwrap());
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
None => Err("Scope stack not available, invalid type.".to_string()),
|
||||
}
|
||||
}
|
||||
_ => Err("Scope stack not available, invalid state value type.".to_string()),
|
||||
},
|
||||
None => Err("Reached end of scope stack.".to_string()),
|
||||
},
|
||||
) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
|
@ -111,7 +111,7 @@ pub(crate) fn get_sub_state(
|
|||
}
|
||||
}
|
||||
|
||||
fn ensure_list(key: &str, state: &mut HashMap<String, StateValue>) {
|
||||
pub(crate) fn ensure_list(key: &str, state: &mut HashMap<String, StateValue>) {
|
||||
match state.get(key) {
|
||||
Some(value) => match value {
|
||||
StateValue::List(_) => (),
|
||||
|
|
70
test/std/flowcontrol/function_scoped_test.ds
Normal file
70
test/std/flowcontrol/function_scoped_test.ds
Normal file
|
@ -0,0 +1,70 @@
|
|||
|
||||
fn global_fn
|
||||
defined = is_defined v1
|
||||
assert ${defined}
|
||||
defined = is_defined v2
|
||||
assert ${defined}
|
||||
|
||||
assert_eq ${v1} 1
|
||||
assert_eq ${v2} 2
|
||||
|
||||
assert_eq ${1} 2
|
||||
assert_eq ${2} 1
|
||||
|
||||
global_var = set global
|
||||
end
|
||||
|
||||
fn <scope> scoped_fn
|
||||
defined = is_defined global_var
|
||||
assert_false ${defined}
|
||||
defined = is_defined scoped
|
||||
assert_false ${defined}
|
||||
|
||||
defined = is_defined v1
|
||||
assert_false ${defined}
|
||||
defined = is_defined v2
|
||||
assert_false ${defined}
|
||||
|
||||
assert_eq ${1} 1
|
||||
defined = is_defined 2
|
||||
assert_false ${defined}
|
||||
|
||||
scoped = set 1
|
||||
end
|
||||
|
||||
fn <scope> scoped_return_fn
|
||||
defined = is_defined global_var
|
||||
assert_false ${defined}
|
||||
defined = is_defined scoped
|
||||
assert_false ${defined}
|
||||
|
||||
defined = is_defined v1
|
||||
assert_false ${defined}
|
||||
defined = is_defined v2
|
||||
assert_false ${defined}
|
||||
|
||||
defined = is_defined 1
|
||||
assert_false ${defined}
|
||||
defined = is_defined 2
|
||||
assert_false ${defined}
|
||||
|
||||
scoped = set 1
|
||||
|
||||
return scoped_return_value
|
||||
end
|
||||
|
||||
fn test_scoped_functions
|
||||
v1 = set 1
|
||||
v2 = set 2
|
||||
|
||||
global_fn ${v2} ${v1}
|
||||
scoped_fn ${v1}
|
||||
output = scoped_return_fn
|
||||
|
||||
defined = is_defined scoped
|
||||
assert_false ${defined}
|
||||
|
||||
assert_eq ${global_var} global
|
||||
assert ${output} scoped_return_value
|
||||
end
|
||||
|
85
test/std/scope/push_pop_stack_test.ds
Normal file
85
test/std/scope/push_pop_stack_test.ds
Normal file
|
@ -0,0 +1,85 @@
|
|||
|
||||
fn test_push_pop
|
||||
root1 = set 1
|
||||
root2 = set 2
|
||||
root3 = set 3
|
||||
root4 = set 4
|
||||
|
||||
result = scope_push_stack
|
||||
|
||||
assert ${result}
|
||||
|
||||
defined = is_defined root1
|
||||
assert_false ${defined}
|
||||
defined = is_defined root2
|
||||
assert_false ${defined}
|
||||
defined = is_defined root3
|
||||
assert_false ${defined}
|
||||
defined = is_defined root4
|
||||
assert_false ${defined}
|
||||
|
||||
child1 = set 1
|
||||
child2 = set 2
|
||||
child3 = set 3
|
||||
child4 = set 4
|
||||
|
||||
result = scope_pop_stack
|
||||
|
||||
assert ${result}
|
||||
assert_eq ${root1} 1
|
||||
assert_eq ${root2} 2
|
||||
assert_eq ${root3} 3
|
||||
assert_eq ${root4} 4
|
||||
|
||||
defined = is_defined child1
|
||||
assert_false ${defined}
|
||||
defined = is_defined child2
|
||||
assert_false ${defined}
|
||||
defined = is_defined child3
|
||||
assert_false ${defined}
|
||||
defined = is_defined child4
|
||||
assert_false ${defined}
|
||||
end
|
||||
|
||||
fn test_push_pop_with_copy
|
||||
root1 = set 1
|
||||
root2 = set 2
|
||||
root3 = set 3
|
||||
root4 = set 4
|
||||
|
||||
result = scope_push_stack --copy root2 root4
|
||||
|
||||
assert ${result}
|
||||
assert_eq ${root2} 2
|
||||
assert_eq ${root4} 4
|
||||
|
||||
defined = is_defined root2
|
||||
assert ${defined}
|
||||
defined = is_defined root1
|
||||
assert_false ${defined}
|
||||
defined = is_defined root3
|
||||
assert_false ${defined}
|
||||
|
||||
child1 = set 1
|
||||
child2 = set 2
|
||||
child3 = set 3
|
||||
child4 = set 4
|
||||
|
||||
result = scope_pop_stack --copy child2 child4
|
||||
|
||||
assert ${result}
|
||||
assert_eq ${root1} 1
|
||||
assert_eq ${root2} 2
|
||||
assert_eq ${root3} 3
|
||||
assert_eq ${root4} 4
|
||||
assert_eq ${child2} 2
|
||||
assert_eq ${child4} 4
|
||||
|
||||
defined = is_defined child2
|
||||
assert ${defined}
|
||||
defined = is_defined child1
|
||||
assert_false ${defined}
|
||||
defined = is_defined child3
|
||||
assert_false ${defined}
|
||||
end
|
||||
|
Loading…
Reference in a new issue