Skip to main content

reifydb_type/
fragment.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	cmp,
6	cmp::Ordering,
7	fmt,
8	fmt::{Display, Formatter},
9	ops::Deref,
10	sync::Arc,
11};
12
13use serde::{Deserialize, Serialize};
14
15// Position types for fragments
16#[repr(transparent)]
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18pub struct StatementColumn(pub u32);
19
20impl Deref for StatementColumn {
21	type Target = u32;
22
23	fn deref(&self) -> &Self::Target {
24		&self.0
25	}
26}
27
28impl PartialEq<i32> for StatementColumn {
29	fn eq(&self, other: &i32) -> bool {
30		self.0 == *other as u32
31	}
32}
33
34#[repr(transparent)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
36pub struct StatementLine(pub u32);
37
38impl Deref for StatementLine {
39	type Target = u32;
40
41	fn deref(&self) -> &Self::Target {
42		&self.0
43	}
44}
45
46impl PartialEq<i32> for StatementLine {
47	fn eq(&self, other: &i32) -> bool {
48		self.0 == *other as u32
49	}
50}
51
52/// Fragment - owns all its data
53#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize, Default)]
54pub enum Fragment {
55	/// No fragment information available
56	#[default]
57	None,
58
59	/// Fragment from a RQL statement with position information
60	Statement {
61		text: Arc<str>,
62		line: StatementLine,
63		column: StatementColumn,
64	},
65
66	/// Fragment from internal/runtime code
67	Internal {
68		text: Arc<str>,
69	},
70}
71
72impl Fragment {
73	/// Get the text value of the fragment
74	pub fn text(&self) -> &str {
75		match self {
76			Fragment::None => "",
77			Fragment::Statement {
78				text,
79				..
80			}
81			| Fragment::Internal {
82				text,
83				..
84			} => text,
85		}
86	}
87
88	/// Get line position
89	pub fn line(&self) -> StatementLine {
90		match self {
91			Fragment::Statement {
92				line,
93				..
94			} => *line,
95			_ => StatementLine(1),
96		}
97	}
98
99	/// Get column position
100	pub fn column(&self) -> StatementColumn {
101		match self {
102			Fragment::Statement {
103				column,
104				..
105			} => *column,
106			_ => StatementColumn(0),
107		}
108	}
109
110	/// Get a sub-fragment starting at the given offset with the given
111	/// length
112	pub fn sub_fragment(&self, offset: usize, length: usize) -> Fragment {
113		let text = self.text();
114		let end = cmp::min(offset + length, text.len());
115		let sub_text = if offset < text.len() {
116			&text[offset..end]
117		} else {
118			""
119		};
120
121		match self {
122			Fragment::None => Fragment::None,
123			Fragment::Statement {
124				line,
125				column,
126				..
127			} => Fragment::Statement {
128				text: Arc::from(sub_text),
129				line: *line,
130				column: StatementColumn(column.0 + offset as u32),
131			},
132			Fragment::Internal {
133				..
134			} => Fragment::Internal {
135				text: Arc::from(sub_text),
136			},
137		}
138	}
139
140	/// Return a new fragment with replaced text, preserving location info.
141	pub fn with_text(&self, text: impl AsRef<str>) -> Fragment {
142		let text = Arc::from(text.as_ref());
143		match self {
144			Fragment::Statement {
145				line,
146				column,
147				..
148			} => Fragment::Statement {
149				text,
150				line: *line,
151				column: *column,
152			},
153			Fragment::Internal {
154				..
155			} => Fragment::Internal {
156				text,
157			},
158			Fragment::None => Fragment::Internal {
159				text,
160			},
161		}
162	}
163}
164
165impl Fragment {
166	/// Create an internal fragment - useful for creating fragments from
167	/// substrings.
168	///
169	/// Takes `impl AsRef<str>` so `Arc::<str>::from(&str)` allocates the
170	/// inline Arc payload directly, skipping the implicit `String` round
171	/// trip that the previous `impl Into<String>` signature forced.
172	pub fn internal(text: impl AsRef<str>) -> Self {
173		Fragment::Internal {
174			text: Arc::from(text.as_ref()),
175		}
176	}
177
178	/// Create a testing fragment - returns a Statement fragment for test
179	/// purposes.
180	///
181	/// Takes `impl AsRef<str>` so `Arc::<str>::from(&str)` allocates the
182	/// inline Arc payload directly, skipping the implicit `String` round
183	/// trip that the previous `impl Into<String>` signature forced.
184	pub fn testing(text: impl AsRef<str>) -> Self {
185		Fragment::Statement {
186			text: Arc::from(text.as_ref()),
187			line: StatementLine(1),
188			column: StatementColumn(0),
189		}
190	}
191
192	/// Create an empty testing fragment
193	pub fn testing_empty() -> Self {
194		Self::testing("")
195	}
196
197	/// Merge multiple fragments (in any order) into one encompassing
198	/// fragment
199	pub fn merge_all(fragments: impl IntoIterator<Item = Fragment>) -> Fragment {
200		let mut fragments: Vec<Fragment> = fragments.into_iter().collect();
201		assert!(!fragments.is_empty());
202
203		fragments.sort();
204
205		let first = fragments.first().unwrap();
206
207		let mut text = String::with_capacity(fragments.iter().map(|f| f.text().len()).sum());
208		for fragment in &fragments {
209			text.push_str(fragment.text());
210		}
211
212		match first {
213			Fragment::None => Fragment::None,
214			Fragment::Statement {
215				line,
216				column,
217				..
218			} => Fragment::Statement {
219				text: Arc::from(text),
220				line: *line,
221				column: *column,
222			},
223			Fragment::Internal {
224				..
225			} => Fragment::Internal {
226				text: Arc::from(text),
227			},
228		}
229	}
230
231	/// Compatibility: expose fragment field for Fragment compatibility
232	pub fn fragment(&self) -> &str {
233		self.text()
234	}
235}
236
237impl AsRef<str> for Fragment {
238	fn as_ref(&self) -> &str {
239		self.text()
240	}
241}
242
243impl Display for Fragment {
244	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
245		Display::fmt(self.text(), f)
246	}
247}
248
249impl PartialOrd for Fragment {
250	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
251		Some(self.cmp(other))
252	}
253}
254
255impl Ord for Fragment {
256	fn cmp(&self, other: &Self) -> Ordering {
257		self.column().cmp(&other.column()).then(self.line().cmp(&other.line()))
258	}
259}
260
261impl Eq for Fragment {}
262
263// Convenience From implementations
264impl From<String> for Fragment {
265	fn from(s: String) -> Self {
266		Fragment::Internal {
267			text: Arc::from(s),
268		}
269	}
270}
271
272impl From<&str> for Fragment {
273	fn from(s: &str) -> Self {
274		Fragment::Internal {
275			text: Arc::from(s),
276		}
277	}
278}
279
280impl Fragment {
281	/// Create a statement fragment with position info
282	pub fn statement(text: impl AsRef<str>, line: u32, column: u32) -> Self {
283		Fragment::Statement {
284			text: Arc::from(text.as_ref()),
285			line: StatementLine(line),
286			column: StatementColumn(column),
287		}
288	}
289
290	/// Create a none fragment
291	pub fn none() -> Self {
292		Fragment::None
293	}
294}
295
296// PartialEq implementations for Fragment with str/String
297impl PartialEq<str> for Fragment {
298	fn eq(&self, other: &str) -> bool {
299		self.text() == other
300	}
301}
302
303impl PartialEq<&str> for Fragment {
304	fn eq(&self, other: &&str) -> bool {
305		self.text() == *other
306	}
307}
308
309impl PartialEq<String> for Fragment {
310	fn eq(&self, other: &String) -> bool {
311		self.text() == other.as_str()
312	}
313}
314
315impl PartialEq<String> for &Fragment {
316	fn eq(&self, other: &String) -> bool {
317		self.text() == other.as_str()
318	}
319}
320
321/// Trait for types that can lazily provide a Fragment
322pub trait LazyFragment {
323	fn fragment(&self) -> Fragment;
324}
325
326impl<F> LazyFragment for F
327where
328	F: Fn() -> Fragment,
329{
330	fn fragment(&self) -> Fragment {
331		self()
332	}
333}
334
335impl LazyFragment for &Fragment {
336	fn fragment(&self) -> Fragment {
337		(*self).clone()
338	}
339}
340
341impl LazyFragment for Fragment {
342	fn fragment(&self) -> Fragment {
343		self.clone()
344	}
345}