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