use crate::lex::token::{LineContainer, LineToken, LineType};
use std::iter::Peekable;
pub fn build_line_container(line_tokens: Vec<LineToken>) -> LineContainer {
let mut tokens_iter = line_tokens.into_iter().peekable();
let children = build_recursive(&mut tokens_iter);
LineContainer::Container { children }
}
fn build_recursive<I>(tokens: &mut Peekable<I>) -> Vec<LineContainer>
where
I: Iterator<Item = LineToken>,
{
let mut children = Vec::new();
while let Some(token) = tokens.peek() {
match token.line_type {
LineType::Indent => {
tokens.next(); let indented_children = build_recursive(tokens);
children.push(LineContainer::Container {
children: indented_children,
});
}
LineType::Dedent => {
tokens.next();
return children;
}
_ => {
if let Some(t) = tokens.next() {
children.push(LineContainer::Token(t));
}
}
}
}
children
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lex::token::Token;
#[allow(clippy::single_range_in_vec_init)]
#[test]
fn test_build_hierarchy_simple() {
let line_tokens = vec![LineToken {
source_tokens: vec![
Token::Text("Hello".to_string()),
Token::Whitespace(1),
Token::Text("world".to_string()),
Token::BlankLine(Some("\n".to_string())),
],
token_spans: vec![0..5, 5..6, 6..11, 11..12],
line_type: LineType::ParagraphLine,
}];
let container = build_line_container(line_tokens);
match container {
LineContainer::Container { children } => {
assert_eq!(children.len(), 1);
match &children[0] {
LineContainer::Token(line_token) => {
assert_eq!(line_token.line_type, LineType::ParagraphLine);
assert_eq!(line_token.source_tokens.len(), 4);
}
_ => panic!("Expected Token"),
}
}
_ => panic!("Expected Container at root"),
}
}
#[allow(clippy::single_range_in_vec_init)]
#[test]
fn test_build_hierarchy_with_indentation() {
let line_tokens = vec![
LineToken {
source_tokens: vec![
Token::Text("Title".to_string()),
Token::Colon,
Token::BlankLine(Some("\n".to_string())),
],
token_spans: vec![0..5, 5..6, 6..7],
line_type: LineType::SubjectLine,
},
LineToken {
source_tokens: vec![Token::Indentation],
token_spans: vec![7..11],
line_type: LineType::Indent,
},
LineToken {
source_tokens: vec![
Token::Text("Content".to_string()),
Token::BlankLine(Some("\n".to_string())),
],
token_spans: vec![11..18, 18..19],
line_type: LineType::ParagraphLine,
},
LineToken {
source_tokens: vec![Token::Dedent(vec![])],
token_spans: vec![0..0],
line_type: LineType::Dedent,
},
];
let container = build_line_container(line_tokens);
match container {
LineContainer::Container { children } => {
assert_eq!(
children.len(),
2,
"Should have two items at the root: the title token and the content container"
);
match &children[0] {
LineContainer::Token(line_token) => {
assert_eq!(line_token.line_type, LineType::SubjectLine);
}
_ => panic!("Expected Token for title"),
}
match &children[1] {
LineContainer::Container {
children: nested_children,
} => {
assert_eq!(
nested_children.len(),
1,
"Nested container should have one item"
);
match &nested_children[0] {
LineContainer::Token(line_token) => {
assert_eq!(line_token.line_type, LineType::ParagraphLine);
}
_ => panic!("Expected Token for content"),
}
}
_ => panic!("Expected Container for indented content"),
}
}
_ => panic!("Expected Container at root"),
}
}
}