summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2021-12-22 02:57:56 -0500
committerJesse Luehrs <doy@tozt.net>2021-12-22 02:58:10 -0500
commit6e9ba966a1fd097196394f00c43eca15ac571843 (patch)
tree9761348cb4fac4a50a4b2ae90932a2e8d78fd444 /src
parentfea9c70861326fdc1c670e965b1c214741f27be7 (diff)
downloadnbsh-6e9ba966a1fd097196394f00c43eca15ac571843.tar.gz
nbsh-6e9ba966a1fd097196394f00c43eca15ac571843.zip
start writing a parser
Diffstat (limited to 'src')
-rw-r--r--src/parse.rs102
-rw-r--r--src/shell.pest17
-rw-r--r--src/state/history.rs17
3 files changed, 111 insertions, 25 deletions
diff --git a/src/parse.rs b/src/parse.rs
index 42ead6e..abeefb0 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -1,30 +1,52 @@
+use pest::Parser as _;
+
+#[derive(pest_derive::Parser)]
+#[grammar = "shell.pest"]
+struct Shell;
+
+#[derive(Debug, Clone)]
pub struct Word {
word: String,
interpolate: bool,
}
impl Word {
- fn new(word: String) -> Self {
+ fn new(word: &str) -> Self {
Self {
- word,
+ word: word.to_string(),
interpolate: true,
}
}
- fn literal(word: String) -> Self {
+ fn literal(word: &str) -> Self {
Self {
- word,
+ word: word.to_string(),
interpolate: false,
}
}
}
+#[derive(Debug, Clone)]
pub struct Exe {
exe: Word,
args: Vec<Word>,
+ original: String,
}
impl Exe {
+ fn parse(pair: pest::iterators::Pair<Rule>) -> Self {
+ assert!(matches!(pair.as_rule(), Rule::exe));
+ let original = pair.as_str().to_string();
+ let mut iter = pair.into_inner();
+ let exe = Word::new(iter.next().unwrap().as_str());
+ let args = iter.map(|word| Word::new(word.as_str())).collect();
+ Self {
+ exe,
+ args,
+ original,
+ }
+ }
+
pub fn exe(&self) -> &str {
&self.exe.word
}
@@ -32,26 +54,72 @@ impl Exe {
pub fn args(&self) -> impl Iterator<Item = &str> {
self.args.iter().map(|arg| arg.word.as_ref())
}
+
+ pub fn input_string(&self) -> String {
+ self.original.clone()
+ }
}
+#[derive(Debug, Clone)]
pub enum Command {
Exe(Exe),
- And(Vec<Command>),
- Or(Vec<Command>),
- Both(Vec<Command>),
- Pipe(Vec<Command>),
+ And(Exe, Box<Command>),
+ Or(Exe, Box<Command>),
+ Both(Exe, Box<Command>),
+ Pipe(Exe, Box<Command>),
}
impl Command {
pub fn parse(full_cmd: &str) -> Self {
- let mut parts = full_cmd.split(' ');
- let cmd = parts.next().unwrap();
- Self::Exe(Exe {
- exe: Word::new(cmd.to_string()),
- args: parts
- .map(std::string::ToString::to_string)
- .map(Word::new)
- .collect(),
- })
+ Self::build_ast(
+ Shell::parse(Rule::line, full_cmd)
+ .unwrap()
+ .next()
+ .unwrap()
+ .into_inner(),
+ )
+ }
+
+ pub fn input_string(&self) -> String {
+ match self {
+ Self::Exe(exe) => exe.input_string(),
+ Self::And(exe, command) => format!(
+ "{} && {}",
+ exe.input_string(),
+ command.input_string()
+ ),
+ Self::Or(exe, command) => format!(
+ "{} || {}",
+ exe.input_string(),
+ command.input_string()
+ ),
+ Self::Both(exe, command) => {
+ format!("{}; {}", exe.input_string(), command.input_string())
+ }
+ Self::Pipe(exe, command) => {
+ format!("{} | {}", exe.input_string(), command.input_string())
+ }
+ }
+ }
+
+ fn build_ast(mut pairs: pest::iterators::Pairs<Rule>) -> Self {
+ let command = pairs.next().unwrap();
+ assert!(matches!(command.as_rule(), Rule::command));
+ let mut inner = command.into_inner();
+ let exe = inner.next().unwrap();
+ let exe = Exe::parse(exe);
+ if let Some(rest) = inner.next() {
+ let rule = rest.as_rule();
+ let ast = Self::build_ast(rest.into_inner());
+ match rule {
+ Rule::and => Self::And(exe, Box::new(ast)),
+ Rule::or => Self::Or(exe, Box::new(ast)),
+ Rule::both => Self::Both(exe, Box::new(ast)),
+ Rule::pipe => Self::Pipe(exe, Box::new(ast)),
+ _ => unreachable!(),
+ }
+ } else {
+ Self::Exe(exe)
+ }
}
}
diff --git a/src/shell.pest b/src/shell.pest
new file mode 100644
index 0000000..f1f39ab
--- /dev/null
+++ b/src/shell.pest
@@ -0,0 +1,17 @@
+char = { ASCII_ALPHANUMERIC }
+
+word = @{ char+ }
+
+exe = { word+ }
+
+and = { "&&" ~ command }
+or = { "||" ~ command }
+both = { ";" ~ command }
+pipe = { "|" ~ command }
+
+command = { exe ~ (and | or | both | pipe)? }
+
+line = { SOI ~ command ~ EOI }
+
+WHITESPACE = _{ " " }
+COMMENT = _{ "#" ~ ANY* }
diff --git a/src/state/history.rs b/src/state/history.rs
index 856153d..685650a 100644
--- a/src/state/history.rs
+++ b/src/state/history.rs
@@ -91,15 +91,16 @@ impl History {
let (input_w, input_r) = async_std::channel::unbounded();
let (resize_w, resize_r) = async_std::channel::unbounded();
+ let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new(
+ Entry::new(cmd.clone(), self.size, input_w, resize_w),
+ ));
+
// for now
let cmd = match cmd {
crate::parse::Command::Exe(exe) => exe,
_ => todo!(),
};
- let entry = async_std::sync::Arc::new(async_std::sync::Mutex::new(
- Entry::new(cmd.exe(), self.size, input_w, resize_w),
- ));
if crate::builtins::is(cmd.exe()) {
let code: i32 =
crate::builtins::run(cmd.exe(), cmd.args()).into();
@@ -247,7 +248,7 @@ impl std::iter::DoubleEndedIterator for VisibleEntries {
}
pub struct Entry {
- cmd: String,
+ cmd: crate::parse::Command,
vt: vt100::Parser,
audible_bell_state: usize,
visual_bell_state: usize,
@@ -261,13 +262,13 @@ pub struct Entry {
impl Entry {
fn new(
- cmd: &str,
+ cmd: crate::parse::Command,
size: (u16, u16),
input: async_std::channel::Sender<Vec<u8>>,
resize: async_std::channel::Sender<(u16, u16)>,
) -> Self {
Self {
- cmd: cmd.into(),
+ cmd,
vt: vt100::Parser::new(size.0, size.1, 0),
audible_bell_state: 0,
visual_bell_state: 0,
@@ -319,7 +320,7 @@ impl Entry {
if self.running() {
out.set_bgcolor(textmode::Color::Rgb(16, 64, 16));
}
- out.write_str(&self.cmd);
+ out.write_str(&self.cmd.input_string());
out.reset_attributes();
set_bgcolor(out, focused);
@@ -429,7 +430,7 @@ impl Entry {
}
pub fn cmd(&self) -> String {
- self.cmd.clone()
+ self.cmd.input_string()
}
pub fn toggle_fullscreen(&mut self) {