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
173            .boolean("ok", true)
174            .number("line", 3)
175            .nothing("error");
176        assert_eq!(object.finish(), "{\"ok\":true,\"line\":3,\"error\":null}");
177    }
178}
179
180/// A value of a document, read.
181#[derive(Debug, Clone, PartialEq)]
182pub enum Read {
183    /// The `null` of the format.
184    Null,
185    /// A truth value.
186    Bool(bool),
187    /// A number, kept as the text of it: the format has no limit on the
188    /// digits, and a report asks a number for what it is and not for how it is
189    /// spelled.
190    Number(String),
191    /// A text.
192    Text(String),
193    /// An array.
194    Array(Vec<Read>),
195    /// An object, in the order of the names it arrived in.
196    Object(BTreeMap<String, Read>),
197}
198
199impl Read {
200    /// The value under a name, when this one is an object that has it.
201    pub fn get(&self, name: &str) -> Option<&Read> {
202        match self {
203            Read::Object(members) => members.get(name),
204            _ => None,
205        }
206    }
207
208    /// The value at a place, when this one is an array that has it.
209    pub fn at(&self, place: usize) -> Option<&Read> {
210        match self {
211            Read::Array(values) => values.get(place),
212            _ => None,
213        }
214    }
215
216    /// The values of an array.
217    pub fn array(&self) -> &[Read] {
218        match self {
219            Read::Array(values) => values,
220            _ => &[],
221        }
222    }
223
224    /// The names of the members of an object, in the order of the document.
225    pub fn names(&self) -> Vec<&str> {
226        match self {
227            Read::Object(members) => members.keys().map(String::as_str).collect(),
228            _ => Vec::new(),
229        }
230    }
231
232    /// The text of the value, when it is a text.
233    pub fn as_str(&self) -> Option<&str> {
234        match self {
235            Read::Text(text) => Some(text),
236            _ => None,
237        }
238    }
239
240    /// The text under a name, when the member is a text.
241    pub fn field_text(&self, name: &str) -> Option<&str> {
242        self.get(name).and_then(Read::as_str)
243    }
244
245    /// The value as a whole number.
246    pub fn as_i64(&self) -> Option<i64> {
247        match self {
248            Read::Number(text) => text.parse().ok(),
249            _ => None,
250        }
251    }
252
253    /// The value as a truth value.
254    pub fn as_bool(&self) -> Option<bool> {
255        match self {
256            Read::Bool(value) => Some(*value),
257            _ => None,
258        }
259    }
260
261    /// Whether the value is a number of any kind.
262    pub fn is_number(&self) -> bool {
263        matches!(self, Read::Number(_))
264    }
265
266    /// Whether the value is a text.
267    pub fn is_string(&self) -> bool {
268        matches!(self, Read::Text(_))
269    }
270
271    /// Whether the value is null.
272    pub fn is_null(&self) -> bool {
273        matches!(self, Read::Null)
274    }
275}
276
277/// The reason a document could not be read.
278#[derive(Debug, Clone, PartialEq, Eq)]
279pub struct ReadError {
280    /// What went wrong, in a sentence.
281    pub reason: String,
282    /// Where in the text it went wrong.
283    pub at: usize,
284}
285
286impl std::fmt::Display for ReadError {
287    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288        write!(formatter, "{} at byte {}", self.reason, self.at)
289    }
290}
291
292impl std::error::Error for ReadError {}
293
294/// Read a document.
295pub fn read(text: &str) -> Result<Read, ReadError> {
296    let mut parser = Reader {
297        bytes: text.as_bytes(),
298        at: 0,
299    };
300    let value = parser.value()?;
301    parser.space();
302    if parser.at != parser.bytes.len() {
303        return Err(parser.the_error("the document ends before its value does"));
304    }
305    Ok(value)
306}
307
308/// The reader of one document.
309struct Reader<'a> {
310    bytes: &'a [u8],
311    at: usize,
312}
313
314impl Reader<'_> {
315    /// The reason of a failure, at the place the reader is.
316    fn the_error(&self, reason: &str) -> ReadError {
317        ReadError {
318            reason: reason.to_string(),
319            at: self.at,
320        }
321    }
322
323    /// Skip the spaces the format allows between the values.
324    fn space(&mut self) {
325        while let Some(byte) = self.bytes.get(self.at) {
326            if matches!(byte, b' ' | b'\t' | b'\n' | b'\r') {
327                self.at += 1;
328            } else {
329                break;
330            }
331        }
332    }
333
334    /// Read one value.
335    fn value(&mut self) -> Result<Read, ReadError> {
336        self.space();
337        match self.bytes.get(self.at) {
338            Some(b'{') => self.object(),
339            Some(b'[') => self.array(),
340            Some(b'"') => Ok(Read::Text(self.text()?)),
341            Some(b't') => self.word("true", Read::Bool(true)),
342            Some(b'f') => self.word("false", Read::Bool(false)),
343            Some(b'n') => self.word("null", Read::Null),
344            Some(byte) if *byte == b'-' || byte.is_ascii_digit() => self.number(),
345            Some(_) => Err(self.the_error("the document holds a character that begins no value")),
346            None => Err(self.the_error("the document ends where a value was wanted")),
347        }
348    }
349
350    /// Read the word that is there, which must be the one named.
351    fn word(&mut self, word: &str, answer: Read) -> Result<Read, ReadError> {
352        if self.bytes[self.at..].starts_with(word.as_bytes()) {
353            self.at += word.len();
354            Ok(answer)
355        } else {
356            Err(self.the_error("the document holds a word that is not a value of the format"))
357        }
358    }
359
360    /// Read an object.
361    fn object(&mut self) -> Result<Read, ReadError> {
362        self.at += 1;
363        let mut members = BTreeMap::new();
364        self.space();
365        if self.bytes.get(self.at) == Some(&b'}') {
366            self.at += 1;
367            return Ok(Read::Object(members));
368        }
369        loop {
370            self.space();
371            if self.bytes.get(self.at) != Some(&b'"') {
372                return Err(self.the_error("a name of an object is not a text"));
373            }
374            let name = self.text()?;
375            self.space();
376            if self.bytes.get(self.at) != Some(&b':') {
377                return Err(self.the_error("a name of an object is not followed by a colon"));
378            }
379            self.at += 1;
380            let value = self.value()?;
381            members.insert(name, value);
382            self.space();
383            match self.bytes.get(self.at) {
384                Some(b',') => self.at += 1,
385                Some(b'}') => {
386                    self.at += 1;
387                    return Ok(Read::Object(members));
388                }
389                _ => return Err(self.the_error("an object ends without its closing brace")),
390            }
391        }
392    }
393
394    /// Read an array.
395    fn array(&mut self) -> Result<Read, ReadError> {
396        self.at += 1;
397        let mut values = Vec::new();
398        self.space();
399        if self.bytes.get(self.at) == Some(&b']') {
400            self.at += 1;
401            return Ok(Read::Array(values));
402        }
403        loop {
404            values.push(self.value()?);
405            self.space();
406            match self.bytes.get(self.at) {
407                Some(b',') => self.at += 1,
408                Some(b']') => {
409                    self.at += 1;
410                    return Ok(Read::Array(values));
411                }
412                _ => return Err(self.the_error("an array ends without its closing bracket")),
413            }
414        }
415    }
416
417    /// Read a text, with its quotes.
418    fn text(&mut self) -> Result<String, ReadError> {
419        self.at += 1;
420        let mut out = String::new();
421        loop {
422            let byte = *self
423                .bytes
424                .get(self.at)
425                .ok_or_else(|| self.the_error("a text ends before its closing quote"))?;
426            match byte {
427                b'"' => {
428                    self.at += 1;
429                    return Ok(out);
430                }
431                b'\\' => {
432                    self.at += 1;
433                    let escape = *self
434                        .bytes
435                        .get(self.at)
436                        .ok_or_else(|| self.the_error("a text ends in the middle of an escape"))?;
437                    self.at += 1;
438                    match escape {
439                        b'"' => out.push('"'),
440                        b'\\' => out.push('\\'),
441                        b'/' => out.push('/'),
442                        b'b' => out.push('\u{08}'),
443                        b'f' => out.push('\u{0C}'),
444                        b'n' => out.push('\n'),
445                        b'r' => out.push('\r'),
446                        b't' => out.push('\t'),
447                        b'u' => {
448                            let place = self.at;
449                            let digits = self.bytes.get(place..place + 4).ok_or_else(|| {
450                                self.the_error("an escape of four digits is short")
451                            })?;
452                            let hex = std::str::from_utf8(digits)
453                                .map_err(|_| self.the_error("an escape is not four digits"))?;
454                            let code = u32::from_str_radix(hex, 16)
455                                .map_err(|_| self.the_error("an escape is not four digits"))?;
456                            self.at += 4;
457                            out.push(char::from_u32(code).unwrap_or('\u{FFFD}'));
458                        }
459                        _ => {
460                            return Err(
461                                self.the_error("a text holds an escape the format does not define")
462                            );
463                        }
464                    }
465                }
466                other => {
467                    // A text of the format is UTF-8, and the bytes of a
468                    // letter that is not ASCII are taken as they are.
469                    let rest = std::str::from_utf8(&self.bytes[self.at..])
470                        .map_err(|_| self.the_error("a text is not a text of the format"))?;
471                    let letter = rest
472                        .chars()
473                        .next()
474                        .ok_or_else(|| self.the_error("a text ends where a letter was wanted"))?;
475                    out.push(letter);
476                    self.at += letter.len_utf8();
477                    if other == 0 {
478                        return Err(self.the_error("a text holds a byte that is not of the format"));
479                    }
480                }
481            }
482        }
483    }
484
485    /// Read a number, and keep the text of it.
486    fn number(&mut self) -> Result<Read, ReadError> {
487        let start = self.at;
488        if self.bytes.get(self.at) == Some(&b'-') {
489            self.at += 1;
490        }
491        while let Some(byte) = self.bytes.get(self.at) {
492            if byte.is_ascii_digit() || matches!(byte, b'.' | b'e' | b'E' | b'+' | b'-') {
493                self.at += 1;
494            } else {
495                break;
496            }
497        }
498        let text = std::str::from_utf8(&self.bytes[start..self.at])
499            .map_err(|_| self.the_error("a number is not a number of the format"))?;
500        if text.is_empty() || text == "-" {
501            return Err(self.the_error("a number holds no digits"));
502        }
503        Ok(Read::Number(text.to_string()))
504    }
505}