refrain-adapters 0.1.0

Differential Refrain Engine: output adapters (audio, visual, code, text)
Documentation
//! Code adapter: emits source code that reconstructs a refrain.
//!
//! Two languages: Python (`refrain_py` API) and Rust (`refrain_core`
//! API). The output is purely a textual template render — no shellouts,
//! no `format!` of user input into compile contexts.

use refrain_core::{Op, Pattern, Refrain};

use crate::{AdapterCaps, AdapterErr, EmitCtx, ExtractedRefrain, RefrainAdapter};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CodeLang {
    Python,
    Rust,
}

pub struct CodeAdapter {
    pub lang: CodeLang,
}

impl CodeAdapter {
    pub fn new(lang: CodeLang) -> Self {
        Self { lang }
    }

    fn emit_python(&self, r: &Refrain) -> String {
        let mut out = String::new();
        out.push_str("# Auto-generated by refrain-adapters code emitter.\n");
        out.push_str("from refrain_py import _native\n\n");
        out.push_str("REFRAIN_SRC = (\n");
        out.push_str("    \"(refrain ");
        out.push_str(&r.name);
        for (kind, p) in r.stages() {
            out.push_str(" (");
            out.push_str(kind.as_str());
            out.push(' ');
            out.push_str(&pattern_to_sexp(p));
            out.push(')');
        }
        out.push_str(")\"\n)\n\n");
        out.push_str("refrain_json = _native.parse_refrain(REFRAIN_SRC)\n");
        out
    }

    fn emit_rust(&self, r: &Refrain) -> String {
        let mut out = String::new();
        out.push_str("// Auto-generated by refrain-adapters code emitter.\n");
        out.push_str("use refrain_core::parse;\n\n");
        out.push_str("pub fn refrain() -> refrain_core::Refrain {\n");
        out.push_str("    parse(r#\"(refrain ");
        out.push_str(&r.name);
        for (kind, p) in r.stages() {
            out.push_str(" (");
            out.push_str(kind.as_str());
            out.push(' ');
            out.push_str(&pattern_to_sexp(p));
            out.push(')');
        }
        out.push_str(")\"#).expect(\"valid refrain\")\n}\n");
        out
    }
}

fn pattern_to_sexp(p: &Pattern) -> String {
    match p {
        Pattern::Op(op) => op_to_sexp(op),
        Pattern::Seq(items) => items
            .iter()
            .map(pattern_to_sexp)
            .collect::<Vec<_>>()
            .join(" "),
    }
}

fn op_to_sexp(op: &Op) -> String {
    match op {
        Op::Note { pitch, dur } => format!("(note {} {})", pitch, dur),
        Op::Loop { count, body } => format!("(loop {} {})", count, pattern_to_sexp(body)),
        Op::Diff { x, t } => format!("(dy/dx {} {})", x, t),
        Op::Quotient { rels } => format!("(quotient {})", rels.join(" ")),
        Op::Sym(s) => s.clone(),
        Op::Call { head, args } => {
            let mut s = String::from("(");
            s.push_str(head);
            for a in args {
                s.push(' ');
                s.push_str(&pattern_to_sexp(a));
            }
            s.push(')');
            s
        }
    }
}

impl RefrainAdapter for CodeAdapter {
    fn name(&self) -> &str {
        match self.lang {
            CodeLang::Python => "code.python",
            CodeLang::Rust => "code.rust",
        }
    }

    fn emit(&self, refrain: &ExtractedRefrain, _ctx: &EmitCtx) -> Result<Vec<u8>, AdapterErr> {
        let s = match self.lang {
            CodeLang::Python => self.emit_python(refrain.refrain),
            CodeLang::Rust => self.emit_rust(refrain.refrain),
        };
        Ok(s.into_bytes())
    }

    fn capabilities(&self) -> AdapterCaps {
        AdapterCaps {
            realtime: false,
            differentiable: false,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use refrain_core::parse;

    #[test]
    fn python_roundtrip_via_sexp() {
        let r = parse("(refrain mel (territorialize (loop 4 (note C4 q))))").unwrap();
        let a = CodeAdapter::new(CodeLang::Python);
        let ex = ExtractedRefrain { refrain: &r };
        let s = String::from_utf8(a.emit(&ex, &EmitCtx::default()).unwrap()).unwrap();
        assert!(s.contains("from refrain_py import _native"));
        assert!(s.contains("(refrain mel"));
        assert!(s.contains("(territorialize (loop 4 (note C4 q)))"));
    }

    #[test]
    fn rust_roundtrip_via_sexp() {
        let r = parse("(refrain m (deterritorialize (dy/dx x t)))").unwrap();
        let a = CodeAdapter::new(CodeLang::Rust);
        let ex = ExtractedRefrain { refrain: &r };
        let s = String::from_utf8(a.emit(&ex, &EmitCtx::default()).unwrap()).unwrap();
        assert!(s.contains("use refrain_core::parse"));
        assert!(s.contains("(deterritorialize (dy/dx x t))"));
    }

    #[test]
    fn emitted_python_is_parseable_back_to_same_refrain() {
        let src = "(refrain n \
                   (territorialize (loop 4 (note C4 q))) \
                   (deterritorialize (dy/dx i t)) \
                   (reterritorialize (quotient ~r ~s)))";
        let original = parse(src).unwrap();
        let a = CodeAdapter::new(CodeLang::Python);
        let emitted = String::from_utf8(
            a.emit(
                &ExtractedRefrain { refrain: &original },
                &EmitCtx::default(),
            )
            .unwrap(),
        )
        .unwrap();
        // Pull out the refrain literal from the emitted Python and reparse.
        let start = emitted.find("\"(refrain").expect("refrain literal found") + 1;
        let end = emitted[start..].find(")\"\n").expect("closing quote") + start + 1;
        let literal = &emitted[start..end];
        let reparsed = parse(literal).unwrap();
        assert_eq!(reparsed, original);
    }

    #[test]
    fn empty_refrain_emits_minimal_python() {
        let r = parse("(refrain x)").unwrap();
        let a = CodeAdapter::new(CodeLang::Python);
        let s = String::from_utf8(
            a.emit(&ExtractedRefrain { refrain: &r }, &EmitCtx::default())
                .unwrap(),
        )
        .unwrap();
        assert!(s.contains("(refrain x)"));
    }

    #[test]
    fn names_distinguish_languages() {
        assert_eq!(CodeAdapter::new(CodeLang::Python).name(), "code.python");
        assert_eq!(CodeAdapter::new(CodeLang::Rust).name(), "code.rust");
    }
}