use crate::lex::annotation::analyze_annotation_header_token_pairs;
use crate::lex::escape::find_structural_lex_marker_pairs;
use crate::lex::token::{LineContainer, LineToken, Token};
use std::ops::Range;
pub(super) fn extract_line_token(token: &LineContainer) -> Result<&LineToken, String> {
match token {
LineContainer::Token(t) => Ok(t),
_ => Err("Expected LineToken, found Container".to_string()),
}
}
pub(super) fn collect_line_tokens(container: &LineContainer, out: &mut Vec<LineToken>) {
match container {
LineContainer::Token(token) => out.push(token.clone()),
LineContainer::Container { children } => {
for child in children {
collect_line_tokens(child, out);
}
}
}
}
pub(super) fn extract_annotation_header_tokens(
start_token: &LineToken,
) -> Result<Vec<(Token, std::ops::Range<usize>)>, String> {
let all_tokens: Vec<_> = start_token
.source_tokens
.clone()
.into_iter()
.zip(start_token.token_spans.clone())
.collect();
let structural = find_structural_lex_marker_pairs(&all_tokens);
let header_tokens: Vec<_> = all_tokens
.into_iter()
.enumerate()
.filter(|(i, _)| !structural.contains(i))
.map(|(_, pair)| pair)
.collect();
ensure_header_has_label(start_token, &header_tokens)?;
Ok(header_tokens)
}
#[derive(Debug, Clone)]
pub(super) struct AnnotationHeaderAndContent {
pub header_tokens: Vec<(Token, Range<usize>)>,
pub children: Vec<crate::lex::parsing::ir::ParseNode>,
}
pub(super) fn extract_annotation_single_content(
start_token: &LineToken,
) -> Result<AnnotationHeaderAndContent, String> {
use crate::lex::parsing::ir::{NodeType, ParseNode};
let all_tokens: Vec<_> = start_token
.source_tokens
.clone()
.into_iter()
.zip(start_token.token_spans.clone())
.collect();
let structural = find_structural_lex_marker_pairs(&all_tokens);
let second_marker_idx = structural.get(1).copied();
let mut header_tokens = Vec::new();
let mut content_tokens = Vec::new();
for (i, (token, span)) in all_tokens.into_iter().enumerate() {
if structural.contains(&i) {
continue;
}
if let Some(boundary) = second_marker_idx {
if i < boundary {
header_tokens.push((token, span));
} else {
content_tokens.push((token, span));
}
} else {
header_tokens.push((token, span));
}
}
ensure_header_has_label(start_token, &header_tokens)?;
let children = if !content_tokens.is_empty() {
vec![ParseNode::new(NodeType::Paragraph, content_tokens, vec![])]
} else {
vec![]
};
Ok(AnnotationHeaderAndContent {
header_tokens,
children,
})
}
fn ensure_header_has_label(
start_token: &LineToken,
header_tokens: &[(Token, Range<usize>)],
) -> Result<(), String> {
let analysis = analyze_annotation_header_token_pairs(header_tokens);
if analysis.has_label {
return Ok(());
}
let byte = header_tokens
.first()
.map(|(_, span)| span.start)
.or_else(|| start_token.token_spans.first().map(|span| span.start))
.unwrap_or(0);
Err(format!(
"Annotation starting at byte {byte} must include a label before any parameters"
))
}