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