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
76fn render_json_array(items: &[JsonValue], depth: usize) -> String {
77    let indent_outer = json_one_level_indent(depth);
78    let indent_inner = json_one_level_indent(depth + 1);
79    if items.is_empty() {
80        return "[]".to_string();
81    }
82
83    let mut output = String::from("[\n");
84    for (i, item) in items.iter().enumerate() {
85        if i > 0 {
86            output.push_str(",\n");
87        }
88        match item {
89            JsonValue::Object(_) => output.push_str(&render_json_value(item, depth + 1)),
90            JsonValue::Array(nested_items) => {
91                output.push_str(&indent_inner);
92                output.push_str(&render_json_array(nested_items, depth + 1));
93            }
94            _ => {
95                output.push_str(&indent_inner);
96                output.push_str(&render_json_primitive(item));
97            }
98        }
99    }
100    output.push('\n');
101    output.push_str(&indent_outer);
102    output.push(']');
103    output
104}
105
106/// `depth` is the indent depth (number of two-space units) of the opening `{` of this object.
107pub fn render_json_value(v: &JsonValue, depth: usize) -> String {
108    match v {
109        JsonValue::Object(fields) => {
110            let indent_outer = json_one_level_indent(depth);
111            let indent_inner = json_one_level_indent(depth + 1);
112            let mut field_lines: Vec<String> = Vec::new();
113            for (key, field_value) in fields.iter() {
114                let line = match field_value {
115                    JsonValue::Array(items) => {
116                        if items.is_empty() {
117                            format!("{}\"{}\": []", indent_inner, key)
118                        } else {
119                            format!(
120                                "{}\"{}\": {}",
121                                indent_inner,
122                                key,
123                                render_json_array(items, depth + 1)
124                            )
125                        }
126                    }
127                    JsonValue::Object(_) => {
128                        let rendered = render_json_value(field_value, depth + 1);
129                        let rendered = rendered
130                            .strip_prefix(indent_inner.as_str())
131                            .unwrap_or(rendered.as_str());
132                        format!("{}\"{}\": {}", indent_inner, key, rendered)
133                    }
134                    _ => {
135                        format!(
136                            "{}\"{}\": {}",
137                            indent_inner,
138                            key,
139                            render_json_primitive(field_value)
140                        )
141                    }
142                };
143                field_lines.push(line);
144            }
145            format!(
146                "{}{{\n{}\n{}}}",
147                indent_outer,
148                field_lines.join(",\n"),
149                indent_outer
150            )
151        }
152        JsonValue::Array(_) => {
153            unreachable!("render_json_value: Array only appears as object field value")
154        }
155        JsonValue::Null | JsonValue::Bool(_) | JsonValue::Number(_) | JsonValue::JsonString(_) => {
156            render_json_primitive(v)
157        }
158    }
159}