css_parse/
cursor_overlay_sink.rs

1use crate::{
2	Cursor, CursorSink, CursorToSourceCursorSink, ParserReturn, SourceCursor, SourceCursorSink, SourceOffset, Span,
3	ToCursors, ToSpan,
4};
5use bumpalo::{Bump, collections::Vec};
6use std::collections::BTreeMap;
7
8#[derive(Debug)]
9pub struct CursorOverlaySet<'a> {
10	bump: &'a Bump,
11	overlays: BTreeMap<SourceOffset, (SourceOffset, Vec<'a, SourceCursor<'a>>)>,
12}
13
14impl<'a> CursorOverlaySet<'a> {
15	pub fn new(bump: &'a Bump) -> Self {
16		Self { bump, overlays: BTreeMap::new() }
17	}
18
19	pub fn insert<T: ToCursors>(&mut self, span: Span, parser_return: ParserReturn<'a, T>) {
20		let start = span.start();
21		let end = span.end();
22		let mut cursors = Vec::new_in(self.bump);
23		let mut sink = CursorToSourceCursorSink::new(parser_return.source_text, &mut cursors);
24		parser_return.to_cursors(&mut sink);
25		self.overlays.insert(start, (end, cursors));
26	}
27
28	pub(crate) fn find(&self, end: SourceOffset) -> Option<&(SourceOffset, Vec<'a, SourceCursor<'a>>)> {
29		self.overlays.range(..=end).rev().find(|&(_, &(overlay_end, _))| end < overlay_end).map(|(_, ret)| ret)
30	}
31}
32
33/// This is a [CursorSink] that wraps a [SourceCursorSink], while also taking a [CursorOverlaySet]. As [Cursor]s get
34/// appended into this sink, it will replay those to the underlying [SourceCursorSink] _unless_ a [CursorOverlaySet]
35/// overlaps the [Cursor]'s span, at which point the overlay wil be replayed to the underlying [SourceCursorSink].
36/// This Sink is useful for collecting new Cursors (say from an AST) to overlap (or, say, transform) the underlying base
37/// Cursors (read: AST). In other words, writing over the top of the source.
38///
39/// Other than replaying overlays in place of the underyling cursors, no other modifications are made to the Cursors,
40/// that is up to the base SourceCursorSink, which can apply additional formatting or logic.
41#[derive(Debug)]
42pub struct CursorOverlaySink<'a, T: SourceCursorSink<'a>> {
43	source_text: &'a str,
44	overlays: &'a CursorOverlaySet<'a>,
45	sink: T,
46	processed_overlay_ranges: BTreeMap<SourceOffset, SourceOffset>,
47}
48
49impl<'a, T: SourceCursorSink<'a>> CursorOverlaySink<'a, T> {
50	pub fn new(source_text: &'a str, overlays: &'a CursorOverlaySet<'a>, sink: T) -> Self {
51		Self { source_text, overlays, sink, processed_overlay_ranges: BTreeMap::new() }
52	}
53}
54
55impl<'a, T: SourceCursorSink<'a>> SourceCursorSink<'a> for CursorOverlaySink<'a, T> {
56	fn append(&mut self, c: SourceCursor<'a>) {
57		let cursor_start = c.to_span().start();
58		let cursor_end = c.to_span().end();
59
60		// Check if this entire cursor falls within an already-processed overlay range
61		// Look for any processed range that starts at or before cursor_start
62		if let Some((&range_start, &range_end)) = self.processed_overlay_ranges.range(..=cursor_start).next_back() {
63			if cursor_start >= range_start && cursor_end <= range_end {
64				// This cursor is entirely within a processed overlay, skip it
65				return;
66			}
67		}
68
69		let mut pos = cursor_start;
70		while pos < cursor_end {
71			// dbg!(pos, self.overlays.find(pos));
72			if let Some((overlay_end, overlay)) = self.overlays.find(pos) {
73				for c in overlay {
74					self.sink.append(*c);
75				}
76				self.processed_overlay_ranges.insert(pos, *overlay_end);
77				pos = *overlay_end;
78			} else {
79				self.sink.append(c);
80				pos = c.to_span().end();
81			}
82		}
83	}
84}
85
86impl<'a, T: SourceCursorSink<'a>> CursorSink for CursorOverlaySink<'a, T> {
87	fn append(&mut self, c: Cursor) {
88		SourceCursorSink::append(self, SourceCursor::from(c, c.str_slice(self.source_text)))
89	}
90}
91
92#[cfg(test)]
93mod test {
94	use super::*;
95	use crate::{ComponentValue, CursorPrettyWriteSink, CursorWriteSink, QuoteStyle, T, ToCursors, ToSpan, parse};
96	use bumpalo::{Bump, collections::Vec};
97
98	#[test]
99	fn test_basic() {
100		// Parse the original AST
101		let source_text = "black white";
102		let bump = Bump::default();
103		let output = parse!(in bump &source_text as (T![Ident], T![Ident])).output.unwrap();
104
105		// Build an overlay AST
106		let overlay_text = "green";
107		let overlay = parse!(in bump &overlay_text as T![Ident]);
108		let mut overlays = CursorOverlaySet::new(&bump);
109		overlays.insert(output.1.to_span(), overlay);
110
111		// Smoosh
112		let mut str = String::new();
113		let mut stream = CursorOverlaySink::new(source_text, &overlays, CursorWriteSink::new(source_text, &mut str));
114		output.to_cursors(&mut stream);
115
116		// str should include overlays
117		assert_eq!(str, "black green");
118	}
119
120	#[test]
121	fn test_with_pretty_writer() {
122		// Parse the original AST
123		let source_text = "foo{use:other;}";
124		let bump = Bump::default();
125		let output = parse!(in bump &source_text as Vec<'_, ComponentValue>).output.unwrap();
126		let ComponentValue::SimpleBlock(ref block) = output[1] else { panic!("output[1] was not a block") };
127		dbg!(block.to_span(), block.values.to_span());
128
129		// Build an overlay AST
130		let overlay_text = "inner{foo: bar;}";
131		let overlay = parse!(in bump &overlay_text as Vec<'_, ComponentValue>);
132		let mut overlays = CursorOverlaySet::new(&bump);
133		overlays.insert(dbg!(block.values.to_span()), overlay);
134
135		// Smoosh
136		let mut str = String::new();
137		let mut stream = CursorOverlaySink::new(
138			source_text,
139			&overlays,
140			CursorPrettyWriteSink::new(source_text, &mut str, None, QuoteStyle::Double),
141		);
142		output.to_cursors(&mut stream);
143
144		// str should include overlays
145		assert_eq!(
146			str,
147			r#"
148foo {
149	inner {
150		foo: bar;
151	}
152}
153			"#
154			.trim()
155		);
156	}
157}