aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAqua-sama <aqua@iserlohn-fortress.net>2020-10-17 17:55:44 +0300
committerAqua-sama <aqua@iserlohn-fortress.net>2020-10-17 19:22:59 +0300
commit3cce88f681029385ac3e80a9990346f9272f15e1 (patch)
tree885e3b994134857f54831761bd1c6cbda25d5772
parentAdd ! builtin to display the last status (diff)
downloadrshell-3cce88f681029385ac3e80a9990346f9272f15e1.tar.xz
Add new tokenizer
-rw-r--r--src/builtins.rs73
-rw-r--r--src/main.rs31
-rw-r--r--src/parser.rs108
-rw-r--r--src/parser/command.rs91
4 files changed, 198 insertions, 105 deletions
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<PathBuf, Error> {
- 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<std::process::ExitStatus, Error> {
- 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<std::process::ExitStatus> = 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<String>,
+ 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<std::process::ExitStatus>,
+ ) -> Result<RunResult, Error> {
+ 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<PathBuf, Error> {
+ 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),
+ }
+}