plotnik_lib/engine/
effect_stream.rs

1//! Effect stream recorded during query execution.
2
3use crate::ir::EffectOp;
4use serde::Serialize;
5use serde::ser::SerializeStruct;
6use tree_sitter::Node;
7
8/// A captured AST node with a reference to the source.
9#[derive(Debug, Clone, Copy)]
10pub struct CapturedNode<'tree> {
11    node: Node<'tree>,
12    source: &'tree str,
13}
14
15impl<'tree> CapturedNode<'tree> {
16    /// Create from a tree-sitter node and source text.
17    pub fn new(node: Node<'tree>, source: &'tree str) -> Self {
18        Self { node, source }
19    }
20
21    /// Returns the underlying tree-sitter node.
22    pub fn node(&self) -> Node<'tree> {
23        self.node
24    }
25
26    /// Returns the source text of the node.
27    pub fn text(&self) -> &'tree str {
28        self.node
29            .utf8_text(self.source.as_bytes())
30            .unwrap_or("<invalid utf8>")
31    }
32
33    pub fn start_byte(&self) -> usize {
34        self.node.start_byte()
35    }
36
37    pub fn end_byte(&self) -> usize {
38        self.node.end_byte()
39    }
40
41    pub fn start_point(&self) -> (usize, usize) {
42        let p = self.node.start_position();
43        (p.row, p.column)
44    }
45
46    pub fn end_point(&self) -> (usize, usize) {
47        let p = self.node.end_position();
48        (p.row, p.column)
49    }
50
51    pub fn kind(&self) -> &'tree str {
52        self.node.kind()
53    }
54}
55
56impl PartialEq for CapturedNode<'_> {
57    fn eq(&self, other: &Self) -> bool {
58        // Compare by node identity (same position in same tree)
59        self.node.id() == other.node.id()
60            && self.start_byte() == other.start_byte()
61            && self.end_byte() == other.end_byte()
62    }
63}
64
65impl Eq for CapturedNode<'_> {}
66
67impl Serialize for CapturedNode<'_> {
68    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
69    where
70        S: serde::Serializer,
71    {
72        let mut state = serializer.serialize_struct("CapturedNode", 3)?;
73        state.serialize_field("kind", self.kind())?;
74        state.serialize_field("text", self.text())?;
75        state.serialize_field("range", &[self.start_byte(), self.end_byte()])?;
76        state.end()
77    }
78}
79
80/// Wrapper for verbose serialization of a captured node.
81/// Includes full positional information (bytes + line/column).
82pub struct VerboseNode<'a, 'tree>(pub &'a CapturedNode<'tree>);
83
84impl Serialize for VerboseNode<'_, '_> {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: serde::Serializer,
88    {
89        let node = self.0;
90        let mut state = serializer.serialize_struct("CapturedNode", 6)?;
91        state.serialize_field("kind", node.kind())?;
92        state.serialize_field("text", node.text())?;
93        state.serialize_field("start_byte", &node.start_byte())?;
94        state.serialize_field("end_byte", &node.end_byte())?;
95        state.serialize_field("start_point", &node.start_point())?;
96        state.serialize_field("end_point", &node.end_point())?;
97        state.end()
98    }
99}
100
101/// A log of effects to be replayed by the materializer.
102/// See ADR-0006 for details.
103#[derive(Debug, Clone, Default)]
104pub struct EffectStream<'tree> {
105    /// The sequence of operations to perform.
106    ops: Vec<EffectOp>,
107    /// The sequence of nodes captured, one for each `CaptureNode` op.
108    nodes: Vec<CapturedNode<'tree>>,
109}
110
111impl<'tree> EffectStream<'tree> {
112    pub fn new() -> Self {
113        Self::default()
114    }
115
116    /// Appends an effect operation to the stream.
117    pub fn push_op(&mut self, op: EffectOp) {
118        self.ops.push(op);
119    }
120
121    /// Appends a captured node to the stream.
122    pub fn push_node(&mut self, node: Node<'tree>, source: &'tree str) {
123        self.nodes.push(CapturedNode::new(node, source));
124    }
125
126    /// Appends a captured node directly.
127    pub fn push_captured_node(&mut self, node: CapturedNode<'tree>) {
128        self.nodes.push(node);
129    }
130
131    /// Returns the operations.
132    pub fn ops(&self) -> &[EffectOp] {
133        &self.ops
134    }
135
136    /// Returns the captured nodes.
137    pub fn nodes(&self) -> &[CapturedNode<'tree>] {
138        &self.nodes
139    }
140
141    /// Truncate streams to watermarks (for backtracking).
142    pub fn truncate(&mut self, ops_len: usize, nodes_len: usize) {
143        self.ops.truncate(ops_len);
144        self.nodes.truncate(nodes_len);
145    }
146}