use super::{ elements::{rune::Rune, verse::Verse, word::Word}, Poem, }; use core::fmt; mod parse; use crate::compose::Environment; use crate::{append, next, poem, remark, 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() { return; } // Check the meter verse.meter = meter; if last == Rune::Couplet || meter == Rune::Couplet { verse.couplet = true; } // Push verse(s) and clear the current verse stack self.push(verse.clone()); verse.clear(); } } /// A [Poem] can parse poetry pub trait Readable { fn read(poetry: String, env: &Environment) -> Result; } 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, env: &Environment) -> Result { // 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); append!(poem, last, Rune::None, verse, env); break; } }; // Determine the meter based on the character let rune = match c { ' ' => Rune::Pause, '/' => Rune::Path, '#' => Rune::Remark, '\'' | '"' => 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); } Rune::Remark => { remark!(chars); } // 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, env); } // 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); append!(poem, last, rune, verse, env); } // 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; } // Increment i, but don't drift over newlines if c != '\n' { i += 1; } } // Return the poem Ok(poem) } }