Skip to main content

jjpwrgem_parse/format/
prettify.rs

1use core::{iter, range::Range};
2
3use crate::{
4    Result,
5    ast::{Document, Value},
6    format::LineEnding,
7    tokens::{FALSE, NULL, TRUE},
8};
9
10const QUOTE_LEN: usize = 1;
11const QUOTE_PAIR_LEN: usize = QUOTE_LEN * 2;
12const COMMA_LEN: usize = 1;
13const COLON_LEN: usize = 1;
14const EXPONENT_MARKER_LEN: usize = 1;
15const BRACKET_PAIR_LEN: usize = 2;
16const BRACE_PAIR_LEN: usize = 2;
17
18type ObjectEntry = (Range<usize>, Value);
19
20#[derive(Debug, PartialEq, Eq, Clone, Copy)]
21pub(crate) struct FormatOptions {
22    key_val_delimiter: Option<(char, usize)>,
23    indent: Option<(char, usize)>,
24    line_ending: LineEnding,
25}
26
27impl FormatOptions {
28    pub(crate) fn prettify(line_ending: LineEnding) -> Self {
29        Self {
30            key_val_delimiter: Some((' ', 1)),
31            indent: Some((' ', 2)),
32            line_ending,
33        }
34    }
35}
36
37struct FormatBuf {
38    opts: FormatOptions,
39    buf: String,
40    line_start: usize,
41    preferred_width: usize,
42}
43
44impl FormatBuf {
45    fn new(buf: String, opts: FormatOptions, preferred_width: usize) -> Self {
46        Self {
47            opts,
48            buf,
49            line_start: 0,
50            preferred_width,
51        }
52    }
53
54    fn push(&mut self, value: char) {
55        self.buf.push(value);
56    }
57    fn push_str(&mut self, value: &str) {
58        self.buf.push_str(value);
59    }
60
61    #[inline]
62    fn push_quoted(&mut self, value: &str) {
63        self.push('"');
64        self.push_str(value);
65        self.push('"');
66    }
67
68    #[inline]
69    fn push_repeat(&mut self, c: char, count: usize) {
70        self.buf.extend(iter::repeat_n(c, count));
71    }
72
73    #[inline]
74    fn write_spec(&mut self, spec: Option<(char, usize)>) {
75        if let Some((c, size)) = spec {
76            self.push_repeat(c, size);
77        }
78    }
79
80    pub fn write_key_val_delimiter(&mut self) {
81        self.write_spec(self.opts.key_val_delimiter);
82    }
83
84    fn delimiter_len(&self) -> usize {
85        self.opts.key_val_delimiter.map_or(0, |(_, size)| size)
86    }
87
88    pub fn write_eol(&mut self) {
89        self.push_str(self.opts.line_ending.as_str());
90        self.line_start = self.buf.len();
91    }
92
93    pub fn write_indent(&mut self, level: usize) {
94        self.write_spec(self.opts.indent.map(|(c, size)| (c, size * level)));
95    }
96
97    fn into_inner(self) -> String {
98        self.buf
99    }
100
101    pub fn column(&self) -> usize {
102        self.buf.len() - self.line_start
103    }
104
105    fn available_bytes(&self) -> usize {
106        self.preferred_width.saturating_sub(self.column())
107    }
108}
109
110pub(crate) fn format_str(
111    json: &str,
112    options: FormatOptions,
113    preferred_width: usize,
114) -> Result<String> {
115    let mut buf = FormatBuf::new(String::with_capacity(json.len()), options, preferred_width);
116    let doc = Document::parse(json)?;
117    format_document_value_into(&mut buf, &doc, doc.root(), 0);
118    Ok(buf.into_inner())
119}
120
121use super::join_into;
122
123fn range_len(range: Range<usize>) -> usize {
124    range.end - range.start
125}
126
127fn number_len(mantissa: Range<usize>, exponent: Option<Range<usize>>) -> usize {
128    range_len(mantissa)
129        + if let Some(exponent) = exponent {
130            EXPONENT_MARKER_LEN + range_len(exponent)
131        } else {
132            0
133        }
134}
135
136fn format_document_value_into<S: AsRef<str>>(
137    buf: &mut FormatBuf,
138    doc: &Document<S>,
139    val: &Value,
140    depth: usize,
141) {
142    match val {
143        Value::Null(_) => buf.push_str(NULL),
144        Value::String(r) => buf.push_quoted(doc.slice(*r)),
145        Value::Number { mantissa, exponent } => {
146            buf.push_str(doc.slice(*mantissa));
147            if let Some(exponent) = exponent {
148                buf.push('e');
149                buf.push_str(doc.slice(*exponent));
150            }
151        }
152        Value::Object(_, entries) if entries.0.is_empty() => buf.push_str("{}"),
153        Value::Object(_, entries) => {
154            if !len::should_expand(doc, val, buf.available_bytes(), buf.delimiter_len()) {
155                compact_format_obj_into(buf, doc, entries.0.as_slice(), depth);
156            } else {
157                expanded_format_obj_into(buf, doc, entries.0.as_slice(), depth);
158            }
159        }
160        Value::Array(_, items) if items.is_empty() => buf.push_str("[]"),
161        Value::Array(_, items) => {
162            if !len::should_expand(doc, val, buf.available_bytes(), buf.delimiter_len()) {
163                compact_format_arr_into(buf, doc, items, depth);
164            } else if items.iter().all(|v| matches!(v, Value::Number { .. })) {
165                fill_format_arr_into(buf, doc, items, depth);
166            } else {
167                expanded_format_arr_into(buf, doc, items, depth);
168            }
169        }
170        Value::Boolean(_, b) => buf.push_str(if *b { TRUE } else { FALSE }),
171    }
172}
173
174fn expanded_format_arr_into<S: AsRef<str>>(
175    buf: &mut FormatBuf,
176    doc: &Document<S>,
177    items: &[Value],
178    depth: usize,
179) {
180    buf.push('[');
181    buf.write_eol();
182    join_into(
183        buf,
184        items,
185        |buf, val| {
186            buf.write_indent(depth + 1);
187            format_document_value_into(buf, doc, val, depth + 1);
188        },
189        |buf, _| {
190            buf.push(',');
191            buf.write_eol();
192        },
193    );
194    buf.write_eol();
195    buf.write_indent(depth);
196    buf.push(']');
197}
198
199fn expanded_format_obj_into<S: AsRef<str>>(
200    buf: &mut FormatBuf,
201    doc: &Document<S>,
202    entries: &[ObjectEntry],
203    depth: usize,
204) {
205    buf.push('{');
206    buf.write_eol();
207    join_into(
208        buf,
209        entries.iter(),
210        |buf, (key_range, val)| {
211            buf.write_indent(depth + 1);
212            write_object_entry_into(buf, doc, key_range, val, depth + 1);
213        },
214        |buf, _| {
215            buf.push(',');
216            buf.write_eol();
217        },
218    );
219    buf.write_eol();
220    buf.write_indent(depth);
221    buf.push('}');
222}
223
224fn fill_format_arr_into<S: AsRef<str>>(
225    buf: &mut FormatBuf,
226    doc: &Document<S>,
227    items: &[Value],
228    depth: usize,
229) {
230    buf.push('[');
231    buf.write_eol();
232    buf.write_indent(depth + 1);
233    for (i, item) in items.iter().enumerate() {
234        let Value::Number { mantissa, exponent } = item else {
235            unreachable!("fill_format_arr_into called with non-Number item");
236        };
237        let item_len = number_len(*mantissa, *exponent);
238        if i > 0 {
239            buf.push(',');
240            let trailing_comma_len = usize::from(i + 1 < items.len());
241            if item_len + COMMA_LEN + trailing_comma_len > buf.available_bytes() {
242                buf.write_eol();
243                buf.write_indent(depth + 1);
244            } else {
245                buf.write_key_val_delimiter();
246            }
247        }
248        format_document_value_into(buf, doc, item, depth + 1);
249    }
250    buf.write_eol();
251    buf.write_indent(depth);
252    buf.push(']');
253}
254
255fn compact_format_arr_into<S: AsRef<str>>(
256    buf: &mut FormatBuf,
257    doc: &Document<S>,
258    items: &[Value],
259    depth: usize,
260) {
261    buf.push('[');
262    join_into(
263        buf,
264        items,
265        |buf, val| format_document_value_into(buf, doc, val, depth + 1),
266        |buf, _| {
267            buf.push(',');
268            buf.write_key_val_delimiter();
269        },
270    );
271    buf.push(']');
272}
273
274fn write_object_entry_into<S: AsRef<str>>(
275    buf: &mut FormatBuf,
276    doc: &Document<S>,
277    key_range: &Range<usize>,
278    val: &Value,
279    depth: usize,
280) {
281    buf.push_quoted(doc.slice(*key_range));
282    buf.push(':');
283    buf.write_key_val_delimiter();
284    format_document_value_into(buf, doc, val, depth);
285}
286
287fn compact_format_obj_into<S: AsRef<str>>(
288    buf: &mut FormatBuf,
289    doc: &Document<S>,
290    entries: &[ObjectEntry],
291    depth: usize,
292) {
293    buf.push('{');
294    buf.write_key_val_delimiter();
295    join_into(
296        buf,
297        entries.iter(),
298        |buf, (key_range, val)| write_object_entry_into(buf, doc, key_range, val, depth + 1),
299        |buf, _| {
300            buf.push(',');
301            buf.write_key_val_delimiter();
302        },
303    );
304    buf.write_key_val_delimiter();
305    buf.push('}');
306}
307
308fn format_document<S: AsRef<str>>(
309    doc: &Document<S>,
310    options: &FormatOptions,
311    preferred_width: usize,
312) -> String {
313    let mut buf = FormatBuf::new(String::new(), *options, preferred_width);
314    format_document_value_into(&mut buf, doc, doc.root(), 0);
315    buf.into_inner()
316}
317
318pub fn prettify_str(json: &str, preferred_width: usize, line_ending: LineEnding) -> Result<String> {
319    format_str(json, FormatOptions::prettify(line_ending), preferred_width)
320}
321
322pub fn prettify_document<S: AsRef<str>>(
323    doc: &Document<S>,
324    preferred_width: usize,
325    line_ending: LineEnding,
326) -> String {
327    format_document(doc, &FormatOptions::prettify(line_ending), preferred_width)
328}
329
330pub fn prettify_document_into<S: AsRef<str>>(
331    buf: &mut String,
332    doc: &Document<S>,
333    preferred_width: usize,
334    line_ending: LineEnding,
335) {
336    let mut fmt_buf = FormatBuf::new(
337        std::mem::take(buf),
338        FormatOptions::prettify(line_ending),
339        preferred_width,
340    );
341    format_document_value_into(&mut fmt_buf, doc, doc.root(), 0);
342    *buf = fmt_buf.into_inner();
343}
344
345mod len {
346    use super::{
347        BRACE_PAIR_LEN, BRACKET_PAIR_LEN, COLON_LEN, COMMA_LEN, QUOTE_PAIR_LEN, number_len,
348    };
349    use crate::{
350        ast::Value,
351        tokens::{FALSE, NULL, TRUE},
352    };
353
354    /// returns if the inline length of the value > limit or it finds a newline
355    pub fn should_expand<S: AsRef<str>>(
356        doc: &crate::ast::Document<S>,
357        val: &Value,
358        limit: usize,
359        delimiter_len: usize,
360    ) -> bool {
361        try_get_value_len(doc, val, limit, delimiter_len).is_none()
362    }
363
364    fn quoted_len(value: &str) -> usize {
365        value.chars().count() + QUOTE_PAIR_LEN
366    }
367
368    fn separated_item_prefix_len(delimiter_len: usize) -> usize {
369        COMMA_LEN + delimiter_len
370    }
371
372    fn try_get_value_len<S: AsRef<str>>(
373        doc: &crate::ast::Document<S>,
374        val: &Value,
375        limit: usize,
376        delimiter_len: usize,
377    ) -> Option<usize> {
378        fn within_limit(len: usize, limit: usize) -> bool {
379            len <= limit
380        }
381
382        let len = match val {
383            Value::Null(_) => NULL.len(),
384            Value::String(r) => quoted_len(doc.slice(*r)),
385            Value::Number { mantissa, exponent } => number_len(*mantissa, *exponent),
386            Value::Object(_, entries) => {
387                if entries.is_empty() {
388                    BRACE_PAIR_LEN
389                } else {
390                    let braces_len = BRACE_PAIR_LEN + (delimiter_len * 2);
391                    let mut sum = braces_len;
392                    for (i, (key_range, value)) in entries.0.iter().enumerate() {
393                        if !within_limit(sum, limit) {
394                            break;
395                        }
396                        if i > 0 {
397                            sum += separated_item_prefix_len(delimiter_len);
398                        }
399                        sum += quoted_len(doc.slice(*key_range));
400                        sum += COLON_LEN + delimiter_len;
401                        let remaining = limit.saturating_sub(sum);
402                        let len = try_get_value_len(doc, value, remaining, delimiter_len)?;
403                        sum += len;
404                    }
405                    sum
406                }
407            }
408            Value::Array(_, values) => {
409                let brackets_len = BRACKET_PAIR_LEN;
410                let mut sum = brackets_len;
411                for (i, value) in values.iter().enumerate() {
412                    if !within_limit(sum, limit) {
413                        break;
414                    }
415                    if i > 0 {
416                        sum += separated_item_prefix_len(delimiter_len);
417                    }
418                    let remaining = limit.saturating_sub(sum);
419                    let len = try_get_value_len(doc, value, remaining, delimiter_len)?;
420                    sum += len;
421                }
422                sum
423            }
424            Value::Boolean(_, b) => {
425                if *b {
426                    TRUE.len()
427                } else {
428                    FALSE.len()
429                }
430            }
431        };
432
433        within_limit(len, limit).then_some(len)
434    }
435}