1use std::fmt;
11use std::ops::Range;
12
13use crate::node::Node;
14
15const MAX_DEPTH: usize = 1024;
17
18#[derive(Debug, Clone, Copy)]
19struct Entry {
20 start: usize,
21 end: usize,
22 parent: u32,
24 index: u32,
26 kid_start: u32,
28 kid_len: u32,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct SourceMapError {
34 pub offset: usize,
36 pub message: &'static str,
38}
39
40impl fmt::Display for SourceMapError {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 write!(f, "{} at byte {}", self.message, self.offset)
43 }
44}
45
46impl std::error::Error for SourceMapError {}
47
48#[derive(Debug, Clone, Default)]
67pub struct SourceMap {
68 entries: Vec<Entry>,
70 kids: Vec<u32>,
71}
72
73impl SourceMap {
74 pub fn build(text: &str) -> Result<SourceMap, SourceMapError> {
76 let mut s = Scanner {
77 b: text.as_bytes(),
78 i: 0,
79 map: SourceMap::default(),
80 stack: Vec::new(),
81 };
82 s.ws();
83 if s.peek() != Some(b'{') {
84 return Err(s.err("expected a JSON object"));
85 }
86 s.node(u32::MAX, 0, 0)?;
87 s.ws();
88 if s.i != s.b.len() {
89 return Err(s.err("trailing characters"));
90 }
91 Ok(s.map)
92 }
93
94 pub fn pretty(node: &Node) -> (String, SourceMap) {
98 let text = serde_json::to_string_pretty(node).expect("Node serializes");
99 let map = SourceMap::build(&text).expect("serde_json output is a node tree");
100 (text, map)
101 }
102
103 #[inline]
105 pub fn len(&self) -> usize {
106 self.entries.len()
107 }
108
109 #[inline]
111 pub fn is_empty(&self) -> bool {
112 self.entries.is_empty()
113 }
114
115 fn entry_at(&self, path: &[usize]) -> Option<usize> {
116 let mut e = 0usize;
117 self.entries.first()?;
118 for &i in path {
119 let n = &self.entries[e];
120 if i >= n.kid_len as usize {
121 return None;
122 }
123 e = self.kids[n.kid_start as usize + i] as usize;
124 }
125 Some(e)
126 }
127
128 pub fn span(&self, path: &[usize]) -> Option<Range<usize>> {
131 self.entry_at(path).map(|e| {
132 let n = &self.entries[e];
133 n.start..n.end
134 })
135 }
136
137 pub fn child_count(&self, path: &[usize]) -> Option<usize> {
139 self.entry_at(path)
140 .map(|e| self.entries[e].kid_len as usize)
141 }
142
143 pub fn path_at(&self, offset: usize) -> Option<Vec<usize>> {
146 let k = self.entries.partition_point(|e| e.start <= offset);
149 let mut e = k.checked_sub(1)?;
150 loop {
151 let n = &self.entries[e];
152 if offset < n.end {
153 break;
154 }
155 if n.parent == u32::MAX {
156 return None;
157 }
158 e = n.parent as usize;
159 }
160 let mut path = Vec::new();
161 while self.entries[e].parent != u32::MAX {
162 path.push(self.entries[e].index as usize);
163 e = self.entries[e].parent as usize;
164 }
165 path.reverse();
166 Some(path)
167 }
168}
169
170struct Scanner<'a> {
171 b: &'a [u8],
172 i: usize,
173 map: SourceMap,
174 stack: Vec<u32>,
176}
177
178impl Scanner<'_> {
179 #[inline]
180 fn peek(&self) -> Option<u8> {
181 self.b.get(self.i).copied()
182 }
183
184 fn err(&self, message: &'static str) -> SourceMapError {
185 SourceMapError {
186 offset: self.i,
187 message,
188 }
189 }
190
191 #[inline]
192 fn ws(&mut self) {
193 while let Some(b' ' | b'\n' | b'\r' | b'\t') = self.peek() {
194 self.i += 1;
195 }
196 }
197
198 fn expect(&mut self, c: u8, message: &'static str) -> Result<(), SourceMapError> {
199 self.ws();
200 if self.peek() == Some(c) {
201 self.i += 1;
202 Ok(())
203 } else {
204 Err(self.err(message))
205 }
206 }
207
208 fn string(&mut self) -> Result<(usize, usize, bool), SourceMapError> {
210 self.i += 1;
211 let start = self.i;
212 let mut escaped = false;
213 loop {
214 match self.b[self.i..]
216 .iter()
217 .position(|&c| c == b'"' || c == b'\\')
218 {
219 None => {
220 self.i = self.b.len();
221 return Err(self.err("unterminated string"));
222 }
223 Some(k) => {
224 self.i += k;
225 if self.b[self.i] == b'"' {
226 let end = self.i;
227 self.i += 1;
228 return Ok((start, end, escaped));
229 }
230 escaped = true;
231 self.i += 2;
232 if self.i > self.b.len() {
233 self.i = self.b.len();
234 return Err(self.err("unterminated string"));
235 }
236 }
237 }
238 }
239 }
240
241 fn is_content_key(&self, (s, e, escaped): (usize, usize, bool)) -> bool {
242 let raw = &self.b[s..e];
243 if !escaped {
244 return raw == b"content";
245 }
246 let quoted = &self.b[s - 1..e + 1];
248 serde_json::from_slice::<String>(quoted).is_ok_and(|k| k == "content")
249 }
250
251 fn value(&mut self, depth: usize) -> Result<(), SourceMapError> {
253 if depth > MAX_DEPTH {
254 return Err(self.err("nesting too deep"));
255 }
256 self.ws();
257 match self.peek() {
258 Some(b'"') => self.string().map(|_| ()),
259 Some(b'{') => {
260 self.i += 1;
261 self.ws();
262 if self.peek() == Some(b'}') {
263 self.i += 1;
264 return Ok(());
265 }
266 loop {
267 self.ws();
268 if self.peek() != Some(b'"') {
269 return Err(self.err("expected a key"));
270 }
271 self.string()?;
272 self.expect(b':', "expected `:`")?;
273 self.value(depth + 1)?;
274 self.ws();
275 match self.peek() {
276 Some(b',') => self.i += 1,
277 Some(b'}') => {
278 self.i += 1;
279 return Ok(());
280 }
281 _ => return Err(self.err("expected `,` or `}`")),
282 }
283 }
284 }
285 Some(b'[') => {
286 self.i += 1;
287 self.ws();
288 if self.peek() == Some(b']') {
289 self.i += 1;
290 return Ok(());
291 }
292 loop {
293 self.value(depth + 1)?;
294 self.ws();
295 match self.peek() {
296 Some(b',') => self.i += 1,
297 Some(b']') => {
298 self.i += 1;
299 return Ok(());
300 }
301 _ => return Err(self.err("expected `,` or `]`")),
302 }
303 }
304 }
305 Some(b'-' | b'0'..=b'9' | b't' | b'f' | b'n') => {
306 let start = self.i;
307 while let Some(c) = self.peek() {
308 if matches!(c, b',' | b'}' | b']' | b' ' | b'\n' | b'\r' | b'\t') {
309 break;
310 }
311 self.i += 1;
312 }
313 (self.i > start)
314 .then_some(())
315 .ok_or_else(|| self.err("expected a value"))
316 }
317 _ => Err(self.err("expected a value")),
318 }
319 }
320
321 fn node(&mut self, parent: u32, index: u32, depth: usize) -> Result<(), SourceMapError> {
323 if depth > MAX_DEPTH {
324 return Err(self.err("nesting too deep"));
325 }
326 let id = self.map.entries.len() as u32;
327 self.map.entries.push(Entry {
328 start: self.i,
329 end: 0,
330 parent,
331 index,
332 kid_start: 0,
333 kid_len: 0,
334 });
335 let kids_from = self.stack.len();
336 self.i += 1;
337 self.ws();
338 if self.peek() == Some(b'}') {
339 self.i += 1;
340 } else {
341 loop {
342 self.ws();
343 if self.peek() != Some(b'"') {
344 return Err(self.err("expected a key"));
345 }
346 let key = self.string()?;
347 self.expect(b':', "expected `:`")?;
348 self.ws();
349 if self.peek() == Some(b'[') && self.is_content_key(key) {
350 self.content(id, depth)?;
351 } else {
352 self.value(depth + 1)?;
353 }
354 self.ws();
355 match self.peek() {
356 Some(b',') => self.i += 1,
357 Some(b'}') => {
358 self.i += 1;
359 break;
360 }
361 _ => return Err(self.err("expected `,` or `}`")),
362 }
363 }
364 }
365 let kid_start = self.map.kids.len() as u32;
366 let kid_len = (self.stack.len() - kids_from) as u32;
367 self.map.kids.extend(self.stack.drain(kids_from..));
368 let e = &mut self.map.entries[id as usize];
369 e.end = self.i;
370 e.kid_start = kid_start;
371 e.kid_len = kid_len;
372 Ok(())
373 }
374
375 fn content(&mut self, parent: u32, depth: usize) -> Result<(), SourceMapError> {
377 self.i += 1;
378 self.ws();
379 if self.peek() == Some(b']') {
380 self.i += 1;
381 return Ok(());
382 }
383 let mut index = 0u32;
384 loop {
385 self.ws();
386 if self.peek() != Some(b'{') {
387 return Err(self.err("expected a node object in `content`"));
388 }
389 self.stack.push(self.map.entries.len() as u32);
390 self.node(parent, index, depth + 1)?;
391 index += 1;
392 self.ws();
393 match self.peek() {
394 Some(b',') => self.i += 1,
395 Some(b']') => {
396 self.i += 1;
397 return Ok(());
398 }
399 _ => return Err(self.err("expected `,` or `]`")),
400 }
401 }
402 }
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::Document;
409
410 const SRC: &str = r#"{"type":"doc","content":[
411 {"type":"heading","attrs":{"level":1,"content":[1,2]},"content":[{"type":"text","text":"T \"q\" }"}]},
412 {"type":"callout","weird":{"content":[{"a":1}]},"content":[]},
413 {"type":"paragraph","content":[{"type":"text","marks":[{"type":"bold"}],"text":"a"},{"type":"hardBreak"}]}
414 ],"extra":[true,null,-1.5e3]}"#;
415
416 #[test]
417 fn spans_match_every_node_exactly() {
418 let doc = Document::from_json_str(SRC).unwrap();
419 let map = SourceMap::build(SRC).unwrap();
420 let mut count = 0;
421 fn go(n: &Node, path: &mut Vec<usize>, map: &SourceMap, src: &str, count: &mut usize) {
422 *count += 1;
423 let span = map.span(path).unwrap();
424 let parsed: Node = serde_json::from_str(&src[span]).unwrap();
425 assert_eq!(&parsed, n, "path {path:?}");
426 assert_eq!(map.child_count(path), Some(n.child_count()));
427 for (i, c) in n.children().iter().enumerate() {
428 path.push(i);
429 go(c, path, map, src, count);
430 path.pop();
431 }
432 }
433 go(doc.root(), &mut Vec::new(), &map, SRC, &mut count);
434 assert_eq!(count, map.len());
435 assert_eq!(map.span(&[0, 1]), None);
436 assert_eq!(map.span(&[9]), None);
437 }
438
439 #[test]
440 fn content_inside_attrs_or_unknown_fields_is_not_a_node() {
441 let map = SourceMap::build(SRC).unwrap();
442 assert_eq!(map.child_count(&[0]), Some(1));
443 assert_eq!(map.child_count(&[1]), Some(0));
444 assert_eq!(map.len(), 7);
445 }
446
447 #[test]
448 fn path_at_finds_deepest() {
449 let map = SourceMap::build(SRC).unwrap();
450 let text = SRC.find("\"a\"}").unwrap();
451 assert_eq!(map.path_at(text), Some(vec![2, 0]));
452 let hb = SRC.find("hardBreak").unwrap();
453 assert_eq!(map.path_at(hb), Some(vec![2, 1]));
454 let comma = map.span(&[2, 0]).unwrap().end;
456 assert_eq!(map.path_at(comma), Some(vec![2]));
457 assert_eq!(map.path_at(SRC.find("extra").unwrap()), Some(vec![]));
458 assert_eq!(map.path_at(0), Some(vec![]));
459 assert_eq!(map.path_at(SRC.len()), None);
460 let s = map.span(&[1]).unwrap();
461 assert_eq!(map.path_at(s.start), Some(vec![1]));
462 assert_eq!(map.path_at(s.end - 1), Some(vec![1]));
463 }
464
465 #[test]
466 fn pretty_matches_document_and_roundtrips_paths() {
467 let doc = Document::from_json_str(SRC).unwrap();
468 let (text, map) = SourceMap::pretty(doc.root());
469 assert_eq!(text, doc.to_string_pretty().unwrap());
470 for path in [vec![], vec![0], vec![0, 0], vec![1], vec![2, 1]] {
471 let s = map.span(&path).unwrap();
472 assert_eq!(map.path_at(s.start), Some(path.clone()));
473 assert!(text[s].starts_with('{'));
474 }
475 }
476
477 #[test]
478 fn escaped_content_key() {
479 let src = r#"{"type":"doc","content":[{"type":"p"}]}"#;
480 assert_eq!(
481 Document::from_json_str(src).unwrap().child_count(),
482 SourceMap::build(src).unwrap().child_count(&[]).unwrap()
483 );
484 }
485
486 #[test]
487 fn errors() {
488 for bad in [
489 "",
490 "[]",
491 "{",
492 r#"{"a":}"#,
493 r#"{"a":"x}"#,
494 r#"{"content":[1]}"#,
495 r#"{"a":1} x"#,
496 ] {
497 assert!(SourceMap::build(bad).is_err(), "{bad}");
498 }
499 let deep = format!("{}{}", "[".repeat(5000), "]".repeat(5000));
500 assert!(SourceMap::build(&format!(r#"{{"a":{deep}}}"#)).is_err());
501 }
502}