use super::{ elements::{rune::Rune, verse::Verse, word::Word}, Poem, }; use core::fmt; mod parse; use crate::compose::Environment; use crate::{poem, remark, string}; use parse::next; /// Custom errors for the parser ([read()][crate::poem::read]) #[derive(Debug, PartialEq, Eq)] pub enum Mishap { /// Generic parser error ParseMishap(usize, usize, char), /// IO operation parse errors /// /// Raised when an IO operation is specified, but a filepath was not /// given. /// /// # Examples /// ```sh /// cat < # No file specified for Rune::Read /// cat file.txt >> # No file specified for Rune::Addendum /// ``` IOMishap(usize, usize, char), /// Missing end character /// /// Some [Rune]s consists of two characters, that may contain other /// characters between them (i.e. [String][Rune::String]). This is /// raised when the ending character was left out. /// /// # Examples /// ```sh /// echo 'Hello # Ending ' character is missing /// mv file.txt hello.txt" # Beginning " character is missing /// ``` 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, env: &mut Environment, ) -> Result<(), Mishap>; } 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. It also: /// - sets the meter of the [Verse], /// - determines the couplet status of the [Verse], /// - and checks for aliases associated the the [Verse]'s verb. /// /// Once the [Verse] is pushed to the [Poem], the verse stack is /// cleared. /// /// # Examples /// ``` /// ... /// verse.push("cat"); /// verse.push("Cargo.toml"); /// poem.add(&mut verse, Rune::None, &mut env); /// ``` fn add( &mut self, verse: &mut Self::Type, meter: Rune, env: &mut Environment, ) -> Result<(), Mishap> { if verse.is_empty() { return Ok(()); } // Get meter of the last verse let last = match self.last() { Some(last) => last.meter, None => Rune::Else, }; // Check the meter verse.meter = meter; if last == Rune::Couplet && meter == Rune::Couplet { verse.couplet = 3; } else if last == Rune::Couplet { verse.couplet = 2; } else if meter == Rune::Couplet { verse.couplet = 1; } // Check for aliases match env.aliases.get(&verse.verb()) { Some(alias) if env.cs == 0 => { // Increase the callstack env.cs = 1; // Interpret the alias (could be a complex poem) let mut poem = Poem::read(alias.to_string(), env)?; // Decrease the callstack env.cs = 0; // Try and get the last verse let lv = match poem.last_mut() { Some(lv) => lv, None => unreachable!(), // Should be caught by a Mishap above }; // The last verse inherits the traits from the original if verse.couplet > 0 { lv.couplet = verse.couplet; } lv.io = verse.io.clone(); lv.op = verse.op.clone(); lv.ep = verse.ep.clone(); lv.poems = verse.poems.clone(); lv.meter = verse.meter; if verse.clause().is_some() { for word in verse.clause().unwrap().iter() { lv.stanza.push(word.to_string()); } } // Push verse(s) for v in poem.iter() { self.push(v.clone()); } } Some(_) | None => { // Push verse(s) self.push(verse.clone()); } } // Clear the current verse stack verse.clear(); // Unit Ok(()) } } /// A [Poem] can parse poetry pub trait Readable { fn read(poetry: String, env: &mut 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, /// [read()][Poem::read] may return a [Mishap]. See /// [recite()][crate::poem::recite] or [incant()][Verse::incant] for /// how each [Verse] in a [Poem] is called. fn read(poetry: String, env: &mut 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 channel let mut channel: Option = 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, channel); // 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, Rune::None, env)?; // 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, '<' => Rune::Read, '>' => next(&mut chars, &mut i, Rune::Write, vec![(">", Rune::Addendum)]), '1' => next( &mut chars, &mut i, Rune::Else, vec![(">", Rune::Write), (">>", Rune::Addendum)], ), '2' => next( &mut chars, &mut i, Rune::Else, vec![(">", Rune::Write2), (">>", Rune::Addendum2)], ), '|' => Rune::Couplet, '&' => next( &mut chars, &mut i, Rune::Quiet, vec![ ("&", Rune::And), (">", Rune::WriteAll), (">>", Rune::AddendumAll), ], ), ';' => 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::Write2 | Rune::WriteAll | Rune::Addendum | Rune::Addendum2 | Rune::AddendumAll => { if (last == Rune::Couplet || last == Rune::Quiet || last == Rune::And || last == Rune::Read || last == Rune::Write || last == Rune::Write2 || last == Rune::WriteAll || last == Rune::Addendum || last == Rune::Addendum2 || last == Rune::AddendumAll) || verse.is_empty() { return Err(Mishap::ParseMishap(j, i, c)); } } Rune::Continue => { if last == Rune::Read || last == Rune::Write || last == Rune::Write2 || last == Rune::WriteAll || last == Rune::Addendum || last == Rune::Addendum2 || last == Rune::AddendumAll { return Err(Mishap::ParseMishap(j, i, c)); } } _ => { if (last == Rune::Read || last == Rune::Write || last == Rune::Write2 || last == Rune::WriteAll || last == Rune::Addendum || last == Rune::Addendum2 || last == Rune::AddendumAll) && rune == Rune::None && rune == Rune::Read && rune == Rune::Write && rune == Rune::Write2 && rune == Rune::WriteAll && rune == Rune::Addendum && rune == Rune::Addendum2 && rune == Rune::AddendumAll && 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, channel); } Rune::Remark => { remark!(chars); } // Indicates a string (' or ") Rune::String => { string!(chars, j, i, c, word); verse.add(&mut word, channel); } // Indicates a sub-poem Rune::Poem => { poem!(chars, j, i, c, verse, word, env); } // Indicates a file operation (<, >, or >>) Rune::Read | Rune::Write | Rune::Write2 | Rune::WriteAll | Rune::Addendum | Rune::Addendum2 | Rune::AddendumAll => { verse.add(&mut word, channel); channel = Some(rune); verse.io.push(rune); } // These meters indicate the end of a verse Rune::Couplet | Rune::Quiet | Rune::And | Rune::Continue => { channel = None; verse.add(&mut word, channel); poem.add(&mut verse, rune, env)?; // 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. Rune::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) } }