Skip to main content

textmate_lang/
json.rs

1//! Emitting `tmLanguage.json`.
2//!
3//! A small JSON writer of our own, and the emitter that drives it from an
4//! explicit work stack. Every string that reaches the output — names,
5//! patterns, scopes, selectors, file types — goes through [`json_string`],
6//! whatever validation already established about it, so no input can add,
7//! remove, or reshape a JSON value.
8//!
9//! Key order is fixed. The grammar object's keys come in the order `name`,
10//! `scopeName`, `fileTypes`, `firstLineMatch`, `injectionSelector`,
11//! `patterns`, `repository`, `injections`; a rule's in the order `include`,
12//! `name`, `contentName`, `match`, `begin`, `beginCaptures`, `end`,
13//! `endCaptures`, `while`, `whileCaptures`, `captures`,
14//! `applyEndPatternLast`, `patterns`. Absent and empty fields are left out,
15//! except the grammar's `patterns`, which is always written. Repository and
16//! injection entries are sorted by name, captures by group number.
17
18use alloc::{string::String, vec::Vec};
19
20use crate::{
21    Grammar,
22    check::Checked,
23    rule::{Capture, Captures, Include, Kind, Rule},
24};
25
26/// How [`Grammar::write_json`] lays out its output.
27///
28/// # Examples
29///
30/// ```
31/// use textmate_lang::{Grammar, Style};
32///
33/// let grammar = Grammar::new("source.demo");
34/// let mut out = String::new();
35/// grammar.write_json(&mut out, Style::Compact)?;
36/// assert_eq!(out, r#"{"scopeName":"source.demo","patterns":[]}"#);
37///
38/// out.clear();
39/// grammar.write_json(&mut out, Style::Pretty)?;
40/// assert_eq!(out, "{\n  \"scopeName\": \"source.demo\",\n  \"patterns\": []\n}\n");
41/// # Ok::<(), textmate_lang::Error>(())
42/// ```
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
44pub enum Style {
45    /// Two-space indentation, one member per line, `"key": value`, and a
46    /// final newline: the layout of hand-written grammar files.
47    #[default]
48    Pretty,
49    /// No whitespace at all: the smallest output, for embedding or
50    /// shipping.
51    Compact,
52}
53
54/// Appends a decimal number without going through `core::fmt`.
55pub(crate) fn push_decimal(out: &mut String, mut value: u64) {
56    let mut digits = [0u8; 20];
57    let mut at = digits.len();
58    loop {
59        at -= 1;
60        // `value % 10` is below 10, so the cast is exact.
61        digits[at] = b'0' + (value % 10) as u8;
62        value /= 10;
63        if value == 0 {
64            break;
65        }
66    }
67    for &d in digits.get(at..).unwrap_or(&[]) {
68        out.push(char::from(d));
69    }
70}
71
72/// Appends `parts`, concatenated, as one JSON string literal.
73///
74/// Escaped: `"`, `\`, every control character (`\n`, `\r`, `\t`, `\b`, `\f`,
75/// or `\u00XX`), and U+2028 and U+2029, which are valid in JSON but end a
76/// line in JavaScript, where grammars are often loaded. Everything else is
77/// copied in bulk between escapes. The characters searched for are ASCII or
78/// start with the byte `0xE2`, never a continuation byte, so every split is
79/// on a character boundary.
80pub(crate) fn json_string(out: &mut String, parts: &[&str]) {
81    out.push('"');
82    for text in parts {
83        let bytes = text.as_bytes();
84        let mut from = 0;
85        let mut at = 0;
86        while let Some(&b) = bytes.get(at) {
87            let (escape, width): (&str, usize) = match b {
88                b'"' => ("\\\"", 1),
89                b'\\' => ("\\\\", 1),
90                b'\n' => ("\\n", 1),
91                b'\r' => ("\\r", 1),
92                b'\t' => ("\\t", 1),
93                0x08 => ("\\b", 1),
94                0x0C => ("\\f", 1),
95                0x00..=0x1F => {
96                    out.push_str(text.get(from..at).unwrap_or(""));
97                    out.push_str("\\u00");
98                    out.push(hex(b >> 4));
99                    out.push(hex(b & 0x0F));
100                    at += 1;
101                    from = at;
102                    continue;
103                }
104                0xE2 => match bytes.get(at + 1..at + 3) {
105                    Some([0x80, 0xA8]) => ("\\u2028", 3),
106                    Some([0x80, 0xA9]) => ("\\u2029", 3),
107                    _ => {
108                        at += 1;
109                        continue;
110                    }
111                },
112                _ => {
113                    at += 1;
114                    continue;
115                }
116            };
117            out.push_str(text.get(from..at).unwrap_or(""));
118            out.push_str(escape);
119            at += width;
120            from = at;
121        }
122        out.push_str(text.get(from..).unwrap_or(""));
123    }
124    out.push('"');
125}
126
127fn hex(nibble: u8) -> char {
128    char::from(b"0123456789abcdef"[usize::from(nibble & 0x0F)])
129}
130
131/// Layout state: the output, the style, the indentation level, and whether
132/// the innermost open object or array is still empty.
133struct Writer<'o> {
134    out: &'o mut String,
135    pretty: bool,
136    level: usize,
137    empty: bool,
138}
139
140impl Writer<'_> {
141    fn newline(&mut self) {
142        if self.pretty {
143            self.out.push('\n');
144            for _ in 0..self.level {
145                self.out.push_str("  ");
146            }
147        }
148    }
149
150    fn open(&mut self, bracket: char) {
151        self.out.push(bracket);
152        self.level += 1;
153        self.empty = true;
154    }
155
156    /// Closes the innermost object or array. Its parent is not empty: it
157    /// holds the value just closed.
158    fn close(&mut self, bracket: char) {
159        self.level = self.level.saturating_sub(1);
160        if !self.empty {
161            self.newline();
162        }
163        self.out.push(bracket);
164        self.empty = false;
165    }
166
167    /// Starts the next member or element.
168    fn next(&mut self) {
169        if !self.empty {
170            self.out.push(',');
171        }
172        self.empty = false;
173        self.newline();
174    }
175
176    fn key(&mut self, parts: &[&str]) {
177        self.next();
178        json_string(self.out, parts);
179        self.out.push_str(if self.pretty { ": " } else { ":" });
180    }
181
182    fn string_member(&mut self, key: &str, value: &[&str]) {
183        self.key(&[key]);
184        json_string(self.out, value);
185    }
186
187    fn group_key(&mut self, group: u32) {
188        self.next();
189        self.out.push('"');
190        push_decimal(self.out, u64::from(group));
191        self.out.push_str(if self.pretty { "\": " } else { "\":" });
192    }
193}
194
195/// One step of the emitter.
196enum Task<'g> {
197    Rule(&'g Rule),
198    Capture(&'g Capture),
199    /// `"key": "value"`, if there is a value.
200    Str(&'static str, Option<&'g str>),
201    /// `"key": [rules]`, if there are any.
202    Rules(&'static str, &'g [Rule]),
203    /// `"key": {captures}`, if there are any.
204    Captures(&'static str, &'g Captures),
205    /// `"applyEndPatternLast": 1`, if set.
206    Flag(&'static str, bool),
207    /// A key naming a repository entry or an injection selector.
208    Key(&'g str),
209    Group(u32),
210    /// The separator before an array element.
211    Element,
212    Close(char),
213}
214
215/// Appends the validated grammar to `out`.
216pub(crate) fn write(grammar: &Grammar, checked: &Checked<'_>, out: &mut String, pretty: bool) {
217    out.reserve(256 + 160 * (grammar.patterns.len() + checked.repository.len()));
218    let mut emitter = Emitter {
219        w: Writer {
220            out,
221            pretty,
222            level: 0,
223            empty: true,
224        },
225        work: Vec::new(),
226        sorted: Vec::new(),
227    };
228    emitter.grammar(grammar, checked);
229    if pretty {
230        emitter.w.out.push('\n');
231    }
232}
233
234struct Emitter<'o, 'g> {
235    w: Writer<'o>,
236    work: Vec<Task<'g>>,
237    /// Reused to sort each captures map.
238    sorted: Vec<&'g (u32, Capture)>,
239}
240
241impl<'g> Emitter<'_, 'g> {
242    fn grammar(&mut self, g: &'g Grammar, checked: &Checked<'g>) {
243        self.w.open('{');
244        if let Some(name) = &g.name {
245            self.w.string_member("name", &[name]);
246        }
247        self.w.string_member("scopeName", &[&g.scope_name]);
248        if !g.file_types.is_empty() {
249            self.w.key(&["fileTypes"]);
250            self.w.open('[');
251            for file_type in &g.file_types {
252                self.w.next();
253                json_string(self.w.out, &[file_type]);
254            }
255            self.w.close(']');
256        }
257        if let Some(regex) = &g.first_line_match {
258            self.w.string_member("firstLineMatch", &[regex]);
259        }
260        if let Some(selector) = &g.injection_selector {
261            self.w.string_member("injectionSelector", &[selector]);
262        }
263        // `patterns` is always written: loaders and the tmLanguage schema
264        // expect it.
265        self.w.key(&["patterns"]);
266        self.w.open('[');
267        self.push_elements(&g.patterns);
268        self.run();
269        self.w.close(']');
270        for (key, entries) in [
271            ("repository", &checked.repository),
272            ("injections", &checked.injections),
273        ] {
274            if entries.is_empty() {
275                continue;
276            }
277            self.w.key(&[key]);
278            self.w.open('{');
279            self.work.push(Task::Close('}'));
280            for (name, rule) in entries.iter().rev() {
281                self.work.push(Task::Rule(rule));
282                self.work.push(Task::Key(name));
283            }
284            self.run();
285        }
286        self.w.close('}');
287    }
288
289    /// Schedules `rules` as array elements, in order.
290    fn push_elements(&mut self, rules: &'g [Rule]) {
291        for rule in rules.iter().rev() {
292            self.work.push(Task::Rule(rule));
293            self.work.push(Task::Element);
294        }
295    }
296
297    fn run(&mut self) {
298        while let Some(task) = self.work.pop() {
299            match task {
300                Task::Rule(rule) => self.rule(rule),
301                Task::Capture(capture) => {
302                    self.w.open('{');
303                    self.work.push(Task::Close('}'));
304                    self.work.push(Task::Rules("patterns", &capture.patterns));
305                    self.work.push(Task::Str("name", capture.scope.as_deref()));
306                }
307                Task::Str(key, Some(value)) => self.w.string_member(key, &[value]),
308                Task::Str(_, None) => {}
309                Task::Rules(key, rules) => {
310                    if !rules.is_empty() {
311                        self.w.key(&[key]);
312                        self.w.open('[');
313                        self.work.push(Task::Close(']'));
314                        self.push_elements(rules);
315                    }
316                }
317                Task::Captures(key, captures) => self.captures(key, captures),
318                Task::Flag(key, true) => {
319                    self.w.key(&[key]);
320                    self.w.out.push('1');
321                }
322                Task::Flag(_, false) => {}
323                Task::Key(name) => self.w.key(&[name]),
324                Task::Group(group) => self.w.group_key(group),
325                Task::Element => self.w.next(),
326                Task::Close(bracket) => self.w.close(bracket),
327            }
328        }
329    }
330
331    /// Writes a rule's opening and schedules its fields. Fields are pushed
332    /// in reverse key order, so they pop in key order.
333    fn rule(&mut self, rule: &'g Rule) {
334        self.w.open('{');
335        self.work.push(Task::Close('}'));
336        match &rule.0 {
337            Kind::Include(include) => self.include(include),
338            Kind::Patterns(rules) => self.work.push(Task::Rules("patterns", rules)),
339            Kind::Match(m) => self.work.extend([
340                Task::Captures("captures", &m.captures),
341                Task::Str("match", Some(&m.regex)),
342                Task::Str("name", m.scope.as_deref()),
343            ]),
344            Kind::BeginEnd(r) => self.work.extend([
345                Task::Rules("patterns", &r.patterns),
346                Task::Flag("applyEndPatternLast", r.apply_end_pattern_last),
347                Task::Captures("captures", &r.captures),
348                Task::Captures("endCaptures", &r.end_captures),
349                Task::Str("end", Some(&r.end)),
350                Task::Captures("beginCaptures", &r.begin_captures),
351                Task::Str("begin", Some(&r.begin)),
352                Task::Str("contentName", r.content_scope.as_deref()),
353                Task::Str("name", r.scope.as_deref()),
354            ]),
355            Kind::BeginWhile(r) => self.work.extend([
356                Task::Rules("patterns", &r.patterns),
357                Task::Captures("captures", &r.captures),
358                Task::Captures("whileCaptures", &r.while_captures),
359                Task::Str("while", Some(&r.while_)),
360                Task::Captures("beginCaptures", &r.begin_captures),
361                Task::Str("begin", Some(&r.begin)),
362                Task::Str("contentName", r.content_scope.as_deref()),
363                Task::Str("name", r.scope.as_deref()),
364            ]),
365        }
366    }
367
368    /// `"include": "..."`, written in pieces so no string is built.
369    fn include(&mut self, include: &'g Include) {
370        let value: [&str; 3] = match include {
371            Include::Repository(name) => ["#", name, ""],
372            Include::SelfRef => ["$self", "", ""],
373            Include::Base => ["$base", "", ""],
374            Include::Scope(scope) => [scope, "", ""],
375            Include::ScopeEntry(scope, name) => [scope, "#", name],
376        };
377        self.w.string_member("include", &value);
378    }
379
380    /// Writes a captures map's opening and schedules its entries in group
381    /// order.
382    fn captures(&mut self, key: &'static str, captures: &'g Captures) {
383        if captures.is_empty() {
384            return;
385        }
386        self.w.key(&[key]);
387        self.w.open('{');
388        self.work.push(Task::Close('}'));
389        self.sorted.clear();
390        self.sorted.extend(captures.iter());
391        self.sorted.sort_unstable_by_key(|(group, _)| *group);
392        for (group, capture) in self.sorted.drain(..).rev() {
393            self.work.push(Task::Capture(capture));
394            self.work.push(Task::Group(*group));
395        }
396    }
397}
398
399#[cfg(test)]
400mod tests {
401    use super::*;
402
403    fn quoted(text: &str) -> String {
404        let mut out = String::new();
405        json_string(&mut out, &[text]);
406        out
407    }
408
409    #[test]
410    fn test_json_string_escapes() {
411        assert_eq!(quoted("plain"), "\"plain\"");
412        assert_eq!(quoted(""), "\"\"");
413        assert_eq!(quoted(r#"a"b\c"#), r#""a\"b\\c""#);
414        assert_eq!(quoted("\n\r\t\u{8}\u{c}"), r#""\n\r\t\b\f""#);
415        assert_eq!(quoted("\u{0}\u{1}\u{1f}"), r#""\u0000\u0001\u001f""#);
416        assert_eq!(quoted("x\u{2028}y\u{2029}z"), r#""x\u2028y\u2029z""#);
417        assert_eq!(quoted("é→\u{7f}/✓"), "\"é→\u{7f}/✓\"");
418        let mut out = String::new();
419        json_string(&mut out, &["a\"", "", "#b\n"]);
420        assert_eq!(out, r##""a\"#b\n""##);
421    }
422
423    #[test]
424    fn test_push_decimal() {
425        for (value, text) in [
426            (0, "0"),
427            (7, "7"),
428            (10, "10"),
429            (u64::MAX, "18446744073709551615"),
430        ] {
431            let mut out = String::new();
432            push_decimal(&mut out, value);
433            assert_eq!(out, text);
434        }
435    }
436
437    #[test]
438    fn test_writer_layout() {
439        let mut out = String::new();
440        let mut w = Writer {
441            out: &mut out,
442            pretty: true,
443            level: 0,
444            empty: true,
445        };
446        w.open('{');
447        w.key(&["a"]);
448        w.open('[');
449        w.close(']');
450        w.key(&["b"]);
451        w.open('[');
452        w.next();
453        w.out.push('1');
454        w.close(']');
455        w.close('}');
456        assert_eq!(out, "{\n  \"a\": [],\n  \"b\": [\n    1\n  ]\n}");
457    }
458}