summaryrefslogtreecommitdiffstats
path: root/src/poem/read.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/poem/read.rs')
-rw-r--r--src/poem/read.rs261
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)
+ }
+}