Skip to main content

flutmax/
lib.rs

1//! # flutmax
2//!
3//! Transpiler between `.flutmax` text and Max/MSP `.maxpat` patches.
4//!
5//! ## Quick Start
6//!
7//! ```rust,no_run
8//! let maxpat = flutmax::compile("wire osc = cycle~(440);\nout audio: signal = osc;").unwrap();
9//! let source = flutmax::decompile(&maxpat).unwrap();
10//! ```
11
12// Re-export sub-crates for advanced usage
13pub use flutmax_ast as ast;
14pub use flutmax_codegen as codegen;
15pub use flutmax_decompile as decompile;
16pub use flutmax_objdb as objdb;
17pub use flutmax_parser as parser;
18pub use flutmax_sema as sema;
19
20/// Compile `.flutmax` source to `.maxpat` JSON string.
21///
22/// # Example
23/// ```no_run
24/// let maxpat = flutmax::compile("wire osc = cycle~(440);\nout audio: signal = osc;").unwrap();
25/// ```
26pub fn compile(source: &str) -> Result<String, String> {
27    let ast = flutmax_parser::parse(source).map_err(|e| e.to_string())?;
28    let graph = flutmax_codegen::build_graph(&ast).map_err(|e| format!("{:?}", e))?;
29    flutmax_codegen::generate(&graph).map_err(|e| format!("{:?}", e))
30}
31
32/// Decompile `.maxpat` JSON string to `.flutmax` source.
33///
34/// # Example
35/// ```rust,no_run
36/// let source = flutmax::decompile("{}").unwrap();
37/// ```
38pub fn decompile(maxpat_json: &str) -> Result<String, String> {
39    flutmax_decompile::decompile(maxpat_json).map_err(|e| format!("{:?}", e))
40}
41
42/// Parse `.flutmax` source and return AST as JSON string.
43///
44/// Useful for language bindings (Python, WASM) that can't access Rust types directly.
45pub fn parse_to_json(source: &str) -> Result<String, String> {
46    let ast = flutmax_parser::parse(source).map_err(|e| e.to_string())?;
47    ast_to_json(&ast)
48}
49
50/// Decompile with multi-file support (subpatchers, codebox, UI data).
51pub fn decompile_multi(
52    maxpat_json: &str,
53    name: &str,
54) -> Result<flutmax_decompile::DecompileResult, String> {
55    flutmax_decompile::decompile_multi(maxpat_json, name).map_err(|e| format!("{:?}", e))
56}
57
58/// Convert an AST Program to a JSON string.
59fn ast_to_json(program: &flutmax_ast::Program) -> Result<String, String> {
60    // Manual serialization since Program doesn't derive Serialize
61    let mut obj = serde_json::Map::new();
62
63    // in_decls
64    let in_decls: Vec<serde_json::Value> = program
65        .in_decls
66        .iter()
67        .map(|d| {
68            serde_json::json!({
69                "index": d.index,
70                "name": d.name,
71                "port_type": format!("{:?}", d.port_type),
72            })
73        })
74        .collect();
75    obj.insert("in_decls".into(), serde_json::Value::Array(in_decls));
76
77    // out_decls
78    let out_decls: Vec<serde_json::Value> = program
79        .out_decls
80        .iter()
81        .map(|d| {
82            serde_json::json!({
83                "index": d.index,
84                "name": d.name,
85                "port_type": format!("{:?}", d.port_type),
86            })
87        })
88        .collect();
89    obj.insert("out_decls".into(), serde_json::Value::Array(out_decls));
90
91    // wires
92    let wires: Vec<serde_json::Value> = program
93        .wires
94        .iter()
95        .map(|w| {
96            serde_json::json!({
97                "name": w.name,
98                "expr": format!("{:?}", w.value),
99                "attrs": w.attrs.iter().map(|a| {
100                    serde_json::json!({"key": a.key, "value": format!("{:?}", a.value)})
101                }).collect::<Vec<_>>(),
102            })
103        })
104        .collect();
105    obj.insert("wires".into(), serde_json::Value::Array(wires));
106
107    // out_assignments
108    let out_assigns: Vec<serde_json::Value> = program
109        .out_assignments
110        .iter()
111        .map(|a| {
112            serde_json::json!({
113                "index": a.index,
114                "value": format!("{:?}", a.value),
115            })
116        })
117        .collect();
118    obj.insert(
119        "out_assignments".into(),
120        serde_json::Value::Array(out_assigns),
121    );
122
123    serde_json::to_string_pretty(&serde_json::Value::Object(obj)).map_err(|e| e.to_string())
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_compile() {
132        let result = compile("wire osc = cycle~(440);\nout audio: signal = osc;");
133        assert!(result.is_ok(), "compile failed: {:?}", result.err());
134        let json = result.unwrap();
135        assert!(json.contains("cycle~"));
136    }
137
138    #[test]
139    fn test_compile_error() {
140        let result = compile("wire osc = ;");
141        assert!(result.is_err());
142    }
143
144    #[test]
145    fn test_parse_to_json() {
146        let result =
147            parse_to_json("in freq: float;\nwire osc = cycle~(freq);\nout audio: signal = osc;");
148        assert!(result.is_ok());
149        let json = result.unwrap();
150        assert!(json.contains("freq"));
151        assert!(json.contains("cycle~"));
152    }
153
154    #[test]
155    fn test_roundtrip() {
156        let source = "in freq: float;\nwire osc = cycle~(freq);\nout audio: signal = osc;\n";
157        let maxpat = compile(source).unwrap();
158        let decompiled = decompile(&maxpat).unwrap();
159        assert!(decompiled.contains("cycle~"));
160    }
161}