Skip to main content

flutmax_parser/
parser.rs

1/// Hand-written recursive descent parser for flutmax.
2///
3/// Consumes a token stream from the lexer and produces
4/// `flutmax_ast::Program` — the exact same AST as the tree-sitter parser.
5use crate::lexer::{LexError, Lexer};
6use crate::tokens::{Token, TokenType};
7use flutmax_ast::{
8    AttrPair, AttrValue, CallArg, DestructuringWire, DirectConnection, Expr, FeedbackAssignment,
9    FeedbackDecl, InDecl, InputPortAccess, LitValue, MsgDecl, OutAssignment, OutDecl,
10    OutputPortAccess, PortType, Program, Span, StateAssignment, StateDecl, Wire,
11};
12
13// ────────────────────────────────────────────────────────────
14// Error type
15// ────────────────────────────────────────────────────────────
16
17#[derive(Debug)]
18pub enum ParseError {
19    Lex(LexError),
20    Syntax {
21        message: String,
22        line: usize,
23        column: usize,
24    },
25}
26
27impl From<LexError> for ParseError {
28    fn from(e: LexError) -> Self {
29        ParseError::Lex(e)
30    }
31}
32
33impl std::fmt::Display for ParseError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        match self {
36            ParseError::Lex(e) => write!(f, "{}", e),
37            ParseError::Syntax {
38                message,
39                line,
40                column,
41            } => write!(f, "Parse error at {}:{}: {}", line, column, message),
42        }
43    }
44}
45
46impl std::error::Error for ParseError {}
47
48// ────────────────────────────────────────────────────────────
49// Parser
50// ────────────────────────────────────────────────────────────
51
52pub struct FlutmaxParser {
53    tokens: Vec<Token>,
54    pos: usize,
55    /// Counter for implicit `in` port indices.
56    implicit_in_index: u32,
57    /// Counter for implicit `out` port indices.
58    implicit_out_index: u32,
59}
60
61impl FlutmaxParser {
62    /// Parse a .flutmax source string into a Program.
63    /// Returns the first error encountered (for backward compatibility).
64    pub fn parse(source: &str) -> Result<Program, ParseError> {
65        let (program, errors) = Self::parse_with_errors(source)?;
66        if let Some(first_err) = errors.into_iter().next() {
67            return Err(first_err);
68        }
69        Ok(program)
70    }
71
72    /// Parse with error recovery: returns a (possibly partial) Program and all errors.
73    /// Continues parsing after errors by skipping to the next semicolon.
74    pub fn parse_with_errors(source: &str) -> Result<(Program, Vec<ParseError>), ParseError> {
75        let tokens = Lexer::tokenize(source)?;
76        let mut parser = FlutmaxParser {
77            tokens,
78            pos: 0,
79            implicit_in_index: 0,
80            implicit_out_index: 0,
81        };
82        let (program, errors) = parser.parse_program_recovering();
83        Ok((program, errors))
84    }
85
86    // ── Helpers ──────────────────────────────────────────────
87
88    fn peek(&self) -> &Token {
89        &self.tokens[self.pos.min(self.tokens.len() - 1)]
90    }
91
92    fn peek_type(&self) -> TokenType {
93        self.peek().token_type
94    }
95
96    fn peek_at(&self, offset: usize) -> &Token {
97        let idx = (self.pos + offset).min(self.tokens.len() - 1);
98        &self.tokens[idx]
99    }
100
101    fn advance(&mut self) -> &Token {
102        let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
103        if self.pos < self.tokens.len() {
104            self.pos += 1;
105        }
106        tok
107    }
108
109    fn expect(&mut self, tt: TokenType) -> Result<Token, ParseError> {
110        let tok = self.peek().clone();
111        if tok.token_type == tt {
112            self.advance();
113            Ok(tok)
114        } else {
115            Err(self.error(format!(
116                "Expected {:?}, got {:?} '{}'",
117                tt, tok.token_type, tok.lexeme
118            )))
119        }
120    }
121
122    fn check(&self, tt: TokenType) -> bool {
123        self.peek_type() == tt
124    }
125
126    fn error(&self, message: String) -> ParseError {
127        let tok = self.peek();
128        ParseError::Syntax {
129            message,
130            line: tok.line,
131            column: tok.column,
132        }
133    }
134
135    /// Check if the current token starts a new statement (for error recovery).
136    fn is_statement_start(&self) -> bool {
137        matches!(
138            self.peek_type(),
139            TokenType::Wire
140                | TokenType::In
141                | TokenType::Out
142                | TokenType::Msg
143                | TokenType::State
144                | TokenType::Feedback
145        )
146    }
147
148    fn make_span_from(&self, start: &Token, end: &Token) -> Option<Span> {
149        Some(Span {
150            start_line: start.line,
151            start_column: start.column,
152            end_line: end.line,
153            end_column: end.column + end.lexeme.len(),
154        })
155    }
156
157    /// Look at the previous token (the one just consumed).
158    fn previous(&self) -> &Token {
159        &self.tokens[(self.pos - 1).max(0)]
160    }
161
162    // ── Top-level ────────────────────────────────────────────
163
164    /// Parse with error recovery: skip to next statement boundary on error and continue.
165    fn parse_program_recovering(&mut self) -> (Program, Vec<ParseError>) {
166        let mut program = Program::new();
167        let mut errors = Vec::new();
168
169        while !self.check(TokenType::Eof) {
170            match self.parse_statement(&mut program) {
171                Ok(()) => {}
172                Err(e) => {
173                    errors.push(e);
174                    // Skip to next statement boundary: `;` or a statement-starting keyword
175                    while !self.check(TokenType::Eof)
176                        && !self.check(TokenType::Semicolon)
177                        && !self.is_statement_start()
178                    {
179                        self.advance();
180                    }
181                    if self.check(TokenType::Semicolon) {
182                        self.advance(); // consume the ;
183                    }
184                    // Don't consume statement-starting keywords — they begin the next statement
185                }
186            }
187        }
188
189        (program, errors)
190    }
191
192    /// Parse a single top-level statement and add it to the Program.
193    fn parse_statement(&mut self, program: &mut Program) -> Result<(), ParseError> {
194        match self.peek_type() {
195            TokenType::Wire => self.parse_wire_or_destructuring(program),
196            TokenType::In => self.parse_in_decl_or_nothing(program),
197            TokenType::Out => self.parse_out_decl_or_assignment(program),
198            TokenType::Msg => self.parse_msg_declaration(program),
199            TokenType::Feedback => self.parse_feedback(program),
200            TokenType::State => self.parse_state(program),
201            TokenType::Identifier => self.parse_direct_connection(program),
202            _ => {
203                let tok = self.peek().clone();
204                Err(self.error(format!(
205                    "Unexpected token {:?} '{}'",
206                    tok.token_type, tok.lexeme
207                )))
208            }
209        }
210    }
211
212    // ── In declaration ───────────────────────────────────────
213    // Explicit: `in 0 (freq): float;`
214    // Implicit: `in freq: float;`
215
216    fn parse_in_decl_or_nothing(&mut self, program: &mut Program) -> Result<(), ParseError> {
217        // `in` keyword
218        self.expect(TokenType::In)?;
219
220        if self.check(TokenType::NumberLit) {
221            // Explicit: in 0 (name): type;
222            let index = self.parse_integer()?;
223            self.expect(TokenType::LParen)?;
224            let name = self.expect_identifier()?;
225            self.expect(TokenType::RParen)?;
226            self.expect(TokenType::Colon)?;
227            let port_type = self.parse_port_type()?;
228            self.expect(TokenType::Semicolon)?;
229
230            program.in_decls.push(InDecl {
231                index,
232                name,
233                port_type,
234            });
235        } else {
236            // Implicit: in name: type;
237            let name = self.expect_identifier()?;
238            self.expect(TokenType::Colon)?;
239            let port_type = self.parse_port_type()?;
240            self.expect(TokenType::Semicolon)?;
241
242            let index = self.implicit_in_index;
243            self.implicit_in_index += 1;
244
245            program.in_decls.push(InDecl {
246                index,
247                name,
248                port_type,
249            });
250        }
251
252        Ok(())
253    }
254
255    // ── Out declaration or assignment ────────────────────────
256    // Explicit: `out 0 (audio): signal;`
257    // Implicit: `out audio: signal;`
258    // Assignment: `out[0] = osc;`
259
260    fn parse_out_decl_or_assignment(&mut self, program: &mut Program) -> Result<(), ParseError> {
261        let start_tok = self.peek().clone();
262        self.expect(TokenType::Out)?;
263
264        if self.check(TokenType::LBracket) {
265            // out[0] = expr;
266            self.expect(TokenType::LBracket)?;
267            let index = self.parse_integer()?;
268            self.expect(TokenType::RBracket)?;
269            self.expect(TokenType::Eq)?;
270            let value = self.parse_expression()?;
271            let end_tok = self.expect(TokenType::Semicolon)?;
272
273            program.out_assignments.push(OutAssignment {
274                index,
275                value,
276                span: self.make_span_from(&start_tok, &end_tok),
277            });
278        } else if self.check(TokenType::NumberLit) {
279            // Explicit: out 0 (name): type; or out 0 (name): type = expr;
280            let index = self.parse_integer()?;
281            self.expect(TokenType::LParen)?;
282            let name = self.expect_identifier()?;
283            self.expect(TokenType::RParen)?;
284            self.expect(TokenType::Colon)?;
285            let port_type = self.parse_port_type()?;
286            let value = if self.check(TokenType::Eq) {
287                self.advance(); // consume =
288                Some(self.parse_expression()?)
289            } else {
290                None
291            };
292            self.expect(TokenType::Semicolon)?;
293
294            program.out_decls.push(OutDecl {
295                index,
296                name,
297                port_type,
298                value,
299            });
300        } else {
301            // Implicit: out name: type; or out name: type = expr;
302            let name = self.expect_identifier()?;
303            self.expect(TokenType::Colon)?;
304            let port_type = self.parse_port_type()?;
305            let value = if self.check(TokenType::Eq) {
306                self.advance(); // consume =
307                Some(self.parse_expression()?)
308            } else {
309                None
310            };
311            self.expect(TokenType::Semicolon)?;
312
313            let index = self.implicit_out_index;
314            self.implicit_out_index += 1;
315
316            program.out_decls.push(OutDecl {
317                index,
318                name,
319                port_type,
320                value,
321            });
322        }
323
324        Ok(())
325    }
326
327    // ── Wire or destructuring wire ───────────────────────────
328    // `wire osc = cycle~(440);`
329    // `wire (a, b) = unpack(x);`
330
331    fn parse_wire_or_destructuring(&mut self, program: &mut Program) -> Result<(), ParseError> {
332        let start_tok = self.peek().clone();
333        self.expect(TokenType::Wire)?;
334
335        if self.check(TokenType::LParen) {
336            // Destructuring wire: `wire (a, b, c) = expr;`
337            self.expect(TokenType::LParen)?;
338            let mut names = Vec::new();
339            names.push(self.expect_identifier()?);
340            while self.check(TokenType::Comma) {
341                self.advance(); // consume `,`
342                names.push(self.expect_identifier()?);
343            }
344            self.expect(TokenType::RParen)?;
345            self.expect(TokenType::Eq)?;
346            let value = self.parse_expression()?;
347            let end_tok = self.expect(TokenType::Semicolon)?;
348
349            program.destructuring_wires.push(DestructuringWire {
350                names,
351                value,
352                span: self.make_span_from(&start_tok, &end_tok),
353            });
354        } else {
355            // Regular wire: `wire name = expr (.attr(...))?;`
356            let name = self.expect_identifier()?;
357            self.expect(TokenType::Eq)?;
358            let value = self.parse_expression()?;
359
360            // Optional attr chain
361            let attrs = if self.check(TokenType::DotAttrLParen) {
362                self.parse_attr_chain()?
363            } else {
364                vec![]
365            };
366
367            let end_tok = self.expect(TokenType::Semicolon)?;
368
369            program.wires.push(Wire {
370                name,
371                value,
372                span: self.make_span_from(&start_tok, &end_tok),
373                attrs,
374            });
375        }
376
377        Ok(())
378    }
379
380    // ── Message declaration ──────────────────────────────────
381    // `msg click = "bang";`
382    // `msg click = "bang".attr(key: val);`
383
384    fn parse_msg_declaration(&mut self, program: &mut Program) -> Result<(), ParseError> {
385        let start_tok = self.peek().clone();
386        self.expect(TokenType::Msg)?;
387        let name = self.expect_identifier()?;
388        self.expect(TokenType::Eq)?;
389
390        let content_tok = self.expect(TokenType::StringLit)?;
391        let content = unescape_string_content(&content_tok.lexeme);
392
393        let attrs = if self.check(TokenType::DotAttrLParen) {
394            self.parse_attr_chain()?
395        } else {
396            vec![]
397        };
398
399        let end_tok = self.expect(TokenType::Semicolon)?;
400
401        program.msg_decls.push(MsgDecl {
402            name,
403            content,
404            span: self.make_span_from(&start_tok, &end_tok),
405            attrs,
406        });
407        Ok(())
408    }
409
410    // ── Feedback ─────────────────────────────────────────────
411    // `feedback fb: signal;`           — declaration
412    // `feedback fb = tapin~(mixed);`   — assignment
413
414    fn parse_feedback(&mut self, program: &mut Program) -> Result<(), ParseError> {
415        let start_tok = self.peek().clone();
416        self.expect(TokenType::Feedback)?;
417        let name = self.expect_identifier()?;
418
419        if self.check(TokenType::Colon) {
420            // Declaration: `feedback name: type;`
421            self.advance(); // consume `:`
422            let port_type = self.parse_port_type()?;
423            let end_tok = self.expect(TokenType::Semicolon)?;
424
425            program.feedback_decls.push(FeedbackDecl {
426                name,
427                port_type,
428                span: self.make_span_from(&start_tok, &end_tok),
429            });
430        } else {
431            // Assignment: `feedback name = expr;`
432            self.expect(TokenType::Eq)?;
433            let value = self.parse_expression()?;
434            let end_tok = self.expect(TokenType::Semicolon)?;
435
436            program.feedback_assignments.push(FeedbackAssignment {
437                target: name,
438                value,
439                span: self.make_span_from(&start_tok, &end_tok),
440            });
441        }
442
443        Ok(())
444    }
445
446    // ── State ────────────────────────────────────────────────
447    // `state counter: int = 0;`    — declaration
448    // `state counter = next;`      — assignment
449
450    fn parse_state(&mut self, program: &mut Program) -> Result<(), ParseError> {
451        let start_tok = self.peek().clone();
452        self.expect(TokenType::State)?;
453        let name = self.expect_identifier()?;
454
455        if self.check(TokenType::Colon) {
456            // Declaration: `state name: type = init;`
457            self.advance(); // consume `:`
458            let port_type = self.parse_control_type()?;
459            self.expect(TokenType::Eq)?;
460            let init_value = self.parse_expression()?;
461            let end_tok = self.expect(TokenType::Semicolon)?;
462
463            program.state_decls.push(StateDecl {
464                name,
465                port_type,
466                init_value,
467                span: self.make_span_from(&start_tok, &end_tok),
468            });
469        } else {
470            // Assignment: `state name = expr;`
471            self.expect(TokenType::Eq)?;
472            let value = self.parse_expression()?;
473            let end_tok = self.expect(TokenType::Semicolon)?;
474
475            program.state_assignments.push(StateAssignment {
476                name,
477                value,
478                span: self.make_span_from(&start_tok, &end_tok),
479            });
480        }
481
482        Ok(())
483    }
484
485    // ── Direct connection ────────────────────────────────────
486    // `node_a.in[0] = trigger;`
487    // The leading identifier has already been peeked.
488
489    fn parse_direct_connection(&mut self, program: &mut Program) -> Result<(), ParseError> {
490        let object = self.expect_identifier()?;
491        self.expect(TokenType::Dot)?;
492        self.expect(TokenType::In)?;
493        // [index] is optional — defaults to 0 (hot inlet)
494        let index = if self.check(TokenType::LBracket) {
495            self.advance(); // consume '['
496            let idx = self.parse_integer()?;
497            self.expect(TokenType::RBracket)?;
498            idx
499        } else {
500            0
501        };
502        self.expect(TokenType::Eq)?;
503        let value = self.parse_expression()?;
504        self.expect(TokenType::Semicolon)?;
505
506        program.direct_connections.push(DirectConnection {
507            target: InputPortAccess { object, index },
508            value,
509        });
510        Ok(())
511    }
512
513    // ── Expressions ──────────────────────────────────────────
514
515    /// Parse an expression:
516    /// - call_expr: `object_name(args)`
517    /// - output_port_access: `node.out[N]`
518    /// - tuple: `(x, y, z)` (2+ elements)
519    /// - ref: plain identifier (possibly dotted like `jit.gl.render` used as ref)
520    /// - literal: number, string
521    fn parse_expression(&mut self) -> Result<Expr, ParseError> {
522        match self.peek_type() {
523            TokenType::LParen => self.parse_tuple_expression(),
524            TokenType::NumberLit => self.parse_number_literal(),
525            TokenType::StringLit => self.parse_string_literal(),
526            TokenType::Identifier | TokenType::Operator => self.parse_name_based_expression(),
527            // Keywords used as identifiers in expression context:
528            // e.g., thispatcher(int), poly~(in), pack(float, float)
529            TokenType::In
530            | TokenType::Out
531            | TokenType::Int
532            | TokenType::Float
533            | TokenType::Bang
534            | TokenType::List
535            | TokenType::Symbol
536            | TokenType::Signal
537            | TokenType::State
538            | TokenType::Msg
539            | TokenType::Feedback
540            | TokenType::Wire => self.parse_name_based_expression(),
541            _ => {
542                let tok = self.peek().clone();
543                Err(self.error(format!(
544                    "Expected expression, got {:?} '{}'",
545                    tok.token_type, tok.lexeme
546                )))
547            }
548        }
549    }
550
551    /// Parse expressions starting with an identifier or operator:
552    /// Handles object calls, output port access, and simple refs.
553    fn parse_name_based_expression(&mut self) -> Result<Expr, ParseError> {
554        // Consume the full object/ref name: `ident(.ident)*` or `operator`
555        // Then check for `~`, then `(` for call, `.out[` for port access, or just ref.
556        let name = self.parse_object_name()?;
557
558        if self.check(TokenType::LParen) {
559            // Call expression: `name(args)`
560            self.advance(); // consume `(`
561            let args = if self.check(TokenType::RParen) {
562                vec![]
563            } else {
564                self.parse_argument_list()?
565            };
566            self.expect(TokenType::RParen)?;
567            Ok(Expr::Call { object: name, args })
568        } else if self.check(TokenType::Dot) {
569            // Could be `.out[N]` for output port access
570            // We already consumed the name; peek ahead to see if it's `.out[`
571            if self.peek_at(1).token_type == TokenType::Out
572                && self.peek_at(2).token_type == TokenType::LBracket
573            {
574                self.advance(); // consume `.`
575                self.advance(); // consume `out`
576                self.expect(TokenType::LBracket)?;
577                let index = self.parse_integer()?;
578                self.expect(TokenType::RBracket)?;
579                Ok(Expr::OutputPortAccess(OutputPortAccess {
580                    object: name,
581                    index,
582                }))
583            } else {
584                // Not port access — it's a dotted identifier that continues
585                // This shouldn't happen because parse_object_name already consumed dots
586                Ok(Expr::Ref(name))
587            }
588        } else {
589            // Simple ref
590            Ok(Expr::Ref(name))
591        }
592    }
593
594    /// Parse an object/identifier name, including dots and tilde:
595    /// `identifier ("." segment)* "~"?`
596    /// Also handles operator names like `?`, `*`, `+`, etc.
597    ///
598    /// Stops consuming dots before `.in[`, `.out[`, `.attr(`.
599    fn parse_object_name(&mut self) -> Result<String, ParseError> {
600        let mut name = String::new();
601
602        match self.peek_type() {
603            TokenType::Identifier | TokenType::Operator => {
604                name.push_str(&self.advance().lexeme.clone());
605            }
606            // Keywords used as object/reference names in expression context
607            TokenType::In
608            | TokenType::Out
609            | TokenType::Int
610            | TokenType::Float
611            | TokenType::Bang
612            | TokenType::List
613            | TokenType::Symbol
614            | TokenType::Signal
615            | TokenType::State
616            | TokenType::Msg
617            | TokenType::Feedback
618            | TokenType::Wire => {
619                name.push_str(&self.advance().lexeme.clone());
620            }
621            _ => {
622                return Err(self.error(format!(
623                    "Expected identifier or operator, got {:?}",
624                    self.peek_type()
625                )));
626            }
627        }
628
629        // Consume dotted segments: `.segment`
630        // Stop before `.in[`, `.out[`, `.attr(`
631        while self.check(TokenType::Dot) {
632            // Peek at what follows the dot
633            let after_dot = &self.peek_at(1);
634            match after_dot.token_type {
635                TokenType::In => {
636                    // `.in[` → stop, it's port access (handled by caller)
637                    if self.peek_at(2).token_type == TokenType::LBracket {
638                        break;
639                    }
640                    // `.in` not followed by `[` — treat as dotted segment (unusual but possible)
641                    self.advance(); // consume `.`
642                    name.push('.');
643                    name.push_str(&self.advance().lexeme.clone());
644                }
645                TokenType::Out => {
646                    // `.out[` → stop, it's port access
647                    if self.peek_at(2).token_type == TokenType::LBracket {
648                        break;
649                    }
650                    self.advance(); // consume `.`
651                    name.push('.');
652                    name.push_str(&self.advance().lexeme.clone());
653                }
654                TokenType::Identifier => {
655                    self.advance(); // consume `.`
656                    name.push('.');
657                    name.push_str(&self.advance().lexeme.clone());
658                }
659                TokenType::NumberLit => {
660                    // Digit-starting segment: `jit.3m`, `omx.5band~`
661                    // The lexer may have emitted this as a separate NumberLit token
662                    // followed by an Identifier for the rest.
663                    self.advance(); // consume `.`
664                    name.push('.');
665                    // Consume the number part
666                    let num_part = self.advance().lexeme.clone();
667                    name.push_str(&num_part);
668                    // If immediately followed by an identifier (no space), consume it too
669                    // e.g. `3` followed by `m` → `3m`
670                    if self.check(TokenType::Identifier) {
671                        // Check if they are adjacent (no whitespace between)
672                        let prev_end = self.tokens[self.pos - 1].column
673                            + self.tokens[self.pos - 1].lexeme.len();
674                        let next_start = self.peek().column;
675                        if prev_end == next_start
676                            && self.tokens[self.pos - 1].line == self.peek().line
677                        {
678                            name.push_str(&self.advance().lexeme.clone());
679                        }
680                    }
681                }
682                TokenType::Operator => {
683                    // Operator segment in dotted name: `mc.+~`, `mc.*~`
684                    self.advance(); // consume `.`
685                    name.push('.');
686                    name.push_str(&self.advance().lexeme.clone());
687                }
688                // Keywords as dotted segments: `jit.bang`, `live.float`, etc.
689                // Note: In/Out are handled above (with `.in[`/`.out[` lookahead)
690                TokenType::Bang
691                | TokenType::Float
692                | TokenType::Int
693                | TokenType::List
694                | TokenType::Symbol
695                | TokenType::Signal
696                | TokenType::State
697                | TokenType::Msg
698                | TokenType::Feedback
699                | TokenType::Wire => {
700                    self.advance(); // consume `.`
701                    name.push('.');
702                    name.push_str(&self.advance().lexeme.clone());
703                }
704                _ => break,
705            }
706        }
707
708        // Optional `=` suffix on dotted names (e.g., `gbr.wind=`)
709        // Only when `=` is adjacent (no space) and followed by `(` (call context)
710        if self.check(TokenType::Eq) && name.contains('.') {
711            let prev_end = self.previous().column + self.previous().lexeme.len();
712            let eq_col = self.peek().column;
713            if prev_end == eq_col
714                && self.previous().line == self.peek().line
715                && self.peek_at(1).token_type == TokenType::LParen
716            {
717                self.advance(); // consume `=`
718                name.push('=');
719            }
720        }
721
722        // Optional tilde suffix
723        if self.check(TokenType::Tilde) {
724            // Check adjacency: tilde must be immediately after the name
725            let prev_end_col = self.previous().column + self.previous().lexeme.len();
726            let tilde_col = self.peek().column;
727            if prev_end_col == tilde_col && self.previous().line == self.peek().line {
728                self.advance(); // consume `~`
729                name.push('~');
730            }
731        }
732
733        Ok(name)
734    }
735
736    /// Parse a comma-separated list of call arguments (positional or named).
737    fn parse_argument_list(&mut self) -> Result<Vec<CallArg>, ParseError> {
738        let mut args = Vec::new();
739        args.push(self.parse_call_arg()?);
740        while self.check(TokenType::Comma) {
741            self.advance(); // consume `,`
742            args.push(self.parse_call_arg()?);
743        }
744        Ok(args)
745    }
746
747    /// Parse a single call argument: either `name: expr` (named) or `expr` (positional).
748    fn parse_call_arg(&mut self) -> Result<CallArg, ParseError> {
749        // Check for named argument: identifier followed by ':'
750        // We must ensure this isn't confused with port type declarations.
751        if self.check_named_arg_ahead() {
752            let name = self.expect_identifier()?;
753            self.expect(TokenType::Colon)?;
754            let value = self.parse_expression()?;
755            Ok(CallArg::named(name, value))
756        } else {
757            let value = self.parse_expression()?;
758            Ok(CallArg::positional(value))
759        }
760    }
761
762    /// Look ahead to detect `identifier ":"` pattern for named arguments.
763    /// Returns true if current token is an identifier (or contextual identifier)
764    /// followed by a colon, AND the token after the colon is NOT a port type keyword
765    /// (to avoid confusing `in freq: float;` with named args — though that
766    /// context doesn't arise inside call argument lists).
767    fn check_named_arg_ahead(&self) -> bool {
768        let cur = self.peek_type();
769        if cur != TokenType::Identifier && !is_contextual_identifier(cur) {
770            return false;
771        }
772        if self.peek_at(1).token_type != TokenType::Colon {
773            return false;
774        }
775        true
776    }
777
778    /// Parse a tuple expression: `(expr, expr, ...)`
779    /// Must have at least 2 elements.
780    fn parse_tuple_expression(&mut self) -> Result<Expr, ParseError> {
781        self.expect(TokenType::LParen)?;
782        let mut elements = Vec::new();
783        elements.push(self.parse_expression()?);
784
785        // Must have at least one comma for it to be a tuple
786        if !self.check(TokenType::Comma) {
787            return Err(self.error("Tuple must have at least 2 elements".to_string()));
788        }
789        while self.check(TokenType::Comma) {
790            self.advance();
791            elements.push(self.parse_expression()?);
792        }
793        self.expect(TokenType::RParen)?;
794
795        Ok(Expr::Tuple(elements))
796    }
797
798    fn parse_number_literal(&mut self) -> Result<Expr, ParseError> {
799        let tok = self.expect(TokenType::NumberLit)?;
800        let text = &tok.lexeme;
801
802        if text.contains('.') || text.contains('e') || text.contains('E') {
803            let val: f64 = text.parse().map_err(|_| ParseError::Syntax {
804                message: format!("Invalid float literal '{}'", text),
805                line: tok.line,
806                column: tok.column,
807            })?;
808            Ok(Expr::Lit(LitValue::Float(val)))
809        } else {
810            let val: i64 = text.parse().map_err(|_| ParseError::Syntax {
811                message: format!("Invalid integer literal '{}'", text),
812                line: tok.line,
813                column: tok.column,
814            })?;
815            Ok(Expr::Lit(LitValue::Int(val)))
816        }
817    }
818
819    fn parse_string_literal(&mut self) -> Result<Expr, ParseError> {
820        let tok = self.expect(TokenType::StringLit)?;
821        let content = unescape_string_content(&tok.lexeme);
822        Ok(Expr::Lit(LitValue::Str(content)))
823    }
824
825    // ── Attribute chain ──────────────────────────────────────
826    // `.attr(key: value, ...)`
827
828    fn parse_attr_chain(&mut self) -> Result<Vec<AttrPair>, ParseError> {
829        self.expect(TokenType::DotAttrLParen)?;
830        let mut pairs = Vec::new();
831
832        if !self.check(TokenType::RParen) {
833            pairs.push(self.parse_attr_pair()?);
834            while self.check(TokenType::Comma) {
835                self.advance();
836                pairs.push(self.parse_attr_pair()?);
837            }
838        }
839
840        self.expect(TokenType::RParen)?;
841        Ok(pairs)
842    }
843
844    fn parse_attr_pair(&mut self) -> Result<AttrPair, ParseError> {
845        let key = self.expect_identifier()?;
846        self.expect(TokenType::Colon)?;
847        let value = self.parse_attr_value()?;
848        Ok(AttrPair { key, value })
849    }
850
851    fn parse_attr_value(&mut self) -> Result<AttrValue, ParseError> {
852        match self.peek_type() {
853            TokenType::NumberLit => {
854                let tok = self.advance().clone();
855                let text = &tok.lexeme;
856                if text.contains('.') || text.contains('e') || text.contains('E') {
857                    let val: f64 = text.parse().map_err(|_| ParseError::Syntax {
858                        message: format!("Invalid float literal '{}'", text),
859                        line: tok.line,
860                        column: tok.column,
861                    })?;
862                    Ok(AttrValue::Float(val))
863                } else {
864                    let val: i64 = text.parse().map_err(|_| ParseError::Syntax {
865                        message: format!("Invalid integer literal '{}'", text),
866                        line: tok.line,
867                        column: tok.column,
868                    })?;
869                    Ok(AttrValue::Int(val))
870                }
871            }
872            TokenType::StringLit => {
873                let tok = self.advance().clone();
874                let content = unescape_string_content(&tok.lexeme);
875                Ok(AttrValue::Str(content))
876            }
877            TokenType::Identifier => {
878                let tok = self.advance().clone();
879                Ok(AttrValue::Ident(tok.lexeme))
880            }
881            _ => Err(self.error(format!(
882                "Expected attribute value (number, string, or identifier), got {:?}",
883                self.peek_type()
884            ))),
885        }
886    }
887
888    // ── Utility parsers ──────────────────────────────────────
889
890    /// Parse an integer from a NumberLit token.
891    fn parse_integer(&mut self) -> Result<u32, ParseError> {
892        let tok = self.expect(TokenType::NumberLit)?;
893        tok.lexeme.parse().map_err(|_| ParseError::Syntax {
894            message: format!("Expected integer, got '{}'", tok.lexeme),
895            line: tok.line,
896            column: tok.column,
897        })
898    }
899
900    /// Expect an identifier token and return its text.
901    /// Accepts `Identifier` token type, and also keyword tokens when used
902    /// in identifier position (e.g., `wire msg = ...` where `msg` is both
903    /// a keyword and a valid wire name).
904    fn expect_identifier(&mut self) -> Result<String, ParseError> {
905        let tok = self.peek().clone();
906        if tok.token_type == TokenType::Identifier || is_contextual_identifier(tok.token_type) {
907            self.advance();
908            Ok(tok.lexeme)
909        } else {
910            Err(self.error(format!(
911                "Expected identifier, got {:?} '{}'",
912                tok.token_type, tok.lexeme
913            )))
914        }
915    }
916
917    /// Parse a port type keyword: signal, float, int, bang, list, symbol.
918    fn parse_port_type(&mut self) -> Result<PortType, ParseError> {
919        let tok = self.peek().clone();
920        let pt = match tok.token_type {
921            TokenType::Signal => PortType::Signal,
922            TokenType::Float => PortType::Float,
923            TokenType::Int => PortType::Int,
924            TokenType::Bang => PortType::Bang,
925            TokenType::List => PortType::List,
926            TokenType::Symbol => PortType::Symbol,
927            _ => {
928                return Err(self.error(format!(
929                    "Expected port type (signal/float/int/bang/list/symbol), got '{}'",
930                    tok.lexeme
931                )));
932            }
933        };
934        self.advance();
935        Ok(pt)
936    }
937
938    /// Parse a control type keyword (same as port_type but without signal).
939    fn parse_control_type(&mut self) -> Result<PortType, ParseError> {
940        let tok = self.peek().clone();
941        let pt = match tok.token_type {
942            TokenType::Float => PortType::Float,
943            TokenType::Int => PortType::Int,
944            TokenType::Bang => PortType::Bang,
945            TokenType::List => PortType::List,
946            TokenType::Symbol => PortType::Symbol,
947            _ => {
948                return Err(self.error(format!(
949                    "Expected control type (float/int/bang/list/symbol), got '{}'",
950                    tok.lexeme
951                )));
952            }
953        };
954        self.advance();
955        Ok(pt)
956    }
957}
958
959// ────────────────────────────────────────────────────────────
960// Contextual identifiers
961// ────────────────────────────────────────────────────────────
962
963/// Keywords that can also be used as identifiers in certain contexts
964/// (e.g., wire names, object names, attribute keys).
965/// This excludes structural keywords like `wire`, `in`, `out`, `state`, `feedback`
966/// which have unambiguous syntactic roles at statement level.
967fn is_contextual_identifier(tt: TokenType) -> bool {
968    matches!(
969        tt,
970        TokenType::Msg
971            | TokenType::Signal
972            | TokenType::Float
973            | TokenType::Int
974            | TokenType::Bang
975            | TokenType::List
976            | TokenType::Symbol
977            | TokenType::State
978            | TokenType::Feedback
979            | TokenType::In
980            | TokenType::Out
981            | TokenType::Wire
982    )
983}
984
985// ────────────────────────────────────────────────────────────
986// String unescaping
987// ────────────────────────────────────────────────────────────
988
989/// Strip surrounding quotes from a string token lexeme and unescape.
990fn unescape_string_content(lexeme: &str) -> String {
991    // Remove surrounding quotes
992    let inner = &lexeme[1..lexeme.len() - 1];
993    let mut result = String::with_capacity(inner.len());
994    let mut chars = inner.chars();
995    while let Some(ch) = chars.next() {
996        if ch == '\\' {
997            match chars.next() {
998                Some('n') => result.push('\n'),
999                Some('t') => result.push('\t'),
1000                Some('\\') => result.push('\\'),
1001                Some('"') => result.push('"'),
1002                Some(other) => {
1003                    result.push('\\');
1004                    result.push(other);
1005                }
1006                None => result.push('\\'),
1007            }
1008        } else {
1009            result.push(ch);
1010        }
1011    }
1012    result
1013}
1014
1015// ────────────────────────────────────────────────────────────
1016// Tests
1017// ────────────────────────────────────────────────────────────
1018
1019#[cfg(test)]
1020mod tests {
1021    use super::*;
1022
1023    // Shorthand
1024    fn parse(source: &str) -> Program {
1025        FlutmaxParser::parse(source).expect("parse failed")
1026    }
1027
1028    // ── L1: minimal ──────────────────────────────────────────
1029
1030    #[test]
1031    fn test_l1_minimal() {
1032        let prog = parse(
1033            r#"
1034out 0 (audio): signal;
1035wire osc = cycle~(440);
1036out[0] = osc;
1037"#,
1038        );
1039
1040        assert_eq!(prog.out_decls.len(), 1);
1041        assert_eq!(prog.out_decls[0].index, 0);
1042        assert_eq!(prog.out_decls[0].name, "audio");
1043        assert_eq!(prog.out_decls[0].port_type, PortType::Signal);
1044
1045        assert_eq!(prog.wires.len(), 1);
1046        assert_eq!(prog.wires[0].name, "osc");
1047        assert_eq!(
1048            prog.wires[0].value,
1049            Expr::Call {
1050                object: "cycle~".to_string(),
1051                args: vec![CallArg::positional(Expr::Lit(LitValue::Int(440)))],
1052            }
1053        );
1054        assert!(prog.wires[0].span.is_some());
1055
1056        assert_eq!(prog.out_assignments.len(), 1);
1057        assert_eq!(prog.out_assignments[0].index, 0);
1058        assert_eq!(prog.out_assignments[0].value, Expr::Ref("osc".to_string()));
1059        assert!(prog.out_assignments[0].span.is_some());
1060
1061        assert!(prog.in_decls.is_empty());
1062        assert!(prog.direct_connections.is_empty());
1063    }
1064
1065    // ── L2: simple synth ─────────────────────────────────────
1066
1067    #[test]
1068    fn test_l2_simple_synth() {
1069        let prog = parse(
1070            r#"
1071// L2_simple_synth.flutmax
1072in 0 (freq): float;
1073out 0 (audio): signal;
1074
1075wire osc = cycle~(freq);
1076wire amp = mul~(osc, 0.5);
1077
1078out[0] = amp;
1079"#,
1080        );
1081
1082        assert_eq!(prog.in_decls.len(), 1);
1083        assert_eq!(prog.in_decls[0].name, "freq");
1084        assert_eq!(prog.in_decls[0].port_type, PortType::Float);
1085
1086        assert_eq!(prog.out_decls.len(), 1);
1087        assert_eq!(prog.wires.len(), 2);
1088        assert_eq!(prog.wires[0].name, "osc");
1089        assert_eq!(
1090            prog.wires[0].value,
1091            Expr::Call {
1092                object: "cycle~".to_string(),
1093                args: vec![CallArg::positional(Expr::Ref("freq".to_string()))],
1094            }
1095        );
1096        assert_eq!(prog.wires[1].name, "amp");
1097        assert_eq!(
1098            prog.wires[1].value,
1099            Expr::Call {
1100                object: "mul~".to_string(),
1101                args: vec![
1102                    CallArg::positional(Expr::Ref("osc".to_string())),
1103                    CallArg::positional(Expr::Lit(LitValue::Float(0.5))),
1104                ],
1105            }
1106        );
1107        assert_eq!(prog.out_assignments.len(), 1);
1108    }
1109
1110    // ── L3b: control fanout ──────────────────────────────────
1111
1112    #[test]
1113    fn test_l3b_control_fanout() {
1114        let prog = parse(
1115            r#"
1116wire trigger = button();
1117wire counter = counter(trigger);
1118wire msg = print(counter);
1119
1120node_a.in[0] = trigger;
1121node_b.in[0] = trigger;
1122"#,
1123        );
1124
1125        assert_eq!(prog.wires.len(), 3);
1126        assert_eq!(
1127            prog.wires[0].value,
1128            Expr::Call {
1129                object: "button".to_string(),
1130                args: vec![],
1131            }
1132        );
1133
1134        assert_eq!(prog.direct_connections.len(), 2);
1135        assert_eq!(prog.direct_connections[0].target.object, "node_a");
1136        assert_eq!(prog.direct_connections[0].target.index, 0);
1137        assert_eq!(
1138            prog.direct_connections[0].value,
1139            Expr::Ref("trigger".to_string())
1140        );
1141        assert_eq!(prog.direct_connections[1].target.object, "node_b");
1142    }
1143
1144    // ── Multiple ports ───────────────────────────────────────
1145
1146    #[test]
1147    fn test_multiple_in_out_ports() {
1148        let prog = parse(
1149            r#"
1150in 0 (input_sig): signal;
1151in 1 (cutoff): float;
1152in 2 (q_factor): float;
1153
1154out 0 (lowpass): signal;
1155out 1 (highpass): signal;
1156"#,
1157        );
1158
1159        assert_eq!(prog.in_decls.len(), 3);
1160        assert_eq!(prog.in_decls[0].name, "input_sig");
1161        assert_eq!(prog.in_decls[0].port_type, PortType::Signal);
1162        assert_eq!(prog.in_decls[1].name, "cutoff");
1163        assert_eq!(prog.in_decls[1].port_type, PortType::Float);
1164        assert_eq!(prog.in_decls[2].name, "q_factor");
1165
1166        assert_eq!(prog.out_decls.len(), 2);
1167        assert_eq!(prog.out_decls[0].name, "lowpass");
1168        assert_eq!(prog.out_decls[1].name, "highpass");
1169    }
1170
1171    // ── Nested call ──────────────────────────────────────────
1172
1173    #[test]
1174    fn test_nested_object_call() {
1175        let prog = parse("wire sig = biquad~(cycle~(440), 1000, 0.7);");
1176
1177        assert_eq!(prog.wires.len(), 1);
1178        assert_eq!(
1179            prog.wires[0].value,
1180            Expr::Call {
1181                object: "biquad~".to_string(),
1182                args: vec![
1183                    CallArg::positional(Expr::Call {
1184                        object: "cycle~".to_string(),
1185                        args: vec![CallArg::positional(Expr::Lit(LitValue::Int(440)))],
1186                    }),
1187                    CallArg::positional(Expr::Lit(LitValue::Int(1000))),
1188                    CallArg::positional(Expr::Lit(LitValue::Float(0.7))),
1189                ],
1190            }
1191        );
1192    }
1193
1194    // ── String literal ───────────────────────────────────────
1195
1196    #[test]
1197    fn test_string_literal() {
1198        let prog = parse(r#"wire msg = print("hello world");"#);
1199        assert_eq!(
1200            prog.wires[0].value,
1201            Expr::Call {
1202                object: "print".to_string(),
1203                args: vec![CallArg::positional(Expr::Lit(LitValue::Str(
1204                    "hello world".to_string()
1205                )))],
1206            }
1207        );
1208    }
1209
1210    // ── Zero-arg call ────────────────────────────────────────
1211
1212    #[test]
1213    fn test_zero_arg_object_call() {
1214        let prog = parse("wire btn = button();");
1215        assert_eq!(
1216            prog.wires[0].value,
1217            Expr::Call {
1218                object: "button".to_string(),
1219                args: vec![],
1220            }
1221        );
1222    }
1223
1224    // ── Empty source ─────────────────────────────────────────
1225
1226    #[test]
1227    fn test_empty_source() {
1228        let prog = parse("");
1229        assert!(prog.in_decls.is_empty());
1230        assert!(prog.out_decls.is_empty());
1231        assert!(prog.wires.is_empty());
1232        assert!(prog.out_assignments.is_empty());
1233        assert!(prog.direct_connections.is_empty());
1234    }
1235
1236    // ── Comments ignored ─────────────────────────────────────
1237
1238    #[test]
1239    fn test_comments_ignored() {
1240        let prog = parse(
1241            r#"
1242// This is a comment
1243wire osc = cycle~(440);
1244// Another comment
1245out 0 (audio): signal;
1246"#,
1247        );
1248        assert_eq!(prog.wires.len(), 1);
1249        assert_eq!(prog.out_decls.len(), 1);
1250    }
1251
1252    // ── Span populated ──────────────────────────────────────
1253
1254    #[test]
1255    fn test_span_populated() {
1256        let prog = parse("wire osc = cycle~(440);");
1257        assert!(prog.wires[0].span.is_some());
1258        let span = prog.wires[0].span.as_ref().unwrap();
1259        assert_eq!(span.start_line, 1);
1260        assert_eq!(span.start_column, 1);
1261    }
1262
1263    #[test]
1264    fn test_out_assignment_span_populated() {
1265        let prog = parse("out 0 (audio): signal;\nwire osc = cycle~(440);\nout[0] = osc;");
1266        assert!(prog.out_assignments[0].span.is_some());
1267        let span = prog.out_assignments[0].span.as_ref().unwrap();
1268        assert_eq!(span.start_line, 3);
1269    }
1270
1271    // ── Tuple / Destructuring ────────────────────────────────
1272
1273    #[test]
1274    fn test_tuple_expression() {
1275        let prog = parse("wire t = (a, b, c);");
1276        assert_eq!(
1277            prog.wires[0].value,
1278            Expr::Tuple(vec![
1279                Expr::Ref("a".to_string()),
1280                Expr::Ref("b".to_string()),
1281                Expr::Ref("c".to_string()),
1282            ])
1283        );
1284    }
1285
1286    #[test]
1287    fn test_tuple_two_elements() {
1288        let prog = parse("wire pair = (x, y);");
1289        assert_eq!(
1290            prog.wires[0].value,
1291            Expr::Tuple(vec![Expr::Ref("x".to_string()), Expr::Ref("y".to_string()),])
1292        );
1293    }
1294
1295    #[test]
1296    fn test_tuple_with_literals() {
1297        let prog = parse("wire nums = (1, 2, 3);");
1298        assert_eq!(
1299            prog.wires[0].value,
1300            Expr::Tuple(vec![
1301                Expr::Lit(LitValue::Int(1)),
1302                Expr::Lit(LitValue::Int(2)),
1303                Expr::Lit(LitValue::Int(3)),
1304            ])
1305        );
1306    }
1307
1308    #[test]
1309    fn test_destructuring_wire() {
1310        let prog = parse("wire (a, b, c) = unpack(coords);");
1311        assert_eq!(prog.destructuring_wires.len(), 1);
1312        let dw = &prog.destructuring_wires[0];
1313        assert_eq!(dw.names, vec!["a", "b", "c"]);
1314        assert_eq!(
1315            dw.value,
1316            Expr::Call {
1317                object: "unpack".to_string(),
1318                args: vec![CallArg::positional(Expr::Ref("coords".to_string()))],
1319            }
1320        );
1321        assert!(dw.span.is_some());
1322    }
1323
1324    #[test]
1325    fn test_destructuring_wire_two_names() {
1326        let prog = parse("wire (x, y) = data;");
1327        assert_eq!(prog.destructuring_wires.len(), 1);
1328        let dw = &prog.destructuring_wires[0];
1329        assert_eq!(dw.names, vec!["x", "y"]);
1330        assert_eq!(dw.value, Expr::Ref("data".to_string()));
1331    }
1332
1333    #[test]
1334    fn test_destructuring_wire_with_tuple_value() {
1335        let prog = parse("wire (a, b) = (x, y);");
1336        let dw = &prog.destructuring_wires[0];
1337        assert_eq!(dw.names, vec!["a", "b"]);
1338        assert_eq!(
1339            dw.value,
1340            Expr::Tuple(vec![Expr::Ref("x".to_string()), Expr::Ref("y".to_string()),])
1341        );
1342    }
1343
1344    #[test]
1345    fn test_l4_tuple_full() {
1346        let prog = parse(
1347            r#"
1348in 0 (x): float;
1349in 1 (y): float;
1350in 2 (z): float;
1351out 0 (coords): list;
1352
1353wire packed = (x, y, z);
1354out[0] = packed;
1355"#,
1356        );
1357
1358        assert_eq!(prog.in_decls.len(), 3);
1359        assert_eq!(prog.out_decls.len(), 1);
1360        assert_eq!(prog.wires.len(), 1);
1361        assert_eq!(
1362            prog.wires[0].value,
1363            Expr::Tuple(vec![
1364                Expr::Ref("x".to_string()),
1365                Expr::Ref("y".to_string()),
1366                Expr::Ref("z".to_string()),
1367            ])
1368        );
1369        assert_eq!(prog.out_assignments.len(), 1);
1370    }
1371
1372    #[test]
1373    fn test_l5_destructure_full() {
1374        let prog = parse(
1375            r#"
1376in 0 (coords): list;
1377out 0 (x): float;
1378out 1 (y): float;
1379
1380wire (a, b) = unpack(coords);
1381out[0] = a;
1382out[1] = b;
1383"#,
1384        );
1385
1386        assert_eq!(prog.in_decls.len(), 1);
1387        assert_eq!(prog.out_decls.len(), 2);
1388        assert_eq!(prog.destructuring_wires.len(), 1);
1389        assert_eq!(prog.destructuring_wires[0].names, vec!["a", "b"]);
1390        assert_eq!(prog.out_assignments.len(), 2);
1391    }
1392
1393    // ── Feedback ─────────────────────────────────────────────
1394
1395    #[test]
1396    fn test_feedback_declaration() {
1397        let prog = parse("feedback fb: signal;");
1398        assert_eq!(prog.feedback_decls.len(), 1);
1399        assert_eq!(prog.feedback_decls[0].name, "fb");
1400        assert_eq!(prog.feedback_decls[0].port_type, PortType::Signal);
1401        assert!(prog.feedback_decls[0].span.is_some());
1402    }
1403
1404    #[test]
1405    fn test_feedback_assignment() {
1406        let prog = parse("feedback fb = tapin~(mixed, 1000);");
1407        assert_eq!(prog.feedback_assignments.len(), 1);
1408        assert_eq!(prog.feedback_assignments[0].target, "fb");
1409        assert_eq!(
1410            prog.feedback_assignments[0].value,
1411            Expr::Call {
1412                object: "tapin~".to_string(),
1413                args: vec![
1414                    CallArg::positional(Expr::Ref("mixed".to_string())),
1415                    CallArg::positional(Expr::Lit(LitValue::Int(1000))),
1416                ],
1417            }
1418        );
1419        assert!(prog.feedback_assignments[0].span.is_some());
1420    }
1421
1422    #[test]
1423    fn test_feedback_full_patch() {
1424        let prog = parse(
1425            r#"
1426in 0 (input): signal;
1427out 0 (output): signal;
1428
1429feedback fb: signal;
1430wire delayed = tapout~(fb, 500);
1431wire mixed = add~(input, mul~(delayed, 0.3));
1432feedback fb = tapin~(mixed, 1000);
1433out[0] = mixed;
1434"#,
1435        );
1436
1437        assert_eq!(prog.feedback_decls.len(), 1);
1438        assert_eq!(prog.feedback_decls[0].name, "fb");
1439        assert_eq!(prog.wires.len(), 2);
1440        assert_eq!(prog.feedback_assignments.len(), 1);
1441        assert_eq!(prog.out_assignments.len(), 1);
1442    }
1443
1444    // ── Output port access ───────────────────────────────────
1445
1446    #[test]
1447    fn test_output_port_access_in_wire() {
1448        let prog = parse("wire x = node.out[0];");
1449        match &prog.wires[0].value {
1450            Expr::OutputPortAccess(opa) => {
1451                assert_eq!(opa.object, "node");
1452                assert_eq!(opa.index, 0);
1453            }
1454            other => panic!("expected OutputPortAccess, got {:?}", other),
1455        }
1456    }
1457
1458    #[test]
1459    fn test_output_port_access_in_call_arg() {
1460        let prog = parse("wire y = mul~(node.out[0], 0.5);");
1461        if let Expr::Call { args, .. } = &prog.wires[0].value {
1462            match &args[0].value {
1463                Expr::OutputPortAccess(opa) => {
1464                    assert_eq!(opa.object, "node");
1465                    assert_eq!(opa.index, 0);
1466                }
1467                other => panic!("expected OutputPortAccess, got {:?}", other),
1468            }
1469        } else {
1470            panic!("expected Call");
1471        }
1472    }
1473
1474    #[test]
1475    fn test_output_port_access_higher_index() {
1476        let prog = parse("wire z = node.out[2];");
1477        match &prog.wires[0].value {
1478            Expr::OutputPortAccess(opa) => {
1479                assert_eq!(opa.object, "node");
1480                assert_eq!(opa.index, 2);
1481            }
1482            other => panic!("expected OutputPortAccess, got {:?}", other),
1483        }
1484    }
1485
1486    #[test]
1487    fn test_input_port_access_in_direct_connection() {
1488        let prog = parse("node_a.in[0] = trigger;");
1489        assert_eq!(prog.direct_connections.len(), 1);
1490        assert_eq!(prog.direct_connections[0].target.object, "node_a");
1491        assert_eq!(prog.direct_connections[0].target.index, 0);
1492    }
1493
1494    #[test]
1495    fn test_direct_connection_index_omitted() {
1496        // .in without [N] defaults to inlet 0
1497        let prog = parse("tap_l.in = add~(input, fb_r);");
1498        assert_eq!(prog.direct_connections.len(), 1);
1499        assert_eq!(prog.direct_connections[0].target.object, "tap_l");
1500        assert_eq!(prog.direct_connections[0].target.index, 0);
1501    }
1502
1503    #[test]
1504    fn test_direct_connection_index_explicit() {
1505        // .in[2] still works
1506        let prog = parse("filter.in[2] = resonance;");
1507        assert_eq!(prog.direct_connections.len(), 1);
1508        assert_eq!(prog.direct_connections[0].target.object, "filter");
1509        assert_eq!(prog.direct_connections[0].target.index, 2);
1510    }
1511
1512    // ── State ────────────────────────────────────────────────
1513
1514    #[test]
1515    fn test_state_declaration_int() {
1516        let prog = parse("state counter: int = 0;");
1517        assert_eq!(prog.state_decls.len(), 1);
1518        assert_eq!(prog.state_decls[0].name, "counter");
1519        assert_eq!(prog.state_decls[0].port_type, PortType::Int);
1520        assert_eq!(prog.state_decls[0].init_value, Expr::Lit(LitValue::Int(0)));
1521        assert!(prog.state_decls[0].span.is_some());
1522    }
1523
1524    #[test]
1525    fn test_state_declaration_float() {
1526        let prog = parse("state volume: float = 0.5;");
1527        assert_eq!(prog.state_decls[0].name, "volume");
1528        assert_eq!(prog.state_decls[0].port_type, PortType::Float);
1529        assert_eq!(
1530            prog.state_decls[0].init_value,
1531            Expr::Lit(LitValue::Float(0.5))
1532        );
1533    }
1534
1535    #[test]
1536    fn test_state_assignment() {
1537        let prog = parse("state counter = next;");
1538        assert_eq!(prog.state_assignments.len(), 1);
1539        assert_eq!(prog.state_assignments[0].name, "counter");
1540        assert_eq!(
1541            prog.state_assignments[0].value,
1542            Expr::Ref("next".to_string())
1543        );
1544        assert!(prog.state_assignments[0].span.is_some());
1545    }
1546
1547    #[test]
1548    fn test_state_assignment_with_call() {
1549        let prog = parse("state counter = add(counter, 1);");
1550        assert_eq!(
1551            prog.state_assignments[0].value,
1552            Expr::Call {
1553                object: "add".to_string(),
1554                args: vec![
1555                    CallArg::positional(Expr::Ref("counter".to_string())),
1556                    CallArg::positional(Expr::Lit(LitValue::Int(1))),
1557                ],
1558            }
1559        );
1560    }
1561
1562    #[test]
1563    fn test_state_full_counter_patch() {
1564        let prog = parse(
1565            r#"
1566state counter: int = 0;
1567wire next = add(counter, 1);
1568state counter = next;
1569out 0 (count): int;
1570out[0] = next;
1571"#,
1572        );
1573        assert_eq!(prog.state_decls.len(), 1);
1574        assert_eq!(prog.wires.len(), 1);
1575        assert_eq!(prog.state_assignments.len(), 1);
1576        assert_eq!(prog.out_decls.len(), 1);
1577        assert_eq!(prog.out_assignments.len(), 1);
1578    }
1579
1580    // ── Dotted identifiers ───────────────────────────────────
1581
1582    #[test]
1583    fn test_dotted_identifier_object_call() {
1584        let prog = parse("wire vid = jit.gl.videoplane();");
1585        assert_eq!(
1586            prog.wires[0].value,
1587            Expr::Call {
1588                object: "jit.gl.videoplane".to_string(),
1589                args: vec![],
1590            }
1591        );
1592    }
1593
1594    #[test]
1595    fn test_dotted_identifier_with_args() {
1596        let prog = parse("wire dial = live.dial(0.5);");
1597        assert_eq!(
1598            prog.wires[0].value,
1599            Expr::Call {
1600                object: "live.dial".to_string(),
1601                args: vec![CallArg::positional(Expr::Lit(LitValue::Float(0.5)))],
1602            }
1603        );
1604    }
1605
1606    #[test]
1607    fn test_dotted_identifier_does_not_conflict_with_port_access() {
1608        let prog = parse(
1609            r#"
1610wire node = cycle~(440);
1611wire x = node.out[0];
1612node.in[0] = 440;
1613"#,
1614        );
1615
1616        assert_eq!(prog.wires.len(), 2);
1617        assert_eq!(
1618            prog.wires[1].value,
1619            Expr::OutputPortAccess(OutputPortAccess {
1620                object: "node".to_string(),
1621                index: 0,
1622            })
1623        );
1624        assert_eq!(prog.direct_connections.len(), 1);
1625        assert_eq!(prog.direct_connections[0].target.object, "node");
1626        assert_eq!(prog.direct_connections[0].target.index, 0);
1627    }
1628
1629    // ── Hyphenated identifiers ───────────────────────────────
1630
1631    #[test]
1632    fn test_hyphenated_identifier() {
1633        let prog = parse("wire x = drunk-walk(10);");
1634        assert_eq!(
1635            prog.wires[0].value,
1636            Expr::Call {
1637                object: "drunk-walk".to_string(),
1638                args: vec![CallArg::positional(Expr::Lit(LitValue::Int(10)))],
1639            }
1640        );
1641    }
1642
1643    // ── Message declarations ─────────────────────────────────
1644
1645    #[test]
1646    fn test_msg_declaration() {
1647        let prog = parse(r#"msg click = "bang";"#);
1648        assert_eq!(prog.msg_decls.len(), 1);
1649        assert_eq!(prog.msg_decls[0].name, "click");
1650        assert_eq!(prog.msg_decls[0].content, "bang");
1651        assert!(prog.msg_decls[0].span.is_some());
1652    }
1653
1654    #[test]
1655    fn test_msg_declaration_with_template() {
1656        let prog = parse(r#"msg format = "set $1 $2";"#);
1657        assert_eq!(prog.msg_decls[0].content, "set $1 $2");
1658    }
1659
1660    #[test]
1661    fn test_msg_declaration_multiple() {
1662        let prog = parse(
1663            r#"
1664msg bang_msg = "bang";
1665msg set_msg = "set 42";
1666"#,
1667        );
1668        assert_eq!(prog.msg_decls.len(), 2);
1669        assert_eq!(prog.msg_decls[0].content, "bang");
1670        assert_eq!(prog.msg_decls[1].content, "set 42");
1671    }
1672
1673    #[test]
1674    fn test_msg_with_wire_and_connection() {
1675        let prog = parse(
1676            r#"
1677msg click = "bang";
1678wire btn = button();
1679btn.in[0] = click;
1680"#,
1681        );
1682        assert_eq!(prog.msg_decls.len(), 1);
1683        assert_eq!(prog.wires.len(), 1);
1684        assert_eq!(prog.direct_connections.len(), 1);
1685        assert_eq!(
1686            prog.direct_connections[0].value,
1687            Expr::Ref("click".to_string())
1688        );
1689    }
1690
1691    // ── Attr chain ───────────────────────────────────────────
1692
1693    #[test]
1694    fn test_wire_with_attrs() {
1695        let prog = parse(r#"wire w = flonum(x).attr(minimum: 0., maximum: 100.);"#);
1696        let wire = &prog.wires[0];
1697        assert_eq!(wire.attrs.len(), 2);
1698        assert_eq!(wire.attrs[0].key, "minimum");
1699        assert_eq!(wire.attrs[0].value, AttrValue::Float(0.0));
1700        assert_eq!(wire.attrs[1].key, "maximum");
1701        assert_eq!(wire.attrs[1].value, AttrValue::Float(100.0));
1702    }
1703
1704    #[test]
1705    fn test_wire_without_attrs() {
1706        let prog = parse("wire osc = cycle~(440);");
1707        assert!(prog.wires[0].attrs.is_empty());
1708    }
1709
1710    #[test]
1711    fn test_wire_with_string_attr() {
1712        let prog = parse(r#"wire dial = live.dial().attr(parameter_longname: "Cutoff");"#);
1713        assert_eq!(prog.wires[0].attrs.len(), 1);
1714        assert_eq!(prog.wires[0].attrs[0].key, "parameter_longname");
1715        assert_eq!(
1716            prog.wires[0].attrs[0].value,
1717            AttrValue::Str("Cutoff".to_string())
1718        );
1719    }
1720
1721    #[test]
1722    fn test_wire_with_ident_attr() {
1723        let prog = parse("wire osc = cycle~(freq).attr(phase: half);");
1724        assert_eq!(prog.wires[0].attrs[0].key, "phase");
1725        assert_eq!(
1726            prog.wires[0].attrs[0].value,
1727            AttrValue::Ident("half".to_string())
1728        );
1729    }
1730
1731    #[test]
1732    fn test_wire_with_int_attr() {
1733        let prog = parse("wire w = flonum(x).attr(minimum: 0, maximum: 100);");
1734        assert_eq!(prog.wires[0].attrs[0].value, AttrValue::Int(0));
1735        assert_eq!(prog.wires[0].attrs[1].value, AttrValue::Int(100));
1736    }
1737
1738    #[test]
1739    fn test_msg_with_attrs() {
1740        let prog = parse(r#"msg click = "bang".attr(patching_rect: 100.);"#);
1741        let msg = &prog.msg_decls[0];
1742        assert_eq!(msg.attrs.len(), 1);
1743        assert_eq!(msg.attrs[0].key, "patching_rect");
1744        assert_eq!(msg.attrs[0].value, AttrValue::Float(100.0));
1745    }
1746
1747    #[test]
1748    fn test_msg_without_attrs() {
1749        let prog = parse(r#"msg click = "bang";"#);
1750        assert!(prog.msg_decls[0].attrs.is_empty());
1751    }
1752
1753    #[test]
1754    fn test_wire_multiline_attrs() {
1755        let prog = parse(
1756            r#"
1757wire dial = live.dial().attr(
1758    parameter_longname: "Cutoff",
1759    parameter_shortname: "Cut",
1760    minimum: 20.,
1761    maximum: 20000.
1762);
1763"#,
1764        );
1765        assert_eq!(prog.wires[0].attrs.len(), 4);
1766        assert_eq!(prog.wires[0].attrs[0].key, "parameter_longname");
1767        assert_eq!(prog.wires[0].attrs[1].key, "parameter_shortname");
1768        assert_eq!(prog.wires[0].attrs[2].key, "minimum");
1769        assert_eq!(prog.wires[0].attrs[3].key, "maximum");
1770    }
1771
1772    // ── Negative numbers ─────────────────────────────────────
1773
1774    #[test]
1775    fn test_negative_integer() {
1776        let prog = parse("wire x = foo(-7);");
1777        assert_eq!(
1778            prog.wires[0].value,
1779            Expr::Call {
1780                object: "foo".to_string(),
1781                args: vec![CallArg::positional(Expr::Lit(LitValue::Int(-7)))],
1782            }
1783        );
1784    }
1785
1786    #[test]
1787    fn test_negative_float() {
1788        let prog = parse("wire x = foo(-3.14);");
1789        assert_eq!(
1790            prog.wires[0].value,
1791            Expr::Call {
1792                object: "foo".to_string(),
1793                args: vec![CallArg::positional(Expr::Lit(LitValue::Float(-3.14)))],
1794            }
1795        );
1796    }
1797
1798    // ── Operator object names ────────────────────────────────
1799
1800    #[test]
1801    fn test_operator_object_call() {
1802        let prog = parse("wire x = ?(a, b);");
1803        assert_eq!(
1804            prog.wires[0].value,
1805            Expr::Call {
1806                object: "?".to_string(),
1807                args: vec![
1808                    CallArg::positional(Expr::Ref("a".to_string())),
1809                    CallArg::positional(Expr::Ref("b".to_string())),
1810                ],
1811            }
1812        );
1813    }
1814
1815    #[test]
1816    fn test_mul_operator_call() {
1817        let prog = parse("wire x = *(a, b);");
1818        assert_eq!(
1819            prog.wires[0].value,
1820            Expr::Call {
1821                object: "*".to_string(),
1822                args: vec![
1823                    CallArg::positional(Expr::Ref("a".to_string())),
1824                    CallArg::positional(Expr::Ref("b".to_string())),
1825                ],
1826            }
1827        );
1828    }
1829
1830    // ── Real-world: simpleFM~ ────────────────────────────────
1831
1832    #[test]
1833    fn test_simple_fm_tilde() {
1834        let prog = parse(
1835            r##"
1836in 0 (Carrier_frequency): float;
1837in 1 (Harmonicity_ratio): float;
1838in 2 (Modulation_index): float;
1839out 0 (FM_signal): signal;
1840
1841wire w_1 = mul~("#1");
1842wire w_2 = mul~("#2");
1843wire w_3 = cycle~(w_1);
1844wire w_4 = mul~(w_3, w_2);
1845wire w_5 = add~(Carrier_frequency, w_4);
1846wire w_6 = cycle~(w_5);
1847
1848w_1.in[1] = Harmonicity_ratio;
1849w_1.in[0] = Carrier_frequency;
1850w_2.in[0] = w_1;
1851w_2.in[1] = Modulation_index;
1852
1853out[0] = w_6;
1854"##,
1855        );
1856
1857        assert_eq!(prog.in_decls.len(), 3);
1858        assert_eq!(prog.out_decls.len(), 1);
1859        assert_eq!(prog.wires.len(), 6);
1860        assert_eq!(prog.direct_connections.len(), 4);
1861        assert_eq!(prog.out_assignments.len(), 1);
1862
1863        // Verify wire with string arg
1864        assert_eq!(
1865            prog.wires[0].value,
1866            Expr::Call {
1867                object: "mul~".to_string(),
1868                args: vec![CallArg::positional(Expr::Lit(LitValue::Str(
1869                    "#1".to_string()
1870                )))],
1871            }
1872        );
1873    }
1874
1875    // ── Real-world: synthFMvoice~ (complex) ──────────────────
1876
1877    #[test]
1878    fn test_synth_fm_voice() {
1879        let prog = parse(
1880            r#"
1881in 0 (MIDi_key___and_velocity): float;
1882in 1 (frequency_bend): float;
1883in 2 (additional_modulation_depth): float;
1884out 0 (voice_output): signal;
1885
1886msg msg_1 = "0 100".attr(background: 0, bgcolor2: "0.867 0.867 0.867 1.0", gradient: 0);
1887msg msg_2 = "setdomain $1".attr(background: 0, bgcolor2: "0.867 0.867 0.867 1.0", gradient: 0);
1888
1889wire w_1 = unpack(MIDi_key___and_velocity).attr(background: 0);
1890wire w_2 = add~(8.0).attr(background: 0);
1891wire w_3 = mtof(w_1.out[0]).attr(background: 0);
1892wire w_4 = select(0).attr(background: 0);
1893
1894w_2.in[0] = additional_modulation_depth;
1895w_4.in[0] = w_1.out[1];
1896
1897out[0] = w_4;
1898"#,
1899        );
1900
1901        assert_eq!(prog.in_decls.len(), 3);
1902        assert_eq!(prog.out_decls.len(), 1);
1903        assert_eq!(prog.msg_decls.len(), 2);
1904        assert_eq!(prog.wires.len(), 4);
1905
1906        // msg with multiple attrs
1907        assert_eq!(prog.msg_decls[0].content, "0 100");
1908        assert_eq!(prog.msg_decls[0].attrs.len(), 3);
1909        assert_eq!(prog.msg_decls[0].attrs[0].key, "background");
1910
1911        // wire with .out[N] in args
1912        assert_eq!(
1913            prog.wires[2].value,
1914            Expr::Call {
1915                object: "mtof".to_string(),
1916                args: vec![CallArg::positional(Expr::OutputPortAccess(
1917                    OutputPortAccess {
1918                        object: "w_1".to_string(),
1919                        index: 0,
1920                    }
1921                ))],
1922            }
1923        );
1924
1925        // direct connection with .out[N] on RHS
1926        assert_eq!(
1927            prog.direct_connections[1].value,
1928            Expr::OutputPortAccess(OutputPortAccess {
1929                object: "w_1".to_string(),
1930                index: 1,
1931            })
1932        );
1933    }
1934
1935    // ── Wire with string arg containing special chars ────────
1936
1937    #[test]
1938    fn test_wire_with_complex_string_arg() {
1939        let prog = parse(r#"wire w_6 = t("b", "f", "f");"#);
1940        assert_eq!(
1941            prog.wires[0].value,
1942            Expr::Call {
1943                object: "t".to_string(),
1944                args: vec![
1945                    CallArg::positional(Expr::Lit(LitValue::Str("b".to_string()))),
1946                    CallArg::positional(Expr::Lit(LitValue::Str("f".to_string()))),
1947                    CallArg::positional(Expr::Lit(LitValue::Str("f".to_string()))),
1948                ],
1949            }
1950        );
1951    }
1952
1953    // ── Wire with expr containing escapes ────────────────────
1954
1955    #[test]
1956    fn test_wire_with_escaped_string() {
1957        let prog = parse(r#"wire w = expr("pow($f1/127.\\,4.)");"#);
1958        assert_eq!(
1959            prog.wires[0].value,
1960            Expr::Call {
1961                object: "expr".to_string(),
1962                args: vec![CallArg::positional(Expr::Lit(LitValue::Str(
1963                    "pow($f1/127.\\,4.)".to_string()
1964                )))],
1965            }
1966        );
1967    }
1968
1969    // ── Trailing-dot float ───────────────────────────────────
1970
1971    #[test]
1972    fn test_trailing_dot_float() {
1973        let prog = parse("wire x = foo(100.);");
1974        assert_eq!(
1975            prog.wires[0].value,
1976            Expr::Call {
1977                object: "foo".to_string(),
1978                args: vec![CallArg::positional(Expr::Lit(LitValue::Float(100.0)))],
1979            }
1980        );
1981    }
1982
1983    // ── Scientific notation ──────────────────────────────────
1984
1985    #[test]
1986    fn test_scientific_notation() {
1987        let prog = parse("wire x = foo(1e-6);");
1988        assert_eq!(
1989            prog.wires[0].value,
1990            Expr::Call {
1991                object: "foo".to_string(),
1992                args: vec![CallArg::positional(Expr::Lit(LitValue::Float(1e-6)))],
1993            }
1994        );
1995    }
1996
1997    // ── msg.in[N] direct connection (msg as identifier) ──────
1998
1999    #[test]
2000    fn test_msg_identifier_direct_connection() {
2001        // `msg_1.in[0] = w_4.out[0];` — `msg_1` is an identifier, not keyword `msg`
2002        let prog = parse("msg_1.in[0] = w_4.out[0];");
2003        assert_eq!(prog.direct_connections.len(), 1);
2004        assert_eq!(prog.direct_connections[0].target.object, "msg_1");
2005        assert_eq!(
2006            prog.direct_connections[0].value,
2007            Expr::OutputPortAccess(OutputPortAccess {
2008                object: "w_4".to_string(),
2009                index: 0,
2010            })
2011        );
2012    }
2013
2014    // ── Implicit port index ──────────────────────────────────
2015
2016    #[test]
2017    fn test_implicit_in_single() {
2018        let prog = parse("in freq: float;");
2019        assert_eq!(prog.in_decls.len(), 1);
2020        assert_eq!(prog.in_decls[0].index, 0);
2021        assert_eq!(prog.in_decls[0].name, "freq");
2022        assert_eq!(prog.in_decls[0].port_type, PortType::Float);
2023    }
2024
2025    #[test]
2026    fn test_implicit_in_multiple() {
2027        let prog = parse("in freq: float;\nin cutoff: float;");
2028        assert_eq!(prog.in_decls.len(), 2);
2029        assert_eq!(prog.in_decls[0].index, 0);
2030        assert_eq!(prog.in_decls[0].name, "freq");
2031        assert_eq!(prog.in_decls[1].index, 1);
2032        assert_eq!(prog.in_decls[1].name, "cutoff");
2033    }
2034
2035    #[test]
2036    fn test_implicit_out_single() {
2037        let prog = parse("out audio: signal;");
2038        assert_eq!(prog.out_decls.len(), 1);
2039        assert_eq!(prog.out_decls[0].index, 0);
2040        assert_eq!(prog.out_decls[0].name, "audio");
2041        assert_eq!(prog.out_decls[0].port_type, PortType::Signal);
2042    }
2043
2044    #[test]
2045    fn test_implicit_out_multiple() {
2046        let prog = parse("out left: signal;\nout right: signal;");
2047        assert_eq!(prog.out_decls.len(), 2);
2048        assert_eq!(prog.out_decls[0].index, 0);
2049        assert_eq!(prog.out_decls[0].name, "left");
2050        assert_eq!(prog.out_decls[1].index, 1);
2051        assert_eq!(prog.out_decls[1].name, "right");
2052    }
2053
2054    #[test]
2055    fn test_explicit_index_still_works() {
2056        let prog = parse("in 5 (freq): float;");
2057        assert_eq!(prog.in_decls.len(), 1);
2058        assert_eq!(prog.in_decls[0].index, 5);
2059        assert_eq!(prog.in_decls[0].name, "freq");
2060    }
2061
2062    #[test]
2063    fn test_implicit_separate_counters_in_out() {
2064        // in and out should have independent counters
2065        let prog = parse("in a: float;\nout x: signal;\nin b: float;\nout y: signal;");
2066        assert_eq!(prog.in_decls.len(), 2);
2067        assert_eq!(prog.in_decls[0].index, 0);
2068        assert_eq!(prog.in_decls[0].name, "a");
2069        assert_eq!(prog.in_decls[1].index, 1);
2070        assert_eq!(prog.in_decls[1].name, "b");
2071        assert_eq!(prog.out_decls.len(), 2);
2072        assert_eq!(prog.out_decls[0].index, 0);
2073        assert_eq!(prog.out_decls[0].name, "x");
2074        assert_eq!(prog.out_decls[1].index, 1);
2075        assert_eq!(prog.out_decls[1].name, "y");
2076    }
2077
2078    #[test]
2079    fn test_implicit_all_port_types() {
2080        let prog = parse(
2081            "in a: signal;\nin b: float;\nin c: int;\nin d: bang;\nin e: list;\nin f: symbol;",
2082        );
2083        assert_eq!(prog.in_decls.len(), 6);
2084        assert_eq!(prog.in_decls[0].port_type, PortType::Signal);
2085        assert_eq!(prog.in_decls[1].port_type, PortType::Float);
2086        assert_eq!(prog.in_decls[2].port_type, PortType::Int);
2087        assert_eq!(prog.in_decls[3].port_type, PortType::Bang);
2088        assert_eq!(prog.in_decls[4].port_type, PortType::List);
2089        assert_eq!(prog.in_decls[5].port_type, PortType::Symbol);
2090        for (i, decl) in prog.in_decls.iter().enumerate() {
2091            assert_eq!(decl.index, i as u32);
2092        }
2093    }
2094
2095    #[test]
2096    fn test_implicit_in_full_patch() {
2097        let prog = parse(
2098            r#"
2099in carrier_freq: float;
2100in harmonicity: float;
2101out fm_signal: signal;
2102
2103wire osc = cycle~(carrier_freq);
2104out[0] = osc;
2105"#,
2106        );
2107        assert_eq!(prog.in_decls.len(), 2);
2108        assert_eq!(prog.in_decls[0].index, 0);
2109        assert_eq!(prog.in_decls[0].name, "carrier_freq");
2110        assert_eq!(prog.in_decls[1].index, 1);
2111        assert_eq!(prog.in_decls[1].name, "harmonicity");
2112        assert_eq!(prog.out_decls.len(), 1);
2113        assert_eq!(prog.out_decls[0].index, 0);
2114        assert_eq!(prog.out_decls[0].name, "fm_signal");
2115        assert_eq!(prog.wires.len(), 1);
2116        assert_eq!(prog.out_assignments.len(), 1);
2117    }
2118
2119    // ── E52: Out declaration with inline assignment ──────────
2120
2121    #[test]
2122    fn test_out_decl_inline_value_implicit() {
2123        // out audio: signal = osc;
2124        let prog = parse(
2125            r#"
2126wire osc = cycle~(440);
2127out audio: signal = osc;
2128"#,
2129        );
2130        assert_eq!(prog.out_decls.len(), 1);
2131        assert_eq!(prog.out_decls[0].index, 0);
2132        assert_eq!(prog.out_decls[0].name, "audio");
2133        assert_eq!(prog.out_decls[0].port_type, PortType::Signal);
2134        assert_eq!(prog.out_decls[0].value, Some(Expr::Ref("osc".to_string())));
2135        // No separate out_assignment
2136        assert!(prog.out_assignments.is_empty());
2137    }
2138
2139    #[test]
2140    fn test_out_decl_inline_value_explicit() {
2141        // out 0 (audio): signal = osc;
2142        let prog = parse(
2143            r#"
2144wire osc = cycle~(440);
2145out 0 (audio): signal = osc;
2146"#,
2147        );
2148        assert_eq!(prog.out_decls.len(), 1);
2149        assert_eq!(prog.out_decls[0].index, 0);
2150        assert_eq!(prog.out_decls[0].name, "audio");
2151        assert_eq!(prog.out_decls[0].port_type, PortType::Signal);
2152        assert_eq!(prog.out_decls[0].value, Some(Expr::Ref("osc".to_string())));
2153        assert!(prog.out_assignments.is_empty());
2154    }
2155
2156    #[test]
2157    fn test_out_decl_without_value_backward_compat() {
2158        // out audio: signal; (no inline value)
2159        let prog = parse(
2160            r#"
2161out audio: signal;
2162wire osc = cycle~(440);
2163out[0] = osc;
2164"#,
2165        );
2166        assert_eq!(prog.out_decls.len(), 1);
2167        assert_eq!(prog.out_decls[0].value, None);
2168        assert_eq!(prog.out_assignments.len(), 1);
2169        assert_eq!(prog.out_assignments[0].index, 0);
2170    }
2171
2172    #[test]
2173    fn test_out_decl_inline_with_call_expr() {
2174        // out audio: signal = cycle~(440);
2175        let prog = parse(
2176            r#"
2177out audio: signal = cycle~(440);
2178"#,
2179        );
2180        assert_eq!(prog.out_decls.len(), 1);
2181        assert_eq!(prog.out_decls[0].name, "audio");
2182        assert_eq!(
2183            prog.out_decls[0].value,
2184            Some(Expr::Call {
2185                object: "cycle~".to_string(),
2186                args: vec![CallArg::positional(Expr::Lit(LitValue::Int(440)))],
2187            })
2188        );
2189        assert!(prog.out_assignments.is_empty());
2190    }
2191
2192    #[test]
2193    fn test_out_assignment_unchanged() {
2194        // out[0] = osc; remains as OutAssignment
2195        let prog = parse(
2196            r#"
2197out audio: signal;
2198wire osc = cycle~(440);
2199out[0] = osc;
2200"#,
2201        );
2202        assert_eq!(prog.out_decls.len(), 1);
2203        assert_eq!(prog.out_decls[0].value, None);
2204        assert_eq!(prog.out_assignments.len(), 1);
2205        assert_eq!(prog.out_assignments[0].index, 0);
2206        assert_eq!(prog.out_assignments[0].value, Expr::Ref("osc".to_string()));
2207    }
2208
2209    #[test]
2210    fn test_out_decl_inline_multiple() {
2211        // Multiple out declarations with inline values
2212        let prog = parse(
2213            r#"
2214wire left = cycle~(440);
2215wire right = cycle~(880);
2216out left_out: signal = left;
2217out right_out: signal = right;
2218"#,
2219        );
2220        assert_eq!(prog.out_decls.len(), 2);
2221        assert_eq!(prog.out_decls[0].index, 0);
2222        assert_eq!(prog.out_decls[0].name, "left_out");
2223        assert_eq!(prog.out_decls[0].value, Some(Expr::Ref("left".to_string())));
2224        assert_eq!(prog.out_decls[1].index, 1);
2225        assert_eq!(prog.out_decls[1].name, "right_out");
2226        assert_eq!(
2227            prog.out_decls[1].value,
2228            Some(Expr::Ref("right".to_string()))
2229        );
2230        assert!(prog.out_assignments.is_empty());
2231    }
2232
2233    // ── Named argument tests ──────────────────────────────────
2234
2235    #[test]
2236    fn test_named_args_single() {
2237        let prog = parse("wire x = cycle~(freq: 440);");
2238        assert_eq!(prog.wires.len(), 1);
2239        if let Expr::Call { object, args } = &prog.wires[0].value {
2240            assert_eq!(object, "cycle~");
2241            assert_eq!(args.len(), 1);
2242            assert_eq!(args[0].name, Some("freq".to_string()));
2243            assert_eq!(args[0].value, Expr::Lit(LitValue::Int(440)));
2244        } else {
2245            panic!("expected Call");
2246        }
2247    }
2248
2249    #[test]
2250    fn test_named_args_multiple() {
2251        let prog = parse("wire x = biquad~(input: osc, freq: cutoff, q: resonance);");
2252        if let Expr::Call { args, .. } = &prog.wires[0].value {
2253            assert_eq!(args.len(), 3);
2254            assert_eq!(args[0].name, Some("input".to_string()));
2255            assert_eq!(args[0].value, Expr::Ref("osc".to_string()));
2256            assert_eq!(args[1].name, Some("freq".to_string()));
2257            assert_eq!(args[1].value, Expr::Ref("cutoff".to_string()));
2258            assert_eq!(args[2].name, Some("q".to_string()));
2259            assert_eq!(args[2].value, Expr::Ref("resonance".to_string()));
2260        } else {
2261            panic!("expected Call");
2262        }
2263    }
2264
2265    #[test]
2266    fn test_mixed_positional_and_named_args() {
2267        let prog = parse("wire x = biquad~(osc, freq: cutoff, 0.7);");
2268        if let Expr::Call { args, .. } = &prog.wires[0].value {
2269            assert_eq!(args.len(), 3);
2270            assert_eq!(args[0].name, None);
2271            assert_eq!(args[0].value, Expr::Ref("osc".to_string()));
2272            assert_eq!(args[1].name, Some("freq".to_string()));
2273            assert_eq!(args[1].value, Expr::Ref("cutoff".to_string()));
2274            assert_eq!(args[2].name, None);
2275            assert_eq!(args[2].value, Expr::Lit(LitValue::Float(0.7)));
2276        } else {
2277            panic!("expected Call");
2278        }
2279    }
2280
2281    #[test]
2282    fn test_named_arg_with_literal() {
2283        let prog = parse(r#"wire x = print(msg: "hello");"#);
2284        if let Expr::Call { args, .. } = &prog.wires[0].value {
2285            assert_eq!(args.len(), 1);
2286            assert_eq!(args[0].name, Some("msg".to_string()));
2287            assert_eq!(args[0].value, Expr::Lit(LitValue::Str("hello".to_string())));
2288        } else {
2289            panic!("expected Call");
2290        }
2291    }
2292
2293    #[test]
2294    fn test_positional_args_still_work() {
2295        // Ensure backward compatibility: positional args have name = None
2296        let prog = parse("wire x = cycle~(440);");
2297        if let Expr::Call { args, .. } = &prog.wires[0].value {
2298            assert_eq!(args.len(), 1);
2299            assert_eq!(args[0].name, None);
2300            assert_eq!(args[0].value, Expr::Lit(LitValue::Int(440)));
2301        } else {
2302            panic!("expected Call");
2303        }
2304    }
2305}