use crate::error::CustomError; use organic::types::Element; use organic::types::Object; use std::collections::HashMap; use super::ast_node::IAstNode; use super::ast_node::IntoIAstNode; use super::IObject; use super::IParagraph; use super::IntermediateContext; type IdCounter = u16; #[derive(Debug)] pub(crate) struct Registry<'orig, 'parse> { id_counter: IdCounter, targets: HashMap<&'parse str, String>, footnote_ids: Vec<(Option<&'parse str>, Vec)>, footnote_reference_counts: HashMap<&'parse str, usize>, on_deck_footnote_ids: HashMap<&'parse str, &'orig Vec>>, } impl<'orig, 'parse> Registry<'orig, 'parse> { pub(crate) fn new() -> Registry<'orig, 'parse> { Registry { id_counter: 0, targets: HashMap::new(), footnote_ids: Vec::new(), footnote_reference_counts: HashMap::new(), on_deck_footnote_ids: HashMap::new(), } } pub(crate) fn get_target<'reg>(&'reg mut self, body: &'parse str) -> &'reg String { self.targets.entry(body).or_insert_with(|| { self.id_counter += 1; format!("target_{}", self.id_counter) }) } pub(crate) fn get_footnote_ids(&self) -> impl Iterator)> { self.footnote_ids .iter() .map(|(_label, definition)| definition) .enumerate() } } /// Get a 0-indexed ID for a footnote. /// /// This needs to be incremented to be 1-indexed for render. pub(crate) async fn get_footnote_reference_id<'orig, 'parse>( intermediate_context: IntermediateContext<'orig, 'parse>, label: Option<&'parse str>, definition: &'orig [Object<'parse>], ) -> Result<(usize, usize), CustomError> { if label.is_none() { // If it has no label then it must always get a new ID. let contents = convert_reference_contents(intermediate_context.clone(), definition).await?; let pos = { let mut registry = intermediate_context.registry.lock().unwrap(); registry.footnote_ids.push((None, contents)); registry.footnote_ids.len() - 1 }; return Ok((pos, 0)); } let reference_count = if let Some(label) = label { promote_footnote_definition(intermediate_context.clone(), label).await?; let mut registry = intermediate_context.registry.lock().unwrap(); let reference_count = registry .footnote_reference_counts .entry(label) .and_modify(|count| *count += 1) .or_insert(0); *reference_count } else { 0 }; let existing_index = intermediate_context .registry .lock() .unwrap() .footnote_ids .iter() .position(|(id, _definition)| *id == label); if let Some(existing_id) = existing_index { if !definition.is_empty() { let contents = convert_reference_contents(intermediate_context.clone(), definition).await?; let mut registry = intermediate_context.registry.lock().unwrap(); let entry = registry .footnote_ids .get_mut(existing_id) .expect("If-statement proves this to be Some."); entry.1 = contents; } Ok((existing_id, reference_count)) } else { let existing_id = { let mut registry = intermediate_context.registry.lock().unwrap(); registry.footnote_ids.push((label, Vec::new())); registry.footnote_ids.len() - 1 }; let contents = convert_reference_contents(intermediate_context.clone(), definition).await?; { let mut registry = intermediate_context.registry.lock().unwrap(); let entry = registry .footnote_ids .get_mut(existing_id) .expect("If-statement proves this to be Some."); entry.1 = contents; } Ok((existing_id, reference_count)) } } /// Update the definition to a footnote but do not mark it as referenced. pub(crate) async fn register_footnote_definition<'orig, 'parse>( intermediate_context: IntermediateContext<'orig, 'parse>, label: &'parse str, definition: &'orig Vec>, ) -> Result<(), CustomError> { let has_existing: bool = { let mut registry = intermediate_context.registry.lock().unwrap(); registry .footnote_ids .iter_mut() .any(|(id, _definition)| *id == Some(label)) }; if !has_existing { let mut registry = intermediate_context.registry.lock().unwrap(); registry.on_deck_footnote_ids.insert(label, definition); return Ok(()); } let contents = convert_definition_contents(intermediate_context.clone(), definition).await?; let mut registry = intermediate_context.registry.lock().unwrap(); if let Some((_existing_id, existing_definition)) = registry .footnote_ids .iter_mut() .find(|(id, _definition)| *id == Some(label)) { *existing_definition = contents; } Ok(()) } async fn convert_reference_contents<'orig, 'parse>( intermediate_context: IntermediateContext<'orig, 'parse>, contents: &'orig [Object<'parse>], ) -> Result, CustomError> { let children = { let mut ret = Vec::new(); for obj in contents.iter() { ret.push(IObject::new(intermediate_context.clone(), obj).await?); } ret }; let containing_paragraph = IParagraph::artificial(intermediate_context.clone(), children, 0).await?; let contents = vec![IAstNode::Paragraph(containing_paragraph)]; Ok(contents) } async fn convert_definition_contents<'orig, 'parse>( intermediate_context: IntermediateContext<'orig, 'parse>, contents: &'orig [Element<'parse>], ) -> Result, CustomError> { let contents = { let mut ret = Vec::new(); for obj in contents.iter() { ret.push(obj.as_ast_node(intermediate_context.clone()).await?); } ret }; Ok(contents) } /// Take a footnote definition that has not yet received a reference and move it into the active footnotes. pub(crate) async fn promote_footnote_definition<'orig, 'parse>( intermediate_context: IntermediateContext<'orig, 'parse>, label: &'parse str, ) -> Result<(), CustomError> { let definition = { let mut registry = intermediate_context.registry.lock().unwrap(); registry.on_deck_footnote_ids.remove(label) }; if let Some(elements) = definition { let existing_id = { let mut registry = intermediate_context.registry.lock().unwrap(); registry.footnote_ids.push((Some(label), Vec::new())); registry.footnote_ids.len() - 1 }; let contents = convert_definition_contents(intermediate_context.clone(), elements).await?; { let mut registry = intermediate_context.registry.lock().unwrap(); let entry = registry .footnote_ids .get_mut(existing_id) .expect("If-statement proves this to be Some."); entry.1 = contents; } } Ok(()) }