sim_codec/implementation/
portable.rs1use sim_kernel::{CodecId, Error, Expr, NumberLiteral, Result, Symbol};
18use sim_lib_net_core::hex_encode;
19
20pub fn encode_portable(codec: CodecId, expr: &Expr) -> Result<String> {
38 let mut out = String::new();
39 write_value(codec, expr, &mut out)?;
40 Ok(out)
41}
42
43pub fn decode_portable(codec: CodecId, source: &str) -> Result<Expr> {
46 let mut parser = Parser {
47 bytes: source.as_bytes(),
48 pos: 0,
49 codec,
50 };
51 let expr = parser.parse_value()?;
52 parser.skip_ws();
53 if parser.pos != parser.bytes.len() {
54 return Err(parser.error("trailing input after value"));
55 }
56 Ok(expr)
57}
58
59fn unsupported(codec: CodecId, form: &str) -> Error {
60 Error::CodecError {
61 codec,
62 message: format!("portable text cannot encode a non-data expression form: {form}"),
63 }
64}
65
66fn write_value(codec: CodecId, expr: &Expr, out: &mut String) -> Result<()> {
67 match expr {
68 Expr::Nil => out.push('_'),
69 Expr::Bool(true) => out.push('T'),
70 Expr::Bool(false) => out.push('F'),
71 Expr::Number(number) => {
72 out.push('N');
73 write_symbol_payload(&number.domain, out);
74 write_qstr(&number.canonical, out);
75 }
76 Expr::Symbol(symbol) => {
77 out.push('S');
78 write_symbol_payload(symbol, out);
79 }
80 Expr::String(text) => {
81 out.push('R');
82 write_qstr(text, out);
83 }
84 Expr::Bytes(bytes) => {
85 out.push('B');
86 write_qstr(&hex_encode(bytes), out);
87 }
88 Expr::List(items) => write_seq(codec, '(', ')', items, out)?,
89 Expr::Vector(items) => write_seq(codec, '[', ']', items, out)?,
90 Expr::Set(items) => {
91 out.push('%');
92 write_seq(codec, '(', ')', items, out)?;
93 }
94 Expr::Map(entries) => {
95 out.push('{');
96 for (key, value) in entries {
97 out.push(' ');
98 write_value(codec, key, out)?;
99 out.push(' ');
100 write_value(codec, value, out)?;
101 }
102 out.push_str(" }");
103 }
104 Expr::Local(_) => return Err(unsupported(codec, "local")),
105 Expr::Call { .. } => return Err(unsupported(codec, "call")),
106 Expr::Infix { .. } => return Err(unsupported(codec, "infix")),
107 Expr::Prefix { .. } => return Err(unsupported(codec, "prefix")),
108 Expr::Postfix { .. } => return Err(unsupported(codec, "postfix")),
109 Expr::Block(_) => return Err(unsupported(codec, "block")),
110 Expr::Quote { .. } => return Err(unsupported(codec, "quote")),
111 Expr::Annotated { .. } => return Err(unsupported(codec, "annotated")),
112 Expr::Extension { .. } => return Err(unsupported(codec, "extension")),
113 }
114 Ok(())
115}
116
117fn write_seq(
118 codec: CodecId,
119 open: char,
120 close: char,
121 items: &[Expr],
122 out: &mut String,
123) -> Result<()> {
124 out.push(open);
125 for item in items {
126 out.push(' ');
127 write_value(codec, item, out)?;
128 }
129 out.push(' ');
130 out.push(close);
131 Ok(())
132}
133
134fn write_symbol_payload(symbol: &Symbol, out: &mut String) {
135 match &symbol.namespace {
136 Some(namespace) => {
137 out.push('Q');
138 write_qstr(namespace, out);
139 write_qstr(&symbol.name, out);
140 }
141 None => {
142 out.push('U');
143 write_qstr(&symbol.name, out);
144 }
145 }
146}
147
148fn write_qstr(text: &str, out: &mut String) {
149 out.push('"');
150 for ch in text.chars() {
151 match ch {
152 '\\' => out.push_str("\\\\"),
153 '"' => out.push_str("\\\""),
154 '\n' => out.push_str("\\n"),
155 '\r' => out.push_str("\\r"),
156 '\t' => out.push_str("\\t"),
157 other => out.push(other),
158 }
159 }
160 out.push('"');
161}
162
163struct Parser<'a> {
164 bytes: &'a [u8],
165 pos: usize,
166 codec: CodecId,
167}
168
169impl Parser<'_> {
170 fn error(&self, message: impl Into<String>) -> Error {
171 Error::CodecError {
172 codec: self.codec,
173 message: format!(
174 "portable text decode error at byte {}: {}",
175 self.pos,
176 message.into()
177 ),
178 }
179 }
180
181 fn skip_ws(&mut self) {
182 while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
183 self.pos += 1;
184 }
185 }
186
187 fn peek(&self) -> Option<u8> {
188 self.bytes.get(self.pos).copied()
189 }
190
191 fn bump(&mut self) -> Option<u8> {
192 let byte = self.peek()?;
193 self.pos += 1;
194 Some(byte)
195 }
196
197 fn expect(&mut self, byte: u8) -> Result<()> {
198 if self.bump() == Some(byte) {
199 Ok(())
200 } else {
201 Err(self.error(format!("expected '{}'", byte as char)))
202 }
203 }
204
205 fn parse_value(&mut self) -> Result<Expr> {
206 self.skip_ws();
207 match self.peek() {
208 Some(b'_') => {
209 self.pos += 1;
210 Ok(Expr::Nil)
211 }
212 Some(b'T') => {
213 self.pos += 1;
214 Ok(Expr::Bool(true))
215 }
216 Some(b'F') => {
217 self.pos += 1;
218 Ok(Expr::Bool(false))
219 }
220 Some(b'N') => {
221 self.pos += 1;
222 let domain = self.parse_symbol_payload()?;
223 let canonical = self.parse_qstr()?;
224 Ok(Expr::Number(NumberLiteral { domain, canonical }))
225 }
226 Some(b'S') => {
227 self.pos += 1;
228 Ok(Expr::Symbol(self.parse_symbol_payload()?))
229 }
230 Some(b'R') => {
231 self.pos += 1;
232 Ok(Expr::String(self.parse_qstr()?))
233 }
234 Some(b'B') => {
235 self.pos += 1;
236 let hex = self.parse_qstr()?;
237 Ok(Expr::Bytes(self.parse_hex(&hex)?))
238 }
239 Some(b'(') => Ok(Expr::List(self.parse_seq(b'(', b')')?)),
240 Some(b'[') => Ok(Expr::Vector(self.parse_seq(b'[', b']')?)),
241 Some(b'%') => {
242 self.pos += 1;
243 Ok(Expr::Set(self.parse_seq(b'(', b')')?))
244 }
245 Some(b'{') => self.parse_map(),
246 Some(other) => Err(self.error(format!("unexpected tag byte '{}'", other as char))),
247 None => Err(self.error("unexpected end of input")),
248 }
249 }
250
251 fn parse_seq(&mut self, open: u8, close: u8) -> Result<Vec<Expr>> {
252 self.expect(open)?;
253 let mut items = Vec::new();
254 loop {
255 self.skip_ws();
256 match self.peek() {
257 Some(byte) if byte == close => {
258 self.pos += 1;
259 return Ok(items);
260 }
261 None => return Err(self.error("unterminated sequence")),
262 _ => items.push(self.parse_value()?),
263 }
264 }
265 }
266
267 fn parse_map(&mut self) -> Result<Expr> {
268 self.expect(b'{')?;
269 let mut entries = Vec::new();
270 loop {
271 self.skip_ws();
272 match self.peek() {
273 Some(b'}') => {
274 self.pos += 1;
275 return Ok(Expr::Map(entries));
276 }
277 None => return Err(self.error("unterminated map")),
278 _ => {
279 let key = self.parse_value()?;
280 let value = self.parse_value()?;
281 entries.push((key, value));
282 }
283 }
284 }
285 }
286
287 fn parse_symbol_payload(&mut self) -> Result<Symbol> {
288 match self.bump() {
289 Some(b'Q') => {
290 let namespace = self.parse_qstr()?;
291 let name = self.parse_qstr()?;
292 Ok(Symbol::qualified(namespace, name))
293 }
294 Some(b'U') => Ok(Symbol::new(self.parse_qstr()?)),
295 _ => Err(self.error("expected symbol payload tag 'Q' or 'U'")),
296 }
297 }
298
299 fn parse_qstr(&mut self) -> Result<String> {
300 self.expect(b'"')?;
301 let mut bytes = Vec::new();
302 loop {
303 match self.bump() {
304 Some(b'"') => {
305 return String::from_utf8(bytes)
306 .map_err(|err| self.error(format!("invalid utf-8 in string: {err}")));
307 }
308 Some(b'\\') => match self.bump() {
309 Some(b'\\') => bytes.push(b'\\'),
310 Some(b'"') => bytes.push(b'"'),
311 Some(b'n') => bytes.push(b'\n'),
312 Some(b'r') => bytes.push(b'\r'),
313 Some(b't') => bytes.push(b'\t'),
314 _ => return Err(self.error("invalid escape sequence")),
315 },
316 Some(byte) => bytes.push(byte),
317 None => return Err(self.error("unterminated string")),
318 }
319 }
320 }
321
322 fn parse_hex(&self, hex: &str) -> Result<Vec<u8>> {
323 if !hex.len().is_multiple_of(2) {
324 return Err(self.error("byte literal has odd hex length"));
325 }
326 let bytes = hex.as_bytes();
327 let mut out = Vec::with_capacity(hex.len() / 2);
328 let mut index = 0;
329 while index < bytes.len() {
330 let hi = hex_digit(bytes[index]).ok_or_else(|| self.error("invalid hex digit"))?;
331 let lo = hex_digit(bytes[index + 1]).ok_or_else(|| self.error("invalid hex digit"))?;
332 out.push((hi << 4) | lo);
333 index += 2;
334 }
335 Ok(out)
336 }
337}
338
339fn hex_digit(byte: u8) -> Option<u8> {
340 match byte {
341 b'0'..=b'9' => Some(byte - b'0'),
342 b'a'..=b'f' => Some(byte - b'a' + 10),
343 b'A'..=b'F' => Some(byte - b'A' + 10),
344 _ => None,
345 }
346}