book_json_parser/
book_json_parser.rs

1use std::collections::HashMap;
2
3/// Define the JSON structures.
4#[derive(Debug)]
5pub enum JSON {
6    Object(HashMap<String, JSON>),
7    Array(Vec<JSON>),
8    String(String),
9    Number(f64),
10    Boolean(bool),
11    Null,
12}
13
14/// Parse a JSON value.  On entry the parser is assumed to be pointing at the first
15/// character of the value.  Trailing whitespace is consumed.
16pub fn parse_value_ws(parser: &mut trivet::Parser) -> trivet::errors::ParseResult<JSON> {
17    match parser.peek() {
18        // Check for an object.
19        '{' => parse_object_ws(parser),
20
21        // Check for an array.
22        '[' => parse_array_ws(parser),
23
24        // Check for a string.
25        '"' => Ok(JSON::String(parser.parse_string_match_delimiter_ws()?)),
26
27        // Check for a number.
28        ch if ch == '-' || ch.is_ascii_digit() => Ok(JSON::Number(parser.parse_f64_decimal_ws()?)),
29
30        // Check for Boolean true.
31        't' if parser.peek_and_consume_str_ws("true") => Ok(JSON::Boolean(true)),
32
33        // Check for Boolean false.
34        'f' if parser.peek_and_consume_str_ws("false") => Ok(JSON::Boolean(false)),
35
36        // Check for null.
37        'n' if parser.peek_and_consume_str_ws("null") => Ok(JSON::Null),
38
39        // Error.
40        ch => Err(trivet::errors::unexpected_text_error(
41            parser.loc(),
42            "the start of a JSON value",
43            &ch.to_string(),
44        )),
45    }
46}
47
48/// Parse a JSON object.  On entry the parser is assumed to be pointing to the first
49/// character of the object (a curly brace `{`).  Trailing whitespace is consumed.
50fn parse_object_ws(parser: &mut trivet::Parser) -> trivet::errors::ParseResult<JSON> {
51    parser.consume(); // Consume opening brace.
52    parser.consume_ws();
53    let mut first = true;
54    let mut map = HashMap::new();
55    while !parser.peek_and_consume_ws('}') {
56        // If not the first element, then we expect to have a comma here.  JSON requires
57        // a comma between members of an object.
58        if first {
59            first = false;
60        } else if !parser.peek_and_consume_ws(',') {
61            return Err(trivet::errors::syntax_error(
62                parser.loc(),
63                "There must be a comma (,) between members of an object.",
64            ));
65        }
66
67        // Look for the quotation mark for a string.
68        if parser.peek() != '"' {
69            return Err(trivet::errors::syntax_error(
70                parser.loc(),
71                "Names in a JSON object must be quoted strings.",
72            ));
73        }
74        let name = parser.parse_string_match_delimiter_ws()?;
75
76        // Look for the colon.
77        if !parser.peek_and_consume_ws(':') {
78            return Err(trivet::errors::syntax_error(
79                parser.loc(),
80                "Names in a JSON object must be followed by a colon (:).",
81            ));
82        }
83
84        // Get the value.
85        let value = parse_value_ws(parser)?;
86
87        // Add the name, value pair to the map.
88        map.insert(name, value);
89    }
90    Ok(JSON::Object(map))
91}
92
93/// Parse a JSON array.  On entry the parser is assumed to be pointing to the first
94/// character of the array (a square bracket `[`).  Trailing whitespace is consumed.
95fn parse_array_ws(parser: &mut trivet::Parser) -> trivet::errors::ParseResult<JSON> {
96    parser.consume(); // Consume opening bracket.
97    parser.consume_ws_only();
98    let mut first = true;
99    let mut array = vec![];
100    while !parser.peek_and_consume_ws(']') {
101        // If not the first element, then we expect to have a comma here.  JSON requires
102        // a comma between members of an array.
103        if first {
104            first = false;
105        } else if !parser.peek_and_consume_ws(',') {
106            return Err(trivet::errors::syntax_error(
107                parser.loc(),
108                "There must be a comma (,) between members of an array.",
109            ));
110        }
111
112        // Get the value.
113        let value = parse_value_ws(parser)?;
114
115        // Add the value to the array.
116        array.push(value);
117    }
118    Ok(JSON::Array(array))
119}
120
121/// Read JSON from standard input and then write JSON to standard output.
122pub fn main() {
123    let mut parser = trivet::parse_from_stdin();
124    parser.parse_comments = false;
125    let numpar = parser.borrow_number_parser();
126    numpar.settings.permit_binary = false;
127    numpar.settings.permit_hexadecimal = false;
128    numpar.settings.permit_octal = false;
129    numpar.settings.permit_underscores = false;
130    numpar.settings.decimal_only_floats = true;
131    numpar.settings.permit_plus = false;
132    numpar.settings.permit_leading_zero = false;
133    numpar.settings.permit_empty_whole = false;
134    numpar.settings.permit_empty_fraction = false;
135    let strpar = parser.borrow_string_parser();
136    strpar.set(trivet::strings::StringStandard::JSON);
137    let _ = parser
138        .borrow_core()
139        .replace_whitespace_test(Box::new(|ch| [' ', '\n', '\r', '\t'].contains(&ch)));
140    parser.consume_ws();
141    let result = parse_value_ws(&mut parser);
142    match result {
143        Err(error) => {
144            println!("ERROR: {}", error);
145            std::process::exit(1);
146        }
147        Ok(json) => {
148            // If there is any trailing stuff that is not whitespace, then this is not a valid
149            // JSON file.
150            if parser.is_at_eof() {
151                // Print the JSON value.
152                println!("{:?}", json);
153                std::process::exit(0)
154            } else {
155                println!("Found unexpected trailing characters after JSON value.");
156                std::process::exit(1);
157            }
158        }
159    }
160}