/home/noah/src/ruchy/src/frontend/parser/operator_precedence.rs
Line | Count | Source |
1 | | //! Operator precedence handling for the Ruchy parser |
2 | | //! |
3 | | //! This module defines operator precedence and associativity rules |
4 | | //! ensuring proper parsing of complex expressions. |
5 | | |
6 | | #![allow(dead_code)] |
7 | | |
8 | | use crate::frontend::lexer::Token; |
9 | | |
10 | | /// Operator associativity |
11 | | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
12 | | pub enum Associativity { |
13 | | Left, |
14 | | Right, |
15 | | } |
16 | | |
17 | | /// Operator precedence levels (higher = tighter binding) |
18 | | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] |
19 | | pub struct Precedence(pub i32); |
20 | | |
21 | | impl Precedence { |
22 | | // Precedence levels from lowest to highest |
23 | | pub const ASSIGNMENT: Precedence = Precedence(10); |
24 | | pub const PIPELINE: Precedence = Precedence(20); |
25 | | pub const LOGICAL_OR: Precedence = Precedence(30); |
26 | | pub const LOGICAL_AND: Precedence = Precedence(40); |
27 | | pub const EQUALITY: Precedence = Precedence(50); |
28 | | pub const COMPARISON: Precedence = Precedence(60); |
29 | | pub const BITWISE_OR: Precedence = Precedence(70); |
30 | | pub const BITWISE_XOR: Precedence = Precedence(80); |
31 | | pub const BITWISE_AND: Precedence = Precedence(90); |
32 | | pub const SHIFT: Precedence = Precedence(100); |
33 | | pub const RANGE: Precedence = Precedence(110); |
34 | | pub const ADDITIVE: Precedence = Precedence(120); |
35 | | pub const MULTIPLICATIVE: Precedence = Precedence(130); |
36 | | pub const POWER: Precedence = Precedence(140); |
37 | | pub const UNARY: Precedence = Precedence(150); |
38 | | pub const POSTFIX: Precedence = Precedence(160); |
39 | | pub const CALL: Precedence = Precedence(170); |
40 | | pub const MEMBER: Precedence = Precedence(180); |
41 | | } |
42 | | |
43 | | /// Get operator precedence and associativity |
44 | 3 | pub fn get_operator_info(token: &Token) -> Option<(Precedence, Associativity)> { |
45 | | use Associativity::{Left, Right}; |
46 | | |
47 | 3 | match token { |
48 | | // Assignment and actor operators (right-associative) |
49 | | Token::Equal |
50 | | | Token::PlusEqual |
51 | | | Token::MinusEqual |
52 | | | Token::StarEqual |
53 | | | Token::SlashEqual |
54 | | | Token::PercentEqual |
55 | | | Token::AmpersandEqual |
56 | | | Token::PipeEqual |
57 | | | Token::CaretEqual |
58 | | | Token::LeftShiftEqual |
59 | | | Token::LeftArrow |
60 | 1 | | Token::ActorQuery => Option::Some((Precedence::ASSIGNMENT, Right)), |
61 | | |
62 | | // Pipeline operator (left-associative) |
63 | 0 | Token::Pipeline => Option::Some((Precedence::PIPELINE, Left)), |
64 | | |
65 | | // Logical operators |
66 | 0 | Token::OrOr => Option::Some((Precedence::LOGICAL_OR, Left)), |
67 | 0 | Token::AndAnd => Option::Some((Precedence::LOGICAL_AND, Left)), |
68 | | |
69 | | // Equality operators |
70 | 0 | Token::EqualEqual | Token::NotEqual => Option::Some((Precedence::EQUALITY, Left)), |
71 | | |
72 | | // Comparison operators |
73 | | Token::Less | Token::Greater | Token::LessEqual | Token::GreaterEqual => { |
74 | 0 | Option::Some((Precedence::COMPARISON, Left)) |
75 | | } |
76 | | |
77 | | // Bitwise operators |
78 | 0 | Token::Pipe => Option::Some((Precedence::BITWISE_OR, Left)), |
79 | 0 | Token::Caret => Option::Some((Precedence::BITWISE_XOR, Left)), |
80 | 0 | Token::Ampersand => Option::Some((Precedence::BITWISE_AND, Left)), |
81 | 0 | Token::LeftShift => Option::Some((Precedence::SHIFT, Left)), |
82 | | |
83 | | // Range operators |
84 | 0 | Token::DotDot | Token::DotDotEqual => Option::Some((Precedence::RANGE, Left)), |
85 | | |
86 | | // Arithmetic operators |
87 | 1 | Token::Plus | Token::Minus => Option::Some((Precedence::ADDITIVE, Left)), |
88 | | Token::Star | Token::Slash | Token::Percent => { |
89 | 0 | Option::Some((Precedence::MULTIPLICATIVE, Left)) |
90 | | } |
91 | 1 | Token::Power => Option::Some((Precedence::POWER, Right)), |
92 | | |
93 | 0 | _ => Option::None, |
94 | | } |
95 | 3 | } |
96 | | |
97 | | /// Check if token is a postfix operator |
98 | 4 | pub fn is_postfix_operator(token: &Token) -> bool { |
99 | 1 | matches!( |
100 | 4 | token, |
101 | | Token::Question // ? (try operator) |
102 | | | Token::Increment // ++ |
103 | | | Token::Decrement // -- |
104 | | | Token::Dot // . (member access) |
105 | | | Token::SafeNav // ?. (optional chaining) |
106 | | | Token::LeftParen // ( (function call) |
107 | | | Token::LeftBracket // [ (indexing) |
108 | | ) |
109 | 4 | } |
110 | | |
111 | | /// Check if token is a prefix operator |
112 | 0 | pub fn is_prefix_operator(token: &Token) -> bool { |
113 | 0 | matches!( |
114 | 0 | token, |
115 | | Token::Bang // ! (logical not) |
116 | | | Token::Minus // - (negation) |
117 | | | Token::Tilde // ~ (bitwise not) |
118 | | | Token::Ampersand // & (reference) |
119 | | | Token::Star // * (dereference) |
120 | | | Token::Increment // ++ (pre-increment) |
121 | | | Token::Decrement // -- (pre-decrement) |
122 | | ) |
123 | 0 | } |
124 | | |
125 | | /// Get postfix operator precedence |
126 | 0 | pub fn get_postfix_precedence(token: &Token) -> Precedence { |
127 | 0 | match token { |
128 | 0 | Token::Dot | Token::SafeNav => Precedence::MEMBER, |
129 | 0 | Token::LeftParen | Token::LeftBracket => Precedence::CALL, |
130 | 0 | Token::Question | Token::Increment | Token::Decrement => Precedence::POSTFIX, |
131 | 0 | _ => Precedence(0), |
132 | | } |
133 | 0 | } |
134 | | |
135 | | /// Check if we should continue parsing based on precedence |
136 | 0 | pub fn should_continue_parsing(current_token: &Token, min_precedence: Precedence) -> bool { |
137 | 0 | if let Option::Some((prec, _)) = get_operator_info(current_token) { |
138 | 0 | prec.0 >= min_precedence.0 |
139 | | } else { |
140 | 0 | false |
141 | | } |
142 | 0 | } |
143 | | |
144 | | #[cfg(test)] |
145 | | mod tests { |
146 | | use super::*; |
147 | | |
148 | | #[test] |
149 | 1 | fn test_operator_precedence_ordering() { |
150 | | // Verify precedence ordering |
151 | 1 | assert!(Precedence::ASSIGNMENT < Precedence::PIPELINE); |
152 | 1 | assert!(Precedence::PIPELINE < Precedence::LOGICAL_OR); |
153 | 1 | assert!(Precedence::LOGICAL_OR < Precedence::LOGICAL_AND); |
154 | 1 | assert!(Precedence::ADDITIVE < Precedence::MULTIPLICATIVE); |
155 | 1 | assert!(Precedence::MULTIPLICATIVE < Precedence::POWER); |
156 | 1 | assert!(Precedence::POWER < Precedence::POSTFIX); |
157 | 1 | assert!(Precedence::POSTFIX < Precedence::CALL); |
158 | 1 | } |
159 | | |
160 | | #[test] |
161 | 1 | fn test_postfix_operators() { |
162 | 1 | assert!(is_postfix_operator(&Token::Question)); |
163 | 1 | assert!(is_postfix_operator(&Token::Dot)); |
164 | 1 | assert!(is_postfix_operator(&Token::LeftParen)); |
165 | 1 | assert!(!is_postfix_operator(&Token::Plus)); |
166 | 1 | } |
167 | | |
168 | | #[test] |
169 | 1 | fn test_operator_associativity() { |
170 | | // Assignment is right-associative |
171 | 1 | let (_, assoc) = get_operator_info(&Token::Equal).expect("Equal should have operator info"); |
172 | 1 | assert_eq!(assoc, Associativity::Right); |
173 | | |
174 | | // Addition is left-associative |
175 | 1 | let (_, assoc) = get_operator_info(&Token::Plus).expect("Plus should have operator info"); |
176 | 1 | assert_eq!(assoc, Associativity::Left); |
177 | | |
178 | | // Power is right-associative |
179 | 1 | let (_, assoc) = get_operator_info(&Token::Power).expect("Power should have operator info"); |
180 | 1 | assert_eq!(assoc, Associativity::Right); |
181 | 1 | } |
182 | | } |