diff --git a/.gitignore b/.gitignore index 27b11f1..4c1af49 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ Cargo.lock # ignore files used for testing stuff like loadf /test* + +# benchmark files +dhat.out.* \ No newline at end of file diff --git a/src/builtins.rs b/src/builtins.rs index 3dd67fe..2b5f305 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -8,7 +8,7 @@ pub const BUILTINS: [( &str, fn(args: Vec, unsplit_args: String, state: &mut super::State) -> i32, &str, -); 15] = [ +); 17] = [ ("cd", cd, "[dir]"), ("exit", exit, ""), ("echo", echo, "[-e] [text ...]"), @@ -24,6 +24,8 @@ pub const BUILTINS: [( ("pastef", pastef, ""), ("setf", setf, "var [var ...]"), ("getf", getf, "var"), + ("()", nop, ""), + ("if", _if, "condition ( statement ) [ ( else_statement )"), ]; /// Change the directory @@ -52,6 +54,10 @@ pub fn exit(_: Vec, _: String, state: &mut super::State) -> i32 { /// Echo a string pub fn echo(args: Vec, mut unsplit_args: String, _: &mut super::State) -> i32 { + if args.len() == 1 { + println!(); + return 0; + } unsplit_args = unsplit_args[(args[0].len() + 1)..].to_string(); if args.len() != 1 && args[1] == "-e" { unsplit_args = unsplit_args[3..].to_string(); @@ -325,3 +331,35 @@ pub fn getf(args: Vec, _: String, state: &mut super::State) -> i32 { state.focus = super::Focus::Str(val); 0 } + +/// Empty function that does nothing. Mainly used for benchmarking evaluating. +pub fn nop(_: Vec, _: String, _: &mut super::State) -> i32 { + 0 +} + +/// if statement +pub fn _if(args: Vec, _: String, state: &mut super::State) -> i32 { + if args.len() < 3 { + println!( + "sesh: {0}: usage: {0} condition (statement) [ (else_statement) ]", + args[0] + ); + return 1; + } + super::eval(&args[1].clone(), state); + state.shell_env.reverse(); + let mut status = 0i32; + for var in &state.shell_env { + if var.name == "STATUS" { + status = var.value.parse().unwrap(); + } + } + state.shell_env.sort_by(|v1, v2| v1.name.cmp(&v2.name)); + if status == 0 { + super::eval(&args[2].clone(), state); + } else if args.len() == 8 { + super::eval(&args[3].clone(), state); + } + + 0 +} diff --git a/src/main.rs b/src/main.rs index 5caa6a8..ce2c1dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ #![warn(missing_docs, clippy::missing_docs_in_private_items)] #![feature(cfg_match)] #![feature(slice_concat_trait)] +#![feature(test)] use std::{ ffi::OsStr, @@ -17,6 +18,8 @@ use termion::raw::IntoRawMode; mod builtins; mod escapes; +#[cfg(test)] +mod tests; /// sesh is a shell designed to be as semantic to use as possible #[derive(Parser, Debug)] @@ -85,8 +88,6 @@ impl Display for Focus { struct State { /// Shell-local variables only accessible via builtins. shell_env: ShellVars, - /// The previous history of the states. - history: Vec, /// Current working directory. working_dir: PathBuf, /// A list of aliases from name to actual. @@ -103,20 +104,37 @@ unsafe impl Send for State {} /// Split a statement. fn split_statement(statement: &str) -> Vec { let mut out = vec![String::new()]; - let mut i: usize = 0; + let mut i = 0usize; let mut in_str = (false, ' '); let mut escape = false; + let mut f = 0usize; for ch in statement.chars() { if ch == '\\' && !in_str.0 { escape = true; } - if ['"', '\'', '`'].contains(&ch) && !escape { - if in_str.0 && in_str.1 == ch { - in_str.0 = false - } else { - in_str = (true, ch); + if in_str.0 && in_str.1 == ch { + in_str.0 = false; + if ch == ']' { + out[i].push(ch); } escape = false; + f += 1; + continue; + } + if !(!['"', '\'', '`', '(', '['].contains(&ch) || escape || in_str.0 || ch == '[' && f <= 1) + { + in_str = (true, ch); + if ch == '(' { + in_str.1 = ')'; + } + if ch == '[' { + in_str.1 = ']'; + } + if ch == '[' { + out[i].push(ch); + } + escape = false; + f += 1; continue; } if !in_str.0 && ch == ' ' { @@ -125,10 +143,12 @@ fn split_statement(statement: &str) -> Vec { out.push(String::new()); } escape = false; + f += 1; continue; } out[i].push(ch); escape = false; + f += 1; } out.iter() .map(|v| v.trim().to_string()) @@ -326,9 +346,6 @@ fn eval(statement: &str, state: &mut State) { } } } - - let s = state.clone(); - state.history.push(s); } /// Write the prompt to the screen. @@ -388,7 +405,6 @@ fn main() -> Result<(), Box> { let mut state = State { shell_env: Vec::new(), - history: Vec::new(), focus: Focus::Str(String::new()), working_dir: std::env::current_dir() .unwrap_or(std::env::home_dir().unwrap_or(PathBuf::from("/"))), diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..2d894cc --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,29 @@ +#![allow(clippy::unit_arg)] + +extern crate test; // needed +use super::*; + +#[bench] +pub fn bench_eval(bencher: &mut test::Bencher) { + bencher.iter(|| { + let mut state = State { + shell_env: Vec::new(), + focus: Focus::Str(String::new()), + working_dir: std::env::current_dir() + .unwrap_or(std::env::home_dir().unwrap_or(PathBuf::from("/"))), + aliases: Vec::new(), + raw_term: None, + }; + state.shell_env.push(ShellVar { + name: "PROMPT1".to_string(), + value: "\x1b[32m$u@$h\x1b[39m \x1b[34m$P\x1b[39m> ".to_string(), + }); + state.shell_env.push(ShellVar { + name: "PROMPT2".to_string(), + value: "> ".to_string(), + }); + core::hint::black_box(eval("", &mut state)); + core::hint::black_box(eval("()", &mut state)); + core::hint::black_box(eval("echo", &mut state)); + }); +}