Running duck cli without arguments, stars up the repl mode #41 #42

This commit is contained in:
sagie gur ari 2020-01-17 13:28:44 +00:00
parent 0f75fafb21
commit eb188b0baf
6 changed files with 135 additions and 95 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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