Skip to main content

rustidy_util/
ast_str.rs

1//! Ast string
2
3// Imports
4use {
5	crate::{ArenaData, ArenaIdx, StrChunk},
6	arcstr::Substr,
7	std::{borrow::Cow, sync::Arc},
8};
9
10/// Ast string
11// TODO: Add an "empty" position for newly created ast nodes?
12#[derive(PartialEq, Eq, Clone, Hash, Debug)]
13#[must_use = "Ast output must not be discarded"]
14pub struct AstStr(ArenaIdx<Inner>);
15
16impl AstStr {
17	/// Creates a new ast string without any associated input range
18	pub fn new(repr: impl Into<AstStrRepr>) -> Self {
19		Self(
20			ArenaIdx::new(Inner { repr: repr.into(), input: None, })
21		)
22	}
23
24	/// Creates a new ast string from an input range
25	pub fn from_input(input: Substr) -> Self {
26		Self(ArenaIdx::new(Inner {
27			repr: AstStrRepr::String(Substr::clone(&input)),
28			input: Some(input),
29		}))
30	}
31
32	/// Replaces this ast string
33	pub fn replace(&mut self, repr: impl Into<AstStrRepr>) {
34		self.0.repr = repr.into();
35	}
36
37	/// Joins two strings
38	pub fn join(self, other: Self) -> Self {
39		let input = match (&self.0.input, &other.0.input) {
40			// TODO: Keep the range more than just when contiguous?
41			(Some(lhs), Some(rhs)) => {
42				let input = lhs.parent();
43				let lhs = lhs.range();
44				let rhs = rhs.range();
45
46				match lhs.end == rhs.start {
47					true => Some(input.substr(lhs.start..rhs.end)),
48					false => None,
49				}
50			},
51			_ => None,
52		};
53
54		Self(ArenaIdx::new(Inner {
55			repr: AstStrRepr::Join { lhs: self, rhs: other },
56			input
57		}))
58	}
59
60	/// Returns the inner representation of this string
61	#[must_use]
62	pub fn repr(&self) -> &AstStrRepr {
63		&self.0.repr
64	}
65
66	/// Returns the input range of this string
67	#[must_use]
68	pub fn input(&self) -> Option<&Substr> {
69		self.0.input.as_ref()
70	}
71
72	/// Returns the length of this string
73	#[must_use]
74	pub fn len(&self) -> usize {
75		self.repr().len()
76	}
77
78	/// Returns if this string is empty
79	#[must_use]
80	pub fn is_empty(&self) -> bool {
81		self.repr().is_empty()
82	}
83
84	/// Returns if this string is blank
85	#[must_use]
86	pub fn is_blank(&self) -> bool {
87		self.repr().is_blank()
88	}
89
90	/// Returns the number of newlines in this string
91	#[must_use]
92	pub fn count_newlines(&self) -> usize {
93		self.repr().count_newlines()
94	}
95
96	/// Returns if this string has newlines
97	#[must_use]
98	pub fn has_newlines(&self) -> bool {
99		self.repr().has_newlines()
100	}
101
102	/// Returns if this string is equal to `other`
103	#[must_use]
104	pub fn is_str(&self, other: &str) -> bool {
105		self.repr().is_str(other)
106	}
107
108	/// Writes this string
109	pub fn write(&self, output: &mut String) {
110		self.repr().write(output);
111	}
112
113	/// Returns the string of this string, if it's cheap to get
114	#[must_use]
115	pub fn str_cheap(&self) -> Option<&str> {
116		self.repr().str_cheap()
117	}
118
119	/// Returns the string of this string
120	#[must_use]
121	pub fn str(&self) -> Cow<'_, str> {
122		self.repr().str()
123	}
124}
125
126// TODO: Make serialization not lossy?
127impl serde::Serialize for AstStr {
128	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
129	where
130		S: serde::Serializer
131	{
132		self.str().serialize(serializer)
133	}
134}
135
136impl<'de> serde::Deserialize<'de> for AstStr {
137	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138	where
139		D: serde::Deserializer<'de>,
140	{
141		let s = String::deserialize(deserializer)?;
142		Ok(Self::new(AstStrRepr::String(s.into())))
143	}
144}
145
146
147#[derive(PartialEq, Eq, Clone, Debug)]
148#[derive(ArenaData)]
149#[derive(derive_more::From)]
150struct Inner {
151	repr:  AstStrRepr,
152	input: Option<Substr>,
153}
154
155/// Ast string representation
156#[derive(PartialEq, Eq, Clone, Debug)]
157#[derive(ArenaData)]
158#[derive(derive_more::From)]
159pub enum AstStrRepr {
160	/// String
161	#[from]
162	String(Substr),
163
164	/// Static string
165	// TODO: Merge this with `Self::String`?
166	#[from]
167	Static(&'static str),
168
169	/// Single character
170	#[from]
171	Char(char),
172
173	/// Spaces
174	Spaces {
175		len: u16,
176	},
177
178	/// Indentation
179	#[from]
180	Indentation {
181		indent:   Arc<str>,
182		newlines: usize,
183		depth:    usize,
184	},
185
186	/// Joined strings
187	Join {
188		lhs: AstStr,
189		rhs: AstStr,
190	},
191}
192
193impl AstStrRepr {
194	/// Returns the length of this representation
195	#[must_use]
196	pub fn len(&self) -> usize {
197		match *self {
198			Self::String(ref s) => s.len(),
199			Self::Static(s) => s.len(),
200			Self::Char(ch) => ch.len_utf8(),
201			Self::Spaces { len } => usize::from(len),
202			Self::Indentation { ref indent, newlines, depth, } => newlines + depth * indent.len(),
203			Self::Join { ref lhs, ref rhs } => lhs.len() + rhs.len(),
204		}
205	}
206
207	/// Returns if this representation is empty
208	#[must_use]
209	pub fn is_empty(&self) -> bool {
210		self.len() == 0
211	}
212
213	/// Returns if this repr has the same string as another
214	#[must_use]
215	pub fn is_str_eq_to(&self, other: &Self) -> bool {
216		match (self, other) {
217			// If they're equal, we're done
218			(lhs, rhs) if lhs == rhs => true,
219
220			// If one of them can be cheaply represented as a string, compare it.
221			(lhs, rhs) if let Some(lhs) = lhs.str_cheap() => rhs.is_str(lhs),
222			(lhs, rhs) if let Some(rhs) = rhs.str_cheap() => lhs.is_str(rhs),
223
224			// Otherwise, turn one of them into a string and compare.
225			// TODO: This is expensive, ensure we only get
226			//       here for very rare representations
227			_ => self.is_str(&other.str()),
228		}
229	}
230
231	/// Returns if this representation is blank
232	#[must_use]
233	pub fn is_blank(&self) -> bool {
234		match *self {
235			Self::String(ref s) => crate::is_str_blank(s),
236			Self::Static(s) => crate::is_str_blank(s),
237			Self::Char(ch) => ch.is_ascii_whitespace(),
238			Self::Spaces { .. } | Self::Indentation { .. } => true,
239			Self::Join { ref lhs, ref rhs } => lhs.is_blank() && rhs.is_blank(),
240		}
241	}
242
243	/// Returns the number of newlines in this string
244	#[must_use]
245	pub fn count_newlines(&self) -> usize {
246		match *self {
247			Self::String(ref s) => crate::str_count_newlines(s),
248			Self::Static(s) => crate::str_count_newlines(s),
249			Self::Char(ch) => match ch == '\n' {
250				true => 1,
251				false => 0,
252			},
253			Self::Spaces { .. } => 0,
254			Self::Indentation { newlines, .. } => newlines,
255			Self::Join { ref lhs, ref rhs } => lhs.count_newlines() + rhs.count_newlines(),
256		}
257	}
258
259	/// Returns if this representation has newlines
260	#[must_use]
261	pub fn has_newlines(&self) -> bool {
262		self.count_newlines() != 0
263	}
264
265	/// Returns if this representation is equal to `other`
266	#[must_use]
267	pub fn is_str(&self, other: &str) -> bool {
268		match *self {
269			Self::String(ref s) => s == other,
270			Self::Static(s) => s == other,
271
272			Self::Char(ch) => {
273				let mut other = other.chars();
274				if other.next() != Some(ch) {
275					return false;
276				}
277
278				other.next().is_none()
279			},
280
281			Self::Spaces { len } => other.len() == usize::from(len) && other.chars().all(|ch| ch == ' '),
282
283			Self::Indentation { ref indent, newlines, depth } => other.len() == newlines + depth && other[..newlines].chars().all(|ch| ch == '\n') && other[newlines..]
284				.chunk(indent.len())
285				.all(|other_indent| other_indent == other),
286
287			Self::Join { ref lhs, ref rhs } => {
288				let Some((lhs_other, rhs_other)) = other.split_at_checked(lhs.len()) else {
289					return false;
290				};
291				lhs.is_str(lhs_other) && rhs.is_str(rhs_other)
292			},
293		}
294	}
295
296	/// Writes this representation
297	pub fn write(&self, output: &mut String) {
298		match *self {
299			Self::String(ref s) => output.push_str(s),
300			Self::Static(s) => output.push_str(s),
301			Self::Char(ch) => output.push(ch),
302			Self::Spaces { len } => for _ in 0..len {
303				output.push(' ');
304			},
305			Self::Indentation { ref indent, newlines, depth, } => {
306				for _ in 0..newlines {
307					output.push('\n');
308				}
309				for _ in 0..depth {
310					output.push_str(indent);
311				}
312			},
313			Self::Join { ref lhs, ref rhs } => {
314				lhs.write(output);
315				rhs.write(output);
316			},
317		}
318	}
319
320	/// Returns the string of this representation, if it's cheap to get it
321	#[must_use]
322	pub fn str_cheap(&self) -> Option<&str> {
323		match self {
324			Self::String(s) => Some(s),
325
326			// Special case these to avoid a `String` allocation
327			Self::Spaces { len: 0 } => "".into(),
328			Self::Spaces { len: 1 } => " ".into(),
329
330			_ => None,
331		}
332	}
333
334	/// Returns a string of this representation
335	// TODO: This can be somewhat expensive, should we replace it with
336	//       functions performing whatever checks the callers need instead?
337	#[must_use]
338	pub fn str(&self) -> Cow<'_, str> {
339		match self.str_cheap() {
340			Some(s) => s.into(),
341			None => {
342				let mut output = String::new();
343				self.write(&mut output);
344				output.into()
345			},
346		}
347	}
348}