Skip to main content

ahk_cli/
json.rs

1//! The JSON the runner writes, and the one it reads back.
2//!
3//! The report is a fixed shape -- the fields the notes of the runner name --
4//! and it is written here field by field rather than through a value tree:
5//! the shape is known at the point of writing, so a tree would be built only
6//! to be walked again.
7//!
8//! The reading is the other half, and a test is what asks for it: a case runs
9//! the runner, takes the report off its standard output, and asks the report
10//! what it holds. The reader answers with a tree of its own, which is small
11//! on purpose: the format has six kinds of value, and a report carries all of
12//! them.
13//!
14//! The escaping is the one of the format: the characters that must be
15//! escaped are escaped, and every other character goes through as it is.
16
17use std::collections::BTreeMap;
18use std::fmt::Write as _;
19
20/// A builder for one JSON object.
21///
22/// It writes the opening brace when the first field arrives, so an object
23/// that has no field is `{}` and not a broken one.
24pub struct Object {
25    text: String,
26    first: bool,
27}
28
29impl Object {
30    /// A new object, with room for `fields` of them.
31    pub fn new(capacity: usize) -> Object {
32        Object {
33            text: String::with_capacity(capacity),
34            first: true,
35        }
36    }
37
38    /// Write the name of a field and its value, which the caller writes.
39    ///
40    /// The value is written by the closure into the same buffer, so a field
41    /// that is an array or another object nests without a value tree in
42    /// between.
43    pub fn field<F>(&mut self, name: &str, value: F) -> &mut Object
44    where
45        F: FnOnce(&mut String),
46    {
47        if self.first {
48            self.text.push('{');
49            self.first = false;
50        } else {
51            self.text.push(',');
52        }
53        write_name(&mut self.text, name);
54        self.text.push(':');
55        value(&mut self.text);
56        self
57    }
58
59    /// Write a field whose value is a text.
60    pub fn text(&mut self, name: &str, value: &str) -> &mut Object {
61        self.field(name, |out| write_text(out, value))
62    }
63
64    /// Write a field whose value is a whole number.
65    pub fn number(&mut self, name: &str, value: i64) -> &mut Object {
66        self.field(name, |out| {
67            let _ = write!(out, "{value}");
68        })
69    }
70
71    /// Write a field whose value is a truth value.
72    pub fn boolean(&mut self, name: &str, value: bool) -> &mut Object {
73        self.field(name, |out| {
74            out.push_str(if value { "true" } else { "false" });
75        })
76    }
77
78    /// Write a field whose value is null.
79    pub fn nothing(&mut self, name: &str) -> &mut Object {
80        self.field(name, |out| out.push_str("null"))
81    }
82
83    /// Write a field whose value is a whole number, or null when it is not
84    /// there.
85    pub fn optional_number(&mut self, name: &str, value: Option<i64>) -> &mut Object {
86        match value {
87            Some(value) => self.number(name, value),
88            None => self.nothing(name),
89        }
90    }
91
92    /// Write a field whose value is an array of texts.
93    pub fn texts(&mut self, name: &str, values: &[String]) -> &mut Object {
94        self.field(name, |out| {
95            out.push('[');
96            for (index, value) in values.iter().enumerate() {
97                if index > 0 {
98                    out.push(',');
99                }
100                write_text(out, value);
101            }
102            out.push(']');
103        })
104    }
105
106    /// The object, as JSON.
107    pub fn finish(self) -> String {
108        if self.first {
109            "{}".to_string()
110        } else {
111            self.text + "}"
112        }
113    }
114}
115
116/// Write the name of a field: the text, quoted and escaped.
117fn write_name(out: &mut String, name: &str) {
118    write_text(out, name);
119}
120
121/// Write a text as a JSON text, with the quotes.
122///
123/// The escapement is the one of the format: the quote and the reverse solidus
124/// are spelled out, and so are the control characters, which have no spelling
125/// of their own. Everything else -- including the letters that are not ASCII
126/// -- goes through as it is, which the format allows and which keeps a report
127/// of a script in the language the script was written in.
128pub fn write_text(out: &mut String, value: &str) {
129    out.push('"');
130    for letter in value.chars() {
131        match letter {
132            '"' => out.push_str("\\\""),
133            '\\' => out.push_str("\\\\"),
134            '\n' => out.push_str("\\n"),
135            '\r' => out.push_str("\\r"),
136            '\t' => out.push_str("\\t"),
137            '\u{08}' => out.push_str("\\b"),
138            '\u{0C}' => out.push_str("\\f"),
139            other if (other as u32) < 0x20 => {
140                let _ = write!(out, "\\u{:04x}", other as u32);
141            }
142            other => out.push(other),
143        }
144    }
145    out.push('"');
146}
147
148#[cfg(test)]
149mod tests {
150    use super::{Object, write_text};
151
152    /// A text is quoted, and the characters the format spells out are the
153    /// ones that are spelled out.
154    #[test]
155    fn a_text_is_quoted_and_escaped() {
156        let mut out = String::new();
157        write_text(&mut out, "a\"b\\c");
158        assert_eq!(out, "\"a\\\"b\\\\c\"");
159    }
160
161    /// An object with no field is `{}`.
162    #[test]
163    fn an_object_with_no_field_is_empty() {
164        assert_eq!(Object::new(8).finish(), "{}");
165    }
166
167    /// The fields are in the order they are written, and each one is
168    /// separated from the one before it.
169    #[test]
170    fn the_fields_are_written_in_the_order_they_arrive() {
171        let mut object = Object::new(32);
172        object.boolean("ok", true).number("line", 3).nothing("error");
173        assert_eq!(object.finish(), "{\"ok\":true,\"line\":3,\"error\":null}");
174    }
175}
176
177/// A value of a document, read.
178#[derive(Debug, Clone, PartialEq)]
179pub enum Read {
180    /// The `null` of the format.
181    Null,
182    /// A truth value.
183    Bool(bool),
184    /// A number, kept as the text of it: the format has no limit on the
185    /// digits, and a report asks a number for what it is and not for how it is
186    /// spelled.
187    Number(String),
188    /// A text.
189    Text(String),
190    /// An array.
191    Array(Vec<Read>),
192    /// An object, in the order of the names it arrived in.
193    Object(BTreeMap<String, Read>),
194}
195
196impl Read {
197    /// The value under a name, when this one is an object that has it.
198    pub fn get(&self, name: &str) -> Option<&Read> {
199        match self {
200            Read::Object(members) => members.get(name),
201            _ => None,
202        }
203    }
204
205    /// The value at a place, when this one is an array that has it.
206    pub fn at(&self, place: usize) -> Option<&Read> {
207        match self {
208            Read::Array(values) => values.get(place),
209            _ => None,
210        }
211    }
212
213    /// The values of an array.
214    pub fn array(&self) -> &[Read] {
215        match self {
216            Read::Array(values) => values,
217            _ => &[],
218        }
219    }
220
221    /// The names of the members of an object, in the order of the document.
222    pub fn names(&self) -> Vec<&str> {
223        match self {
224            Read::Object(members) => members.keys().map(String::as_str).collect(),
225            _ => Vec::new(),
226        }
227    }
228
229    /// The text of the value, when it is a text.
230    pub fn as_str(&self) -> Option<&str> {
231        match self {
232            Read::Text(text) => Some(text),
233            _ => None,
234        }
235    }
236
237    /// The text under a name, when the member is a text.
238    pub fn field_text(&self, name: &str) -> Option<&str> {
239        self.get(name).and_then(Read::as_str)
240    }
241
242    /// The value as a whole number.
243    pub fn as_i64(&self) -> Option<i64> {
244        match self {
245            Read::Number(text) => text.parse().ok(),
246            _ => None,
247        }
248    }
249
250    /// The value as a truth value.
251    pub fn as_bool(&self) -> Option<bool> {
252        match self {
253            Read::Bool(value) => Some(*value),
254            _ => None,
255        }
256    }
257
258    /// Whether the value is a number of any kind.
259    pub fn is_number(&self) -> bool {
260        matches!(self, Read::Number(_))
261    }
262
263    /// Whether the value is a text.
264    pub fn is_string(&self) -> bool {
265        matches!(self, Read::Text(_))
266    }
267
268    /// Whether the value is null.
269    pub fn is_null(&self) -> bool {
270        matches!(self, Read::Null)
271    }
272}
273
274/// The reason a document could not be read.
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub struct ReadError {
277    /// What went wrong, in a sentence.
278    pub reason: String,
279    /// Where in the text it went wrong.
280    pub at: usize,
281}
282
283impl std::fmt::Display for ReadError {
284    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        write!(formatter, "{} at byte {}", self.reason, self.at)
286    }
287}
288
289impl std::error::Error for ReadError {}
290
291/// Read a document.
292pub fn read(text: &str) -> Result<Read, ReadError> {
293    let mut parser = Reader {
294        bytes: text.as_bytes(),
295        at: 0,
296    };
297    let value = parser.value()?;
298    parser.space();
299    if parser.at != parser.bytes.len() {
300        return Err(parser.the_error("the document ends before its value does"));
301    }
302    Ok(value)
303}
304
305/// The reader of one document.
306struct Reader<'a> {
307    bytes: &'a [u8],
308    at: usize,
309}
310
311impl Reader<'_> {
312    /// The reason of a failure, at the place the reader is.
313    fn the_error(&self, reason: &str) -> ReadError {
314        ReadError {
315            reason: reason.to_string(),
316            at: self.at,
317        }
318    }
319
320    /// Skip the spaces the format allows between the values.
321    fn space(&mut self) {
322        while let Some(byte) = self.bytes.get(self.at) {
323            if matches!(byte, b' ' | b'\t' | b'\n' | b'\r') {
324                self.at += 1;
325            } else {
326                break;
327            }
328        }
329    }
330
331    /// Read one value.
332    fn value(&mut self) -> Result<Read, ReadError> {
333        self.space();
334        match self.bytes.get(self.at) {
335            Some(b'{') => self.object(),
336            Some(b'[') => self.array(),
337            Some(b'"') => Ok(Read::Text(self.text()?)),
338            Some(b't') => self.word("true", Read::Bool(true)),
339            Some(b'f') => self.word("false", Read::Bool(false)),
340            Some(b'n') => self.word("null", Read::Null),
341            Some(byte) if *byte == b'-' || byte.is_ascii_digit() => self.number(),
342            Some(_) => Err(self.the_error("the document holds a character that begins no value")),
343            None => Err(self.the_error("the document ends where a value was wanted")),
344        }
345    }
346
347    /// Read the word that is there, which must be the one named.
348    fn word(&mut self, word: &str, answer: Read) -> Result<Read, ReadError> {
349        if self.bytes[self.at..].starts_with(word.as_bytes()) {
350            self.at += word.len();
351            Ok(answer)
352        } else {
353            Err(self.the_error("the document holds a word that is not a value of the format"))
354        }
355    }
356
357    /// Read an object.
358    fn object(&mut self) -> Result<Read, ReadError> {
359        self.at += 1;
360        let mut members = BTreeMap::new();
361        self.space();
362        if self.bytes.get(self.at) == Some(&b'}') {
363            self.at += 1;
364            return Ok(Read::Object(members));
365        }
366        loop {
367            self.space();
368            if self.bytes.get(self.at) != Some(&b'"') {
369                return Err(self.the_error("a name of an object is not a text"));
370            }
371            let name = self.text()?;
372            self.space();
373            if self.bytes.get(self.at) != Some(&b':') {
374                return Err(self.the_error("a name of an object is not followed by a colon"));
375            }
376            self.at += 1;
377            let value = self.value()?;
378            members.insert(name, value);
379            self.space();
380            match self.bytes.get(self.at) {
381                Some(b',') => self.at += 1,
382                Some(b'}') => {
383                    self.at += 1;
384                    return Ok(Read::Object(members));
385                }
386                _ => return Err(self.the_error("an object ends without its closing brace")),
387            }
388        }
389    }
390
391    /// Read an array.
392    fn array(&mut self) -> Result<Read, ReadError> {
393        self.at += 1;
394        let mut values = Vec::new();
395        self.space();
396        if self.bytes.get(self.at) == Some(&b']') {
397            self.at += 1;
398            return Ok(Read::Array(values));
399        }
400        loop {
401            values.push(self.value()?);
402            self.space();
403            match self.bytes.get(self.at) {
404                Some(b',') => self.at += 1,
405                Some(b']') => {
406                    self.at += 1;
407                    return Ok(Read::Array(values));
408                }
409                _ => return Err(self.the_error("an array ends without its closing bracket")),
410            }
411        }
412    }
413
414    /// Read a text, with its quotes.
415    fn text(&mut self) -> Result<String, ReadError> {
416        self.at += 1;
417        let mut out = String::new();
418        loop {
419            let byte = *self
420                .bytes
421                .get(self.at)
422                .ok_or_else(|| self.the_error("a text ends before its closing quote"))?;
423            match byte {
424                b'"' => {
425                    self.at += 1;
426                    return Ok(out);
427                }
428                b'\\' => {
429                    self.at += 1;
430                    let escape = *self
431                        .bytes
432                        .get(self.at)
433                        .ok_or_else(|| self.the_error("a text ends in the middle of an escape"))?;
434                    self.at += 1;
435                    match escape {
436                        b'"' => out.push('"'),
437                        b'\\' => out.push('\\'),
438                        b'/' => out.push('/'),
439                        b'b' => out.push('\u{08}'),
440                        b'f' => out.push('\u{0C}'),
441                        b'n' => out.push('\n'),
442                        b'r' => out.push('\r'),
443                        b't' => out.push('\t'),
444                        b'u' => {
445                            let place = self.at;
446                            let digits = self
447                                .bytes
448                                .get(place..place + 4)
449                                .ok_or_else(|| self.the_error("an escape of four digits is short"))?;
450                            let hex = std::str::from_utf8(digits)
451                                .map_err(|_| self.the_error("an escape is not four digits"))?;
452                            let code = u32::from_str_radix(hex, 16)
453                                .map_err(|_| self.the_error("an escape is not four digits"))?;
454                            self.at += 4;
455                            out.push(char::from_u32(code).unwrap_or('\u{FFFD}'));
456                        }
457                        _ => return Err(self.the_error("a text holds an escape the format does not define")),
458                    }
459                }
460                other => {
461                    // A text of the format is UTF-8, and the bytes of a
462                    // letter that is not ASCII are taken as they are.
463                    let rest = std::str::from_utf8(&self.bytes[self.at..])
464                        .map_err(|_| self.the_error("a text is not a text of the format"))?;
465                    let letter = rest
466                        .chars()
467                        .next()
468                        .ok_or_else(|| self.the_error("a text ends where a letter was wanted"))?;
469                    out.push(letter);
470                    self.at += letter.len_utf8();
471                    if other == 0 {
472                        return Err(self.the_error("a text holds a byte that is not of the format"));
473                    }
474                }
475            }
476        }
477    }
478
479    /// Read a number, and keep the text of it.
480    fn number(&mut self) -> Result<Read, ReadError> {
481        let start = self.at;
482        if self.bytes.get(self.at) == Some(&b'-') {
483            self.at += 1;
484        }
485        while let Some(byte) = self.bytes.get(self.at) {
486            if byte.is_ascii_digit() || matches!(byte, b'.' | b'e' | b'E' | b'+' | b'-') {
487                self.at += 1;
488            } else {
489                break;
490            }
491        }
492        let text = std::str::from_utf8(&self.bytes[start..self.at])
493            .map_err(|_| self.the_error("a number is not a number of the format"))?;
494        if text.is_empty() || text == "-" {
495            return Err(self.the_error("a number holds no digits"));
496        }
497        Ok(Read::Number(text.to_string()))
498    }
499}