diff options
author | Rory Dudley | 2024-03-23 02:45:54 -0600 |
---|---|---|
committer | Rory Dudley | 2024-03-23 02:45:54 -0600 |
commit | 5a7718698373d07a29fffcb792acdb81aa7712d7 (patch) | |
tree | e0147ced4a484e02295cd6a6f0f6dd2250d381c8 /src/poem/recite.rs | |
parent | 37e1ae98dc9309715e9415962f21484a807d2c56 (diff) | |
download | dwarvish-5a7718698373d07a29fffcb792acdb81aa7712d7.tar.gz |
read() and recite() overhaul
Rebuilt the LR parser (i.e. read()) from the ground up. This required
that some changes be made to recite(), in order to accomodate the new
data structures. These data structures were each split out into their
own file, in order to make working with each component a bit easier.
In addition to reworking the parts of the parser already present, some
new features were also added, such as:
- Support for strings (' and ")
- Support for environment variables ($)
- Support for interpreting tild as $HOME (~)
- Support for sub-reading and sub-reciting (`)
Notes
Notes:
This is a huge commit that changes almost the entire program (main.rs is
still the same, except for imports). Ideally, huge sweeping changes like
this should not occur on the codebase, but since this is still
pre-alpha, I guess this is acceptable. This is far from the end of
patch set, however, as there is quite a lot of cleanup that needs to be
done. For instance, checking for internal poems and environment
variables should get split out to their own functions/macros. There is
also some defunct code (that's commented out), that is unlikely to be
useful in the future.
Diffstat (limited to 'src/poem/recite.rs')
-rw-r--r-- | src/poem/recite.rs | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/src/poem/recite.rs b/src/poem/recite.rs new file mode 100644 index 0000000..ba739c2 --- /dev/null +++ b/src/poem/recite.rs @@ -0,0 +1,222 @@ +mod ps; +use super::Poem; +use crate::path::prefresh; +use crate::poem::elements::rune::Rune; +use std::env; +use std::process::exit; +use std::{ + io, + path::Path, + sync::{Arc, Mutex}, +}; + +pub trait Reciteable { + fn recite( + &self, + path: &Vec<&Path>, + bins: &mut Vec<String>, + stdout: Option<bool>, + ) -> Result<String, io::Error>; +} + +impl Reciteable for Poem { + fn recite( + &self, + path: &Vec<&Path>, + bins: &mut Vec<String>, + stdout: Option<bool>, + ) -> Result<String, io::Error> { + // Should we print to stdout or always capture it + let stdout = stdout.unwrap_or(true); + + // Variable for storing the output of a piped verse + let mut out: String = String::new(); + + // Keep track of pids for background processes + let mut pids: Arc<Mutex<Vec<i32>>> = Arc::new(Mutex::new(Vec::new())); + + // Loop through each verse in the poem + for verse in self.iter() { + // Verse may need to be mutable + let mut verse = verse.clone(); + + // Check for environment variables + for word in verse.stanza.iter_mut() { + while word.contains("$") { + let mut name = vec![]; + let mut idx = word.chars().position(|c| c == '$').unwrap() + 1; + let bytes = word.as_bytes(); + while idx < word.len() { + let c = bytes[idx] as char; + if !c.is_alphanumeric() { + break; + } + name.push(c); + idx += 1; + } + let name: String = format!("${}", name.iter().collect::<String>()); + let envar = name[1..].to_string(); + let envar = match env::var(envar) { + Ok(envar) => envar.to_string(), + Err(_) => "".to_string(), + }; + *word = word.replace(name.as_str(), envar.as_str()); + } + } + + // Run interal poems + let v = verse.clone(); + let mut new_stanza = None; + if verse.poems() { + // Collect all the words that have vertical tabs + let mut wordp_indicies = vec![]; + let wordps = verse + .stanza + .iter_mut() + .enumerate() + .filter(|(_, w)| w.contains("\x0b")) + .map(|(i, w)| { + wordp_indicies.push(i + 1); + w + }) + .collect::<Vec<&mut String>>(); + + // Loop through each word and replace with the output of the poem + let mut poems = verse.poems.iter(); + let mut j = 0; + for wordp in wordps { + let times = wordp + .chars() + .filter(|c| c == &'\x0b') + .collect::<String>() + .len(); + for _ in 0..times { + let poem = match poems.next() { + Some(poem) => poem, + None => break, // TODO: Return an error + }; + let out = poem.recite(path, bins, Some(false))?; + if out.contains("\n") { + let mut out = out.split("\n"); + let next = out.next().unwrap_or("").trim(); + *wordp = wordp.replacen("\x0b", next, 1).to_string(); + let (_, right) = v.stanza.split_at(wordp_indicies[j]); + let mut left = vec![]; + let mut right = right.to_vec(); + loop { + let next = match out.next() { + Some(next) => next, + None => break, + } + .to_string(); + left.push(next); + } + left.append(&mut right); + new_stanza = Some(left.clone()); + } else { + *wordp = wordp.replacen("\x0b", out.as_str(), 1).to_string(); + } + } + j += 1; + } + + // // Get indices of words in the verse that begin with a vertical tab + // let mut indicies = vec![]; + // for (i, word) in verse.stanza.iter().enumerate() { + // if word.starts_with("\x0b") { + // indicies.push(i); + // } + // } + + // // Try to recite the internal poem, and update the parent poem + // for (i, poem) in verse.poems.iter().enumerate() { + // let out = poem.recite(path, bins, Some(false))?; + // verse.stanza[indicies[i]] = out; + // } + } + + match new_stanza { + Some(stanza) => { + let mut stanza = stanza.clone(); + verse.stanza.append(&mut stanza); + } + None => {} + } + + // Check if the user wants to exit the shell + if verse.verb() == "exit" || verse.verb() == "quit" { + exit(0); + } + + // Check if the user wants to change directories + if verse.verb() == "cd" { + let path = match verse.clause() { + Some(path) => path[0].to_string(), + None => env!("HOME").to_string(), + }; + + match std::env::set_current_dir(&path) { + Ok(_) => continue, + Err(e) => { + eprintln!( + "cd: unable to change into {}: {}", + path, + e.to_string().to_lowercase() + ); + continue; + } + } + } + + // Check if the verb exists + // If it doesn't exist, try refreshing the binary cache, and check + // again + // If it still doesn't exist, print an error + if !verse.spellcheck(bins) { + *bins = prefresh(path); + if !verse.spellcheck(bins) { + eprintln!("dwvsh: {}: command not found", verse.verb()); + + if verse.meter != Rune::And { + continue; + } + } + } + + // Incant the verse, based on its meter + let status = if stdout { + match verse.io { + Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, + Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, + Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, + _ => match verse.meter { + Rune::None => Rune::incant_none(&verse, &mut out)?, + Rune::Couplet => Rune::incant_couplet(&verse, &mut out)?, + Rune::Quiet => Rune::incant_quiet(&verse, &mut out, &mut pids)?, + Rune::And => Rune::incant_and(&verse, &mut out)?, + Rune::Continue => Rune::incant_continue(&verse, &mut out)?, + _ => unreachable!(), + }, + } + } else { + match verse.io { + Rune::Read => Rune::incant_read(&mut verse, &mut out, &mut pids)?, + Rune::Write => Rune::incant_write(&mut verse, &mut out, &mut pids)?, + Rune::Addendum => Rune::incant_addendum(&mut verse, &mut out, &mut pids)?, + _ => Rune::incant_couplet(&verse, &mut out)?, + } + }; + + // Break from the loop if the meter is not [Rune::Continue], and + // if the status is not 0 + // For [Rune::Quiet], [Rune::incant_quiet] will always return 0 + if verse.meter != Rune::Continue && status != 0 { + break; + } + } + + // If we've successfully exited the loop, then all verses were properly + // recited + Ok(out.trim().to_string()) + } +} |