Skip to main content

litex/common/
json_value.rs

1use crate::common::defaults::{is_default_line_file, LineFile};
2
3/// Minimal JSON AST for pretty-printing (matches the previous `runtime_display_*` indentation style).
4#[derive(Debug, Clone)]
5pub enum JsonValue {
6    Null,
7    Bool(bool),
8    Number(usize),
9    JsonString(String),
10    Array(Vec<JsonValue>),
11    Object(Vec<(String, JsonValue)>),
12}
13
14pub fn json_string_literal(source_text: &str) -> String {
15    let mut output = String::with_capacity(source_text.len() + 2);
16    output.push('"');
17    for ch in source_text.chars() {
18        match ch {
19            '"' => output.push_str("\\\""),
20            '\\' => output.push_str("\\\\"),
21            '\n' => output.push_str("\\n"),
22            '\r' => output.push_str("\\r"),
23            '\t' => output.push_str("\\t"),
24            c if (c as u32) < 32 => {
25                output.push_str(format!("\\u{:04x}", c as u32).as_str());
26            }
27            c => output.push(c),
28        }
29    }
30    output.push('"');
31    output
32}
33
34pub fn json_one_level_indent(unit_count: usize) -> String {
35    "  ".repeat(unit_count)
36}
37
38pub fn line_file_line_json_value(line_file: &LineFile) -> JsonValue {
39    if is_default_line_file(line_file) {
40        JsonValue::Null
41    } else {
42        JsonValue::Number(line_file.0)
43    }
44}
45
46pub fn line_file_source_json_value(line_file: &LineFile) -> JsonValue {
47    if is_default_line_file(line_file) {
48        JsonValue::Null
49    } else {
50        JsonValue::JsonString(line_file.1.as_ref().to_string())
51    }
52}
53
54fn render_json_primitive(v: &JsonValue) -> String {
55    match v {
56        JsonValue::Null => "null".to_string(),
57        JsonValue::Bool(b) => b.to_string(),
58        JsonValue::Number(n) => n.to_string(),
59        JsonValue::JsonString(s) => json_string_literal(s),
60        JsonValue::Array(_) | JsonValue::Object(_) => {
61            unreachable!("render_json_primitive: non-primitive")
62        }
63    }
64}
65
66/// JSON fragment for `"line"`: `null` when [`is_default_line_file`], else the line number (unquoted).
67pub fn line_file_line_json_fragment(line_file: &LineFile) -> String {
68    render_json_primitive(&line_file_line_json_value(line_file))
69}
70
71/// JSON fragment for `"source"`: `null` when [`is_default_line_file`], else a quoted path string.
72pub fn line_file_source_json_fragment(line_file: &LineFile) -> String {
73    render_json_primitive(&line_file_source_json_value(line_file))
74}
75
76/// `depth` is the indent depth (number of two-space units) of the opening `{` of this object.
77pub fn render_json_value(v: &JsonValue, depth: usize) -> String {
78    match v {
79        JsonValue::Object(fields) => {
80            let indent_outer = json_one_level_indent(depth);
81            let indent_inner = json_one_level_indent(depth + 1);
82            let mut field_lines: Vec<String> = Vec::new();
83            for (key, field_value) in fields.iter() {
84                let line = match field_value {
85                    JsonValue::Array(items) => {
86                        if items.is_empty() {
87                            format!("{}\"{}\": []", indent_inner, key)
88                        } else {
89                            let mut line = format!("{}\"{}\": [\n", indent_inner, key);
90                            for (i, item) in items.iter().enumerate() {
91                                if i > 0 {
92                                    line.push_str(",\n");
93                                }
94                                match item {
95                                    JsonValue::Object(_) => {
96                                        line.push_str(&render_json_value(item, depth + 2));
97                                    }
98                                    _ => {
99                                        line.push_str(&json_one_level_indent(depth + 2));
100                                        line.push_str(&render_json_primitive(item));
101                                    }
102                                }
103                            }
104                            line.push('\n');
105                            line.push_str(&indent_inner);
106                            line.push(']');
107                            line
108                        }
109                    }
110                    JsonValue::Object(_) => {
111                        format!(
112                            "{}\"{}\": {}",
113                            indent_inner,
114                            key,
115                            render_json_value(field_value, depth + 1)
116                        )
117                    }
118                    _ => {
119                        format!(
120                            "{}\"{}\": {}",
121                            indent_inner,
122                            key,
123                            render_json_primitive(field_value)
124                        )
125                    }
126                };
127                field_lines.push(line);
128            }
129            format!(
130                "{}{{\n{}\n{}}}",
131                indent_outer,
132                field_lines.join(",\n"),
133                indent_outer
134            )
135        }
136        JsonValue::Array(_) => {
137            unreachable!("render_json_value: Array only appears as object field value")
138        }
139        JsonValue::Null | JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::JsonString(_) => {
140            render_json_primitive(v)
141        }
142    }
143}