Skip to main content

flutmax_ast/
lib.rs

1/// flutmax AST (Abstract Syntax Tree)
2///
3/// Type definitions representing the structure of `.flutmax` source code.
4/// Converted from Tree-sitter CST to this AST, then passed to semantic analysis.
5///
6/// Top-level program structure
7#[derive(Debug, Clone, PartialEq)]
8pub struct Program {
9    pub in_decls: Vec<InDecl>,
10    pub out_decls: Vec<OutDecl>,
11    pub wires: Vec<Wire>,
12    pub destructuring_wires: Vec<DestructuringWire>,
13    pub msg_decls: Vec<MsgDecl>,
14    pub out_assignments: Vec<OutAssignment>,
15    pub direct_connections: Vec<DirectConnection>,
16    pub feedback_decls: Vec<FeedbackDecl>,
17    pub feedback_assignments: Vec<FeedbackAssignment>,
18    pub state_decls: Vec<StateDecl>,
19    pub state_assignments: Vec<StateAssignment>,
20}
21
22impl Default for Program {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl Program {
29    pub fn new() -> Self {
30        Self {
31            in_decls: Vec::new(),
32            out_decls: Vec::new(),
33            wires: Vec::new(),
34            destructuring_wires: Vec::new(),
35            msg_decls: Vec::new(),
36            out_assignments: Vec::new(),
37            direct_connections: Vec::new(),
38            feedback_decls: Vec::new(),
39            feedback_assignments: Vec::new(),
40            state_decls: Vec::new(),
41            state_assignments: Vec::new(),
42        }
43    }
44}
45
46/// Source code location information
47#[derive(Debug, Clone, PartialEq, Default)]
48pub struct Span {
49    pub start_line: usize,
50    pub start_column: usize,
51    pub end_line: usize,
52    pub end_column: usize,
53}
54
55/// Port type
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub enum PortType {
58    Signal,
59    Float,
60    Int,
61    Bang,
62    List,
63    Symbol,
64}
65
66impl PortType {
67    pub fn parse(s: &str) -> Option<Self> {
68        match s {
69            "signal" => Some(Self::Signal),
70            "float" => Some(Self::Float),
71            "int" => Some(Self::Int),
72            "bang" => Some(Self::Bang),
73            "list" => Some(Self::List),
74            "symbol" => Some(Self::Symbol),
75            _ => None,
76        }
77    }
78
79    pub fn is_signal(&self) -> bool {
80        matches!(self, Self::Signal)
81    }
82}
83
84/// Input port declaration: `in 0 (freq): float;`
85#[derive(Debug, Clone, PartialEq)]
86pub struct InDecl {
87    pub index: u32,
88    pub name: String,
89    pub port_type: PortType,
90}
91
92/// Output port declaration: `out 0 (audio): signal;` or `out audio: signal = expr;`
93#[derive(Debug, Clone, PartialEq)]
94pub struct OutDecl {
95    pub index: u32,
96    pub name: String,
97    pub port_type: PortType,
98    pub value: Option<Expr>,
99}
100
101/// Wire declaration: `wire osc = cycle~(440);`
102/// Optional `.attr()` chain: `wire w = flonum(x).attr(minimum: 0., maximum: 100.);`
103#[derive(Debug, Clone, PartialEq)]
104pub struct Wire {
105    pub name: String,
106    pub value: Expr,
107    pub span: Option<Span>,
108    pub attrs: Vec<AttrPair>,
109}
110
111/// Output assignment: `out[0] = osc;`
112#[derive(Debug, Clone, PartialEq)]
113pub struct OutAssignment {
114    pub index: u32,
115    pub value: Expr,
116    pub span: Option<Span>,
117}
118
119/// Direct connection: `node_a.in[0] = trigger;`
120#[derive(Debug, Clone, PartialEq)]
121pub struct DirectConnection {
122    pub target: InputPortAccess,
123    pub value: Expr,
124}
125
126/// Input port access (lvalue): `node_a.in[0]`
127#[derive(Debug, Clone, PartialEq)]
128pub struct InputPortAccess {
129    pub object: String,
130    pub index: u32,
131}
132
133/// Output port access (rvalue): `node_a.out[0]`
134#[derive(Debug, Clone, PartialEq)]
135pub struct OutputPortAccess {
136    pub object: String,
137    pub index: u32,
138}
139
140/// Destructuring wire: `wire (a, b, c) = expr;`
141#[derive(Debug, Clone, PartialEq)]
142pub struct DestructuringWire {
143    pub names: Vec<String>,
144    pub value: Expr,
145    pub span: Option<Span>,
146}
147
148/// Feedback declaration: `feedback fb: signal;`
149#[derive(Debug, Clone, PartialEq)]
150pub struct FeedbackDecl {
151    pub name: String,
152    pub port_type: PortType,
153    pub span: Option<Span>,
154}
155
156/// Feedback assignment: `feedback fb = tapin~(mixed, 1000);`
157#[derive(Debug, Clone, PartialEq)]
158pub struct FeedbackAssignment {
159    pub target: String,
160    pub value: Expr,
161    pub span: Option<Span>,
162}
163
164/// State declaration: `state counter: int = 0;`
165#[derive(Debug, Clone, PartialEq)]
166pub struct StateDecl {
167    pub name: String,
168    pub port_type: PortType,
169    pub init_value: Expr,
170    pub span: Option<Span>,
171}
172
173/// State assignment: `state counter = next;`
174#[derive(Debug, Clone, PartialEq)]
175pub struct StateAssignment {
176    pub name: String,
177    pub value: Expr,
178    pub span: Option<Span>,
179}
180
181/// Message declaration: `msg click = "bang";`
182/// Optional `.attr()` chain: `msg click = "bang".attr(patching_rect: 100.);`
183#[derive(Debug, Clone, PartialEq)]
184pub struct MsgDecl {
185    pub name: String,
186    pub content: String,
187    pub span: Option<Span>,
188    pub attrs: Vec<AttrPair>,
189}
190
191/// Call argument (positional or named)
192#[derive(Debug, Clone, PartialEq)]
193pub struct CallArg {
194    /// None = positional argument, Some = named argument (e.g., `freq: 440`)
195    pub name: Option<String>,
196    /// Argument value
197    pub value: Expr,
198}
199
200impl CallArg {
201    /// Create a positional argument.
202    pub fn positional(value: Expr) -> Self {
203        CallArg { name: None, value }
204    }
205
206    /// Create a named argument.
207    pub fn named(name: impl Into<String>, value: Expr) -> Self {
208        CallArg {
209            name: Some(name.into()),
210            value,
211        }
212    }
213}
214
215/// Expression
216#[derive(Debug, Clone, PartialEq)]
217pub enum Expr {
218    /// Object call: `cycle~(440)`, `*~(osc, 0.5)`, `biquad~(input: osc, freq: cutoff)`
219    Call { object: String, args: Vec<CallArg> },
220    /// Variable reference: `osc`, `freq`
221    Ref(String),
222    /// Literal value
223    Lit(LitValue),
224    /// Output port access (rvalue): `node_a.out[0]`
225    OutputPortAccess(OutputPortAccess),
226    /// Tuple expression: `(x, y, z)` -- converted to `[pack]`
227    Tuple(Vec<Expr>),
228}
229
230/// Literal value
231#[derive(Debug, Clone, PartialEq)]
232pub enum LitValue {
233    Int(i64),
234    Float(f64),
235    Str(String),
236}
237
238/// Attribute key-value pair: `.attr(key: value, ...)`
239#[derive(Debug, Clone, PartialEq)]
240pub struct AttrPair {
241    pub key: String,
242    pub value: AttrValue,
243}
244
245/// Attribute value
246#[derive(Debug, Clone, PartialEq)]
247pub enum AttrValue {
248    Int(i64),
249    Float(f64),
250    Str(String),
251    Ident(String),
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_port_type_from_str() {
260        assert_eq!(PortType::parse("signal"), Some(PortType::Signal));
261        assert_eq!(PortType::parse("float"), Some(PortType::Float));
262        assert_eq!(PortType::parse("int"), Some(PortType::Int));
263        assert_eq!(PortType::parse("bang"), Some(PortType::Bang));
264        assert_eq!(PortType::parse("unknown"), None);
265    }
266
267    #[test]
268    fn test_program_new() {
269        let prog = Program::new();
270        assert!(prog.in_decls.is_empty());
271        assert!(prog.out_decls.is_empty());
272        assert!(prog.wires.is_empty());
273        assert!(prog.out_assignments.is_empty());
274    }
275
276    #[test]
277    fn test_build_l2_ast() {
278        // Manually construct the AST for L2_simple_synth.flutmax
279        let prog = Program {
280            in_decls: vec![InDecl {
281                index: 0,
282                name: "freq".to_string(),
283                port_type: PortType::Float,
284            }],
285            out_decls: vec![OutDecl {
286                index: 0,
287                name: "audio".to_string(),
288                port_type: PortType::Signal,
289                value: None,
290            }],
291            wires: vec![
292                Wire {
293                    name: "osc".to_string(),
294                    value: Expr::Call {
295                        object: "cycle~".to_string(),
296                        args: vec![CallArg::positional(Expr::Ref("freq".to_string()))],
297                    },
298                    span: None,
299                    attrs: vec![],
300                },
301                Wire {
302                    name: "amp".to_string(),
303                    value: Expr::Call {
304                        object: "*~".to_string(),
305                        args: vec![
306                            CallArg::positional(Expr::Ref("osc".to_string())),
307                            CallArg::positional(Expr::Lit(LitValue::Float(0.5))),
308                        ],
309                    },
310                    span: None,
311                    attrs: vec![],
312                },
313            ],
314            destructuring_wires: Vec::new(),
315            msg_decls: Vec::new(),
316            out_assignments: vec![OutAssignment {
317                index: 0,
318                value: Expr::Ref("amp".to_string()),
319                span: None,
320            }],
321            direct_connections: Vec::new(),
322            feedback_decls: Vec::new(),
323            feedback_assignments: Vec::new(),
324            state_decls: Vec::new(),
325            state_assignments: Vec::new(),
326        };
327
328        assert_eq!(prog.in_decls.len(), 1);
329        assert_eq!(prog.out_decls.len(), 1);
330        assert_eq!(prog.wires.len(), 2);
331        assert_eq!(prog.out_assignments.len(), 1);
332    }
333
334    #[test]
335    fn test_call_arg_helpers() {
336        let pos = CallArg::positional(Expr::Lit(LitValue::Int(440)));
337        assert_eq!(pos.name, None);
338        assert_eq!(pos.value, Expr::Lit(LitValue::Int(440)));
339
340        let named = CallArg::named("freq", Expr::Lit(LitValue::Int(440)));
341        assert_eq!(named.name, Some("freq".to_string()));
342        assert_eq!(named.value, Expr::Lit(LitValue::Int(440)));
343    }
344}