Skip to main content

microsoft_fast_build/
json.rs

1use std::collections::HashMap;
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum JsonValue {
5    Null,
6    Bool(bool),
7    Number(f64),
8    String(String),
9    Array(Vec<JsonValue>),
10    Object(HashMap<String, JsonValue>),
11}
12
13impl JsonValue {
14    pub fn is_truthy(&self) -> bool {
15        match self {
16            JsonValue::Null => false,
17            JsonValue::Bool(b) => *b,
18            JsonValue::Number(n) => *n != 0.0,
19            JsonValue::String(s) => !s.is_empty(),
20            JsonValue::Array(a) => !a.is_empty(),
21            JsonValue::Object(_) => true,
22        }
23    }
24
25    pub fn to_display_string(&self) -> String {
26        match self {
27            JsonValue::Null => String::new(),
28            JsonValue::Bool(b) => b.to_string(),
29            JsonValue::Number(n) => {
30                if *n == n.floor() && n.abs() < 1e15 {
31                    format!("{}", *n as i64)
32                } else {
33                    format!("{}", n)
34                }
35            }
36            JsonValue::String(s) => s.clone(),
37            JsonValue::Array(_) => "[Array]".to_string(),
38            JsonValue::Object(_) => "[Object]".to_string(),
39        }
40    }
41}
42
43#[derive(Debug)]
44pub struct JsonError {
45    pub message: String,
46}
47
48impl std::fmt::Display for JsonError {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        write!(f, "JsonError: {}", self.message)
51    }
52}
53
54impl std::error::Error for JsonError {}
55
56pub fn parse(input: &str) -> Result<JsonValue, JsonError> {
57    let input = input.trim();
58    let (value, rest) = parse_value(input)?;
59    let rest = rest.trim();
60    if !rest.is_empty() {
61        return Err(JsonError { message: format!("Unexpected trailing content: {}", rest) });
62    }
63    Ok(value)
64}
65
66fn parse_value(input: &str) -> Result<(JsonValue, &str), JsonError> {
67    let input = input.trim_start();
68    if input.is_empty() {
69        return Err(JsonError { message: "Unexpected end of input".to_string() });
70    }
71    match input.chars().next().unwrap() {
72        '{' => parse_object(input),
73        '[' => parse_array(input),
74        '"' => {
75            let (s, rest) = parse_string(input)?;
76            Ok((JsonValue::String(s), rest))
77        }
78        't' => {
79            if input.starts_with("true") {
80                Ok((JsonValue::Bool(true), &input[4..]))
81            } else {
82                Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
83            }
84        }
85        'f' => {
86            if input.starts_with("false") {
87                Ok((JsonValue::Bool(false), &input[5..]))
88            } else {
89                Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
90            }
91        }
92        'n' => {
93            if input.starts_with("null") {
94                Ok((JsonValue::Null, &input[4..]))
95            } else {
96                Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
97            }
98        }
99        c if c == '-' || c.is_ascii_digit() => parse_number(input),
100        c => Err(JsonError { message: format!("Unexpected character: '{}'", c) }),
101    }
102}
103
104fn parse_object(input: &str) -> Result<(JsonValue, &str), JsonError> {
105    let input = input.trim_start();
106    if !input.starts_with('{') {
107        return Err(JsonError { message: "Expected '{'".to_string() });
108    }
109    let mut rest = input[1..].trim_start();
110    let mut map = HashMap::new();
111    if rest.starts_with('}') {
112        return Ok((JsonValue::Object(map), &rest[1..]));
113    }
114    loop {
115        rest = rest.trim_start();
116        let (key, after_key) = parse_string(rest)?;
117        rest = after_key.trim_start();
118        if !rest.starts_with(':') {
119            return Err(JsonError { message: "Expected ':'".to_string() });
120        }
121        rest = rest[1..].trim_start();
122        let (value, after_value) = parse_value(rest)?;
123        map.insert(key, value);
124        rest = after_value.trim_start();
125        match rest.chars().next() {
126            Some(',') => { rest = rest[1..].trim_start(); }
127            Some('}') => { return Ok((JsonValue::Object(map), &rest[1..])); }
128            _ => return Err(JsonError { message: "Expected ',' or '}'".to_string() }),
129        }
130    }
131}
132
133fn parse_array(input: &str) -> Result<(JsonValue, &str), JsonError> {
134    let input = input.trim_start();
135    if !input.starts_with('[') {
136        return Err(JsonError { message: "Expected '['".to_string() });
137    }
138    let mut rest = input[1..].trim_start();
139    let mut arr = Vec::new();
140    if rest.starts_with(']') {
141        return Ok((JsonValue::Array(arr), &rest[1..]));
142    }
143    loop {
144        rest = rest.trim_start();
145        let (value, after_value) = parse_value(rest)?;
146        arr.push(value);
147        rest = after_value.trim_start();
148        match rest.chars().next() {
149            Some(',') => { rest = rest[1..].trim_start(); }
150            Some(']') => { return Ok((JsonValue::Array(arr), &rest[1..])); }
151            _ => return Err(JsonError { message: "Expected ',' or ']'".to_string() }),
152        }
153    }
154}
155
156fn parse_string(input: &str) -> Result<(String, &str), JsonError> {
157    if !input.starts_with('"') {
158        return Err(JsonError { message: "Expected '\"'".to_string() });
159    }
160    let bytes = input.as_bytes();
161    let mut i = 1;
162    let mut s = String::new();
163    while i < bytes.len() {
164        match bytes[i] {
165            b'"' => {
166                return Ok((s, &input[i + 1..]));
167            }
168            b'\\' => {
169                i += 1;
170                if i >= bytes.len() {
171                    return Err(JsonError { message: "Unexpected end of string escape".to_string() });
172                }
173                match bytes[i] {
174                    b'"' => s.push('"'),
175                    b'\\' => s.push('\\'),
176                    b'/' => s.push('/'),
177                    b'n' => s.push('\n'),
178                    b't' => s.push('\t'),
179                    b'r' => s.push('\r'),
180                    b'b' => s.push('\x08'),
181                    b'f' => s.push('\x0C'),
182                    b'u' => {
183                        if i + 4 >= bytes.len() {
184                            return Err(JsonError { message: "Invalid unicode escape".to_string() });
185                        }
186                        let hex = &input[i + 1..i + 5];
187                        let code_point = u32::from_str_radix(hex, 16).map_err(|_| JsonError {
188                            message: format!("Invalid unicode escape: \\u{}", hex),
189                        })?;
190                        let ch = char::from_u32(code_point).ok_or_else(|| JsonError {
191                            message: format!("Invalid unicode code point: {}", code_point),
192                        })?;
193                        s.push(ch);
194                        i += 4;
195                    }
196                    c => {
197                        return Err(JsonError { message: format!("Unknown escape sequence: \\{}", c as char) });
198                    }
199                }
200                i += 1;
201            }
202            b => {
203                s.push(b as char);
204                i += 1;
205            }
206        }
207    }
208    Err(JsonError { message: "Unterminated string".to_string() })
209}
210
211fn parse_number(input: &str) -> Result<(JsonValue, &str), JsonError> {
212    let bytes = input.as_bytes();
213    let mut i = 0;
214    if i < bytes.len() && bytes[i] == b'-' {
215        i += 1;
216    }
217    while i < bytes.len() && bytes[i].is_ascii_digit() {
218        i += 1;
219    }
220    if i < bytes.len() && bytes[i] == b'.' {
221        i += 1;
222        while i < bytes.len() && bytes[i].is_ascii_digit() {
223            i += 1;
224        }
225    }
226    if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
227        i += 1;
228        if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
229            i += 1;
230        }
231        while i < bytes.len() && bytes[i].is_ascii_digit() {
232            i += 1;
233        }
234    }
235    let num_str = &input[..i];
236    let n: f64 = num_str.parse().map_err(|_| JsonError {
237        message: format!("Invalid number: {}", num_str),
238    })?;
239    Ok((JsonValue::Number(n), &input[i..]))
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_parse_object() {
248        let v = parse(r#"{"name": "Alice", "age": 30}"#).unwrap();
249        if let JsonValue::Object(map) = v {
250            assert_eq!(map.get("name").unwrap().to_display_string(), "Alice");
251            assert_eq!(map.get("age").unwrap().to_display_string(), "30");
252        } else {
253            panic!("Expected object");
254        }
255    }
256
257    #[test]
258    fn test_parse_array() {
259        let v = parse(r#"[1, 2, 3]"#).unwrap();
260        if let JsonValue::Array(arr) = v {
261            assert_eq!(arr.len(), 3);
262            assert_eq!(arr[0].to_display_string(), "1");
263        } else {
264            panic!("Expected array");
265        }
266    }
267
268    #[test]
269    fn test_parse_string_with_escapes() {
270        let v = parse(r#""hello\nworld\t!""#).unwrap();
271        if let JsonValue::String(s) = v {
272            assert_eq!(s, "hello\nworld\t!");
273        } else {
274            panic!("Expected string");
275        }
276    }
277
278    #[test]
279    fn test_parse_number() {
280        let v = parse("3.14").unwrap();
281        if let JsonValue::Number(n) = v {
282            assert!((n - 3.14).abs() < 1e-10);
283        } else {
284            panic!("Expected number");
285        }
286    }
287
288    #[test]
289    fn test_parse_bool() {
290        assert!(matches!(parse("true").unwrap(), JsonValue::Bool(true)));
291        assert!(matches!(parse("false").unwrap(), JsonValue::Bool(false)));
292    }
293
294    #[test]
295    fn test_parse_null() {
296        assert!(matches!(parse("null").unwrap(), JsonValue::Null));
297    }
298}