diff options
Diffstat (limited to 'src/poem/read.rs')
-rw-r--r-- | src/poem/read.rs | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/src/poem/read.rs b/src/poem/read.rs new file mode 100644 index 0000000..01ddfc3 --- /dev/null +++ b/src/poem/read.rs @@ -0,0 +1,261 @@ +use super::{ + elements::{rune::Rune, verse::Verse, word::Word}, + Poem, +}; +use core::fmt; +mod parse; +use crate::{next, poem, string}; + +#[derive(Debug, PartialEq, Eq)] +pub enum Mishap { + ParseMishap(usize, usize, char), + IOMishap(usize, usize, char), + PartialMishap(usize, usize, char), +} + +impl fmt::Display for Mishap { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let message = match self { + Mishap::ParseMishap(j, i, c) => { + format!("parse error on line {} pos {} near '{}'", j, i, c) + } + Mishap::IOMishap(j, i, c) => { + format!( + "must provide file for io operation on line {} pos {} near '{}'", + j, i, c + ) + } + Mishap::PartialMishap(j, i, c) => { + format!( + "partial string or action on line {} pos {} near '{}'", + j, i, c + ) + } + }; + + write!(f, "{}", message) + } +} + +/// A [Poem] can add more [Verse]s to itself +trait Appendable { + type Type; + fn add(&mut self, verse: &mut Self::Type, meter: Rune, last: Rune); +} + +impl Appendable for Poem { + type Type = Verse; + + /// Push a [Verse] to the [Poem] + /// + /// Push a [Verse] to the [Poem] after checking that the [Verse] is not + /// empty. Also sets the meter of the [Verse]. + fn add(&mut self, verse: &mut Self::Type, last: Rune, meter: Rune) { + if !verse.is_empty() { + verse.meter = meter; + if last == Rune::Couplet || meter == Rune::Couplet { + verse.couplet = true; + } + self.push(verse.clone()); + verse.clear(); + } + } +} + +/// A [Poem] can parse poetry +pub trait Readable { + fn read(poetry: String) -> Result<Poem, Mishap>; +} + +impl Readable for Poem { + /// Parse a [Poem] from a raw [String] input + /// + /// Takes a shell command/program or file and converts it to a + /// machine-runnable [Poem]. If there is a parse error, [Poem::read] may + /// return a [Mishap]. See [Poem::recite][super::recite] for how each + /// [Verse] in a [Poem] is called. + fn read(poetry: String) -> Result<Poem, Mishap> { + // Get all the characters in the input string as an iterator + let mut chars = poetry.chars().into_iter(); + + // Create a stack to store words + let mut word: Word = Word::new(); + + // Create a stack to store the current verse + let mut verse: Verse = Verse::new(); + + // Create a vector to return + let mut poem: Self = Poem::new(); + + // Keep track of the last rune + let mut last = Rune::None; + + // Keep track of the line + let mut j = 0; + + // Keep track of the column + let mut i = 0; + + // Loop through every char in the iterator + loop { + // Get the next character, and unwrap it + let c = chars.next(); + let c = match c { + Some(c) => c, + None => { + // Check for IO parse errors + if last == Rune::Read || last == Rune::Write || last == Rune::Addendum { + return Err(Mishap::IOMishap(j, i, ' ')); + } + + // If c is none, it indicates the end of a poem, so wrap up and + // then break from the loop + verse.add(&mut word); + + // Throw an error if the verse is empty + if verse.is_empty() && (last == Rune::Couplet || last == Rune::And) { + return Err(Mishap::ParseMishap(j, i, ' ')); + } + + // Push the verse and break + poem.add(&mut verse, last, Rune::None); + break; + } + }; + + // Determine the meter based on the character + let rune = match c { + ' ' => Rune::Pause, + '/' => Rune::Path, + '\'' | '"' => Rune::String, + '`' => Rune::Poem, + '<' => { + verse.couplet = true; + Rune::Read + } + '>' => next!(chars, i, Rune::Write, Rune::Addendum, '>'), + '|' => Rune::Couplet, + '&' => next!(chars, i, Rune::Quiet, Rune::And, '&'), + ';' => Rune::Continue, + '\n' => { + j += 1; + i = 0; + Rune::Continue + } + '~' => Rune::Home, + _ => Rune::Else, + }; + + // Some error checking, based on the last character + match rune { + Rune::Couplet + | Rune::Quiet + | Rune::And + | Rune::Read + | Rune::Write + | Rune::Addendum => { + if (last == Rune::Couplet + || last == Rune::Quiet + || last == Rune::And + || last == Rune::Read + || last == Rune::Write + || last == Rune::Addendum) + || verse.is_empty() + { + return Err(Mishap::ParseMishap(j, i, c)); + } + } + + Rune::Continue => { + if last == Rune::Read || last == Rune::Write || last == Rune::Addendum { + return Err(Mishap::ParseMishap(j, i, c)); + } + } + + _ => { + if (last == Rune::Read || last == Rune::Write || last == Rune::Addendum) + && rune == Rune::None + && rune == Rune::Read + && rune == Rune::Write + && rune == Rune::Addendum + && rune == Rune::Couplet + && rune == Rune::Quiet + && rune == Rune::And + && rune == Rune::Continue + { + return Err(Mishap::IOMishap(j, i, c)); + } + } + }; + + // Do some action, based on the rune + match rune { + // Indicates the end of a word (space dilineated) + Rune::Pause => { + verse.add(&mut word); + } + + // Indicates a string (' or ") + Rune::String => { + string!(chars, j, i, c, word); + } + + // Indicates a sub-poem + Rune::Poem => { + poem!(chars, j, i, c, verse, word); + // let sp = Poem::read(word.iter().collect()); + // let sp = match sp { + // Ok(sp) => sp, + // Err(e) => return Err(e), + // }; + // verse.poems.push(sp); + // word.push('\x0b'); + // verse.push(format!("\x0b{}", verse.poems.len() - 1)); + // word.clear(); + } + + // Indicates a file operation (<, >, or >>) + Rune::Read | Rune::Write | Rune::Addendum => { + verse.add(&mut word); + word.push('<'); + verse.add(&mut word); + verse.io = rune; + } + + // These meters indicate the end of a verse + Rune::Couplet | Rune::Quiet | Rune::And | Rune::Continue => { + verse.add(&mut word); + poem.add(&mut verse, last, rune); + } + + // Interpret ~ as $HOME + Rune::Home => { + let mut chars = env!("HOME").chars().collect(); + word.append(&mut chars); + } + + // Any other char i.e. Meter::Else + _ => { + word.push(c); + } + } + + // Set the last meter + if rune != Rune::Pause { + last = rune; + } + // last = match poem.last() { + // Some(last) => last.meter, + // None => Rune::None, + // }; + + // Increment i, but don't drift over newlines + if c != '\n' { + i += 1; + } + } + + // Return the poem + Ok(poem) + } +} |