214 lines
7.3 KiB
Rust
214 lines
7.3 KiB
Rust
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<IAstNode>)>,
|
|
footnote_reference_counts: HashMap<&'parse str, usize>,
|
|
on_deck_footnote_ids: HashMap<&'parse str, &'orig Vec<Element<'parse>>>,
|
|
}
|
|
|
|
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<Item = (usize, &Vec<IAstNode>)> {
|
|
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 Vec<Object<'parse>>,
|
|
) -> Result<(usize, usize), CustomError> {
|
|
if let None = label {
|
|
// 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<Element<'parse>>,
|
|
) -> 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 Vec<Object<'parse>>,
|
|
) -> Result<Vec<IAstNode>, 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 = {
|
|
let mut ret = Vec::new();
|
|
ret.push(IAstNode::Paragraph(containing_paragraph));
|
|
ret
|
|
};
|
|
|
|
Ok(contents)
|
|
}
|
|
|
|
async fn convert_definition_contents<'orig, 'parse>(
|
|
intermediate_context: IntermediateContext<'orig, 'parse>,
|
|
contents: &'orig Vec<Element<'parse>>,
|
|
) -> Result<Vec<IAstNode>, CustomError> {
|
|
let contents = {
|
|
let mut ret = Vec::new();
|
|
for obj in contents.iter() {
|
|
ret.push(obj.into_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();
|
|
let definition = registry.on_deck_footnote_ids.remove(label);
|
|
definition
|
|
};
|
|
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(())
|
|
}
|