Terrace/packages/rust/src/document.rs
Joshua Bemenderfer 9d9757e868 Updates.
2025-09-08 16:24:38 -04:00

307 lines
8.8 KiB
Rust

//! 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<usize>) -> &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<Self::Item> {
// 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<Self::Item> {
// 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<dyn Reader + Send + Sync>,
indent: char,
line_data: LineData,
current_line_number: usize,
pushed_back_nodes: VecDeque<TerraceNode>,
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<R: Reader + Send + Sync + 'static>(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<R: Reader + Send + Sync + 'static>(reader: R) -> Self {
Self::new(reader, ' ')
}
/// Get the next node from the document.
pub async fn next(&mut self) -> Option<TerraceNode> {
// 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<TerraceNode> {
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<F>(mut self, mut predicate: F) -> Vec<TerraceNode>
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<F>(mut self, mut predicate: F) -> Option<TerraceNode>
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<F, T>(mut self, mut mapper: F) -> Vec<T>
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);
}
}