diff options
author | Rory Dudley | 2024-02-20 16:29:29 -0700 |
---|---|---|
committer | Rory Dudley | 2024-02-20 16:29:29 -0700 |
commit | 309a101a0e09ffe2bcd4f0032744f8532a5988d1 (patch) | |
tree | e963762d2f7a55c5f9529359e88bd305422749d3 | |
parent | 5d7e3646b7a267bdcf068d5667201033b3aa9207 (diff) | |
download | dwarvish-309a101a0e09ffe2bcd4f0032744f8532a5988d1.tar.gz |
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).
Notes
Notes:
Even with the improvements to the parsing, this will likely get scrapped
in favor of a cleaner approach. There are a lot of edge cases that are
either difficult to handle with the current way things are, or just
aren't being handled at all. The current implementation is also wont for
better error detection and messages.
-rw-r--r-- | src/main.rs | 367 |
1 files 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<String>, -// clause: Option<Vec<String>>, -// } - -// 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<Vec<String>>; -// 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<Stanza>, -// } - -// #[derive(Debug)] -// struct Poem { -// verses: Vec<Verse>, -// } - -// 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<Verse>, @@ -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<Poem> { + let mut chars = poetry.chars(); let mut verses: Vec<Verse> = Vec::new(); let mut stanza: Vec<String> = Vec::new(); - + let mut word: Vec<char> = 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(); } } |