1use std::fmt;
9
10const HEX_RADIX: u32 = 16;
12
13use crate::value::{Map, Path, Seg, Value};
14
15mod comments;
16
17pub use comments::with_comments;
18
19pub const MAX_DEPTH: usize = 256;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub struct Span {
25 pub start: u32,
26 pub end: u32,
27}
28
29#[derive(Debug, Clone, Default, PartialEq)]
32pub struct Spans {
33 pub span: Span,
34 pub key: Option<Span>,
35 pub children: Vec<Spans>,
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct Doc {
41 pub value: Value,
42 pub spans: Spans,
43}
44
45impl Doc {
46 pub fn spans_at(&self, path: &Path) -> &Spans {
49 let mut v = &self.value;
50 let mut s = &self.spans;
51 for seg in path.segments() {
52 let i = match (seg, v) {
53 (Seg::Key(k), Value::Map(m)) => m.index_of(k),
54 (Seg::Index(i), Value::List(l)) if *i < l.len() => Some(*i),
55 _ => None,
56 };
57 let Some(i) = i else { break };
58 let Some(child) = s.children.get(i) else {
59 break;
60 };
61 v = match v {
62 Value::Map(m) => m.value_at(i).unwrap_or(v),
63 Value::List(l) => &l[i],
64 _ => v,
65 };
66 s = child;
67 }
68 s
69 }
70
71 pub fn locate(&self, text: &str, path: &Path) -> LineCol {
73 LineCol::of(text, self.spans_at(path).span.start as usize)
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub struct LineCol {
80 pub line: u32,
81 pub col: u32,
82}
83
84impl LineCol {
85 pub fn of(text: &str, offset: usize) -> LineCol {
87 let offset = offset.min(text.len());
88 let before = &text.as_bytes()[..offset];
89 let line = before.iter().filter(|&&b| b == b'\n').count() as u32 + 1;
90 let line_start = before
91 .iter()
92 .rposition(|&b| b == b'\n')
93 .map_or(0, |i| i + 1);
94 let col = text
95 .get(line_start..offset)
96 .map_or(offset - line_start, |s| s.chars().count()) as u32
97 + 1;
98 LineCol { line, col }
99 }
100}
101
102impl fmt::Display for LineCol {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(f, "{}:{}", self.line, self.col)
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct SyntaxError {
111 pub message: String,
112 pub at: LineCol,
113 pub offset: usize,
114}
115
116impl fmt::Display for SyntaxError {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}: {}", self.at, self.message)
119 }
120}
121
122impl std::error::Error for SyntaxError {}
123
124pub fn parse(text: &str) -> Result<Doc, SyntaxError> {
126 let (value, spans) = run::<true>(text)?;
127 Ok(Doc { value, spans })
128}
129
130pub fn parse_value(text: &str) -> Result<Value, SyntaxError> {
132 run::<false>(text).map(|(v, _)| v)
133}
134
135fn run<const SPANS: bool>(text: &str) -> Result<(Value, Spans), SyntaxError> {
136 let mut p = Parser::<SPANS> {
137 src: text.as_bytes(),
138 text,
139 i: 0,
140 depth: 0,
141 };
142 if text.starts_with('\u{feff}') {
143 p.i = 3;
144 }
145 p.ws()?;
146 let (value, spans) = p.value()?;
147 p.ws()?;
148 if p.i < p.src.len() {
149 return Err(p.error("unexpected text after the value"));
150 }
151 Ok((value, spans))
152}
153
154struct Parser<'a, const SPANS: bool> {
156 src: &'a [u8],
157 text: &'a str,
158 i: usize,
159 depth: usize,
160}
161
162impl<const SPANS: bool> Parser<'_, SPANS> {
163 fn error(&self, message: impl Into<String>) -> SyntaxError {
164 self.error_at(self.i, message)
165 }
166
167 fn error_at(&self, offset: usize, message: impl Into<String>) -> SyntaxError {
168 let mut message = message.into();
169 if offset >= self.src.len() {
170 message.push_str(", found the end of the file");
171 }
172 SyntaxError {
173 message,
174 at: LineCol::of(self.text, offset),
175 offset,
176 }
177 }
178
179 fn peek(&self) -> Option<u8> {
180 self.src.get(self.i).copied()
181 }
182
183 fn ws(&mut self) -> Result<(), SyntaxError> {
185 loop {
186 let rest = &self.src[self.i..];
187 self.i += rest
188 .iter()
189 .position(|&b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
190 .unwrap_or(rest.len());
191 if self.peek() != Some(b'/') {
192 return Ok(());
193 }
194 match self.src.get(self.i + 1) {
195 Some(b'/') => {
196 let rest = &self.src[self.i..];
197 self.i += rest.iter().position(|&b| b == b'\n').unwrap_or(rest.len());
198 }
199 Some(b'*') => {
200 let start = self.i;
201 match self.text[self.i + 2..].find("*/") {
202 Some(end) => self.i += 2 + end + 2,
203 None => return Err(self.error_at(start, "unclosed `/*` comment")),
204 }
205 }
206 _ => return Ok(()),
207 }
208 }
209 }
210
211 fn value(&mut self) -> Result<(Value, Spans), SyntaxError> {
212 let start = self.i;
213 let (value, children) = match self.peek() {
214 Some(b'{') => self.nested(|p| p.map())?,
215 Some(b'[') => self.nested(|p| p.list())?,
216 Some(b'"') => (Value::Str(self.string()?), Vec::new()),
217 Some(b'-' | b'0'..=b'9') => (self.number()?, Vec::new()),
218 Some(b't') if self.src[self.i..].starts_with(b"true") => {
219 self.i += 4;
220 (Value::Bool(true), Vec::new())
221 }
222 Some(b'f') if self.src[self.i..].starts_with(b"false") => {
223 self.i += 5;
224 (Value::Bool(false), Vec::new())
225 }
226 Some(b'n') if self.src[self.i..].starts_with(b"null") => {
227 self.i += 4;
228 (Value::Null, Vec::new())
229 }
230 Some(b'\'') => return Err(self.error("strings use double quotes")),
231 _ => return Err(self.error("expected a value")),
232 };
233 let span = Span {
234 start: start as u32,
235 end: self.i as u32,
236 };
237 Ok((
238 value,
239 Spans {
240 span,
241 key: None,
242 children,
243 },
244 ))
245 }
246
247 fn nested(
248 &mut self,
249 f: impl FnOnce(&mut Self) -> Result<(Value, Vec<Spans>), SyntaxError>,
250 ) -> Result<(Value, Vec<Spans>), SyntaxError> {
251 self.depth += 1;
252 if self.depth > MAX_DEPTH {
253 return Err(self.error(format!("nested deeper than {MAX_DEPTH} levels")));
254 }
255 let r = f(self);
256 self.depth -= 1;
257 r
258 }
259
260 fn list(&mut self) -> Result<(Value, Vec<Spans>), SyntaxError> {
261 self.i += 1;
262 let mut items = Vec::new();
263 let mut spans = Vec::new();
264 loop {
265 self.ws()?;
266 if self.peek() == Some(b']') {
267 self.i += 1;
268 break;
269 }
270 let (v, s) = self.value()?;
271 items.push(v);
272 if SPANS {
273 spans.push(s);
274 }
275 self.ws()?;
276 match self.peek() {
277 Some(b',') => self.i += 1,
278 Some(b']') => {
279 self.i += 1;
280 break;
281 }
282 _ => return Err(self.error("expected `,` or `]`")),
283 }
284 }
285 Ok((Value::List(items), spans))
286 }
287
288 fn map(&mut self) -> Result<(Value, Vec<Spans>), SyntaxError> {
289 self.i += 1;
290 let mut map = Map::new();
291 let mut spans = Vec::new();
292 let mut seen: Option<std::collections::HashSet<String>> = None;
294 loop {
295 self.ws()?;
296 if self.peek() == Some(b'}') {
297 self.i += 1;
298 break;
299 }
300 let key_start = self.i;
301 let key = match self.peek() {
302 Some(b'"') => self.string()?,
303 Some(c) if c.is_ascii_alphabetic() || c == b'_' || c == b'$' => {
304 while self
305 .peek()
306 .is_some_and(|c| c.is_ascii_alphanumeric() || c == b'_' || c == b'$')
307 {
308 self.i += 1;
309 }
310 self.text[key_start..self.i].to_string()
311 }
312 _ => return Err(self.error("expected a key")),
313 };
314 let key_span = Span {
315 start: key_start as u32,
316 end: self.i as u32,
317 };
318 let duplicate = match &mut seen {
319 Some(set) => !set.insert(key.clone()),
320 None if map.len() < 16 => map.get(&key).is_some(),
321 None => {
322 let mut set: std::collections::HashSet<String> =
323 map.keys().map(str::to_string).collect();
324 let dup = !set.insert(key.clone());
325 seen = Some(set);
326 dup
327 }
328 };
329 if duplicate {
330 return Err(
331 self.error_at(key_start, format!("`{key}` is already set in this object"))
332 );
333 }
334 self.ws()?;
335 if self.peek() != Some(b':') {
336 return Err(self.error("expected `:` after the key"));
337 }
338 self.i += 1;
339 self.ws()?;
340 let (v, mut s) = self.value()?;
341 s.key = Some(key_span);
342 map.push_unchecked(key.into(), v);
343 if SPANS {
344 spans.push(s);
345 }
346 self.ws()?;
347 match self.peek() {
348 Some(b',') => self.i += 1,
349 Some(b'}') => {
350 self.i += 1;
351 break;
352 }
353 _ => return Err(self.error("expected `,` or `}`")),
354 }
355 }
356 Ok((Value::Map(map), spans))
357 }
358
359 fn string(&mut self) -> Result<String, SyntaxError> {
360 let open = self.i;
361 self.i += 1;
362 let mut out = String::new();
363 loop {
364 let rest = &self.src[self.i..];
365 let Some(n) = rest
366 .iter()
367 .position(|&b| b == b'"' || b == b'\\' || b < 0x20)
368 else {
369 return Err(self.error_at(open, "unclosed string"));
370 };
371 out.push_str(&self.text[self.i..self.i + n]);
372 self.i += n;
373 match self.src[self.i] {
374 b'"' => {
375 self.i += 1;
376 return Ok(out);
377 }
378 b'\\' => self.escape(&mut out)?,
379 b'\n' => return Err(self.error("line break in a string (write `\\n`)")),
380 _ => return Err(self.error("control character in a string")),
381 }
382 }
383 }
384
385 fn escape(&mut self, out: &mut String) -> Result<(), SyntaxError> {
386 let at = self.i;
387 self.i += 1;
388 let c = self.peek().ok_or_else(|| self.error("unclosed string"))?;
389 self.i += 1;
390 match c {
391 b'"' => out.push('"'),
392 b'\\' => out.push('\\'),
393 b'/' => out.push('/'),
394 b'b' => out.push('\u{8}'),
395 b'f' => out.push('\u{c}'),
396 b'n' => out.push('\n'),
397 b'r' => out.push('\r'),
398 b't' => out.push('\t'),
399 b'u' => {
400 let hi = self.hex4(at)?;
401 let ch = if (0xD800..0xDC00).contains(&hi) {
402 if !self.src[self.i..].starts_with(b"\\u") {
403 return Err(self.error_at(at, "unpaired surrogate in `\\u` escape"));
404 }
405 self.i += 2;
406 let lo = self.hex4(at)?;
407 if !(0xDC00..0xE000).contains(&lo) {
408 return Err(self.error_at(at, "unpaired surrogate in `\\u` escape"));
409 }
410 char::from_u32(0x10000 + ((hi - 0xD800) << 10) + (lo - 0xDC00))
411 } else {
412 char::from_u32(hi)
413 };
414 out.push(
415 ch.ok_or_else(|| self.error_at(at, "unpaired surrogate in `\\u` escape"))?,
416 );
417 }
418 _ => {
419 return Err(self.error_at(
420 at,
421 "unknown escape (`\\n`, `\\t`, `\\\"`, `\\\\`, `\\uXXXX`, ...)",
422 ));
423 }
424 }
425 Ok(())
426 }
427
428 fn hex4(&mut self, at: usize) -> Result<u32, SyntaxError> {
429 let digits = self
430 .text
431 .get(self.i..self.i + 4)
432 .filter(|d| d.bytes().all(|b| b.is_ascii_hexdigit()));
433 let d = digits.ok_or_else(|| self.error_at(at, "`\\u` needs four hex digits"))?;
434 let code = u32::from_str_radix(d, HEX_RADIX)
435 .map_err(|_| self.error_at(at, "`\\u` needs four hex digits"))?;
436 self.i += 4;
437 Ok(code)
438 }
439
440 fn number(&mut self) -> Result<Value, SyntaxError> {
441 let start = self.i;
442 if self.peek() == Some(b'-') {
443 self.i += 1;
444 }
445 let digits = |p: &mut Self| {
446 let s = p.i;
447 while p.peek().is_some_and(|c| c.is_ascii_digit()) {
448 p.i += 1;
449 }
450 p.i - s
451 };
452 let int_start = self.i;
453 if digits(self) == 0 {
454 return Err(self.error("expected digits"));
455 }
456 if self.src[int_start] == b'0' && self.i - int_start > 1 {
457 return Err(self.error_at(int_start, "numbers don't start with 0"));
458 }
459 let mut float = false;
460 if self.peek() == Some(b'.') {
461 self.i += 1;
462 float = true;
463 if digits(self) == 0 {
464 return Err(self.error("expected digits after `.`"));
465 }
466 }
467 if matches!(self.peek(), Some(b'e' | b'E')) {
468 self.i += 1;
469 float = true;
470 if matches!(self.peek(), Some(b'+' | b'-')) {
471 self.i += 1;
472 }
473 if digits(self) == 0 {
474 return Err(self.error("expected digits in the exponent"));
475 }
476 }
477 let s = &self.text[start..self.i];
478 if !float && let Ok(n) = s.parse::<i64>() {
479 return Ok(Value::Int(n));
480 }
481 Ok(Value::Float(
482 s.parse::<f64>()
483 .map_err(|_| self.error_at(start, "invalid number"))?,
484 ))
485 }
486}
487
488pub fn to_string(value: &Value) -> String {
492 let mut out = String::with_capacity(256);
493 write(value, None, 0, &mut out);
494 out
495}
496
497pub fn to_string_pretty(value: &Value) -> String {
499 let mut out = String::new();
500 write(value, Some(" "), 0, &mut out);
501 out.push('\n');
502 out
503}
504
505fn write(value: &Value, indent: Option<&str>, level: usize, out: &mut String) {
506 let newline = |out: &mut String, level: usize| {
507 if let Some(unit) = indent {
508 out.push('\n');
509 for _ in 0..level {
510 out.push_str(unit);
511 }
512 }
513 };
514 match value {
515 Value::Null => out.push_str("null"),
516 Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
517 Value::Int(n) => {
518 use std::fmt::Write as _;
519 let _ = write!(out, "{n}");
520 }
521 Value::Float(f) => write_float(*f, out),
522 Value::List(items) => {
523 if items.is_empty() {
524 out.push_str("[]");
525 return;
526 }
527 out.push('[');
528 for (i, v) in items.iter().enumerate() {
529 if i > 0 {
530 out.push(',');
531 }
532 newline(out, level + 1);
533 write(v, indent, level + 1, out);
534 }
535 newline(out, level);
536 out.push(']');
537 }
538 Value::Map(map) => {
539 if map.is_empty() {
540 out.push_str("{}");
541 return;
542 }
543 out.push('{');
544 for (i, (k, v)) in map.iter().enumerate() {
545 if i > 0 {
546 out.push(',');
547 }
548 newline(out, level + 1);
549 write_string(k, out);
550 out.push_str(if indent.is_some() { ": " } else { ":" });
551 write(v, indent, level + 1, out);
552 }
553 newline(out, level);
554 out.push('}');
555 }
556 Value::Str(s) | Value::Text(s) => {
557 if s.starts_with('@') {
558 out.push('"');
559 out.push('@');
560 write_string_body(s, out);
561 out.push('"');
562 } else {
563 write_string(s, out);
564 }
565 }
566 typed => write_string(&crate::typed::typed_string(typed).unwrap_or_default(), out),
567 }
568}
569
570fn write_float(f: f64, out: &mut String) {
571 if !f.is_finite() {
572 out.push_str("null");
573 } else if f.fract() == 0.0 && f.abs() < 1e16 {
574 out.push_str(&format!("{f:.1}"));
575 } else {
576 out.push_str(&f.to_string());
577 }
578}
579
580pub fn write_string(s: &str, out: &mut String) {
582 out.push('"');
583 write_string_body(s, out);
584 out.push('"');
585}
586
587fn write_string_body(s: &str, out: &mut String) {
588 let bytes = s.as_bytes();
589 let mut run = 0;
590 while let Some(n) = bytes[run..]
591 .iter()
592 .position(|&b| b == b'"' || b == b'\\' || b < 0x20)
593 {
594 let i = run + n;
595 out.push_str(&s[run..i]);
596 match bytes[i] {
597 b'"' => out.push_str("\\\""),
598 b'\\' => out.push_str("\\\\"),
599 b'\n' => out.push_str("\\n"),
600 b'\r' => out.push_str("\\r"),
601 b'\t' => out.push_str("\\t"),
602 b => {
603 use std::fmt::Write as _;
604 let _ = write!(out, "\\u{b:04x}");
605 }
606 }
607 run = i + 1;
608 }
609 out.push_str(&s[run..]);
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn json5_subset() {
618 let doc = parse("\u{feff}{\n // comment\n name: \"Ruby\", /* inline */ $schema: 1,\n \"list\": [1, 2.5, -3e2, true, null,],\n}").unwrap();
619 let m = doc.value.as_map().unwrap();
620 assert_eq!(m.keys().collect::<Vec<_>>(), ["name", "$schema", "list"]);
621 assert_eq!(
622 doc.value.get("list").unwrap(),
623 &Value::List(vec![
624 Value::Int(1),
625 Value::Float(2.5),
626 Value::Float(-300.0),
627 Value::Bool(true),
628 Value::Null
629 ])
630 );
631 }
632
633 #[test]
634 fn locates_values_and_keys() {
635 let text = "{\n \"a\": [\n 1,\n {\"b\": \"x\"}\n ]\n}";
636 let doc = parse(text).unwrap();
637 let path = Path::root().key("a").index(1).key("b");
638 assert_eq!(doc.locate(text, &path), LineCol { line: 4, col: 11 });
639 assert_eq!(
640 LineCol::of(text, doc.spans_at(&path).key.unwrap().start as usize),
641 LineCol { line: 4, col: 6 }
642 );
643 assert_eq!(
645 doc.locate(text, &Path::root().key("a").index(9)),
646 LineCol { line: 2, col: 8 }
647 );
648 }
649
650 #[test]
651 fn errors_say_where() {
652 let cases = [
653 ("{\"a\": 1,, }", "1:9", "expected a key"),
654 ("{\"a\": 1 \"b\": 2}", "1:9", "expected `,` or `}`"),
655 ("{\"a\": 1, \"a\": 2}", "1:10", "already set"),
656 ("[1, 2", "1:6", "found the end of the file"),
657 ("\"ab\ncd\"", "1:4", "line break"),
658 ("'x'", "1:1", "double quotes"),
659 ("01", "1:1", "start with 0"),
660 ("{} x", "1:4", "unexpected text"),
661 ("/* never closed", "1:1", "unclosed"),
662 ("\"\\q\"", "1:2", "unknown escape"),
663 ("\"\\ud800\"", "1:2", "surrogate"),
664 ("{\n \"é\": tru }", "2:8", "expected a value"),
665 ];
666 for (text, at, msg) in cases {
667 let e = parse(text).unwrap_err();
668 assert_eq!(e.at.to_string(), at, "{text:?}: {e}");
669 assert!(e.message.contains(msg), "{text:?}: {e}");
670 }
671 assert!(
672 parse(&"[".repeat(MAX_DEPTH + 1))
673 .unwrap_err()
674 .message
675 .contains("nested deeper")
676 );
677 }
678
679 #[test]
680 fn strings_and_numbers() {
681 let v = parse_value(
682 r#"["a\"b\\c\n\u00e9\ud83d\ude00", 9223372036854775807, 9223372036854775808, 0.1]"#,
683 )
684 .unwrap();
685 let l = v.as_list().unwrap();
686 assert_eq!(l[0], Value::Str("a\"b\\c\né😀".into()));
687 assert_eq!(l[1], Value::Int(i64::MAX));
688 assert_eq!(l[2], Value::Float(9223372036854775808.0));
689 assert_eq!(l[3], Value::Float(0.1));
690 }
691
692 #[test]
693 fn writes_what_it_reads() {
694 let text = "{\n \"name\": \"Ruby \\\"red\\\"\\n\",\n \"n\": [1, 2.0, 0.1, -5],\n \"empty\": {},\n \"none\": [],\n \"tab\\u0001\": null\n}\n";
695 let v = parse_value(text).unwrap();
696 assert_eq!(parse_value(&to_string(&v)).unwrap(), v);
697 let pretty = to_string_pretty(&v);
698 assert_eq!(parse_value(&pretty).unwrap(), v);
699 assert!(
700 pretty.starts_with(
701 "{\n \"name\": \"Ruby \\\"red\\\"\\n\",\n \"n\": [\n 1,\n 2.0,"
702 ),
703 "{pretty}"
704 );
705 assert_eq!(
706 to_string(&v),
707 r#"{"name":"Ruby \"red\"\n","n":[1,2.0,0.1,-5],"empty":{},"none":[],"tab\u0001":null}"#
708 );
709 }
710
711 #[test]
712 fn typed_values_are_strings_in_text() {
713 let v = Value::List(vec![
714 Value::Ref(crate::Ref::new("item", "minecraft:stick")),
715 Value::Color(0xFF112233),
716 Value::Str("@not a ref".into()),
717 ]);
718 assert_eq!(
719 to_string(&v),
720 r##"["@item:minecraft:stick","#112233","@@not a ref"]"##
721 );
722 }
723}