sim_codec/implementation/
domain_form.rs1#[derive(Clone, Debug, PartialEq)]
20pub enum DomainValue {
21 Form(DomainForm),
23 List(Vec<DomainValue>),
25 String(String),
27 Atom(String),
29}
30
31#[derive(Clone, Debug, PartialEq)]
33pub struct DomainForm {
34 pub name: String,
36 pub fields: Vec<(String, DomainValue)>,
38 pub positional: Vec<DomainValue>,
40}
41
42impl DomainForm {
43 pub fn field(&self, key: &str) -> Option<&DomainValue> {
45 self.fields
46 .iter()
47 .find_map(|(name, value)| (name == key).then_some(value))
48 }
49
50 pub fn atom(&self, key: &str) -> Result<&str, DomainFormError> {
52 match self.field(key) {
53 Some(DomainValue::Atom(value)) => Ok(value),
54 Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
55 None => Err(DomainFormError::MissingField(key.to_owned())),
56 }
57 }
58
59 pub fn string(&self, key: &str) -> Result<&str, DomainFormError> {
61 match self.field(key) {
62 Some(DomainValue::String(value)) => Ok(value),
63 Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
64 None => Err(DomainFormError::MissingField(key.to_owned())),
65 }
66 }
67
68 pub fn list(&self, key: &str) -> Result<&[DomainValue], DomainFormError> {
70 match self.field(key) {
71 Some(DomainValue::List(items)) => Ok(items),
72 Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
73 None => Err(DomainFormError::MissingField(key.to_owned())),
74 }
75 }
76
77 pub fn form(&self, key: &str) -> Result<&DomainForm, DomainFormError> {
79 match self.field(key) {
80 Some(DomainValue::Form(value)) => Ok(value),
81 Some(_) => Err(DomainFormError::WrongFieldKind(key.to_owned())),
82 None => Err(DomainFormError::MissingField(key.to_owned())),
83 }
84 }
85
86 pub fn field_atom_or_string(&self, name: &str) -> Result<&str, DomainFormError> {
92 match self.field(name) {
93 Some(DomainValue::Atom(value) | DomainValue::String(value)) => Ok(value),
94 Some(_) => Err(DomainFormError::WrongFieldKind(name.to_owned())),
95 None => Err(DomainFormError::MissingField(name.to_owned())),
96 }
97 }
98
99 pub fn field_text(&self, name: &str) -> Result<String, DomainFormError> {
107 match self.field(name) {
108 Some(value) => Ok(value.render_text()),
109 None => Err(DomainFormError::MissingField(name.to_owned())),
110 }
111 }
112}
113
114impl DomainValue {
115 pub fn as_form(&self) -> Result<&DomainForm, DomainFormError> {
120 match self {
121 DomainValue::Form(form) => Ok(form),
122 _ => Err(DomainFormError::WrongValueKind),
123 }
124 }
125
126 pub fn atom_or_string(&self) -> Result<&str, DomainFormError> {
131 match self {
132 DomainValue::Atom(value) | DomainValue::String(value) => Ok(value),
133 _ => Err(DomainFormError::WrongValueKind),
134 }
135 }
136
137 pub fn render_text(&self) -> String {
143 match self {
144 DomainValue::Form(form) => format_domain_form(form),
145 DomainValue::List(items) => format!(
146 "[{}]",
147 items
148 .iter()
149 .map(DomainValue::render_text)
150 .collect::<Vec<_>>()
151 .join(",")
152 ),
153 DomainValue::String(value) => {
154 format!("\"{}\"", value.replace('\\', "\\\\").replace('"', "\\\""))
155 }
156 DomainValue::Atom(value) => value.clone(),
157 }
158 }
159}
160
161#[derive(Clone, Debug, PartialEq, Eq)]
163pub enum DomainFormError {
164 ExpectedForm,
166 UnexpectedEof,
168 InvalidToken,
170 DuplicateField(String),
172 TrailingInput,
174 MissingField(String),
176 WrongFieldKind(String),
178 WrongValueKind,
181}
182
183pub fn parse_domain_form(input: &str) -> Result<DomainForm, DomainFormError> {
199 let mut parser = Parser { input, index: 0 };
200 parser.skip_ws();
201 if !parser.consume_str("#(") {
202 return Err(DomainFormError::ExpectedForm);
203 }
204 let form = parser.parse_form_body()?;
205 parser.skip_ws();
206 if parser.index != parser.input.len() {
207 return Err(DomainFormError::TrailingInput);
208 }
209 Ok(form)
210}
211
212pub fn format_domain_form(form: &DomainForm) -> String {
226 let mut out = String::from("#(");
227 out.push_str(&form.name);
228 for value in &form.positional {
229 out.push(' ');
230 format_value(value, &mut out);
231 }
232 for (key, value) in &form.fields {
233 out.push(' ');
234 out.push_str(key);
235 out.push('=');
236 format_value(value, &mut out);
237 }
238 out.push(')');
239 out
240}
241
242fn format_value(value: &DomainValue, out: &mut String) {
243 match value {
244 DomainValue::Form(form) => out.push_str(&format_domain_form(form)),
245 DomainValue::List(items) => {
246 out.push('[');
247 for (index, item) in items.iter().enumerate() {
248 if index > 0 {
249 out.push(',');
250 }
251 format_value(item, out);
252 }
253 out.push(']');
254 }
255 DomainValue::String(text) => {
256 out.push('"');
257 for ch in text.chars() {
258 if ch == '\\' || ch == '"' {
259 out.push('\\');
260 }
261 out.push(ch);
262 }
263 out.push('"');
264 }
265 DomainValue::Atom(text) => out.push_str(text),
266 }
267}
268
269struct Parser<'a> {
270 input: &'a str,
271 index: usize,
272}
273
274impl Parser<'_> {
275 fn parse_form_body(&mut self) -> Result<DomainForm, DomainFormError> {
276 let name = self.parse_ident()?;
277 let mut fields: Vec<(String, DomainValue)> = Vec::new();
278 let mut positional = Vec::new();
279 loop {
280 self.skip_ws();
281 if self.consume_char(')') {
282 break;
283 }
284 match self.peek_char() {
285 Some('#') | Some('[') | Some('"') => positional.push(self.parse_value()?),
286 _ => {
287 let atom = self.parse_atom()?;
288 if self.consume_char('=') {
289 if fields.iter().any(|(key, _)| key == &atom) {
290 return Err(DomainFormError::DuplicateField(atom));
291 }
292 fields.push((atom, self.parse_value()?));
293 } else {
294 positional.push(DomainValue::Atom(atom));
295 }
296 }
297 }
298 }
299 Ok(DomainForm {
300 name,
301 fields,
302 positional,
303 })
304 }
305
306 fn parse_value(&mut self) -> Result<DomainValue, DomainFormError> {
307 self.skip_ws();
308 if self.consume_str("#(") {
309 return self.parse_form_body().map(DomainValue::Form);
310 }
311 if self.consume_char('[') {
312 return self.parse_list();
313 }
314 if self.peek_char() == Some('"') {
315 return self.parse_string().map(DomainValue::String);
316 }
317 self.parse_atom().map(DomainValue::Atom)
318 }
319
320 fn parse_list(&mut self) -> Result<DomainValue, DomainFormError> {
321 let mut items = Vec::new();
322 loop {
323 self.skip_ws();
324 if self.consume_char(']') {
325 break;
326 }
327 items.push(self.parse_value()?);
328 self.skip_ws();
329 if self.consume_char(',') {
330 continue;
331 }
332 self.expect_char(']')?;
333 break;
334 }
335 Ok(DomainValue::List(items))
336 }
337
338 fn parse_string(&mut self) -> Result<String, DomainFormError> {
339 self.expect_char('"')?;
340 let mut out = String::new();
341 while let Some(ch) = self.next_char() {
342 match ch {
343 '"' => return Ok(out),
344 '\\' => out.push(self.next_char().ok_or(DomainFormError::UnexpectedEof)?),
345 other => out.push(other),
346 }
347 }
348 Err(DomainFormError::UnexpectedEof)
349 }
350
351 fn parse_atom(&mut self) -> Result<String, DomainFormError> {
352 let start = self.index;
353 while let Some(ch) = self.peek_char() {
354 if ch.is_whitespace() || [',', ')', ']', '='].contains(&ch) {
355 break;
356 }
357 self.index += ch.len_utf8();
358 }
359 if self.index == start {
360 return Err(DomainFormError::UnexpectedEof);
361 }
362 Ok(self.input[start..self.index].to_owned())
363 }
364
365 fn parse_ident(&mut self) -> Result<String, DomainFormError> {
366 let atom = self.parse_atom()?;
367 if atom
368 .chars()
369 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_' || ch == '-')
370 {
371 Ok(atom)
372 } else {
373 Err(DomainFormError::InvalidToken)
374 }
375 }
376
377 fn expect_char(&mut self, expected: char) -> Result<(), DomainFormError> {
378 match self.next_char() {
379 Some(ch) if ch == expected => Ok(()),
380 Some(_) => Err(DomainFormError::InvalidToken),
381 None => Err(DomainFormError::UnexpectedEof),
382 }
383 }
384
385 fn consume_char(&mut self, expected: char) -> bool {
386 if self.peek_char() == Some(expected) {
387 self.index += expected.len_utf8();
388 true
389 } else {
390 false
391 }
392 }
393
394 fn consume_str(&mut self, expected: &str) -> bool {
395 if self.input[self.index..].starts_with(expected) {
396 self.index += expected.len();
397 true
398 } else {
399 false
400 }
401 }
402
403 fn skip_ws(&mut self) {
404 while let Some(ch) = self.peek_char() {
405 if ch.is_whitespace() {
406 self.index += ch.len_utf8();
407 } else {
408 break;
409 }
410 }
411 }
412
413 fn peek_char(&self) -> Option<char> {
414 self.input[self.index..].chars().next()
415 }
416
417 fn next_char(&mut self) -> Option<char> {
418 let ch = self.peek_char()?;
419 self.index += ch.len_utf8();
420 Some(ch)
421 }
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427
428 #[test]
429 fn parses_keyed_form_with_list_and_nested_form() {
430 let form = parse_domain_form("#(Note dur=4/4 pitch=C4 tags=[a,b] inner=#(Rest dur=1/4))")
431 .expect("parse");
432 assert_eq!(form.name, "Note");
433 assert_eq!(form.atom("dur").unwrap(), "4/4");
434 assert_eq!(form.atom("pitch").unwrap(), "C4");
435 assert_eq!(
436 form.list("tags").unwrap(),
437 &[
438 DomainValue::Atom("a".to_owned()),
439 DomainValue::Atom("b".to_owned())
440 ]
441 );
442 assert_eq!(form.form("inner").unwrap().name, "Rest");
443 }
444
445 #[test]
446 fn parses_positional_values() {
447 let form = parse_domain_form("#(Chord 60 64 67)").expect("parse");
448 assert_eq!(form.positional.len(), 3);
449 assert!(form.fields.is_empty());
450 }
451
452 #[test]
453 fn round_trips_through_format() {
454 let source = "#(Note dur=4/4 sym=\"a\\\"b\" pitches=[60,64])";
455 let form = parse_domain_form(source).expect("parse");
456 let rendered = format_domain_form(&form);
457 assert_eq!(parse_domain_form(&rendered).unwrap(), form);
458 }
459
460 #[test]
461 fn missing_close_paren_is_unexpected_eof() {
462 assert_eq!(
463 parse_domain_form("#(Note dur=4/4"),
464 Err(DomainFormError::UnexpectedEof)
465 );
466 }
467
468 #[test]
469 fn duplicate_field_is_rejected() {
470 assert_eq!(
471 parse_domain_form("#(Note dur=4/4 dur=1/2)"),
472 Err(DomainFormError::DuplicateField("dur".to_owned()))
473 );
474 }
475
476 #[test]
477 fn trailing_input_is_rejected() {
478 assert_eq!(
479 parse_domain_form("#(Note dur=4/4) extra"),
480 Err(DomainFormError::TrailingInput)
481 );
482 }
483
484 #[test]
485 fn non_form_input_is_rejected() {
486 assert_eq!(
487 parse_domain_form("Note"),
488 Err(DomainFormError::ExpectedForm)
489 );
490 }
491
492 #[test]
493 fn escaped_quotes_round_trip() {
494 let form = parse_domain_form("#(S v=\"he said \\\"hi\\\"\")").expect("parse");
495 assert_eq!(form.string("v").unwrap(), "he said \"hi\"");
496 }
497
498 #[test]
499 fn field_atom_or_string_accepts_both_kinds() {
500 let form = parse_domain_form("#(N a=bare s=\"quoted\")").expect("parse");
501 assert_eq!(form.field_atom_or_string("a").unwrap(), "bare");
502 assert_eq!(form.field_atom_or_string("s").unwrap(), "quoted");
503 }
504
505 #[test]
506 fn field_atom_or_string_rejects_list_and_missing() {
507 let form = parse_domain_form("#(N tags=[a,b])").expect("parse");
508 assert_eq!(
509 form.field_atom_or_string("tags"),
510 Err(DomainFormError::WrongFieldKind("tags".to_owned()))
511 );
512 assert_eq!(
513 form.field_atom_or_string("nope"),
514 Err(DomainFormError::MissingField("nope".to_owned()))
515 );
516 }
517
518 #[test]
519 fn field_text_renders_every_value_kind() {
520 let form = parse_domain_form("#(N a=bare s=\"q\\\"x\" tags=[a,b] inner=#(Rest dur=1/4))")
521 .expect("parse");
522 assert_eq!(form.field_text("a").unwrap(), "bare");
523 assert_eq!(form.field_text("s").unwrap(), "\"q\\\"x\"");
524 assert_eq!(form.field_text("tags").unwrap(), "[a,b]");
525 assert_eq!(form.field_text("inner").unwrap(), "#(Rest dur=1/4)");
526 assert_eq!(
527 form.field_text("nope"),
528 Err(DomainFormError::MissingField("nope".to_owned()))
529 );
530 }
531
532 #[test]
533 fn value_as_form_and_atom_or_string() {
534 let form = parse_domain_form("#(N inner=#(Rest dur=1/4) a=bare s=\"q\")").expect("parse");
535 assert_eq!(form.field("inner").unwrap().as_form().unwrap().name, "Rest");
536 assert_eq!(
537 form.field("a").unwrap().as_form(),
538 Err(DomainFormError::WrongValueKind)
539 );
540 assert_eq!(form.field("a").unwrap().atom_or_string().unwrap(), "bare");
541 assert_eq!(form.field("s").unwrap().atom_or_string().unwrap(), "q");
542 assert_eq!(
543 form.field("inner").unwrap().atom_or_string(),
544 Err(DomainFormError::WrongValueKind)
545 );
546 }
547
548 #[test]
549 fn render_text_round_trips_a_form_value() {
550 let source = "#(Note dur=4/4 sym=\"a\\\"b\" pitches=[60,64])";
551 let form = parse_domain_form(source).expect("parse");
552 let value = DomainValue::Form(form.clone());
553 let rendered = value.render_text();
554 assert_eq!(parse_domain_form(&rendered).unwrap(), form);
555 }
556
557 #[test]
558 fn render_text_formats_list_and_string_and_atom() {
559 assert_eq!(DomainValue::Atom("60".to_owned()).render_text(), "60");
560 assert_eq!(
561 DomainValue::String("a\"b\\c".to_owned()).render_text(),
562 "\"a\\\"b\\\\c\""
563 );
564 let list = DomainValue::List(vec![
565 DomainValue::Atom("a".to_owned()),
566 DomainValue::String("b".to_owned()),
567 ]);
568 assert_eq!(list.render_text(), "[a,\"b\"]");
569 }
570}