Skip to main content

json_syntax/
code_map.rs

1use core::fmt;
2use std::{borrow::Borrow, ops::Deref};
3
4use locspan::Span;
5
6use crate::{
7	JsonValue,
8	print::{
9		Indent, JsonPrintOptions,
10		sizes::{JsonSize, JsonSizesVisitor, printed_string_size},
11	},
12	visitor::JsonVisit,
13};
14
15pub type JsonCodeMapOffset = usize;
16
17/// Code-map.
18#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct JsonCodeMap(Vec<JsonCodeMapEntry>);
20
21impl JsonCodeMap {
22	/// Generates a code map for the given value.
23	///
24	/// The result is semantically equivalent to printing the value with the
25	/// given options and parsing it back, but much cheaper.
26	pub fn from_value(value: &JsonValue, options: &JsonPrintOptions) -> Self {
27		// First pass: decide for each array/object whether it is expanded or inline.
28		let mut sizes = Vec::new();
29		value.visit(JsonSizesVisitor::new(options, &mut sizes));
30
31		// Second pass: walk the value in traversal order, tracking the byte
32		// position and building one code-map entry per fragment.
33		let mut result = Self::default();
34		let mut pos = 0usize;
35		let mut sizes_offset = 0usize;
36		result.extent_from_value(value, options, &sizes, &mut sizes_offset, &mut pos, 0);
37		result
38	}
39
40	fn extent_from_value(
41		&mut self,
42		value: &JsonValue,
43		options: &JsonPrintOptions,
44		sizes: &[JsonSize],
45		sizes_offset: &mut usize,
46		pos: &mut usize,
47		indent: usize,
48	) -> usize {
49		let start = *pos;
50		let my_index = self.len();
51		self.0.push(JsonCodeMapEntry::default()); // placeholder filled at the end
52
53		let volume = match value {
54			JsonValue::Null => {
55				*pos += 4; // "null"
56				1
57			}
58			JsonValue::Boolean(b) => {
59				*pos += if *b { 4 } else { 5 }; // "true" / "false"
60				1
61			}
62			JsonValue::Number(n) => {
63				*pos += n.as_str().len();
64				1
65			}
66			JsonValue::String(s) => {
67				*pos += printed_string_size(s);
68				1
69			}
70			JsonValue::Array(items) => {
71				let size = sizes[*sizes_offset];
72				*sizes_offset += 1;
73				*pos += 1; // '['
74				let mut volume = 1;
75
76				match size {
77					JsonSize::Expanded => {
78						*pos += 1; // '\n'
79						let mut first = true;
80						for item in items.iter() {
81							if first {
82								first = false;
83							} else {
84								*pos += options.array_before_comma; // spaces before comma
85								*pos += 2; // ',\n'
86							}
87							*pos += indent_bytes(options.indent, indent + 1);
88							volume += self.extent_from_value(
89								item,
90								options,
91								sizes,
92								sizes_offset,
93								pos,
94								indent + 1,
95							);
96						}
97						if !first {
98							*pos += 1; // trailing '\n'
99						}
100						*pos += indent_bytes(options.indent, indent); // indent for ']'
101					}
102					JsonSize::Width(_) => {
103						let mut first = true;
104						for item in items.iter() {
105							if first {
106								first = false;
107								*pos += options.array_begin;
108							} else {
109								*pos += options.array_before_comma;
110								*pos += 1; // ','
111								*pos += options.array_after_comma;
112							}
113							volume += self.extent_from_value(
114								item,
115								options,
116								sizes,
117								sizes_offset,
118								pos,
119								indent + 1,
120							);
121						}
122						*pos += if first {
123							options.array_empty
124						} else {
125							options.array_end
126						};
127					}
128				}
129
130				*pos += 1; // ']'
131				volume
132			}
133			JsonValue::Object(obj) => {
134				let size = sizes[*sizes_offset];
135				*sizes_offset += 1;
136				*pos += 1; // '{'
137				let mut volume = 1;
138
139				match size {
140					JsonSize::Expanded => {
141						*pos += 1; // '\n'
142						let mut first = true;
143						for (key, value) in obj.iter() {
144							if first {
145								first = false;
146							} else {
147								*pos += options.object_before_comma;
148								*pos += 2; // ',\n'
149							}
150							*pos += indent_bytes(options.indent, indent + 1);
151							volume += self.extend_from_entry(
152								options,
153								sizes,
154								sizes_offset,
155								pos,
156								indent,
157								key.as_str(),
158								value,
159							);
160						}
161						if !first {
162							*pos += 1; // trailing '\n'
163						}
164						*pos += indent_bytes(options.indent, indent); // indent for '}'
165					}
166					JsonSize::Width(_) => {
167						let mut first = true;
168						for (key, value) in obj.iter() {
169							if first {
170								first = false;
171								*pos += options.object_begin;
172							} else {
173								*pos += options.object_before_comma;
174								*pos += 1; // ','
175								*pos += options.object_after_comma;
176							}
177							volume += self.extend_from_entry(
178								options,
179								sizes,
180								sizes_offset,
181								pos,
182								indent,
183								key.as_str(),
184								value,
185							);
186						}
187						*pos += if first {
188							options.object_empty
189						} else {
190							options.object_end
191						};
192					}
193				}
194
195				*pos += 1; // '}'
196				volume
197			}
198		};
199
200		self.0[my_index] = JsonCodeMapEntry::new(Span::new(start, *pos), volume);
201		volume
202	}
203
204	#[allow(clippy::too_many_arguments)]
205	fn extend_from_entry(
206		&mut self,
207		options: &JsonPrintOptions,
208		sizes: &[JsonSize],
209		sizes_offset: &mut usize,
210		pos: &mut usize,
211		indent: usize,
212		key: &str,
213		value: &JsonValue,
214	) -> usize {
215		let entry_start = *pos;
216		let entry_index = self.len();
217		self.0.push(JsonCodeMapEntry::default());
218
219		// Key (always a leaf — no further recursion needed).
220		let key_start = *pos;
221		let key_index = self.len();
222		self.0.push(JsonCodeMapEntry::default());
223		*pos += printed_string_size(key);
224		self.0[key_index] = JsonCodeMapEntry::new(Span::new(key_start, *pos), 1);
225
226		*pos += options.object_before_colon;
227		*pos += 1; // ':'
228		*pos += options.object_after_colon;
229
230		let value_volume =
231			self.extent_from_value(value, options, sizes, sizes_offset, pos, indent + 1);
232
233		let entry_volume = 1 + 1 + value_volume; // entry + key + value subtree
234		self.0[entry_index] = JsonCodeMapEntry::new(Span::new(entry_start, *pos), entry_volume);
235		entry_volume
236	}
237
238	pub fn as_slice(&self) -> &[JsonCodeMapEntry] {
239		&self.0
240	}
241
242	pub(crate) fn reserve(&mut self, position: JsonCodeMapOffset) -> usize {
243		let i = self.0.len();
244
245		self.0.push(JsonCodeMapEntry {
246			span: Span::new(position, position),
247			volume: 0,
248		});
249
250		i
251	}
252
253	pub(crate) fn get_mut(&mut self, offset: JsonCodeMapOffset) -> Option<&mut JsonCodeMapEntry> {
254		self.0.get_mut(offset)
255	}
256
257	pub fn iter(&self) -> Iter<'_> {
258		self.0.iter().enumerate()
259	}
260}
261
262impl Deref for JsonCodeMap {
263	type Target = [JsonCodeMapEntry];
264
265	fn deref(&self) -> &Self::Target {
266		self.as_slice()
267	}
268}
269
270impl AsRef<[JsonCodeMapEntry]> for JsonCodeMap {
271	fn as_ref(&self) -> &[JsonCodeMapEntry] {
272		self.as_slice()
273	}
274}
275
276impl Borrow<[JsonCodeMapEntry]> for JsonCodeMap {
277	fn borrow(&self) -> &[JsonCodeMapEntry] {
278		self.as_slice()
279	}
280}
281
282pub type Iter<'a> = std::iter::Enumerate<std::slice::Iter<'a, JsonCodeMapEntry>>;
283
284pub type IntoIter = std::iter::Enumerate<std::vec::IntoIter<JsonCodeMapEntry>>;
285
286impl<'a> IntoIterator for &'a JsonCodeMap {
287	type IntoIter = Iter<'a>;
288	type Item = (JsonCodeMapOffset, &'a JsonCodeMapEntry);
289
290	fn into_iter(self) -> Self::IntoIter {
291		self.iter()
292	}
293}
294
295impl IntoIterator for JsonCodeMap {
296	type IntoIter = IntoIter;
297	type Item = (JsonCodeMapOffset, JsonCodeMapEntry);
298
299	fn into_iter(self) -> Self::IntoIter {
300		self.0.into_iter().enumerate()
301	}
302}
303
304/// Code-map entry.
305///
306/// Provides code-mapping metadata about a fragment of JSON value.
307#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
308pub struct JsonCodeMapEntry {
309	/// Byte span of the fragment in the original source code.
310	pub span: Span,
311
312	/// Number of sub-fragment (including the fragment itself).
313	pub volume: usize,
314}
315
316impl JsonCodeMapEntry {
317	pub fn new(span: Span, volume: usize) -> Self {
318		Self { span, volume }
319	}
320}
321
322#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
323pub struct JsonMapped<T>(pub T, pub JsonCodeMapOffset);
324
325impl<T> JsonMapped<T> {
326	pub fn offset(&self) -> JsonCodeMapOffset {
327		self.1
328	}
329
330	pub fn into_offset(self) -> JsonCodeMapOffset {
331		self.1
332	}
333
334	pub fn inner(&self) -> &T {
335		&self.0
336	}
337
338	pub fn into_inner(self) -> T {
339		self.0
340	}
341}
342
343impl<T: fmt::Display> fmt::Display for JsonMapped<T> {
344	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345		self.0.fmt(f)
346	}
347}
348
349impl<T: 'static + std::error::Error> std::error::Error for JsonMapped<T> {
350	fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
351		Some(&self.0)
352	}
353}
354
355// ── Helpers for `JsonCodeMap::from_print` ───────────────────────────────────
356
357fn indent_bytes(indent: Indent, level: usize) -> usize {
358	match indent {
359		Indent::Spaces(n) => n as usize * level,
360		Indent::Tabs(n) => n as usize * level, // \t is one byte
361	}
362}
363
364#[cfg(test)]
365mod tests {
366	use super::{JsonCodeMap, JsonCodeMapEntry};
367	use crate::{JsonParse, JsonPrint, JsonValue, print::JsonPrintOptions};
368	use locspan::Span;
369
370	/// Assert that `JsonCodeMap::from_print` agrees with print-then-parse
371	/// for the given value and options.
372	fn check(value: &JsonValue, options: &JsonPrintOptions) {
373		let printed = value.print_with(options).to_string();
374		let (_, expected) = JsonValue::parse_str(&printed).unwrap();
375		let got = JsonCodeMap::from_value(value, options);
376		assert_eq!(
377			got.as_slice(),
378			expected.as_slice(),
379			"mismatch for printed: {printed:?}"
380		);
381	}
382
383	#[test]
384	fn from_value_compact() {
385		// Scalars
386		for src in ["null", "true", "false", "42", "-3.14", "\"hello\""] {
387			let (value, _) = JsonValue::parse_str(src).unwrap();
388			check(&value, &JsonPrintOptions::COMPACT);
389		}
390
391		// Nested structure
392		let (value, _) = JsonValue::parse_str(r#"{"a":0,"b":[1,2]}"#).unwrap();
393		check(&value, &JsonPrintOptions::COMPACT);
394	}
395
396	#[test]
397	fn from_value_inline() {
398		let (value, _) = JsonValue::parse_str(r#"{"a":0,"b":[1,2]}"#).unwrap();
399		check(&value, &JsonPrintOptions::INLINE);
400	}
401
402	#[test]
403	fn from_value_pretty() {
404		let (value, _) = JsonValue::parse_str(r#"{"a":0,"b":[1,2,3,4,5,6,7,8,9,10]}"#).unwrap();
405		check(&value, &JsonPrintOptions::PRETTY);
406	}
407
408	#[test]
409	fn code_map_t1() {
410		let (value, code_map) = JsonValue::parse_str(r#"{ "a": 0, "b": [1, 2] }"#).unwrap();
411		let expected = [
412			JsonCodeMapEntry::new(Span::new(0, 23), 9), // { "a": 0, "b": [1, 2] }
413			JsonCodeMapEntry::new(Span::new(2, 8), 3),  // "a": 0
414			JsonCodeMapEntry::new(Span::new(2, 5), 1),  // "a"
415			JsonCodeMapEntry::new(Span::new(7, 8), 1),  // 0
416			JsonCodeMapEntry::new(Span::new(10, 21), 5), // "b": [1, 2]
417			JsonCodeMapEntry::new(Span::new(10, 13), 1), // "b"
418			JsonCodeMapEntry::new(Span::new(15, 21), 3), // [1, 2]
419			JsonCodeMapEntry::new(Span::new(16, 17), 1), // 1
420			JsonCodeMapEntry::new(Span::new(19, 20), 1), // 2
421		];
422
423		assert_eq!(code_map.len(), expected.len());
424		assert_eq!(value.traverse().count(), expected.len());
425		for (i, entry) in code_map {
426			assert_eq!(entry, expected[i])
427		}
428	}
429
430	#[test]
431	fn code_map_t2() {
432		let (value, code_map) =
433			JsonValue::parse_str(r#"{ "a": 0, "b": { "c": 1, "d": [2, 3] }, "e": [4, [5, 6]] }"#)
434				.unwrap();
435		let expected = [
436			JsonCodeMapEntry::new(Span::new(0, 58), 22), // { "a": 0, "b": { "c": 1, "d": [2, 3] }, "e": [4, [5, 6]] }
437			JsonCodeMapEntry::new(Span::new(2, 8), 3),   // "a": 0
438			JsonCodeMapEntry::new(Span::new(2, 5), 1),   // "a"
439			JsonCodeMapEntry::new(Span::new(7, 8), 1),   // 0
440			JsonCodeMapEntry::new(Span::new(10, 38), 11), // "b": { "c": 1, "d": [2, 3] }
441			JsonCodeMapEntry::new(Span::new(10, 13), 1), // "b"
442			JsonCodeMapEntry::new(Span::new(15, 38), 9), // { "c": 1, "d": [2, 3] }
443			JsonCodeMapEntry::new(Span::new(17, 23), 3), // "c": 1
444			JsonCodeMapEntry::new(Span::new(17, 20), 1), // "c"
445			JsonCodeMapEntry::new(Span::new(22, 23), 1), // 1
446			JsonCodeMapEntry::new(Span::new(25, 36), 5), // "d": [2, 3]
447			JsonCodeMapEntry::new(Span::new(25, 28), 1), // "d"
448			JsonCodeMapEntry::new(Span::new(30, 36), 3), // [2, 3]
449			JsonCodeMapEntry::new(Span::new(31, 32), 1), // 2
450			JsonCodeMapEntry::new(Span::new(34, 35), 1), // 3
451			JsonCodeMapEntry::new(Span::new(40, 56), 7), // "e": [4, [5, 6]]
452			JsonCodeMapEntry::new(Span::new(40, 43), 1), // "e"
453			JsonCodeMapEntry::new(Span::new(45, 56), 5), // [4, [5, 6]]
454			JsonCodeMapEntry::new(Span::new(46, 47), 1), // 4
455			JsonCodeMapEntry::new(Span::new(49, 55), 3), // [5, 6]
456			JsonCodeMapEntry::new(Span::new(50, 51), 1), // 5
457			JsonCodeMapEntry::new(Span::new(53, 54), 1), // 6
458		];
459
460		assert_eq!(code_map.len(), expected.len());
461		assert_eq!(value.traverse().count(), expected.len());
462		for (i, entry) in code_map {
463			assert_eq!(entry, expected[i])
464		}
465	}
466}