summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-02-20 16:29:29 -0700
committerRory Dudley2024-02-20 16:29:29 -0700
commit309a101a0e09ffe2bcd4f0032744f8532a5988d1 (patch)
treee963762d2f7a55c5f9529359e88bd305422749d3
parent5d7e3646b7a267bdcf068d5667201033b3aa9207 (diff)
downloaddwarvish-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.rs367
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();
}
}