//! Document structure and node implementation for the Terrace language. use crate::parser::{LineData, create_line_data, parse_line}; use crate::readers::Reader; use std::collections::VecDeque; /// Represents a single node/line in a Terrace document. #[derive(Debug, Clone)] pub struct TerraceNode { line_data: LineData, content: String, line_number: usize, document: *const TerraceDocument, // Raw pointer to avoid circular reference } impl TerraceNode { /// Create a new TerraceNode. fn new(line_data: LineData, content: String, line_number: usize, document: *const TerraceDocument) -> Self { Self { line_data, content, line_number, document, } } /// Get the head of the node (the first word before any space). pub fn head(&self) -> &str { &self.content[self.line_data.offset_head..self.line_data.offset_tail] } /// Get the tail of the node (everything after the first space). pub fn tail(&self) -> &str { if self.line_data.offset_tail + 1 >= self.content.len() { "" } else { &self.content[self.line_data.offset_tail + 1..] } } /// Get the content of the node (everything after the indentation). pub fn content(&self) -> &str { &self.content[self.line_data.offset_head..] } /// Get the indentation level of the node. pub fn level(&self) -> usize { self.line_data.level } /// Get the line number of the node. pub fn line_number(&self) -> usize { self.line_number } /// Check if the node's head matches the given value. pub fn is(&self, value: &str) -> bool { self.head() == value } /// Check if the node is empty (contains only whitespace). pub fn is_empty(&self) -> bool { self.content.trim().is_empty() } /// Get the raw content starting from the given offset. pub fn raw(&self, offset: Option) -> &str { &self.content[offset.unwrap_or(0)..] } /// Get an iterator over the children of this node. pub fn children(&self) -> TerraceNodeChildrenIterator { TerraceNodeChildrenIterator::new(self.document, self.level()) } /// Get an iterator over the siblings of this node. pub fn siblings(&self) -> TerraceNodeSiblingsIterator { TerraceNodeSiblingsIterator::new(self.document, self.level()) } } /// Iterator for children of a TerraceNode. pub struct TerraceNodeChildrenIterator { document: *const TerraceDocument, parent_level: usize, } impl TerraceNodeChildrenIterator { fn new(document: *const TerraceDocument, parent_level: usize) -> Self { Self { document, parent_level, } } } impl Iterator for TerraceNodeChildrenIterator { type Item = TerraceNode; fn next(&mut self) -> Option { // This is a simplified implementation - in a real async context, // we'd need to handle the async nature properly // For now, this is a placeholder that would need to be implemented // with proper async iteration None } } /// Iterator for siblings of a TerraceNode. pub struct TerraceNodeSiblingsIterator { document: *const TerraceDocument, current_level: usize, } impl TerraceNodeSiblingsIterator { fn new(document: *const TerraceDocument, current_level: usize) -> Self { Self { document, current_level, } } } impl Iterator for TerraceNodeSiblingsIterator { type Item = TerraceNode; fn next(&mut self) -> Option { // This is a simplified implementation - in a real async context, // we'd need to handle the async nature properly None } } /// Main document iterator for Terrace documents. pub struct TerraceDocument { reader: Box, indent: char, line_data: LineData, current_line_number: usize, pushed_back_nodes: VecDeque, is_exhausted: bool, } impl TerraceDocument { /// Create a new TerraceDocument with the given reader. /// /// # Arguments /// * `reader` - The reader to read lines from /// * `indent` - The character used for indentation (default: space) pub fn new(reader: R, indent: char) -> Self { Self { reader: Box::new(reader), indent, line_data: create_line_data(indent), current_line_number: 0, pushed_back_nodes: VecDeque::new(), is_exhausted: false, } } /// Create a new TerraceDocument with default space indentation. pub fn with_reader(reader: R) -> Self { Self::new(reader, ' ') } /// Get the next node from the document. pub async fn next(&mut self) -> Option { // Check for pushed back nodes first (LIFO order) if let Some(node) = self.pushed_back_nodes.pop_back() { return Some(node); } // If we've exhausted the reader, return None if self.is_exhausted { return None; } let line = match self.reader.read_line().await { Ok(Some(line)) => line, Ok(None) => { self.is_exhausted = true; return None; } Err(_) => return None, // In real implementation, should handle errors properly }; self.current_line_number += 1; parse_line(&line, &mut self.line_data); Some(TerraceNode::new( self.line_data.clone(), line, self.current_line_number, self as *const Self, )) } /// Push a node back to be returned on the next call to next(). fn push_back(&mut self, node: TerraceNode) { self.pushed_back_nodes.push_back(node); } /// Collect all nodes into a vector. pub async fn collect(mut self) -> Vec { let mut nodes = Vec::new(); while let Some(node) = self.next().await { nodes.push(node); } nodes } /// Filter nodes based on a predicate. pub async fn filter(mut self, mut predicate: F) -> Vec where F: FnMut(&TerraceNode) -> bool, { let mut results = Vec::new(); while let Some(node) = self.next().await { if predicate(&node) { results.push(node); } } results } /// Find the first node that matches the predicate. pub async fn find(mut self, mut predicate: F) -> Option where F: FnMut(&TerraceNode) -> bool, { while let Some(node) = self.next().await { if predicate(&node) { return Some(node); } } None } /// Map nodes using a mapper function. pub async fn map(mut self, mut mapper: F) -> Vec where F: FnMut(TerraceNode) -> T, { let mut results = Vec::new(); while let Some(node) = self.next().await { results.push(mapper(node)); } results } } #[cfg(test)] mod tests { use super::*; use crate::readers::StringReader; #[tokio::test] async fn test_document_iteration() { let content = "hello\n world\n world\nhello again\n terrace"; let reader = StringReader::new(content); let mut doc = TerraceDocument::with_reader(reader); let nodes = doc.collect().await; assert_eq!(nodes.len(), 5); assert_eq!(nodes[0].head(), "hello"); assert_eq!(nodes[0].level(), 0); assert_eq!(nodes[1].head(), "world"); assert_eq!(nodes[1].level(), 1); assert_eq!(nodes[2].head(), "world"); assert_eq!(nodes[2].level(), 1); assert_eq!(nodes[3].head(), "hello"); assert_eq!(nodes[3].level(), 0); assert_eq!(nodes[4].head(), "terrace"); assert_eq!(nodes[4].level(), 1); } #[tokio::test] async fn test_node_properties() { let content = " config database localhost"; let reader = StringReader::new(content); let mut doc = TerraceDocument::with_reader(reader); let node = doc.next().await.unwrap(); assert_eq!(node.level(), 2); assert_eq!(node.head(), "config"); assert_eq!(node.tail(), "database localhost"); assert_eq!(node.content(), "config database localhost"); } #[tokio::test] async fn test_filter_nodes() { let content = "config\n database\n server\n database"; let reader = StringReader::new(content); let doc = TerraceDocument::with_reader(reader); let filtered = doc.filter(|node| node.head() == "database").await; assert_eq!(filtered.len(), 2); assert_eq!(filtered[0].level(), 1); assert_eq!(filtered[1].level(), 1); } }