Skip to main content

flutmax_cli/
lib.rs

1pub mod validate;
2
3use flutmax_codegen::{
4    build_graph, build_graph_with_objdb, generate, generate_with_options, generate_with_ui,
5    BuildError, CodeFiles, CodegenError, GenerateOptions, UiData,
6};
7use flutmax_objdb::ObjectDb;
8use flutmax_parser::parse;
9use flutmax_sema::registry::AbstractionRegistry;
10use flutmax_sema::type_check::{type_check, type_check_with_registry, TypeError};
11
12/// Compilation error type that wraps all pipeline errors.
13#[derive(Debug)]
14pub enum CompileError {
15    Parse(flutmax_parser::ParseError),
16    Type(Vec<TypeError>),
17    BuildGraph(BuildError),
18    Codegen(CodegenError),
19}
20
21impl std::fmt::Display for CompileError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            CompileError::Parse(e) => write!(f, "parse error: {}", e),
25            CompileError::Type(errors) => {
26                let msg = errors
27                    .iter()
28                    .map(|e| format!("{}", e))
29                    .collect::<Vec<_>>()
30                    .join("\n");
31                write!(f, "{}", msg)
32            }
33            CompileError::BuildGraph(e) => write!(f, "graph build error: {}", e),
34            CompileError::Codegen(e) => write!(f, "codegen error: {}", e),
35        }
36    }
37}
38
39impl std::error::Error for CompileError {}
40
41impl From<flutmax_parser::ParseError> for CompileError {
42    fn from(e: flutmax_parser::ParseError) -> Self {
43        CompileError::Parse(e)
44    }
45}
46
47impl From<BuildError> for CompileError {
48    fn from(e: BuildError) -> Self {
49        CompileError::BuildGraph(e)
50    }
51}
52
53impl From<CodegenError> for CompileError {
54    fn from(e: CodegenError) -> Self {
55        CompileError::Codegen(e)
56    }
57}
58
59/// Compile a .flutmax source string into a .maxpat JSON string.
60pub fn compile(source: &str) -> Result<String, Box<dyn std::error::Error>> {
61    let ast = parse(source)?;
62    let type_errors = type_check(&ast);
63    if !type_errors.is_empty() {
64        let msg = type_errors
65            .iter()
66            .map(|e| format!("{}", e))
67            .collect::<Vec<_>>()
68            .join("\n");
69        return Err(msg.into());
70    }
71    let graph = build_graph(&ast)?;
72    let json = generate(&graph)?;
73    Ok(json)
74}
75
76/// Compile a .flutmax source string with an AbstractionRegistry.
77///
78/// When a registry is provided, it resolves Abstraction inlet/outlet counts
79/// by referencing `in`/`out` declarations from other `.flutmax` files.
80pub fn compile_with_registry(
81    source: &str,
82    registry: Option<&AbstractionRegistry>,
83) -> Result<String, Box<dyn std::error::Error>> {
84    compile_with_registry_and_code_files(source, registry, None)
85}
86
87/// Compile with AbstractionRegistry and CodeFiles for codebox support.
88pub fn compile_with_registry_and_code_files(
89    source: &str,
90    registry: Option<&AbstractionRegistry>,
91    code_files: Option<&CodeFiles>,
92) -> Result<String, Box<dyn std::error::Error>> {
93    compile_full(source, registry, code_files, None)
94}
95
96/// Compile with all options: registry, code_files, and objdb.
97pub fn compile_full(
98    source: &str,
99    registry: Option<&AbstractionRegistry>,
100    code_files: Option<&CodeFiles>,
101    objdb: Option<&ObjectDb>,
102) -> Result<String, Box<dyn std::error::Error>> {
103    let ast = parse(source)?;
104    let type_errors = type_check_with_registry(&ast, registry);
105    if !type_errors.is_empty() {
106        let msg = type_errors
107            .iter()
108            .map(|e| format!("{}", e))
109            .collect::<Vec<_>>()
110            .join("\n");
111        return Err(msg.into());
112    }
113    let graph = build_graph_with_objdb(&ast, registry, code_files, objdb)?;
114    let json = generate(&graph)?;
115    Ok(json)
116}
117
118/// Compile a .flutmax source as an RNBO patcher (classnamespace: "rnbo").
119pub fn compile_rnbo(source: &str) -> Result<String, Box<dyn std::error::Error>> {
120    let ast = parse(source)?;
121    let type_errors = type_check(&ast);
122    if !type_errors.is_empty() {
123        let msg = type_errors
124            .iter()
125            .map(|e| format!("{}", e))
126            .collect::<Vec<_>>()
127            .join("\n");
128        return Err(msg.into());
129    }
130    let graph = build_graph(&ast)?;
131    let opts = GenerateOptions {
132        classnamespace: "rnbo".to_string(),
133    };
134    let json = generate_with_options(&graph, &opts)?;
135    Ok(json)
136}
137
138/// Compile with all options including UI data from .uiflutmax sidecar file.
139pub fn compile_full_with_ui(
140    source: &str,
141    registry: Option<&AbstractionRegistry>,
142    code_files: Option<&CodeFiles>,
143    objdb: Option<&ObjectDb>,
144    ui_data: Option<&UiData>,
145) -> Result<String, Box<dyn std::error::Error>> {
146    let ast = parse(source)?;
147    let type_errors = type_check_with_registry(&ast, registry);
148    if !type_errors.is_empty() {
149        let msg = type_errors
150            .iter()
151            .map(|e| format!("{}", e))
152            .collect::<Vec<_>>()
153            .join("\n");
154        return Err(msg.into());
155    }
156    let graph = build_graph_with_objdb(&ast, registry, code_files, objdb)?;
157    let json = generate_with_ui(&graph, &GenerateOptions::default(), ui_data)?;
158    Ok(json)
159}
160
161/// Compile a .flutmax source as a gen~ patcher (classnamespace: "dsp.gen").
162///
163/// Inside gen~, all objects operate at signal rate, so
164/// E001 (Signal->Control connection) and E005 (output type mismatch) are suppressed.
165/// E002 (undefined reference) and E003 (duplicate definition) are still detected.
166pub fn compile_gen(source: &str) -> Result<String, Box<dyn std::error::Error>> {
167    let ast = parse(source)?;
168    let type_errors: Vec<_> = type_check(&ast)
169        .into_iter()
170        .filter(|e| e.code != "E001" && e.code != "E005")
171        .collect();
172    if !type_errors.is_empty() {
173        let msg = type_errors
174            .iter()
175            .map(|e| format!("{}", e))
176            .collect::<Vec<_>>()
177            .join("\n");
178        return Err(msg.into());
179    }
180    let graph = build_graph(&ast)?;
181    let opts = GenerateOptions {
182        classnamespace: "dsp.gen".to_string(),
183    };
184    let json = generate_with_options(&graph, &opts)?;
185    Ok(json)
186}