mirror of
https://github.com/sagiegurari/duckscript
synced 2024-10-06 08:02:06 +00:00
parent
0f75fafb21
commit
eb188b0baf
|
@ -9,6 +9,7 @@
|
|||
* \[Breaking Change\] Invoking a command that does not exist should crash and not error
|
||||
* cat command to support multiple files #62
|
||||
* New debug commands (dump_instructions, dump_state, dump_variables) #58 #59 #60
|
||||
* Running **duck** cli without arguments, stars up the repl mode #41 #42
|
||||
|
||||
### v0.1.6 (2020-01-12)
|
||||
|
||||
|
|
|
@ -16,6 +16,13 @@ use crate::types::instruction::{
|
|||
};
|
||||
use crate::types::runtime::{Context, Runtime, StateValue};
|
||||
use std::collections::HashMap;
|
||||
use std::io::stdin;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum EndReason {
|
||||
ExitCalled,
|
||||
ReachedEnd,
|
||||
}
|
||||
|
||||
/// Executes the provided script with the given context
|
||||
pub fn run_script(text: &str, context: Context) -> Result<Context, ScriptError> {
|
||||
|
@ -33,10 +40,53 @@ pub fn run_script_file(file: &str, context: Context) -> Result<Context, ScriptEr
|
|||
}
|
||||
}
|
||||
|
||||
/// Provides the REPL entry point
|
||||
pub fn repl(mut context: Context) -> Result<Context, ScriptError> {
|
||||
let mut text = String::new();
|
||||
let mut instructions = vec![];
|
||||
|
||||
loop {
|
||||
text.clear();
|
||||
|
||||
match stdin().read_line(&mut text) {
|
||||
Ok(_) => {
|
||||
match parser::parse_text(&text) {
|
||||
Ok(mut new_instructions) => {
|
||||
// get start line
|
||||
let start = instructions.len();
|
||||
|
||||
// add new instructions
|
||||
instructions.append(&mut new_instructions);
|
||||
let runtime = create_runtime(instructions.clone(), context);
|
||||
|
||||
let (updated_context, end_reason) = run_instructions(runtime, start)?;
|
||||
|
||||
context = updated_context;
|
||||
|
||||
match end_reason {
|
||||
EndReason::ExitCalled => return Ok(context),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(ScriptError {
|
||||
info: ErrorInfo::Runtime(error.to_string(), Some(InstructionMetaInfo::new())),
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn run(instructions: Vec<Instruction>, context: Context) -> Result<Context, ScriptError> {
|
||||
let runtime = create_runtime(instructions, context);
|
||||
|
||||
run_instructions(runtime, 0)
|
||||
match run_instructions(runtime, 0) {
|
||||
Ok((context, _)) => Ok(context),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_runtime(instructions: Vec<Instruction>, context: Context) -> Runtime {
|
||||
|
@ -65,15 +115,19 @@ fn create_runtime(instructions: Vec<Instruction>, context: Context) -> Runtime {
|
|||
runtime
|
||||
}
|
||||
|
||||
fn run_instructions(mut runtime: Runtime, start_at: usize) -> Result<Context, ScriptError> {
|
||||
fn run_instructions(
|
||||
mut runtime: Runtime,
|
||||
start_at: usize,
|
||||
) -> Result<(Context, EndReason), ScriptError> {
|
||||
let mut line = start_at;
|
||||
let mut state = runtime.context.state.clone();
|
||||
|
||||
let instructions = match runtime.instructions {
|
||||
Some(ref instructions) => instructions,
|
||||
None => return Ok(runtime.context),
|
||||
None => return Ok((runtime.context, EndReason::ReachedEnd)),
|
||||
};
|
||||
|
||||
let mut end_reason = EndReason::ReachedEnd;
|
||||
loop {
|
||||
let (instruction, meta_info) = if instructions.len() > line {
|
||||
let instruction = instructions[line].clone();
|
||||
|
@ -95,6 +149,7 @@ fn run_instructions(mut runtime: Runtime, start_at: usize) -> Result<Context, Sc
|
|||
match command_result {
|
||||
CommandResult::Exit(output) => {
|
||||
update_output(&mut runtime.context.variables, output_variable, output);
|
||||
end_reason = EndReason::ExitCalled;
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -162,7 +217,7 @@ fn run_instructions(mut runtime: Runtime, start_at: usize) -> Result<Context, Sc
|
|||
|
||||
runtime.context.state = state;
|
||||
|
||||
Ok(runtime.context)
|
||||
Ok((runtime.context, end_reason))
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
|
|
|
@ -230,7 +230,9 @@ fn run_instructions_exit_result_no_output() {
|
|||
let context_result = run_instructions(runtime, 0);
|
||||
|
||||
assert!(context_result.is_ok());
|
||||
assert!(context_result.unwrap().variables.is_empty());
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert!(updated_context.variables.is_empty());
|
||||
assert_eq!(end_reason, EndReason::ExitCalled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -261,8 +263,10 @@ fn run_instructions_exit_result_with_output() {
|
|||
let context_result = run_instructions(runtime, 0);
|
||||
|
||||
assert!(context_result.is_ok());
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ExitCalled);
|
||||
assert_eq!(
|
||||
context_result.unwrap().variables.get("out"),
|
||||
updated_context.variables.get("out"),
|
||||
Some(&"value".to_string())
|
||||
);
|
||||
}
|
||||
|
@ -288,7 +292,9 @@ fn run_instructions_error_result() {
|
|||
let context_result = run_instructions(runtime, 0);
|
||||
|
||||
assert!(context_result.is_ok());
|
||||
let variables = context_result.unwrap().variables;
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
let variables = updated_context.variables;
|
||||
assert!(!variables.is_empty());
|
||||
assert_eq!(variables.get("out").unwrap(), "false");
|
||||
}
|
||||
|
@ -328,7 +334,9 @@ fn run_instructions_error_result_with_on_error() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
let variables = context_result.unwrap().variables;
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
let variables = updated_context.variables;
|
||||
assert!(!variables.is_empty());
|
||||
assert_eq!(variables.get("1").unwrap(), "test");
|
||||
assert_eq!(variables.get("2").unwrap(), "2");
|
||||
|
@ -375,7 +383,9 @@ fn run_instructions_continue_result_no_output() {
|
|||
let context_result = run_instructions(runtime, 0);
|
||||
|
||||
assert!(context_result.is_ok());
|
||||
assert!(context_result.unwrap().variables.is_empty());
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert!(updated_context.variables.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -409,9 +419,16 @@ fn run_instructions_continue_result_with_output() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
context = context_result.unwrap();
|
||||
assert_eq!(context.variables.get("out1"), Some(&"value1".to_string()));
|
||||
assert_eq!(context.variables.get("out2"), Some(&"value2".to_string()));
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out1"),
|
||||
Some(&"value1".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out2"),
|
||||
Some(&"value2".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -455,8 +472,12 @@ fn run_instructions_goto_label_result_no_output() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
context = context_result.unwrap();
|
||||
assert_eq!(context.variables.get("out2"), Some(&"value2".to_string()));
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out2"),
|
||||
Some(&"value2".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -501,9 +522,16 @@ fn run_instructions_goto_label_result_with_output() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
context = context_result.unwrap();
|
||||
assert_eq!(context.variables.get("out1"), Some(&"my_label".to_string()));
|
||||
assert_eq!(context.variables.get("out2"), Some(&"value2".to_string()));
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out1"),
|
||||
Some(&"my_label".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out2"),
|
||||
Some(&"value2".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -547,8 +575,12 @@ fn run_instructions_goto_line_result_no_output() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
context = context_result.unwrap();
|
||||
assert_eq!(context.variables.get("out2"), Some(&"value2".to_string()));
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out2"),
|
||||
Some(&"value2".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -593,9 +625,16 @@ fn run_instructions_goto_line_result_with_output() {
|
|||
|
||||
assert!(context_result.is_ok());
|
||||
|
||||
context = context_result.unwrap();
|
||||
assert_eq!(context.variables.get("out1"), Some(&"2".to_string()));
|
||||
assert_eq!(context.variables.get("out2"), Some(&"value2".to_string()));
|
||||
let (updated_context, end_reason) = context_result.unwrap();
|
||||
assert_eq!(end_reason, EndReason::ReachedEnd);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out1"),
|
||||
Some(&"2".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
updated_context.variables.get("out2"),
|
||||
Some(&"value2".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
//! # error
|
||||
//!
|
||||
//! The error structure and types.
|
||||
//!
|
||||
|
||||
use duckscript::types::error::ScriptError;
|
||||
use std::fmt;
|
||||
use std::fmt::Display;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "./error_test.rs"]
|
||||
mod error_test;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Holds the error information
|
||||
pub(crate) enum ErrorInfo {
|
||||
/// Error Info Type
|
||||
Script(ScriptError),
|
||||
/// Error Info Type
|
||||
Cli(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Cli error struct
|
||||
pub(crate) struct CliError {
|
||||
/// Holds the error information
|
||||
pub(crate) info: ErrorInfo,
|
||||
}
|
||||
|
||||
impl Display for CliError {
|
||||
/// Formats the script error using the given formatter.
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match self.info {
|
||||
ErrorInfo::Script(ref cause) => cause.fmt(formatter),
|
||||
ErrorInfo::Cli(ref message) => write!(formatter, "{}", message),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
use duckscript::types::error;
|
||||
|
||||
#[test]
|
||||
fn display_script() {
|
||||
let error = CliError {
|
||||
info: ErrorInfo::Script(ScriptError {
|
||||
info: error::ErrorInfo::Initialization("test".to_string()),
|
||||
}),
|
||||
};
|
||||
println!("{}", error);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_cli() {
|
||||
let error = CliError {
|
||||
info: ErrorInfo::Cli("test"),
|
||||
};
|
||||
println!("{}", error);
|
||||
}
|
|
@ -116,9 +116,6 @@
|
|||
//! [Apache 2](https://github.com/sagiegurari/duckscript/blob/master/LICENSE) open source license.
|
||||
//!
|
||||
|
||||
mod error;
|
||||
|
||||
use crate::error::{CliError, ErrorInfo};
|
||||
use duckscript::runner;
|
||||
use duckscript::types::error::ScriptError;
|
||||
use duckscript::types::runtime::Context;
|
||||
|
@ -136,13 +133,11 @@ fn main() {
|
|||
};
|
||||
}
|
||||
|
||||
fn run_cli() -> Result<(), CliError> {
|
||||
fn run_cli() -> Result<(), ScriptError> {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
Err(CliError {
|
||||
info: ErrorInfo::Cli("script file path not provided."),
|
||||
})
|
||||
run_repl()
|
||||
} else if args[1] == "--help" || args[1] == "-h" {
|
||||
let usage = include_str!("help.txt");
|
||||
println!(
|
||||
|
@ -162,19 +157,20 @@ fn run_cli() -> Result<(), CliError> {
|
|||
}
|
||||
};
|
||||
|
||||
match run_script(&value, is_file) {
|
||||
Err(error) => Err(CliError {
|
||||
info: ErrorInfo::Script(error),
|
||||
}),
|
||||
_ => Ok(()),
|
||||
}
|
||||
run_script(&value, is_file)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_script(value: &str, is_file: bool) -> Result<(), ScriptError> {
|
||||
fn create_context() -> Result<Context, ScriptError> {
|
||||
let mut context = Context::new();
|
||||
duckscriptsdk::load(&mut context.commands)?;
|
||||
|
||||
Ok(context)
|
||||
}
|
||||
|
||||
fn run_script(value: &str, is_file: bool) -> Result<(), ScriptError> {
|
||||
let context = create_context()?;
|
||||
|
||||
if is_file {
|
||||
runner::run_script_file(value, context)?;
|
||||
} else {
|
||||
|
@ -183,3 +179,11 @@ fn run_script(value: &str, is_file: bool) -> Result<(), ScriptError> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_repl() -> Result<(), ScriptError> {
|
||||
let context = create_context()?;
|
||||
|
||||
runner::repl(context)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue