Skip to content

Commit

Permalink
Merge pull request #451 from sagiegurari/0.10.0
Browse files Browse the repository at this point in the history
0.10.0
  • Loading branch information
sagiegurari authored Oct 3, 2024
2 parents 34c46ca + 1db4769 commit c5f2212
Show file tree
Hide file tree
Showing 195 changed files with 1,556 additions and 2,654 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
## CHANGELOG

### v0.10.0

* Enhancement: Runtime - \[Breaking Change\] New Env struct enabling commands to redirect out/err to provided streams #440
* Enhancement: Runtime - \[Breaking Change\] Commands now get new CommandArgs struct instead of multiple fields.
* Enhancement: Runtime - Enable to halt execution via env.

### v0.9.4 (2024-09-28)

* Enhancement: Runtime - Adding halt interrupt to env #448 (thanks @nickheyer)
Expand Down
66 changes: 27 additions & 39 deletions docs/_includes/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,21 +356,16 @@ If you want to know how to write your own commands or embed the duckscript runti
Want to write new custom commands so you can use them in your duckscripts? great!<br>
Hopefully the following sections will help you gain the basic knowledge on how to write them.<br>
First of all it is important to understand that there are two types of commands:
* Commands which execute some action like copying files, printing some text to the console or returning an environment variable.
* Commands which provide flow control or some more complex action and require modifying the internal context in runtime.
<a name="sdk-tutorial-standard-commands"></a>
## Standard Commands
<a name="sdk-tutorial-commands"></a>
## Commands
Commands are structs that must implement the Command trait.<br>
* They must have a name, which is used to invoke the command.<br>
* They optionally may have aliases which can also be used to invoke the command.<br>
* They should return help documentation in markdown format in order to generate SDK documentation (must for PRs to duckscript official SDK).<br>
* They must implement the **run** function which holds the command logic.<br>
The run function accepts the command arguments (variables already replaced to actual values) and returns the command result.<br>
The run function accepts the command arguments (args array contains actual values and not original variables) and returns the command result.<br>
The command result can be one of the following:
* Continue(Option<String>) - Tells the runner to continue to the next command and optionally set the output variable the given value.
Expand All @@ -393,11 +388,15 @@ impl Command for SetCommand {
"set".to_string()
}
fn run(&self, arguments: Vec<String>) -> CommandResult {
let output = if arguments.is_empty() {
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new((*self).clone())
}
fn run(&self, arguments: CommandArgs) -> CommandResult {
let output = if arguments.args.is_empty() {
None
} else {
Some(arguments[0].clone())
Some(arguments.args[0].clone())
};
CommandResult::Continue(output)
Expand All @@ -420,11 +419,15 @@ impl Command for GetEnvCommand {
"get_env".to_string()
}
fn run(&self, arguments: Vec<String>) -> CommandResult {
if arguments.is_empty() {
fn clone_and_box(&self) -> Box<dyn Command> {
Box::new((*self).clone())
}
fn run(&self, arguments: CommandArgs) -> CommandResult {
if arguments.args.is_empty() {
CommandResult::Error("Missing environment variable name.".to_string())
} else {
match env::var(&arguments[0]) {
match env::var(&arguments.args[0]) {
Ok(value) => CommandResult::Continue(Some(value)),
Err(_) => CommandResult::Continue(None),
}
Expand All @@ -435,38 +438,23 @@ impl Command for GetEnvCommand {
You can look at more examples in the duckscript_sdk folder.
<a name="sdk-tutorial-context-commands"></a>
## Context Commands
Context commands are exactly the same as standard commands except that they have access to the runtime context.<br>
Therefore they implement the same Command trait but this time instead of implementing the run function, they need to implement the following:
* requires_context - Must return true
* run_with_context - The same logic you would put in the run function but now you have access to a lot more of the runtime context.
The run_with_context signature is as follows:
<a name="sdk-tutorial-commands-context"></a>
## Access The Context
The duckscript runtime context is available in the CommandArgs struc.<br>
```rust
/// Run the instruction with access to the runtime context.
///
/// # Arguments
///
/// * `arguments` - The command arguments array
/// The CommandArgs has the following members:
/// * `args` - The command arguments array
/// * `state` - Internal state which is only used by commands to store/pull data
/// * `variables` - All script variables
/// * `output_variable` - The output variable name (if defined)
/// * `instructions` - The entire list of instructions which make up the currently running script
/// * `commands` - The currently known commands
/// * `line` - The current instruction line number (global line number after including all scripts into one global script)
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;
/// * `env` - The current runtime env with access to out/err writers, etc...
fn run(&self, arguments: CommandArgs) -> CommandResult;
```
With access to this context you can add/remove/switch commands in runtime, store/pull internal state, add/remove/change variables and so on...
Expand All @@ -479,7 +467,7 @@ The duckscript cli basically embeds duckscript so you can look at it as a refere
```rust
let mut context = Context::new();
duckscriptsdk::load(&mut context.commands)?;
runner::run_script_file(file, context)?;
runner::run_script_file(file, context, None)?;
```
That's it!<br>
Expand All @@ -502,10 +490,10 @@ The following public functions are available:
```rust
/// Executes the provided script with the given context
pub fn run_script(text: &str, context: Context) -> Result<Context, ScriptError>;
pub fn run_script(text: &str, context: Context, env: Option<Env>) -> Result<Context, ScriptError>;
/// Executes the provided script file with the given context
pub fn run_script_file(file: &str, context: Context) -> Result<Context, ScriptError>;
pub fn run_script_file(file: &str, context: Context, env: Option<Env>) -> Result<Context, ScriptError>;
```
<a name="editor-support"></a>
Expand Down
4 changes: 2 additions & 2 deletions docs/_includes/nav.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
* [Full SDK Docs](https://github.com/sagiegurari/duckscript/blob/master/docs/sdk.md)
* [Final Notes](#tutorial-final-notes)
* [Duckscript Command Implementation Tutorial](#sdk-tutorial)
* [Standard Commands](#sdk-tutorial-standard-commands)
* [Context Commands](#sdk-tutorial-context-commands)
* [Commands](#sdk-tutorial-commands)
* [Access The Context](#sdk-tutorial-commands-context)
* [Duckscript Embedding Tutorial](#embed-tutorial)
* [Setting Up The Context](#embed-tutorial-setup-context)
* [Running The Script](#embed-tutorial-running)
Expand Down
75 changes: 50 additions & 25 deletions duckscript/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,41 @@ mod runner_test;

use crate::expansion::{self, ExpandedValue};
use crate::parser;
use crate::types::command::{CommandResult, Commands, GoToValue};
use crate::types::command::{CommandArgs, CommandResult, Commands, GoToValue};
use crate::types::env::Env;
use crate::types::error::ScriptError;
use crate::types::instruction::{
Instruction, InstructionMetaInfo, InstructionType, ScriptInstruction,
};
use crate::types::runtime::{Context, Runtime, StateValue};
use std::collections::HashMap;
use std::io::stdin;
use std::sync::atomic::Ordering;

#[derive(Debug)]
enum EndReason {
ExitCalled,
ReachedEnd,
Crash(ScriptError),
Halted,
}

/// Executes the provided script with the given context
pub fn run_script(text: &str, context: Context) -> Result<Context, ScriptError> {
pub fn run_script(text: &str, context: Context, env: Option<Env>) -> Result<Context, ScriptError> {
match parser::parse_text(text) {
Ok(instructions) => run(instructions, context),
Ok(instructions) => run(instructions, context, env),
Err(error) => Err(error),
}
}

/// Executes the provided script file with the given context
pub fn run_script_file(file: &str, context: Context) -> Result<Context, ScriptError> {
pub fn run_script_file(
file: &str,
context: Context,
env: Option<Env>,
) -> Result<Context, ScriptError> {
match parser::parse_file(file) {
Ok(instructions) => run(instructions, context),
Ok(instructions) => run(instructions, context, env),
Err(error) => Err(error),
}
}
Expand All @@ -58,7 +65,7 @@ pub fn repl(mut context: Context) -> Result<Context, ScriptError> {

// add new instructions
instructions.append(&mut new_instructions);
let runtime = create_runtime(instructions.clone(), context);
let runtime = create_runtime(instructions.clone(), context, None);

let (updated_context, end_reason) = run_instructions(runtime, start, true)?;

Expand All @@ -83,17 +90,21 @@ pub fn repl(mut context: Context) -> Result<Context, ScriptError> {
}
}

fn run(instructions: Vec<Instruction>, context: Context) -> Result<Context, ScriptError> {
let runtime = create_runtime(instructions, context);
fn run(
instructions: Vec<Instruction>,
context: Context,
env: Option<Env>,
) -> Result<Context, ScriptError> {
let runtime = create_runtime(instructions, context, env);

match run_instructions(runtime, 0, false) {
Ok((context, _)) => Ok(context),
Err(error) => Err(error),
}
}

fn create_runtime(instructions: Vec<Instruction>, context: Context) -> Runtime {
let mut runtime = Runtime::new(context);
fn create_runtime(instructions: Vec<Instruction>, context: Context, env: Option<Env>) -> Runtime {
let mut runtime = Runtime::new(context, env);

let mut line = 0;
for instruction in &instructions {
Expand Down Expand Up @@ -126,6 +137,11 @@ fn run_instructions(

let mut end_reason = EndReason::ReachedEnd;
loop {
if runtime.env.halt.load(Ordering::SeqCst) {
end_reason = EndReason::Halted;
break;
}

let (instruction, meta_info) = if instructions.len() > line {
let instruction = instructions[line].clone();
let meta_info = instruction.meta_info.clone();
Expand All @@ -141,6 +157,7 @@ fn run_instructions(
instructions,
instruction,
line,
&mut runtime.env,
);

match command_result {
Expand Down Expand Up @@ -185,6 +202,7 @@ fn run_instructions(
instructions,
error,
meta_info.clone(),
&mut runtime.env,
) {
return Err(ScriptError::Runtime(error, Some(meta_info.clone())));
};
Expand Down Expand Up @@ -249,6 +267,7 @@ fn run_on_error_instruction(
instructions: &Vec<Instruction>,
error: String,
meta_info: InstructionMetaInfo,
env: &mut Env,
) -> Result<(), String> {
if commands.exists("on_error") {
let mut script_instruction = ScriptInstruction::new();
Expand All @@ -263,8 +282,15 @@ fn run_on_error_instruction(
instruction_type: InstructionType::Script(script_instruction),
};

let (command_result, output_variable) =
run_instruction(commands, variables, state, instructions, instruction, 0);
let (command_result, output_variable) = run_instruction(
commands,
variables,
state,
instructions,
instruction,
0,
env,
);

match command_result {
CommandResult::Exit(output) => {
Expand All @@ -288,6 +314,7 @@ pub fn run_instruction(
instructions: &Vec<Instruction>,
instruction: Instruction,
line: usize,
env: &mut Env,
) -> (CommandResult, Option<String>) {
let mut output_variable = None;
let command_result = match instruction.instruction_type {
Expand All @@ -305,19 +332,17 @@ pub fn run_instruction(
&instruction.meta_info,
);

if command_instance.requires_context() {
command_instance.run_with_context(
command_arguments,
state,
variables,
output_variable.clone(),
instructions,
commands,
line,
)
} else {
command_instance.run(command_arguments)
}
let command_args = CommandArgs {
args: command_arguments,
state,
variables,
output_variable: output_variable.clone(),
instructions,
commands,
line,
env,
};
command_instance.run(command_args)
}
None => CommandResult::Crash(format!("Command: {} not found.", &command)),
},
Expand Down
Loading

0 comments on commit c5f2212

Please sign in to comment.