use crate::{lex::Stanza, Dict}; pub struct ParseState { dict: Dict, status: Status, current_entry: EntryBuilder, errors: Vec, } #[derive(Debug)] enum ParseError { Build(EntryBuilderError), UndefinedState(Status, EntryBuilder, Stanza), } impl ParseState { pub fn new() -> Self { Self { dict: Dict::new(), status: Status::Start, current_entry: EntryBuilder::new(), errors: Vec::new(), } } pub fn advance(mut self, stanza: Stanza) -> Self { match (self.status, stanza) { (Status::Start, Stanza::Entry(e)) => { self.current_entry.set_name(e); self.status = Status::ContainsName; } (Status::ContainsName, Stanza::Defn(d)) => { self.current_entry.push_defn(d); self.status = Status::ContainsOneDefn; } (Status::ContainsOneDefn | Status::ContainsMulDefn, Stanza::Defn(d)) => { self.current_entry.push_defn(d); self.status = Status::ContainsMulDefn; } ( Status::ContainsOneDefn | Status::ContainsMulDefn | Status::ContainsSynonym, Stanza::Note(n), ) => { self.current_entry.set_note(n); self.status = Status::ContainsNote; } ( Status::ContainsOneDefn | Status::ContainsMulDefn | Status::ContainsNote, Stanza::Synonym(s), ) => { self.current_entry.set_synonym(s); self.status = Status::ContainsSynonym; } ( Status::ContainsOneDefn | Status::ContainsMulDefn | Status::ContainsNote | Status::ContainsSynonym, Stanza::Entry(e), ) => { // flush the current entry match self.current_entry.build() { Ok(entry) => self.dict.insert(entry.name, entry.into()), Err(b) => self.register_error(ParseError::Build(b)), }; // begin with the new one self.current_entry.clear(); self.current_entry.set_name(e); self.status = Status::ContainsName; } (Status::ContainsName, Stanza::Entry(e)) => { // dump unfinished entry and enter new entry self.current_entry.clear(); self.current_entry.set_name(e); self.status = Status::ContainsName; } (_, new_entry) => { // any other states means our parser is entering undefined teritorry // register an error if we have anything in current_entry self.register_undefined_state_error(new_entry); // and set the status to Start and fast forward to the next entry self.current_entry.clear(); self.status = Status::Start; } } self } fn register_error(&mut self, error: ParseError) { self.errors.push(error) } fn register_undefined_state_error(&mut self, new_entry: Stanza) { self.register_error(ParseError::UndefinedState( self.status, self.current_entry.clone(), new_entry, )); } pub fn finish(self) -> Dict { self.dict } pub fn dump(&self) { for err in &self.errors { eprintln!("{err:?}"); } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] enum Status { // ready to accept a new entry Start, // ready to accept a defn ContainsName, // can accept notes or synonyms, or flush this entry ContainsOneDefn, // can accept notes or synonyms, or flush this entry ContainsMulDefn, // can accept a synonym ContainsNote, // can accept a note ContainsSynonym, // mangled stanza, skip until the next entry occurs } #[derive(Debug, Clone)] struct Entry { name: &'static str, defn: Vec<&'static str>, note: Option<&'static str>, synonym: Option<&'static str>, } impl From for crate::DictValue { fn from(entry: Entry) -> Self { Self { defn: entry.defn, note: entry.note, synonym: entry.synonym, } } } #[derive(Debug, Default, Clone)] struct EntryBuilder { name: Option<&'static str>, defn: Vec<&'static str>, note: Option<&'static str>, synonym: Option<&'static str>, } #[derive(Debug)] enum EntryBuilderError { MissingField(&'static str), } impl EntryBuilder { fn new() -> Self { Self::default() } fn clear(&mut self) { *self = Self::default(); } fn set_name(&mut self, name: &'static str) { self.name = Some(name); } fn push_defn(&mut self, defn: &'static str) { self.defn.push(defn); } fn set_note(&mut self, note: &'static str) { self.note = Some(note); } fn set_synonym(&mut self, synonym: &'static str) { self.synonym = Some(synonym); } fn build(&self) -> Result { let name = self.name.ok_or(EntryBuilderError::MissingField("name"))?; let defn = if self.defn.is_empty() { return Err(EntryBuilderError::MissingField("defn")); } else { self.defn.clone() }; Ok(Entry { name, defn, note: self.note, synonym: self.synonym, }) } }