From 3cce88f681029385ac3e80a9990346f9272f15e1 Mon Sep 17 00:00:00 2001 From: Aqua-sama Date: Sat, 17 Oct 2020 17:55:44 +0300 Subject: Add new tokenizer --- src/builtins.rs | 73 ---------------------------------- src/main.rs | 31 +++++---------- src/parser.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++----- src/parser/command.rs | 91 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 105 deletions(-) delete mode 100644 src/builtins.rs create mode 100644 src/parser/command.rs diff --git a/src/builtins.rs b/src/builtins.rs deleted file mode 100644 index bcc14f1..0000000 --- a/src/builtins.rs +++ /dev/null @@ -1,73 +0,0 @@ -use std::io::{Error, ErrorKind}; -use std::path::{Path, PathBuf}; -use std::process::Command; - -pub fn cd(args: &[&str], home: &PathBuf) -> Result { - if args.len() > 1 { - return Err(Error::new( - ErrorKind::InvalidInput, - "Too many arguments passed to cd", - )); - } - - if args.len() == 0 { - return Ok(home.to_path_buf()); - } - - let root = match std::fs::canonicalize(Path::new(args[0])) { - Ok(p) => p, - Err(_) => Path::new("/").to_path_buf(), - }; - - match std::env::set_current_dir(&root) { - Ok(_) => Ok(root.to_path_buf()), - Err(e) => Err(e), - } -} - -pub fn run(command: &str, args: &[&str]) -> Result { - let mut child = Command::new(command).args(args).spawn()?; - child.wait() -} -/* - match command { - - command => { - let stdin = match previous_command { - None => Stdio::inherit(), - Some(cmd) => Stdio::from(cmd.stdout.unwrap()), - }; - let stdout = match commands.peek().is_some() { - true => Stdio::piped(), - false => Stdio::inherit(), - }; - - let child = Command::new(command) - .args(args) - .stdin(stdin) - .stdout(stdout) - .spawn(); - match child { - Ok(c) => previous_command = Some(c), - Err(e) => { - previous_command = None; - eprintln!("{}", e); - } - } - } - } -} - -if let Some(mut final_command) = previous_command { - match final_command.wait() { - Ok(ret) => match ret.code() { - Some(code) => { - if cfg!(debug_assertions) { - println!("exit code [{}]", code); - } - } - None => println!("Process termed by signal"), - }, - Err(e) => eprintln!("error waiting on final command: {}", e), - } -}*/ diff --git a/src/main.rs b/src/main.rs index 24aa9b8..ed0a576 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ use rustyline::error::ReadlineError; use rustyline::Editor; -mod builtins; mod parser; mod prompt; @@ -13,7 +12,7 @@ fn main() { let prompt = prompt::Prompt::new().unwrap(); let mut cwd = std::env::current_dir().unwrap(); - let mut status = None; //: std::process::ExitStatus; + let mut status: Option = None; // `()` can be used when no completer is required let mut rl = Editor::<()>::new(); @@ -25,25 +24,13 @@ fn main() { match readline { Ok(line) => { rl.add_history_entry(line.as_str()); - let commands = parser::parse(&line); - match commands[0] { - "!" => println!("{:?}", status), - "cd" => match builtins::cd(&commands[1..], &prompt.home) { - Ok(p) => cwd = p, - Err(e) => eprintln!("Error: {}", e), - }, - "exit" => { - break 'repl; - } - _ => match builtins::run(commands[0], &commands[1..]) { - Ok(s) => { - println!("Ok({})", s); - status = Some(s); - }, - Err(e) => println!("Err({})", e), - }, + + let i = parser::tokenize(&line); + match i.run(&prompt.home, &mut cwd, &status) { + Ok(parser::command::RunResult::Command(s)) => status = Some(s), + Ok(parser::command::RunResult::Builtin) => {} + Err(e) => eprintln!("{}", e), } - //println!("Line: {}", line); } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); @@ -51,11 +38,11 @@ fn main() { } Err(ReadlineError::Eof) => { println!("CTRL-D"); - break; + break 'repl; } Err(err) => { println!("Error: {:?}", err); - break; + break 'repl; } } } diff --git a/src/parser.rs b/src/parser.rs index f69aced..af4aed4 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,15 +5,103 @@ // && on prev cmd okay // || on prev cmd fail -pub fn parse(line: &std::string::String) -> Vec<&str> { - line.split(' ').collect() - /* let mut pipe_append = line.split(">>").peekable(); - if let Some(f) = pipe_append.next() { - println!("append to: {:?}", pipe_append.peek()); - - let mut commands = f.trim().split(" | ").peekable(); - while let Some(command) = commands.next() { - println!("run {}", command); +pub mod command; + +use command::*; + +enum TokenType { + Argument, + StdoutFileOverwrite, + StdoutFileAppend, +} + +pub fn tokenize(line: &str) -> CommandInfo { + let mut r = CommandInfo::new(); + + let mut iter = line.chars().peekable(); + let mut token = String::new(); + let mut token_type = TokenType::Argument; + let mut quote = false; + + while let Some(i) = iter.next() { + match i { + ' ' => { + if quote { + token.push(' '); + } + } + '\'' => match iter.peek() { + Some(&'\'') | Some(&'>') | Some(&'&') => { + token.push(iter.next().unwrap()); + } + _ => { + quote = !quote; + } + }, + '>' => { + if iter.peek() == Some(&'>') { + token_type = TokenType::StdoutFileAppend; + iter.next(); + } else { + token_type = TokenType::StdoutFileOverwrite; + } + } + '&' => { + if iter.peek() == Some(&'&') { + r.when = RunOn::ExitSuccess; + iter.next(); + } + } + '|' => { + if iter.peek() == Some(&'|') { + r.when = RunOn::ExitFailure; + iter.next(); + } + } + _ => { + token.push(i); + } } - }*/ + + if !token.is_empty() && ((iter.peek() == Some(&' ') && !quote) || iter.peek() == None) { + match token_type { + TokenType::Argument => r.args.push(token), + TokenType::StdoutFileOverwrite => r.stdout = Redirect::FileOverwrite(token), + TokenType::StdoutFileAppend => r.stdout = Redirect::FileAppend(token), + } + token = String::new(); + } + } + + r +} + +#[test] +fn test_tokenizer() { + { + let ls = tokenize("ls -l"); + assert_eq!(ls.args, vec!("ls", "-l")); + let string = tokenize("ls -l 'something else'"); + assert_eq!(string.args, vec!("ls", "-l", "something else")); + let escape = tokenize("ls -l 'junction jan''s'"); + assert_eq!(escape.args, vec!("ls", "-l", "junction jan\'s")); + } + { + let o = tokenize("&& ls"); + assert_eq!(o.args, vec!("ls")); + assert_eq!(o.when, RunOn::ExitSuccess); + let f = tokenize("|| ls"); + assert_eq!(f.args, vec!("ls")); + assert_eq!(f.when, RunOn::ExitFailure); + } + /* + println!("{:?}", tokenize("ls -l something'>")); + println!("{:?}", tokenize("ls -l something'>'>")); + println!("{:?}", tokenize("ls -l something >output")); + println!("{:?}", tokenize("ls -l something > output")); + println!("{:?}", tokenize("ls -l something >'junction jan''s'")); + println!("{:?}", tokenize("ls -l something >>output")); + println!("{:?}", tokenize("ls -l something >> output")); + println!("{:?}", tokenize("ls -l something >>'junction jan''s'")); + */ } diff --git a/src/parser/command.rs b/src/parser/command.rs new file mode 100644 index 0000000..496614f --- /dev/null +++ b/src/parser/command.rs @@ -0,0 +1,91 @@ +use std::io::{Error, ErrorKind}; +use std::path::{Path, PathBuf}; + +#[derive(Debug)] +pub enum Redirect { + Std, + FileOverwrite(String), + FileAppend(String), +} + +#[derive(Debug, PartialEq)] +pub enum RunOn { + Always, + ExitSuccess, + ExitFailure, +} + +pub enum RunResult { + Command(std::process::ExitStatus), + Builtin, +} + +#[derive(Debug)] +pub struct CommandInfo { + pub args: Vec, + pub stdout: Redirect, + pub when: RunOn, +} + +impl CommandInfo { + pub fn new() -> CommandInfo { + CommandInfo { + args: Vec::new(), + stdout: Redirect::Std, + when: RunOn::Always, + } + } + + pub fn run( + &self, + home: &PathBuf, + cwd: &mut PathBuf, + status: &Option, + ) -> Result { + match self.args[0].as_str() { + "!" => { + println!("{:?}", status); + Ok(RunResult::Builtin) + } + "cd" => match cd(&self.args[1..], home) { + Ok(p) => { + *cwd = p; + Ok(RunResult::Builtin) + } + Err(e) => Err(e), + }, + "exit" => { + std::process::exit(0); + } + _ => { + let mut child = std::process::Command::new(&self.args[0]) + .args(&self.args[1..]) + .spawn()?; + Ok(RunResult::Command(child.wait().unwrap())) + } + } + } +} + +fn cd(args: &[String], home: &PathBuf) -> Result { + if args.len() > 1 { + return Err(Error::new( + ErrorKind::InvalidInput, + "Too many arguments passed to cd", + )); + } + + if args.len() == 0 { + return Ok(home.to_path_buf()); + } + + let root = match std::fs::canonicalize(Path::new(args[0].as_str())) { + Ok(p) => p, + Err(_) => Path::new("/").to_path_buf(), + }; + + match std::env::set_current_dir(&root) { + Ok(_) => Ok(root.to_path_buf()), + Err(e) => Err(e), + } +} -- cgit v1.2.1