1use std::fmt::Write as _;
8
9use serde_json::{Map, Value as JsonValue};
10use sim_kernel::{Error, Expr, QuoteMode, Result, Symbol};
11
12pub(crate) use sim_codec_binary_base64::{
13 decode_base64 as base64_decode, encode_base64 as base64_encode,
14};
15
16pub fn json_escape(input: &str) -> String {
20 let mut out = String::with_capacity(input.len());
21 for ch in input.chars() {
22 match ch {
23 '"' => out.push_str("\\\""),
24 '\\' => out.push_str("\\\\"),
25 '\n' => out.push_str("\\n"),
26 '\r' => out.push_str("\\r"),
27 '\t' => out.push_str("\\t"),
28 '\u{08}' => out.push_str("\\b"),
29 '\u{0c}' => out.push_str("\\f"),
30 '\u{00}'..='\u{1f}' => {
31 write!(&mut out, "\\u{:04x}", ch as u32).expect("string writes do not fail");
32 }
33 other => out.push(other),
34 }
35 }
36 out
37}
38
39pub(crate) fn symbol_to_json(symbol: &Symbol) -> JsonValue {
40 let mut object = Map::new();
41 object.insert("$expr".to_owned(), JsonValue::String("symbol".to_owned()));
42 if let Some(namespace) = &symbol.namespace {
43 object.insert(
44 "namespace".to_owned(),
45 JsonValue::String(namespace.to_string()),
46 );
47 }
48 object.insert(
49 "name".to_owned(),
50 JsonValue::String(symbol.name.to_string()),
51 );
52 JsonValue::Object(object)
53}
54
55pub(crate) fn json_to_symbol(codec: sim_kernel::CodecId, value: &JsonValue) -> Result<Expr> {
56 Ok(Expr::Symbol(symbol_from_json(codec, value)?))
57}
58
59pub(crate) fn symbol_from_json(codec: sim_kernel::CodecId, value: &JsonValue) -> Result<Symbol> {
67 if let Some(text) = value.as_str() {
68 return Ok(parse_symbol(text));
69 }
70 let name = required_str(codec, value, "name")?;
71 let namespace = optional_str(codec, value, "namespace")?;
72 Ok(match namespace {
73 Some(namespace) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
74 None => Symbol::new(name.to_owned()),
75 })
76}
77
78pub(crate) fn local_to_json(symbol: &Symbol) -> JsonValue {
83 let mut object = Map::new();
84 object.insert("$expr".to_owned(), JsonValue::String("local".to_owned()));
85 if let Some(namespace) = &symbol.namespace {
86 object.insert(
87 "namespace".to_owned(),
88 JsonValue::String(namespace.to_string()),
89 );
90 }
91 object.insert(
92 "name".to_owned(),
93 JsonValue::String(symbol.name.to_string()),
94 );
95 JsonValue::Object(object)
96}
97
98pub(crate) fn quote_mode_name(mode: QuoteMode) -> &'static str {
99 match mode {
100 QuoteMode::Quote => "quote",
101 QuoteMode::QuasiQuote => "quasiquote",
102 QuoteMode::Unquote => "unquote",
103 QuoteMode::Splice => "splice",
104 QuoteMode::Syntax => "syntax",
105 }
106}
107
108pub(crate) fn parse_quote_mode(codec: sim_kernel::CodecId, raw: &str) -> Result<QuoteMode> {
109 match raw {
110 "quote" => Ok(QuoteMode::Quote),
111 "quasiquote" => Ok(QuoteMode::QuasiQuote),
112 "unquote" => Ok(QuoteMode::Unquote),
113 "splice" => Ok(QuoteMode::Splice),
114 "syntax" => Ok(QuoteMode::Syntax),
115 other => Err(json_error(codec, format!("unknown quote mode {other}"))),
116 }
117}
118
119pub(crate) fn required_value<'a>(
120 codec: sim_kernel::CodecId,
121 value: &'a JsonValue,
122 field: &str,
123) -> Result<&'a JsonValue> {
124 value
125 .get(field)
126 .ok_or_else(|| json_error(codec, format!("missing field {field}")))
127}
128
129pub(crate) fn required_str<'a>(
130 codec: sim_kernel::CodecId,
131 value: &'a JsonValue,
132 field: &str,
133) -> Result<&'a str> {
134 required_value(codec, value, field)?
135 .as_str()
136 .ok_or_else(|| json_error(codec, format!("field {field} must be a string")))
137}
138
139pub(crate) fn optional_str<'a>(
140 codec: sim_kernel::CodecId,
141 value: &'a JsonValue,
142 field: &str,
143) -> Result<Option<&'a str>> {
144 match value.get(field) {
145 None | Some(JsonValue::Null) => Ok(None),
146 Some(other) => other
147 .as_str()
148 .map(Some)
149 .ok_or_else(|| json_error(codec, format!("field {field} must be a string"))),
150 }
151}
152
153pub(crate) fn required_bool(
154 codec: sim_kernel::CodecId,
155 value: &JsonValue,
156 field: &str,
157) -> Result<bool> {
158 required_value(codec, value, field)?
159 .as_bool()
160 .ok_or_else(|| json_error(codec, format!("field {field} must be a bool")))
161}
162
163pub(crate) fn required_u64(
164 codec: sim_kernel::CodecId,
165 value: &JsonValue,
166 field: &str,
167) -> Result<u64> {
168 required_value(codec, value, field)?
169 .as_u64()
170 .ok_or_else(|| json_error(codec, format!("field {field} must be a u64")))
171}
172
173pub(crate) fn required_array<'a>(
174 codec: sim_kernel::CodecId,
175 value: &'a JsonValue,
176 field: &str,
177) -> Result<&'a Vec<JsonValue>> {
178 required_value(codec, value, field)?
179 .as_array()
180 .ok_or_else(|| json_error(codec, format!("field {field} must be an array")))
181}
182
183pub(crate) fn optional_bytes(
184 codec: sim_kernel::CodecId,
185 value: &JsonValue,
186 field: &str,
187) -> Result<Option<Vec<u8>>> {
188 match value.get(field) {
189 None | Some(JsonValue::Null) => Ok(None),
190 Some(other) => Ok(Some(
191 serde_json::from_value::<Vec<u8>>(other.clone())
192 .map_err(|err| json_error(codec, err.to_string()))?,
193 )),
194 }
195}
196
197pub(crate) fn parse_symbol(raw: &str) -> Symbol {
198 match raw.split_once('/') {
199 Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
200 None => Symbol::new(raw.to_owned()),
201 }
202}
203
204pub(crate) fn json_error(codec: sim_kernel::CodecId, message: impl Into<String>) -> Error {
205 Error::CodecError {
206 codec,
207 message: message.into(),
208 }
209}