summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRory Dudley2024-03-05 00:42:28 -0700
committerRory Dudley2024-03-05 00:42:28 -0700
commit37e1ae98dc9309715e9415962f21484a807d2c56 (patch)
tree69444755d1a0095f5a7cfad66ab966f83f46d4aa
parente03ceec834aeca1f383b19354e32987bc2d59506 (diff)
downloaddwarvish-37e1ae98dc9309715e9415962f21484a807d2c56.tar.gz
Poem::read macros
Added the following macros: push!: Creates a Verse from a stanza, taking into account some extra options (such as the Meter). push1!: Creates a Verse from a stanza, but also allows looking ahead by a single character, in order to pattern match certain meters (i.e. And ('&&') and Addendum ('>>')). Replaced the code in the huge, redundant match statements in Poem::read with the macros described above.
-rw-r--r--src/recite.rs288
-rw-r--r--src/recite/parse.rs135
2 files changed, 189 insertions, 234 deletions
diff --git a/src/recite.rs b/src/recite.rs
index 05e49f3..07e6276 100644
--- a/src/recite.rs
+++ b/src/recite.rs
@@ -1,6 +1,7 @@
+mod parse;
pub mod path;
mod ps;
-use crate::{btask, ctask, task};
+use crate::{btask, ctask, push, push1, task};
use core::fmt;
use libc::{waitpid, WNOHANG};
use path::prefresh;
@@ -695,232 +696,71 @@ impl Poem {
// The character represents the Couplet Meter
Some(meter) if meter == '|' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Couplet,
+ push!(
+ word,
+ stanza,
Verse::couplet(prev),
- Verse::rw(prev),
- ));
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ prev,
+ verses,
+ Meter::Couplet
+ );
}
// The character represents the Quiet (or And) Meter
Some(meter) if meter == '&' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // Need to peek at the next character, since '&' can mean
- // Meter::Quiet, or '&&' can mean Meter::And
- match chars.clone().peekable().peek() {
- // Indicated Meter::And
- Some(c) if c == &'&' => {
- // Pop the next character from the input string
- // (since we know what it is)
- chars.next();
-
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::And,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Indicates Meter::Quiet
- Some(_) => {
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Quiet,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Indicated the end of the input
- None => {
- // The end of input also indicates the end of a
- // verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Quiet,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
-
- // We can break out of the loop here, since it's
- // the end of the raw input
- break;
- }
- }
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ push1!(
+ word,
+ stanza,
+ chars,
+ prev,
+ verses,
+ Meter::Quiet,
+ '&',
+ Meter::And
+ );
}
// The character represents the String Meter
Some(meter) if meter == ';' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // Check if the last verse was a meter of Read, Write, or
- // Addendum, and throw an error if it is
- if Verse::rw(prev) && stanza.is_empty() {
- let rw = match prev.unwrap().meter {
- Meter::Read => "read",
- Meter::Write => "write",
- Meter::Addendum => "append",
- _ => "",
- };
- eprintln!("dwvsh: parse error: no {} file(s) specified", rw);
- return None;
- }
-
- // A meter indicates the end of a verse
- if !stanza.is_empty() {
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::String,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ push!(
+ word,
+ stanza,
+ Verse::couplet(prev),
+ prev,
+ verses,
+ Meter::String
+ );
}
// The character represents the Read Meter
Some(meter) if meter == '<' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Read,
- true,
- Verse::rw(prev),
- ));
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ push!(word, stanza, true, prev, verses, Meter::Read);
}
// The character represents the Write Meter
Some(meter) if meter == '>' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // Check if the last verse was a meter of Read, Write, or
- // Addendum, and throw an error if it is
- if Verse::rw(prev) && stanza.is_empty() {
- let rw = match prev.unwrap().meter {
- Meter::Read => "read",
- Meter::Write => "write",
- Meter::Addendum => "append",
- _ => "",
- };
- eprintln!("dwvsh: parse error: no {} file(s) specified", rw);
- return None;
- }
-
- // Need to peek at the next character, since '>' can mean
- // Meter::Write, or '>>' can mean Meter::Addendum
- match chars.clone().peekable().peek() {
- // Indicated Meter::Addendum
- Some(c) if c == &'>' => {
- // Pop the next character from the input string
- // (since we know what it is)
- chars.next();
-
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Addendum,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Indicates Meter::Write
- Some(_) => {
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Write,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Indicated the end of the input
- None => {
- // A meter indicates the end of a verse
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::Write,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
-
- // We can break out of the loop here, since it's
- // the end of the raw input
- }
- }
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ push1!(
+ word,
+ stanza,
+ chars,
+ prev,
+ verses,
+ Meter::Write,
+ '>',
+ Meter::Addendum
+ );
}
// The character is a newline (may happen if parsing from a file)
Some(char) if char == '\n' => {
- // If there are chars on the word stack, push that word
- // onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // A newline indicates the end of a verse
- if !stanza.is_empty() {
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::String,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
-
- // Clear the stacks
- stanza.clear();
- word.clear();
+ push!(
+ word,
+ stanza,
+ Verse::couplet(prev),
+ prev,
+ verses,
+ Meter::String
+ );
}
// The character is whitespace
@@ -941,34 +781,14 @@ impl Poem {
// Indicates the end of the list of characters
None => {
- // Always push the last word onto the stanza
- if !word.is_empty() {
- stanza.push(word.iter().collect());
- }
-
- // Check if the last verse was a meter of Read, Write, or
- // Addendum, and throw an error if it is
- if Verse::rw(prev) && stanza.is_empty() {
- let rw = match prev.unwrap().meter {
- Meter::Read => "read",
- Meter::Write => "write",
- Meter::Addendum => "append",
- _ => "",
- };
- eprintln!("dwvsh: parse error: no {} file(s) specified", rw);
- return None;
- }
-
- // Only push the stanza into a verse if it contains
- // any words
- if !stanza.is_empty() {
- verses.push(Verse::new(
- Stanza::new(stanza.clone()),
- Meter::None,
- Verse::couplet(prev),
- Verse::rw(prev),
- ));
- }
+ push!(
+ word,
+ stanza,
+ Verse::couplet(prev),
+ prev,
+ verses,
+ Meter::None
+ );
// Break from the loop, since we are out of chars
break;
diff --git a/src/recite/parse.rs b/src/recite/parse.rs
new file mode 100644
index 0000000..63b16ff
--- /dev/null
+++ b/src/recite/parse.rs
@@ -0,0 +1,135 @@
+/// Add a Verse to a Poem
+///
+/// Takes the current word stack, and pushes it onto the stanza. Then, takes
+/// the stanza, meter, and some metadata details to create a Verse. That Verse
+/// then gets added to the Poem.
+///
+/// # Arguments
+/// * `$word` - The word stack, used to hold chars dilineated via whitespace
+/// * `$stanza` - The stanza stack, used to hold words when they are popped off
+/// the word stack
+/// * `$cprev` - Indicates this is the second half of a couplet (normally this
+/// is just Verse::couplet(prev), but we may need to set it in
+/// case of Meter::Read), since this basically just tells the
+/// shell to use `$out` in the `task!` macro
+/// * `$prev` - The previous verse (or none if there is no previous verse)
+/// * `$verses` - The list of verses that make up the poem
+/// * `$meter` - The meter corresponding to the verse
+#[macro_export]
+macro_rules! push {
+ ($word:expr, $stanza:expr, $cprev:expr, $prev:expr, $verses:expr, $meter:expr) => {
+ // If there are chars on the word stack, push that word onto the stanza
+ if !$word.is_empty() {
+ $stanza.push($word.iter().collect());
+ }
+
+ // Check if the last verse was a meter of Read, Write, or
+ // Addendum, and throw an error if it is
+ if Verse::rw($prev) && $stanza.is_empty() {
+ let rw = match $prev.unwrap().meter {
+ Meter::Read => "read",
+ Meter::Write => "write",
+ Meter::Addendum => "append",
+ _ => "",
+ };
+ eprintln!("dwvsh: parse error: no {} file(s) specified", rw);
+ return None;
+ }
+
+ // A meter indicates the end of a verse
+ if !$stanza.is_empty() {
+ $verses.push(Verse::new(
+ Stanza::new($stanza.clone()),
+ $meter,
+ $cprev,
+ Verse::rw($prev),
+ ));
+ }
+
+ // Clear the stacks
+ $stanza.clear();
+ $word.clear();
+ };
+}
+
+/// Add a Verse to a Poem, but allow looking ahead by one char
+///
+/// This works the exact same as [[push]], except that it doesn't take
+/// `$cprev` (since there is no need to set it manually right now), and it
+/// takes `$ahead`, which is the next character to look for in the pattern,
+/// along with `$aheadm`, which is the corresponding Meter if that `$ahead` is
+/// found.
+///
+/// # Examples
+/// ```
+/// push1!(word, stanza, prev, verses, Meter::Quiet, '&', Meter::And);
+/// push1!(word, stanza, prev, verses, Meter::Write, '>', Meter::Addendum);
+/// ```
+#[macro_export]
+macro_rules! push1 {
+ ($word:expr, $stanza:expr, $chars:expr, $prev:expr, $verses: expr, $meter:expr, $ahead:expr, $aheadm:expr) => {
+ // If there are chars on the word stack, push that word
+ // onto the stanza
+ if !$word.is_empty() {
+ $stanza.push($word.iter().collect());
+ }
+
+ // Check if the last verse was a meter of Read, Write, or
+ // Addendum, and throw an error if it is
+ if Verse::rw($prev) && $stanza.is_empty() {
+ let rw = match $prev.unwrap().meter {
+ Meter::Read => "read",
+ Meter::Write => "write",
+ Meter::Addendum => "append",
+ _ => "",
+ };
+ eprintln!("dwvsh: parse error: no {} file(s) specified", rw);
+ return None;
+ }
+
+ // Need to peek at the next character, since '>' can mean
+ // Meter::Write, or '>>' can mean Meter::Addendum
+ match $chars.clone().peekable().peek() {
+ // Indicated Meter::Addendum
+ Some(c) if c == &$ahead => {
+ // Pop the next character from the input string
+ // (since we know what it is)
+ $chars.next();
+
+ // A meter indicates the end of a verse
+ $verses.push(Verse::new(
+ Stanza::new($stanza.clone()),
+ $aheadm,
+ Verse::couplet($prev),
+ Verse::rw($prev),
+ ));
+ }
+
+ // Indicates Meter::Write
+ Some(_) => {
+ // A meter indicates the end of a verse
+ $verses.push(Verse::new(
+ Stanza::new($stanza.clone()),
+ $meter,
+ Verse::couplet($prev),
+ Verse::rw($prev),
+ ));
+ }
+
+ // Indicated the end of the input
+ None => {
+ // A meter indicates the end of a verse
+ $verses.push(Verse::new(
+ Stanza::new($stanza.clone()),
+ $meter,
+ Verse::couplet($prev),
+ Verse::rw($prev),
+ ));
+ }
+ }
+
+ // Clear the stacks
+ $stanza.clear();
+ $word.clear();
+ };
+}