Skip to main content

swan_core/
parser.rs

1//! The Swan parser: turns tokens into an [`ast::Node`](crate::ast::Node) tree.
2//!
3//! [`Parser`] is a Pratt (top-down operator precedence) parser. It reads the
4//! whole source as one expression and never fails: syntax errors become
5//! [`Node::Unclosed`] and [`Node::UnexpectedToken`] leaves, which evaluate to
6//! `Undefined` values. Comments are skipped.
7//!
8//! # Precedence
9//!
10//! A higher binding power binds tighter. Prefix operators (`+`, `-`, `!`,
11//! `$`) bind tighter than everything else, including `.` and application.
12//!
13//! | Power | Operators                          | Associativity |
14//! |------:|------------------------------------|---------------|
15//! |    30 | `.`                                | left          |
16//! |    28 | application (`f x`)                | left          |
17//! |    27 | `^`                                | right         |
18//! |    26 | `*` `/` `%`                        | left          |
19//! |    25 | `+` `-`                            | left          |
20//! |    24 | `==` `!=` `<` `<=` `>` `>=`        | left          |
21//! |    23 | `&` `\|`                           | left          |
22//! |    22 | `?`                                | left          |
23//! |    21 | `;`                                | left          |
24//! |    15 | `->`                               | right         |
25//! |    14 | `>>` `<<`                          | left          |
26//! |    13 | `=>`                               | right         |
27//! |    13 | `~>`                               | left          |
28//! |    12 | `=` `:`                            | left          |
29//! |    10 | `,`                                | left          |
30//!
31//! # Example
32//!
33//! ```
34//! use swan_core::ast::Node;
35//! use swan_core::lexer::Lexer;
36//! use swan_core::parser::Parser;
37//!
38//! let ast = Parser::new(Lexer::new("1 + 2 * 3")).parse();
39//! assert!(matches!(ast, Node::SumOp(_)));
40//! ```
41
42// =============================================================================
43// Implementation notes
44// =============================================================================
45//
46// What is a Pratt parser?
47// -----------------------
48// A Pratt parser is a recursive-descent parser where every token has two
49// optional "parsing functions" associated with it:
50//
51//   • prefix_fn  — called when the token appears at the START of an
52//                  expression (e.g. a number, an identifier, a `(`, a
53//                  unary `-`).
54//
55//   • infix_fn   — called when the token appears BETWEEN two expressions
56//                  (e.g. `+`, `*`, `.`).  The left-hand expression is
57//                  passed in as an argument.
58//
59// The core loop (`parse_expr`) works like this:
60//
61//   1. Call `parse_prefix` to parse the left-hand side (lhs).
62//   2. Peek at the next token.  If its BINDING POWER (precedence) is
63//      greater than `min_bp`, consume the token and call `parse_infix`
64//      to combine the lhs with the rhs into a new node.
65//   3. Repeat step 2 until the next token's binding power is ≤ `min_bp`.
66//
67// Binding power controls associativity:
68//   • Left-associative  (+, -, *, …) : pass the SAME power as the infix op
69//                                      when recursing for the rhs.
70//   • Right-associative (^, ->)      : pass ONE LESS than the infix op's
71//                                      power so that the rhs grabs more.
72//
73// References
74// ----------
75// • Vaughan Pratt, "Top Down Operator Precedence" (1973)
76// • https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
77
78use crate::ast::{BinaryOp, Leaf, Node, UnaryOp};
79use crate::lexer::Lexer;
80use crate::token::Token;
81
82
83
84
85
86// =============================================================================
87// Binding-power table
88// =============================================================================
89//
90// Each infix operator has a LEFT and a RIGHT binding power (lbp / rbp).
91// The parser compares the NEXT token's lbp against the current minimum; it
92// consumes the token only when  lbp > min_bp.
93//
94// For a left-associative operator the rbp equals the lbp (so the same level
95// cannot re-associate on the right).  For a right-associative operator the
96// rbp is lbp - 1 (one less), allowing the same operator to appear again on
97// the right and bind first.
98//
99// The numbers below match the precedence values documented in token.rs.
100
101/// Returns the LEFT binding power of an infix/postfix operator token.
102///
103/// Returns `0` for tokens that are NOT infix operators (terminators, closing
104/// brackets, unknown tokens). This causes the main loop to stop.
105fn lbp(token: &Token) -> u32 {
106    match token {
107        // Lowest-precedence infix operators come first.
108        Token::PairingOp             => 10,   // ,
109
110        Token::AssignmentOp          => 12,   // =
111        Token::LabellingOp           => 12,   // :
112
113        Token::MappingOp             => 13,   // =>
114        Token::ThenOp                => 13,   // ~>
115
116        Token::ReverseCompositionOp  => 14,   // >>
117        Token::CompositionOp         => 14,   // <<
118
119        Token::FunctionDefinitionOp  => 15,   // ->
120
121        Token::AlternativeOp         => 21,   // ;
122        Token::ConditionalOp         => 22,   // ?
123
124        Token::OrOp                  => 23,   // |
125        Token::AndOp                 => 23,   // &
126
127        Token::EqualToOp             => 24,   // ==
128        Token::NotEqualToOp          => 24,   // !=
129        Token::LessThanOp            => 24,   // <
130        Token::LessThanOrEqualToOp   => 24,   // <=
131        Token::GreaterThanOp         => 24,   // >
132        Token::GreaterThanOrEqualToOp => 24,  // >=
133
134        Token::PlusOp                => 25,   // +
135        Token::MinusOp               => 25,   // -
136
137        Token::ProductOp             => 26,   // *
138        Token::DivisionOp            => 26,   // /
139        Token::ModuloOp              => 26,   // %
140
141        Token::PowerOp               => 27,   // ^  (right-assoc, see rbp)
142
143        Token::SubcontextingOp       => 30,   // .
144
145        // Everything else is not an infix operator → lbp = 0 stops the loop.
146        _ => 0,
147    }
148}
149
150/// Returns the RIGHT binding power of an infix operator token.
151///
152/// For left-associative operators this equals `lbp(token)` (the rhs
153/// recurses at the same level, preventing re-association at this level).
154/// For right-associative operators this is `lbp(token) - 1` (the rhs
155/// recurses one level lower, so two operators at the same level bind
156/// right-to-left).
157fn rbp(token: &Token) -> u32 {
158    match token {
159        // Right-associative operators.
160        Token::PowerOp              => lbp(token) - 1,   // ^
161        Token::FunctionDefinitionOp => lbp(token) - 1,   // ->
162        Token::MappingOp            => lbp(token) - 1,   // =>
163
164        // All other infix operators are left-associative.
165        _ => lbp(token),
166    }
167}
168
169
170
171
172
173// =============================================================================
174// Application (empty) operator
175// =============================================================================
176//
177// The application operator has no token.  It is triggered when the look-ahead
178// can begin an expression (i.e. it is in "prefix position") while the parser
179// is in the middle of the infix loop.
180//
181// Binding power: 28 — tighter than exponentiation (27) so `f x ^ 2` parses
182// as `(f x) ^ 2`, but looser than sub-contexting (30) so `f x.y` parses
183// as `f (x.y)`.  Application is left-associative (lbp == rbp).
184
185/// The left *and* right binding power of the implicit application operator.
186const APPLICATION_BP: u32 = 28;
187
188/// Returns `true` when `token` can start a new expression (prefix position).
189///
190/// These are exactly the tokens for which `parse_prefix` has a real (non-`_`)
191/// branch.  When one of them appears in the look-ahead after a complete lhs,
192/// we synthesise an `ApplicationOp` without consuming any infix token.
193fn is_application_starter(token: &Token) -> bool {
194    matches!(
195        token,
196        Token::Identifier(_)
197            | Token::Number(_)
198            | Token::Text(_)
199            | Token::LeftRoundBracket
200            | Token::LeftSquareBracket
201            | Token::LeftCurlyBracket
202    )
203}
204
205
206
207
208
209// =============================================================================
210// Parser struct
211// =============================================================================
212//
213// The parser keeps a ONE-TOKEN look-ahead.
214//
215// Why own Token instead of borrowing it?
216// ---------------------------------------
217// The lexer returns `(usize, Token<'a>)` by VALUE each time `next_token` is
218// called. Storing those values as `&'a Token<'a>` would be storing a reference
219// to a LOCAL variable that is about to be dropped — a classic Rust lifetime
220// error. Owning the token (no `&`) is the correct fix.
221
222/// A Pratt parser for Swan expressions.
223///
224/// Create one with [`Parser::new`] and call [`Parser::parse`] once to get the
225/// root node of the expression.
226pub struct Parser<'a> {
227    /// The lexer feeds us tokens on demand.
228    lexer: Lexer<'a>,
229
230    /// The CURRENT token (just consumed) and its byte position in the source.
231    /// Initialised to `(0, Token::EndOfExpression)` before the first real
232    /// token is fetched.
233    current_token: Token<'a>,
234    current_pos:   usize,
235
236    /// The NEXT token (one step ahead) and its position.
237    /// Pre-filled by `new` so every call to peek is O(1).
238    peek_token: Token<'a>,
239    peek_pos:   usize,
240}
241
242impl<'a> Parser<'a> {
243
244    // -------------------------------------------------------------------------
245    // Construction
246    // -------------------------------------------------------------------------
247
248    /// Creates a new parser wrapping `lexer` and pre-loads the look-ahead.
249    pub fn new(mut lexer: Lexer<'a>) -> Self {
250        // Prime the look-ahead with the very first real token from the lexer.
251        // `current_token` starts as a dummy; it will be replaced on the first
252        // call to `advance`.
253        let (peek_pos, peek_token) = lexer.next_token();
254        Self {
255            lexer,
256            current_token: Token::EndOfExpression,
257            current_pos:   0,
258            peek_token,
259            peek_pos,
260        }
261    }
262
263    // -------------------------------------------------------------------------
264    // Token management helpers
265    // -------------------------------------------------------------------------
266
267    /// Advances the parser by one token.
268    ///
269    /// Moves the look-ahead into `current_*` and fetches a new look-ahead from
270    /// the lexer.
271    ///
272    /// `std::mem::replace` lets us MOVE `self.peek_token` out of the struct
273    /// (which we can't do with a plain assignment because the struct must
274    /// always remain fully initialised) while simultaneously writing the new
275    /// look-ahead value into its place.
276    fn advance(&mut self) {
277        let (new_peek_pos, new_peek_token) = self.lexer.next_token();
278
279        self.current_token = std::mem::replace(&mut self.peek_token, new_peek_token);
280        self.current_pos   = self.peek_pos;
281        self.peek_pos      = new_peek_pos;
282    }
283
284    /// Skips any `Comment` tokens from the look-ahead.
285    ///
286    /// The lexer yields comments as real tokens; the parser ignores them.
287    /// Call this whenever you are about to inspect the look-ahead.
288    fn skip_comments(&mut self) {
289        // Keep advancing while the upcoming token is a comment.
290        while matches!(self.peek_token, Token::Comment(_)) {
291            self.advance();
292        }
293    }
294
295    // -------------------------------------------------------------------------
296    // Core Pratt parsing engine
297    // -------------------------------------------------------------------------
298
299    /// Parses a single complete expression at the given minimum binding power.
300    ///
301    /// `min_bp` is the binding power of the operator that called us.  We only
302    /// consume infix operators whose left-binding-power is GREATER than
303    /// `min_bp`; everything else is left for the caller to handle.
304    ///
305    /// Call with `min_bp = 0` to parse a full expression.
306    ///
307    /// # Algorithm (step by step)
308    ///
309    /// 1. Parse a PREFIX production → gives us the left-hand node (lhs).
310    /// 2. Loop:
311    ///    a. Skip comments and peek at the next token.
312    ///    b. If `lbp(peek) <= min_bp` → STOP; return lhs.
313    ///    c. Consume the infix token (advance).
314    ///    d. Recursively parse the rhs with `min_bp = rbp(infix_token)`.
315    ///    e. Combine lhs + rhs into a new node; that becomes the new lhs.
316    pub fn parse_expr(&mut self, min_bp: u32) -> Node {
317
318        // --- Step 1: prefix production ----------------------------------------
319        self.skip_comments();
320        if min_bp == 0 && matches!(self.peek_token, Token::EndOfExpression) {
321            return Node::Nothing(Leaf { pos: self.peek_pos, val: () });
322        }
323        let mut lhs = self.parse_prefix();
324
325        // --- Step 2: infix loop -----------------------------------------------
326        loop {
327            self.skip_comments();
328
329            // --- Case A: implicit application operator ------------------------
330            //
331            // When the look-ahead can start an expression AND the application
332            // binding power beats the current minimum, we treat the two adjacent
333            // expressions as `lhs rhs` (function application).
334            //
335            // We do NOT consume any token here — `parse_prefix` inside the
336            // recursive call to `parse_expr` will advance past the rhs on its
337            // own.  The position we record for the node is the byte offset of
338            // the rhs (i.e. `self.peek_pos`), since there is no operator token.
339            if APPLICATION_BP > min_bp && is_application_starter(&self.peek_token) {
340                let pos = self.peek_pos;
341                // Right binding power equals left (left-associative), so we
342                // recurse at APPLICATION_BP to prevent right-nesting at the
343                // same level.
344                let rhs = self.parse_expr(APPLICATION_BP);
345                lhs = Node::ApplicationOp(BinaryOp {
346                    pos,
347                    lho: Box::new(lhs),
348                    rho: Box::new(rhs),
349                });
350                continue;
351            }
352
353            // --- Case B: explicit infix operator ------------------------------
354
355            // Peek: if the next token isn't an infix operator (or has too low
356            // a binding power), we're done with this sub-expression.
357            let next_lbp = lbp(&self.peek_token);
358            if next_lbp <= min_bp {
359                break;
360            }
361
362            // Consume the infix operator so `current_token` holds it.
363            self.advance();
364
365            // IMPORTANT: snapshot the infix operator NOW, before we recurse.
366            // The recursive call to parse_expr will call advance() many times,
367            // overwriting self.current_token with whatever the recursion
368            // consumes last. If we don't save it here, parse_infix will see
369            // the wrong token and panic.
370            //
371            // `Token` derives `Clone`, so this is a cheap copy for unit
372            // variants (operators have no heap data) and a heap allocation only
373            // for string-carrying variants like Identifier — which never appear
374            // in the infix position anyway.
375            let infix_token = self.current_token.clone();
376            let infix_pos   = self.current_pos;
377
378            // Parse the right-hand side at the right binding power of this op.
379            // Using rbp instead of lbp here is what makes right-associative
380            // operators work: rbp < lbp means the recursive call can "steal"
381            // another operator at the same level.
382            let right_bp = rbp(&infix_token);
383            let rhs = self.parse_expr(right_bp);
384
385            // Combine lhs and rhs using the saved infix token.
386            lhs = self.parse_infix(lhs, rhs, infix_token, infix_pos);
387        }
388
389        lhs
390    }
391
392    // -------------------------------------------------------------------------
393    // Prefix productions
394    // -------------------------------------------------------------------------
395
396    /// Called at the start of each expression.
397    ///
398    /// Advances to the next token, then dispatches on its kind to build the
399    /// appropriate AST node.  Each branch handles one "prefix" form:
400    ///   • Literal values (number, text, identifier)
401    ///   • Unary operators (prefix `+`, prefix `-`)
402    ///   • Grouping (`(`, `[`, `{`)
403    fn parse_prefix(&mut self) -> Node {
404        self.advance();                     // consume the first token of this expression
405        let pos = self.current_pos;
406
407        match &self.current_token {
408
409            // -----------------------------------------------------------------
410            // Literals
411            // -----------------------------------------------------------------
412
413            // A numeric literal becomes a `Node::Numb` leaf.
414            Token::Number(val) => {
415                let val = *val;             // copy the f64 out before the borrow ends
416                Node::Numb(Leaf { pos, val })
417            }
418
419            // A string literal becomes a `Node::Text` leaf.
420            Token::Text(val) => {
421                let val = val.to_string();
422                Node::Text(Leaf { pos, val })
423            }
424
425            // An identifier becomes a `Node::Identifier` leaf.
426            Token::Identifier(name) => {
427                let val = name.to_string();
428                Node::Identifier(Leaf { pos, val })
429            }
430
431            // -----------------------------------------------------------------
432            // Grouped expressions
433            // -----------------------------------------------------------------
434
435            // A `(` starts a parenthesised sub-expression.
436            // Empty parentheses `()` represent Nothing.
437            // Otherwise, parse the inner expression at precedence 0 (so ALL
438            // operators inside bind), then expect a closing `)`.
439            Token::LeftRoundBracket => {
440                self.skip_comments();
441                if matches!(self.peek_token, Token::RightRoundBracket) {
442                    self.advance();
443                    Node::Nothing(Leaf { pos, val: () })
444                } else {
445                    let inner = self.parse_expr(0);
446                    // Consume the matching `)`.
447                    self.advance();
448                    if matches!(self.current_token, Token::RightRoundBracket) {
449                        inner
450                    } else {
451                        Node::Unclosed(Leaf {pos:self.current_pos, val:')'})
452                    }
453                }
454            }
455
456            // A `[` starts a list literal.
457            // An empty bracket pair `[]` is a list containing Nothing (which yields an empty list).
458            // Parse the inner expression at precedence 0, then expect `]`.
459            Token::LeftSquareBracket => {
460                self.skip_comments();
461                let inner = if matches!(self.peek_token, Token::RightSquareBracket) {
462                    let void_pos = self.peek_pos;
463                    self.advance();
464                    Node::Nothing(Leaf { pos: void_pos, val: () })
465                } else {
466                    let inner = self.parse_expr(0);
467                    self.advance();
468                    if matches!(self.current_token, Token::RightSquareBracket) {
469                        inner
470                    } else {
471                        Node::Unclosed(Leaf {pos:self.current_pos, val:']'})
472                    }
473                };
474                Node::List(UnaryOp { pos, opd: Box::new(inner) })
475            }
476
477            // A `{` starts a namespace literal.
478            // Parse the inner expression at precedence 0, then expect `}`.
479            Token::LeftCurlyBracket => {
480                self.skip_comments();
481                let inner = if matches!(self.peek_token, Token::RightCurlyBracket) {
482                    let void_pos = self.peek_pos;
483                    self.advance();
484                    Node::Nothing(Leaf { pos: void_pos, val: () })
485                } else {
486                    let inner = self.parse_expr(0);
487                    self.advance();
488                    if matches!(self.current_token, Token::RightCurlyBracket) {
489                        inner
490                    } else {
491                        Node::Unclosed(Leaf {pos:self.current_pos, val:'}'})
492                    }
493                };
494                Node::Namespace(UnaryOp { pos, opd: Box::new(inner) })
495            }
496
497            // -----------------------------------------------------------------
498            // Unary (prefix) operators
499            // -----------------------------------------------------------------
500
501            // Prefix `+` — unary affirmation (no real effect on numbers, but
502            // it is syntactically valid: `+x`).
503            // We parse the operand at a HIGH binding power (50) so that the
504            // unary op grabs its argument tightly.
505            Token::PlusOp => {
506                let opd = Box::new(self.parse_expr(50));
507                Node::AffirmOp(UnaryOp { pos, opd })
508            }
509
510            // Prefix `-` — unary negation.
511            Token::MinusOp => {
512                let opd = Box::new(self.parse_expr(50));
513                Node::NegateOp(UnaryOp { pos, opd })
514            }
515
516            // Prefix `!` — unary lofical negation.
517            Token::NotOp => {
518                let opd = Box::new(self.parse_expr(50));
519                Node::LogicalNegationOp(UnaryOp { pos, opd })
520            }
521
522            // Prefix `$` — unary lifting operation.
523            Token::LiftingOp => {
524                let opd = Box::new(self.parse_expr(50));
525                Node::LiftingOp(UnaryOp { pos, opd })
526            }
527
528            // -----------------------------------------------------------------
529            // Anything else is unexpected at the start of an expression.
530            // -----------------------------------------------------------------
531            _ => { 
532                let val = format!("{:?}", self.current_token);
533                Node::UnexpectedToken( Leaf {pos, val})
534            }
535        }
536    }
537
538    // -------------------------------------------------------------------------
539    // Infix productions
540    // -------------------------------------------------------------------------
541
542    /// Combines a parsed `lhs` and `rhs` into the correct binary `Node`.
543    ///
544    /// # Why take the token as a parameter?
545    ///
546    /// After `parse_expr(right_bp)` returns, `self.current_token` has been
547    /// overwritten many times by the recursive calls to `advance()` inside it.
548    /// It no longer holds the infix operator — it holds whatever the deepest
549    /// recursion consumed last.  We therefore snapshot the infix token in
550    /// `parse_expr` (with `.clone()`) before recursing and pass it in here.
551    ///
552    /// `pos`   — byte position of the operator in the source.
553    /// `token` — the infix operator token (already consumed, passed by value).
554    fn parse_infix(&mut self, lhs: Node, rhs: Node, token: Token<'a>, pos: usize) -> Node {
555
556        // Helper closure — packages lhs + rhs into a `BinaryOp`.
557        let bin = |lho: Node, rho: Node| BinaryOp {
558            pos,
559            lho: Box::new(lho),
560            rho: Box::new(rho),
561        };
562
563        match token {
564
565            // Arithmetic
566            Token::PlusOp       => Node::SumOp(bin(lhs, rhs)),
567            Token::MinusOp      => Node::SubtractionOp(bin(lhs, rhs)),
568            Token::ProductOp    => Node::ProductOp(bin(lhs, rhs)),
569            Token::DivisionOp   => Node::DivisionOp(bin(lhs, rhs)),
570            Token::ModuloOp     => Node::ModuloOp(bin(lhs, rhs)),
571            Token::PowerOp      => Node::PowerOp(bin(lhs, rhs)),
572
573            // Comparison
574            Token::EqualToOp              => Node::EqualToOp(bin(lhs, rhs)),
575            Token::NotEqualToOp           => Node::NotEqualToOp(bin(lhs, rhs)),
576            Token::LessThanOp             => Node::LessThanOp(bin(lhs, rhs)),
577            Token::LessThanOrEqualToOp    => Node::LessThanOrEqualToOp(bin(lhs, rhs)),
578            Token::GreaterThanOp          => Node::GreaterThanOp(bin(lhs, rhs)),
579            Token::GreaterThanOrEqualToOp => Node::GreaterThanOrEqualToOp(bin(lhs, rhs)),
580
581            // Logic
582            Token::AndOp => Node::AndOp(bin(lhs, rhs)),
583            Token::OrOp  => Node::OrOp(bin(lhs, rhs)),
584
585            // Control flow / short-circuit
586            Token::ConditionalOp  => Node::ConditionalOp(bin(lhs, rhs)),
587            Token::AlternativeOp  => Node::AlternativeOp(bin(lhs, rhs)),
588
589            // Function-related
590            Token::FunctionDefinitionOp => Node::FunctionDefinitionOp(bin(lhs, rhs)),
591            Token::MappingOp            => Node::MappingOp(bin(lhs, rhs)),
592            Token::ThenOp               => Node::ThenOp(bin(lhs, rhs)),
593
594            // Composition
595            Token::CompositionOp        => Node::CompositionOp(bin(lhs, rhs)),
596            Token::ReverseCompositionOp => Node::ReverseCompositionOp(bin(lhs, rhs)),
597
598            // Namespace access: `foo.bar`
599            Token::SubcontextingOp => Node::SubcontextingOp(bin(lhs, rhs)),
600
601            // Assignment and labelling
602            Token::AssignmentOp => Node::AssignmentOp(bin(lhs, rhs)),
603            Token::LabellingOp  => Node::LabellingOp(bin(lhs, rhs)),
604
605            // Pairing (tuple / comma list): `a, b, c`
606            Token::PairingOp => Node::PairingOp(bin(lhs, rhs)),
607
608            // Anything that reaches here is a parser bug.
609            _ => {
610                let val = format!("{:?}", token);
611                Node::UnexpectedToken( Leaf {pos, val})
612            }
613        }
614    }
615
616    // -------------------------------------------------------------------------
617    // Public entry point
618    // -------------------------------------------------------------------------
619
620    /// Parses the entire input as a single expression and returns its root node.
621    ///
622    /// An empty source (or one containing only comments) gives
623    /// [`Node::Nothing`]. Parsing never fails; see the
624    /// [module documentation](self) for how syntax errors are represented.
625    ///
626    /// # Examples
627    ///
628    /// ```
629    /// use swan_core::lexer::Lexer;
630    /// use swan_core::parser::Parser;
631    ///
632    /// let lexer = Lexer::new("1 + 2 * 3");
633    /// let mut parser = Parser::new(lexer);
634    /// let ast = parser.parse();   // SumOp(1, ProductOp(2, 3))
635    /// ```
636    pub fn parse(&mut self) -> Node {
637        // Start with the lowest possible binding power so nothing is "held
638        // back" — the entire input becomes one big expression.
639        self.parse_expr(0)
640    }
641}
642
643
644
645
646
647// =============================================================================
648// Tests
649// =============================================================================
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::ast::{BinaryOp, Leaf, Node, UnaryOp};
655    use crate::lexer::Lexer;
656
657    /// Convenience: parse `src` and return the root node.
658    fn parse(src: &str) -> Node {
659        Parser::new(Lexer::new(src)).parse()
660    }
661
662    // -------------------------------------------------------------------------
663    // Literals
664    // -------------------------------------------------------------------------
665
666    #[test]
667    fn test_number_literal() {
668        assert_eq!(parse("42"), Node::Numb(Leaf { pos: 0, val: 42.0 }));
669    }
670
671    #[test]
672    fn test_empty_expression() {
673        assert_eq!(parse(""), Node::Nothing(Leaf { pos: 0, val: () }));
674    }
675
676    #[test]
677    fn test_whitespace_empty_expression() {
678        assert_eq!(parse("   "), Node::Nothing(Leaf { pos: 3, val: () }));
679    }
680
681    #[test]
682    fn test_comment_only_expression() {
683        assert_eq!(parse("# comment\n"), Node::Nothing(Leaf { pos: 10, val: () }));
684    }
685
686    #[test]
687    fn test_nothing_literal() {
688        assert_eq!(parse("()"), Node::Nothing(Leaf { pos: 0, val: () }));
689    }
690
691    #[test]
692    fn test_nothing_literal_with_whitespace_and_comment() {
693        assert_eq!(parse("(\n  # comment\n)"), Node::Nothing(Leaf { pos: 0, val: () }));
694    }
695
696    #[test]
697    fn test_nested_nothing_literal() {
698        assert_eq!(parse("(())"), Node::Nothing(Leaf { pos: 1, val: () }));
699    }
700
701    #[test]
702    fn test_empty_list_literal() {
703        assert_eq!(
704            parse("[]"),
705            Node::List(UnaryOp {
706                pos: 0,
707                opd: Box::new(Node::Nothing(Leaf { pos: 1, val: () })),
708            })
709        );
710    }
711
712    #[test]
713    fn test_empty_namespace_literal() {
714        assert_eq!(
715            parse("{}"),
716            Node::Namespace(UnaryOp {
717                pos: 0,
718                opd: Box::new(Node::Nothing(Leaf { pos: 1, val: () })),
719            })
720        );
721    }
722
723    #[test]
724    fn test_application_with_nothing_argument() {
725        assert_eq!(
726            parse("f ()"),
727            Node::ApplicationOp(BinaryOp {
728                pos: 2,
729                lho: Box::new(Node::Identifier(Leaf { pos: 0, val: "f".to_string() })),
730                rho: Box::new(Node::Nothing(Leaf { pos: 2, val: () })),
731            })
732        );
733    }
734
735    #[test]
736    fn test_text_literal() {
737        assert_eq!(
738            parse("\"hello\""),
739            Node::Text(Leaf { pos: 0, val: "hello".to_string() })
740        );
741    }
742
743    #[test]
744    fn test_identifier_literal() {
745        assert_eq!(
746            parse("foo"),
747            Node::Identifier(Leaf { pos: 0, val: "foo".to_string() })
748        );
749    }
750
751
752    // -------------------------------------------------------------------------
753    // Arithmetic — precedence and associativity
754    // -------------------------------------------------------------------------
755
756    #[test]
757    fn test_addition() {
758        // `1 + 2` → PlusOp(1, 2)
759        assert_eq!(
760            parse("1 + 2"),
761            Node::SumOp(BinaryOp {
762                pos: 2,
763                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
764                rho: Box::new(Node::Numb(Leaf { pos: 4, val: 2.0 })),
765            })
766        );
767    }
768
769    #[test]
770    fn test_precedence_mul_over_add() {
771        // `1 + 2 * 3` must parse as `1 + (2 * 3)` because `*` binds tighter.
772        // The expected tree: PlusOp(1, ProductOp(2, 3))
773        let expected = Node::SumOp(BinaryOp {
774            pos: 2,
775            lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
776            rho: Box::new(Node::ProductOp(BinaryOp {
777                pos: 6,
778                lho: Box::new(Node::Numb(Leaf { pos: 4, val: 2.0 })),
779                rho: Box::new(Node::Numb(Leaf { pos: 8, val: 3.0 })),
780            })),
781        });
782        assert_eq!(parse("1 + 2 * 3"), expected);
783    }
784
785    #[test]
786    fn test_left_associativity_of_addition() {
787        // `1 + 2 + 3` must parse as `(1 + 2) + 3` (left-associative).
788        // The expected tree: PlusOp(PlusOp(1, 2), 3)
789        let expected = Node::SumOp(BinaryOp {
790            pos: 6,
791            lho: Box::new(Node::SumOp(BinaryOp {
792                pos: 2,
793                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
794                rho: Box::new(Node::Numb(Leaf { pos: 4, val: 2.0 })),
795            })),
796            rho: Box::new(Node::Numb(Leaf { pos: 8, val: 3.0 })),
797        });
798        assert_eq!(parse("1 + 2 + 3"), expected);
799    }
800
801    #[test]
802    fn test_right_associativity_of_power() {
803        // `2 ^ 3 ^ 4` must parse as `2 ^ (3 ^ 4)` (right-associative).
804        let expected = Node::PowerOp(BinaryOp {
805            pos: 2,
806            lho: Box::new(Node::Numb(Leaf { pos: 0, val: 2.0 })),
807            rho: Box::new(Node::PowerOp(BinaryOp {
808                pos: 6,
809                lho: Box::new(Node::Numb(Leaf { pos: 4, val: 3.0 })),
810                rho: Box::new(Node::Numb(Leaf { pos: 8, val: 4.0 })),
811            })),
812        });
813        assert_eq!(parse("2 ^ 3 ^ 4"), expected);
814    }
815
816    #[test]
817    fn test_left_associativity_of_then() {
818        // `p ~> f ~> g` must parse as `(p ~> f) ~> g` (left-associative), so
819        // that `g` is chained onto the promise returned by `p ~> f`.
820        let ident = |pos, name: &str| Box::new(Node::Identifier(Leaf { pos, val: name.to_string() }));
821        let expected = Node::ThenOp(BinaryOp {
822            pos: 7,
823            lho: Box::new(Node::ThenOp(BinaryOp {
824                pos: 2,
825                lho: ident(0, "p"),
826                rho: ident(5, "f"),
827            })),
828            rho: ident(10, "g"),
829        });
830        assert_eq!(parse("p ~> f ~> g"), expected);
831    }
832
833
834    #[test]
835    fn test_unary_minus() {
836        // `-3` → NegateOp(3)
837        assert_eq!(
838            parse("-3"),
839            Node::NegateOp(UnaryOp {
840                pos: 0,
841                opd: Box::new(Node::Numb(Leaf { pos: 1, val: 3.0 })),
842            })
843        );
844    }
845
846    #[test]
847    fn test_parentheses_grouping() {
848        // `(1 + 2)` → PlusOp(1, 2)  — parentheses are transparent in the AST.
849        assert_eq!(
850            parse("(1 + 2)"),
851            Node::SumOp(BinaryOp {
852                pos: 3,
853                lho: Box::new(Node::Numb(Leaf { pos: 1, val: 1.0 })),
854                rho: Box::new(Node::Numb(Leaf { pos: 5, val: 2.0 })),
855            })
856        );
857    }
858
859    #[test]
860    fn test_list_literal() {
861        // `[42]` → List(42)
862        assert_eq!(
863            parse("[42]"),
864            Node::List(UnaryOp {
865                pos: 0,
866                opd: Box::new(Node::Numb(Leaf { pos: 1, val: 42.0 })),
867            })
868        );
869    }
870
871    #[test]
872    fn test_namespace_literal() {
873        // `{42}` → Namespace(42)
874        assert_eq!(
875            parse("{42}"),
876            Node::Namespace(UnaryOp {
877                pos: 0,
878                opd: Box::new(Node::Numb(Leaf { pos: 1, val: 42.0 })),
879            })
880        );
881    }
882
883
884    // -------------------------------------------------------------------------
885    // Application operator (empty / juxtaposition)
886    // -------------------------------------------------------------------------
887
888    #[test]
889    fn test_application_simple() {
890        // `1 2` → ApplicationOp(1, 2)
891        // pos recorded is the position of the rhs token (2), which is at byte 2.
892        assert_eq!(
893            parse("1 2"),
894            Node::ApplicationOp(BinaryOp {
895                pos: 2,
896                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
897                rho: Box::new(Node::Numb(Leaf { pos: 2, val: 2.0 })),
898            })
899        );
900    }
901
902    #[test]
903    fn test_application_left_associative() {
904        // `1 2 3` must parse as `(1 2) 3` (left-associative).
905        // Expected tree: ApplicationOp(ApplicationOp(1, 2), 3)
906        let expected = Node::ApplicationOp(BinaryOp {
907            pos: 4,
908            lho: Box::new(Node::ApplicationOp(BinaryOp {
909                pos: 2,
910                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
911                rho: Box::new(Node::Numb(Leaf { pos: 2, val: 2.0 })),
912            })),
913            rho: Box::new(Node::Numb(Leaf { pos: 4, val: 3.0 })),
914        });
915        assert_eq!(parse("1 2 3"), expected);
916    }
917
918    #[test]
919    fn test_application_binds_tighter_than_arithmetic() {
920        // `1 2 + 3` must parse as `(1 2) + 3`
921        // because application (bp=28) > addition (bp=25).
922        let expected = Node::SumOp(BinaryOp {
923            pos: 4,
924            lho: Box::new(Node::ApplicationOp(BinaryOp {
925                pos: 2,
926                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
927                rho: Box::new(Node::Numb(Leaf { pos: 2, val: 2.0 })),
928            })),
929            rho: Box::new(Node::Numb(Leaf { pos: 6, val: 3.0 })),
930        });
931        assert_eq!(parse("1 2 + 3"), expected);
932    }
933
934    #[test]
935    fn test_application_parenthesised_argument() {
936        // `1(2)` — the rhs is a parenthesised sub-expression.
937        // parse("1(2)") → ApplicationOp(1, 2)
938        // The inner `(2)` just passes through the grouping rule.
939        assert_eq!(
940            parse("1(2)"),
941            Node::ApplicationOp(BinaryOp {
942                pos: 1,
943                lho: Box::new(Node::Numb(Leaf { pos: 0, val: 1.0 })),
944                rho: Box::new(Node::Numb(Leaf { pos: 2, val: 2.0 })),
945            })
946        );
947    }
948}
949