From 309a101a0e09ffe2bcd4f0032744f8532a5988d1 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Tue, 20 Feb 2024 16:29:29 -0700 Subject: Parsing improvements Now the parser goes char by char, since special characters like '|' and '&' don't necessarily have to be whitespace seperated. Also added some VERY basic error detection for the parser (revolving around special chars). --- src/main.rs | 367 +++++++++++++++++++++++++----------------------------------- 1 file changed, 153 insertions(+), 214 deletions(-) diff --git a/src/main.rs b/src/main.rs index 09fc70a..d167d04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,86 +1,9 @@ +use core::fmt; use ctrlc; use std::fs; use std::io::{self, Write}; use std::path::Path; -use std::process::{Command, Stdio}; - -// #[derive(Debug)] -// enum Meter { -// Verb, // A command (and possible some arguments) -// Pipe, // Pipe the output of this command into the next -// Daemon, // Fork the command into the background -// And, // Run the next command only if this succeeds -// } - -// impl fmt::Display for Meter { -// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { -// let meter = match self { -// Meter::Verb => "verb", -// Meter::Pipe => "pipe", -// Meter::Daemon => "daemon", -// Meter::And => "and", -// }; -// write!(f, "{}", meter) -// } -// } - -// #[derive(Debug)] -// struct Stanza { -// verb: Option, -// clause: Option>, -// } - -// impl Stanza { -// fn new(line: String) -> Stanza { -// let mut split = line.split(' '); -// let cmd = match split.next() { -// Some(verb) if verb.trim().is_empty() => None, -// Some(verb) => Some(String::from(verb.trim())), -// None => None, -// }; - -// let mut args: Option>; -// match cmd { -// Some(_) => { -// args = Some(Vec::new()); -// loop { -// let next = split.next(); -// match next { -// Some(clause) => args.unwrap().push(clause.to_string()), -// None => break, -// } -// } -// } -// None => args = None, -// } - -// Stanza { -// verb: cmd, -// clause: args, -// } -// } -// } - -// #[derive(Debug)] -// struct Verse { -// meter: Meter, -// stanza: Option, -// } - -// #[derive(Debug)] -// struct Poem { -// verses: Vec, -// } - -// impl Poem { -// fn new() -> Poem { -// Poem { verses: Vec::new() } -// } - -// fn recite(&self) { -// unimplemented!(); -// } -// } +use std::process::{exit, Command, Stdio}; #[derive(Debug)] enum Meter { @@ -88,6 +11,21 @@ enum Meter { Pipe, // Pipe the output of this command into the next Daemon, // Fork the command into the background And, // Run the next command only if this succeeds + String, // Run the next command, even if this doesn't succeed +} + +impl fmt::Display for Meter { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let meter = match self { + Meter::None => "", + Meter::Pipe => "|", + Meter::Daemon => "&", + Meter::And => "&&", + Meter::String => ";", + }; + + write!(f, "{}", meter) + } } #[derive(Debug)] @@ -152,6 +90,12 @@ impl Verse { } } +impl fmt::Display for Verse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} {}", self.verb(), self.clause().join(" ")) + } +} + #[derive(Debug)] struct Poem { verses: Vec, @@ -167,10 +111,10 @@ impl Poem { let mut out: String = String::new(); for verse in self.verses.iter() { - // let verse = match self.verses.iter().next() { - // Some(verse) => verse, - // None => break, - // }; + // Check if user wants to exit the shell + if verse.verb() == "exit" || verse.verb() == "quit" { + exit(0); + } if !verse.spellcheck(bins) { *bins = prefresh(paths); @@ -190,12 +134,13 @@ impl Poem { .spawn() .expect("dwvsh: error 0"); - let mut stdin = child.stdin.as_mut().expect("dwvsh: error 6"); + let stdin = child.stdin.as_mut().expect("dwvsh: error 6"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 7"); out.clear(); let output = child.wait_with_output().unwrap(); - out = String::from_utf8_lossy(&output.stdout).to_string(); + // out = String::from_utf8_lossy(&output.stdout).to_string(); + out = String::from_utf8(output.stdout).unwrap(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) @@ -204,7 +149,25 @@ impl Poem { .spawn() .expect("dwvsh: error 1"); - let mut stdin = child.stdin.as_mut().expect("dwvsh: error 8"); + let stdin = child.stdin.as_mut().expect("dwvsh: error 8"); + stdin.write_all(&out.as_bytes()).expect("dwvsh: error 9"); + out.clear(); + + print!("[f] {}", child.id()); + // let p = prompt.to_owned(); + std::thread::spawn(move || { + child.wait().unwrap(); + println!("[f] +done {}", child.id()); + io::stdout().flush().unwrap(); + }); + } + Meter::String => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .spawn() + .expect("dwvsh: error 5"); + + let stdin = child.stdin.as_mut().expect("dwvsh: error 8"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 9"); out.clear(); @@ -217,17 +180,19 @@ impl Poem { .spawn() .expect("dwvsh: error 2"); - let mut stdin = child.stdin.as_mut().expect("dwvsh: error 10"); + let stdin = child.stdin.as_mut().expect("dwvsh: error 10"); stdin.write_all(&out.as_bytes()).expect("dwvsh: error 11"); out.clear(); - child.wait().unwrap(); + if !child.wait().unwrap().success() { + break; + } } }; } else { match verse.meter { Meter::Pipe => { - let mut child = Command::new(verse.verb()) + let child = Command::new(verse.verb()) .args(verse.clause()) .stdout(Stdio::piped()) .spawn() @@ -235,6 +200,7 @@ impl Poem { let output = child.wait_with_output().unwrap(); out = String::from_utf8_lossy(&output.stdout).to_string(); + // out = String::from_utf8(output.stdout).unwrap(); } Meter::Daemon => { let mut child = Command::new(verse.verb()) @@ -242,6 +208,19 @@ impl Poem { .spawn() .expect("dwvsh: error 4"); + println!("[f] {}", child.id()); + std::thread::spawn(move || { + child.wait().unwrap(); + print!("[f] +done {}\n", child.id()); + io::stdout().flush().unwrap(); + }); + } + Meter::String => { + let mut child = Command::new(verse.verb()) + .args(verse.clause()) + .spawn() + .expect("dwvsh: error 5"); + child.wait().unwrap(); } Meter::And | Meter::None => { @@ -250,7 +229,9 @@ impl Poem { .spawn() .expect("dwvsh: error 5"); - child.wait().unwrap(); + if !child.wait().unwrap().success() { + break; + } } }; } @@ -260,44 +241,94 @@ impl Poem { } } -fn read(poetry: String) -> Poem { - let mut words = poetry.split(' '); +fn read(poetry: String) -> Option { + let mut chars = poetry.chars(); let mut verses: Vec = Vec::new(); let mut stanza: Vec = Vec::new(); - + let mut word: Vec = Vec::new(); let mut prev: Option<&Verse> = None; + loop { - let word = words.next(); - let stdin = match prev { + let char = chars.next(); + + let pipe = match prev { Some(prev) => match prev.meter { Meter::Pipe => true, - Meter::Daemon | Meter::And | Meter::None => false, + Meter::Daemon | Meter::And | Meter::String | Meter::None => false, + }, + None => false, + }; + + let metered = match prev { + Some(prev) => match prev.meter { + Meter::Pipe | Meter::Daemon | Meter::And | Meter::String => true, + Meter::None => false, }, None => false, }; - match word { - Some(verb) if verb == "|" => { - verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Pipe, stdin)); + match char { + Some(meter) + if (meter == '|' || meter == '&' || meter == ';') + && metered + && stanza.is_empty() => + { + println!("dwvsh: parse error"); + return None; + } + Some(meter) if meter == '|' => { + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Pipe, pipe)); stanza = Vec::new(); + word.clear(); } - Some(verb) if verb == "&" => { - verses.push(Verse::new( - Stanza::new(stanza.clone()), - Meter::Daemon, - stdin, - )); + Some(meter) if meter == '&' => { + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + + match chars.clone().peekable().peek() { + Some(c) if c == &'&' => { + chars.next(); + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::And, pipe)); + } + Some(_) => { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Daemon, pipe)); + } + None => { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::Daemon, pipe)); + } + } + stanza = Vec::new(); + word.clear(); } - Some(verb) if verb == "&&" => { - verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::And, stdin)); + Some(meter) if meter == ';' => { + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::String, pipe)); stanza = Vec::new(); + word.clear(); + } + Some(char) if char == ' ' => { + if !word.is_empty() { + stanza.push(word.iter().collect()); + word.clear(); + } } - Some(verb) => { - stanza.push(verb.trim().to_string()); + Some(char) => { + word.push(char); } None => { - verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::None, stdin)); + if !word.is_empty() { + stanza.push(word.iter().collect()); + } + if !stanza.is_empty() { + verses.push(Verse::new(Stanza::new(stanza.clone()), Meter::None, pipe)); + } break; } } @@ -308,7 +339,7 @@ fn read(poetry: String) -> Poem { }; } - Poem::new(verses) + Some(Poem::new(verses)) } /// Refresh the shell's $PATH @@ -393,110 +424,18 @@ fn repl(paths: &Vec<&Path>, prompt: &str) { // Trim the input let poetry = String::from(poetry.trim()); - // Check if user wants to exit the shell - if poetry == "exit" || poetry == "quit" { - break; + // Skip parsing if there is no poetry + if !poetry.is_empty() { + // Parse a poem + let poem = read(poetry); + match poem { + Some(poem) => { + // poem.recite(paths, &mut bins, prompt); + poem.recite(paths, &mut bins); + } + None => {} + } } - - // Parse a poem - let poem = read(poetry); - poem.recite(paths, &mut bins); - - // // Parse command - // let mut split = poetry.split(' '); - // let mut cmd = match split.next() { - // Some(str) if str.trim().is_empty() => continue, - // Some(str) => String::from(str.trim()), - // None => continue, - // }; - - // // Parse arguments - // let mut args = vec![]; - // loop { - // let next = split.next(); - // match next { - // Some(str) => args.push(str), - // None => break, - // } - // } - - // // Check if user wants to change directories - // if cmd == "cd" { - // let path = match args.first() { - // Some(str) => str, - // None => env!("HOME"), - // }; - - // match std::env::set_current_dir(path) { - // Ok(_) => continue, - // Err(_) => { - // println!("cd: unable to change into {}", path); - // continue; - // } - // } - // } - - // Check if the file exists, if given a pull or relative path - // TODO: Check if file at the path is executable (i.e. +x) - // if !Path::new(cmd.as_str()).exists() { - // let b = bins.clone(); - // // Check if the command exists in $PATH if a full or relative path - // // was not given, or if the path does not exist - // // - // // If the command is not found the first time, try refreshing the - // // path first, and only print an error if if it's not found after - // // the path refresh - // cmd = match b - // .clone() - // .iter() - // .find(|b| b.split("/").last().unwrap() == cmd) - // { - // Some(cmd) => cmd.clone(), - // None => { - // bins = prefresh(&paths); - // match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { - // Some(cmd) => cmd.clone(), - // None => { - // println!("dwvsh: {}: command not found", cmd); - // continue; - // } - // } - // } - // } - // } - - // Run the command (and wait for it to finish) - // let mut child = match Command::new(&cmd).args(args).spawn() { - // Ok(ch) => ch, - // Err(err) => { - // match err.kind() { - // // Occurs when the user doesn't have read access, or if the +x bit is not set - // std::io::ErrorKind::PermissionDenied => { - // println!( - // "dwvsh: permission denied: trying to fork to {}", - // cmd.split("/").last().unwrap() - // ) - // } - - // // Occurs if a command was removed from the path - // // If this is the case, refresh the path - // std::io::ErrorKind::NotFound => { - // bins = prefresh(&paths); - // println!( - // "dwvsh: {}: command not found", - // cmd.split("/").last().unwrap() - // ); - // } - - // // Otherwise print the OS error - // _ => println!("dwvsh: fork: {}", err), - // } - - // // Restart the loop - // continue; - // } - // }; - // child.wait().unwrap(); } } -- cgit v1.2.3