Skip to main content

refrain_core/
lib.rs

1//! refrain-core: Differential Refrain Engine core library.
2//!
3//! This crate defines the AST, public types, and parser entry points for the
4//! Refrain DSL. The DSL is an S-expression language describing time-pattern
5//! refrains and their compositional transformations.
6
7pub mod ast;
8pub mod error;
9pub mod parser;
10
11pub use ast::{Op, Pattern, Refrain};
12pub use error::{RefrainError, Result};
13pub use parser::parse;
14
15#[cfg(test)]
16mod tests {
17    use super::*;
18
19    #[test]
20    fn refrain_constructor_smoke() {
21        let r = Refrain::new("melody-a");
22        assert_eq!(r.name, "melody-a");
23        assert!(r.territorialize.is_none());
24        assert!(r.deterritorialize.is_none());
25        assert!(r.reterritorialize.is_none());
26    }
27
28    #[test]
29    fn ast_serde_roundtrip() {
30        let mut r = Refrain::new("t");
31        r.territorialize = Some(Pattern::Op(Op::Note {
32            pitch: "C4".into(),
33            dur: "q".into(),
34        }));
35        let s = serde_json::to_string(&r).unwrap();
36        let r2: Refrain = serde_json::from_str(&s).unwrap();
37        assert_eq!(r, r2);
38    }
39
40    #[test]
41    fn op_loop_holds_body() {
42        let body = Box::new(Pattern::Op(Op::Note {
43            pitch: "G4".into(),
44            dur: "e".into(),
45        }));
46        let op = Op::Loop { count: 4, body };
47        let s = serde_json::to_string(&op).unwrap();
48        assert!(s.contains("Loop"));
49        assert!(s.contains("\"count\":4"));
50    }
51
52    #[test]
53    fn parser_parses_empty_refrain() {
54        let r = parser::parse("(refrain x)").unwrap();
55        assert_eq!(r.name, "x");
56        assert!(r.territorialize.is_none());
57    }
58}