Skip to main content

flutmax_cli/
lib.rs

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