1use std::fmt;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
19pub enum BinaryOperator {
20 Pipe,
22 Or,
24 And,
26 Eq,
28 Neq,
30 Lt,
32 Gt,
34 Lte,
36 Gte,
38 In,
40 NotIn,
42 NilCoalesce,
44 Add,
46 Sub,
48 Mul,
50 Div,
52 Mod,
54 Pow,
56}
57
58impl BinaryOperator {
59 pub const ALL: [BinaryOperator; 18] = [
60 BinaryOperator::Pipe,
61 BinaryOperator::Or,
62 BinaryOperator::And,
63 BinaryOperator::Eq,
64 BinaryOperator::Neq,
65 BinaryOperator::Lt,
66 BinaryOperator::Gt,
67 BinaryOperator::Lte,
68 BinaryOperator::Gte,
69 BinaryOperator::In,
70 BinaryOperator::NotIn,
71 BinaryOperator::NilCoalesce,
72 BinaryOperator::Add,
73 BinaryOperator::Sub,
74 BinaryOperator::Mul,
75 BinaryOperator::Div,
76 BinaryOperator::Mod,
77 BinaryOperator::Pow,
78 ];
79
80 #[must_use]
82 pub const fn symbol(self) -> &'static str {
83 match self {
84 BinaryOperator::Pipe => "|>",
85 BinaryOperator::Or => "||",
86 BinaryOperator::And => "&&",
87 BinaryOperator::Eq => "==",
88 BinaryOperator::Neq => "!=",
89 BinaryOperator::Lt => "<",
90 BinaryOperator::Gt => ">",
91 BinaryOperator::Lte => "<=",
92 BinaryOperator::Gte => ">=",
93 BinaryOperator::In => "in",
94 BinaryOperator::NotIn => "not in",
95 BinaryOperator::NilCoalesce => "??",
96 BinaryOperator::Add => "+",
97 BinaryOperator::Sub => "-",
98 BinaryOperator::Mul => "*",
99 BinaryOperator::Div => "/",
100 BinaryOperator::Mod => "%",
101 BinaryOperator::Pow => "**",
102 }
103 }
104
105 #[must_use]
108 pub const fn wire_name(self) -> &'static str {
109 match self {
110 BinaryOperator::NotIn => "not_in",
111 other => other.symbol(),
112 }
113 }
114
115 #[must_use]
117 pub fn from_wire_name(name: &str) -> Option<Self> {
118 Self::ALL.into_iter().find(|op| op.wire_name() == name)
119 }
120
121 #[must_use]
122 pub const fn precedence(self) -> Precedence {
123 match self {
124 BinaryOperator::Pipe => Precedence::Pipe,
125 BinaryOperator::Or => Precedence::LogicalOr,
126 BinaryOperator::And => Precedence::LogicalAnd,
127 BinaryOperator::Eq | BinaryOperator::Neq => Precedence::Equality,
128 BinaryOperator::Lt
129 | BinaryOperator::Gt
130 | BinaryOperator::Lte
131 | BinaryOperator::Gte
132 | BinaryOperator::In
133 | BinaryOperator::NotIn => Precedence::Comparison,
134 BinaryOperator::NilCoalesce => Precedence::NilCoalescing,
135 BinaryOperator::Add | BinaryOperator::Sub => Precedence::Additive,
136 BinaryOperator::Mul | BinaryOperator::Div | BinaryOperator::Mod => {
137 Precedence::Multiplicative
138 }
139 BinaryOperator::Pow => Precedence::Exponent,
140 }
141 }
142
143 #[must_use]
148 pub const fn continues_across_newline(self) -> bool {
149 !matches!(
150 self,
151 BinaryOperator::Sub | BinaryOperator::In | BinaryOperator::NotIn
152 )
153 }
154
155 #[must_use]
157 pub const fn is_compound_assignable(self) -> bool {
158 matches!(
159 self,
160 BinaryOperator::Add
161 | BinaryOperator::Sub
162 | BinaryOperator::Mul
163 | BinaryOperator::Div
164 | BinaryOperator::Mod
165 )
166 }
167
168 #[must_use]
170 pub const fn is_comparison(self) -> bool {
171 matches!(
172 self.precedence(),
173 Precedence::Equality | Precedence::Comparison
174 )
175 }
176
177 #[must_use]
178 pub const fn is_logical(self) -> bool {
179 matches!(self, BinaryOperator::And | BinaryOperator::Or)
180 }
181
182 #[must_use]
183 pub const fn is_arithmetic(self) -> bool {
184 matches!(
185 self,
186 BinaryOperator::Add
187 | BinaryOperator::Sub
188 | BinaryOperator::Mul
189 | BinaryOperator::Div
190 | BinaryOperator::Mod
191 | BinaryOperator::Pow
192 )
193 }
194}
195
196impl fmt::Display for BinaryOperator {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 f.write_str(self.symbol())
199 }
200}
201
202impl serde::Serialize for BinaryOperator {
203 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
204 serializer.serialize_str(self.wire_name())
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
210pub enum UnaryOperator {
211 Not,
213 Neg,
215}
216
217impl UnaryOperator {
218 pub const ALL: [UnaryOperator; 2] = [UnaryOperator::Not, UnaryOperator::Neg];
219
220 #[must_use]
221 pub const fn symbol(self) -> &'static str {
222 match self {
223 UnaryOperator::Not => "!",
224 UnaryOperator::Neg => "-",
225 }
226 }
227}
228
229impl fmt::Display for UnaryOperator {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 f.write_str(self.symbol())
232 }
233}
234
235impl serde::Serialize for UnaryOperator {
236 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
237 serializer.serialize_str(self.symbol())
238 }
239}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum Associativity {
244 Left,
245 Right,
246 None,
248}
249
250impl Associativity {
251 #[must_use]
252 pub const fn as_str(self) -> &'static str {
253 match self {
254 Associativity::Left => "left",
255 Associativity::Right => "right",
256 Associativity::None => "none",
257 }
258 }
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
264pub enum Precedence {
265 Pipe,
267 Ternary,
269 LogicalOr,
270 LogicalAnd,
271 Equality,
272 Comparison,
274 NilCoalescing,
276 Range,
278 Additive,
279 Multiplicative,
280 Prefix,
282 Exponent,
284 Postfix,
286}
287
288impl Precedence {
289 pub const ALL: [Precedence; 13] = [
290 Precedence::Pipe,
291 Precedence::Ternary,
292 Precedence::LogicalOr,
293 Precedence::LogicalAnd,
294 Precedence::Equality,
295 Precedence::Comparison,
296 Precedence::NilCoalescing,
297 Precedence::Range,
298 Precedence::Additive,
299 Precedence::Multiplicative,
300 Precedence::Prefix,
301 Precedence::Exponent,
302 Precedence::Postfix,
303 ];
304
305 #[must_use]
308 pub const fn name(self) -> &'static str {
309 match self {
310 Precedence::Pipe => "pipe",
311 Precedence::Ternary => "ternary",
312 Precedence::LogicalOr => "logical_or",
313 Precedence::LogicalAnd => "logical_and",
314 Precedence::Equality => "equality",
315 Precedence::Comparison => "comparison",
316 Precedence::NilCoalescing => "nil_coalescing",
317 Precedence::Range => "range",
318 Precedence::Additive => "additive",
319 Precedence::Multiplicative => "multiplicative",
320 Precedence::Prefix => "prefix",
321 Precedence::Exponent => "exponent",
322 Precedence::Postfix => "postfix",
323 }
324 }
325
326 #[must_use]
327 pub const fn associativity(self) -> Associativity {
328 match self {
329 Precedence::Ternary | Precedence::Prefix | Precedence::Exponent => Associativity::Right,
330 Precedence::Range => Associativity::None,
331 _ => Associativity::Left,
332 }
333 }
334
335 #[must_use]
337 pub const fn description(self) -> &'static str {
338 match self {
339 Precedence::Pipe => "Pipe",
340 Precedence::Ternary => "Ternary conditional",
341 Precedence::LogicalOr => "Logical OR",
342 Precedence::LogicalAnd => "Logical AND",
343 Precedence::Equality => "Equality",
344 Precedence::Comparison => "Comparison / membership",
345 Precedence::NilCoalescing => "Nil coalescing",
346 Precedence::Range => "Range formation",
347 Precedence::Additive => "Additive",
348 Precedence::Multiplicative => "Multiplicative",
349 Precedence::Prefix => "Unary prefix",
350 Precedence::Exponent => "Exponentiation",
351 Precedence::Postfix => "Postfix",
352 }
353 }
354
355 #[must_use]
357 pub const fn spellings(self) -> &'static [&'static str] {
358 match self {
359 Precedence::Pipe => &["|>"],
360 Precedence::Ternary => &["? :"],
361 Precedence::LogicalOr => &["||"],
362 Precedence::LogicalAnd => &["&&"],
363 Precedence::Equality => &["==", "!="],
364 Precedence::Comparison => &["<", ">", "<=", ">=", "in", "not in"],
365 Precedence::NilCoalescing => &["??"],
366 Precedence::Range => &[RANGE_EXCLUSIVE, RANGE_INCLUSIVE],
367 Precedence::Additive => &["+", "-"],
368 Precedence::Multiplicative => &["*", "/", "%"],
369 Precedence::Prefix => &["!", "-"],
370 Precedence::Exponent => &["**"],
371 Precedence::Postfix => &[".", "?.", "[]", "?.[]", "[:]", "()", "?", "!"],
372 }
373 }
374
375 #[must_use]
377 pub fn level(self) -> usize {
378 Self::ALL
379 .iter()
380 .position(|group| *group == self)
381 .map_or(0, |index| index + 1)
382 }
383
384 #[must_use]
386 pub fn of_node(node: &crate::Node) -> Precedence {
387 match node {
388 crate::Node::BinaryOp { op, .. } => op.precedence(),
389 crate::Node::UnaryOp { .. } => Precedence::Prefix,
390 crate::Node::RangeExpr { .. } => Precedence::Range,
391 crate::Node::Ternary { .. } => Precedence::Ternary,
392 _ => Precedence::Postfix,
393 }
394 }
395}
396
397pub const RANGE_EXCLUSIVE: &str = "..";
399pub const RANGE_INCLUSIVE: &str = "..=";
401
402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
404pub enum OperandSide {
405 Left,
406 Right,
407 Prefix,
409 Postfix,
411 TernaryCondition,
413 TernaryBranch,
415}
416
417#[must_use]
420pub fn loosest_bare_operand(parent: Precedence, side: OperandSide) -> Precedence {
421 match side {
422 OperandSide::Prefix => Precedence::Prefix,
423 OperandSide::Postfix => Precedence::Postfix,
424 OperandSide::TernaryCondition => Precedence::LogicalOr,
425 OperandSide::TernaryBranch => Precedence::Ternary,
426 OperandSide::Right if parent == Precedence::Exponent => Precedence::Prefix,
429 OperandSide::Left | OperandSide::Right => {
430 let same_group_allowed = matches!(
431 (parent.associativity(), side),
432 (Associativity::Left, OperandSide::Left)
433 | (Associativity::Right, OperandSide::Right)
434 );
435 if same_group_allowed {
436 parent
437 } else {
438 parent.tighter().unwrap_or(Precedence::Postfix)
439 }
440 }
441 }
442}
443
444#[must_use]
447pub fn operand_needs_parens(parent: Precedence, child: Precedence, side: OperandSide) -> bool {
448 child < loosest_bare_operand(parent, side)
449}
450
451impl Precedence {
452 #[must_use]
454 pub fn tighter(self) -> Option<Precedence> {
455 Self::ALL.get(self.level()).copied()
456 }
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
464pub struct OperatorGrammar {
465 ladder: &'static [Precedence],
466 legacy_ranges: bool,
467}
468
469impl OperatorGrammar {
470 pub const CURRENT: OperatorGrammar = OperatorGrammar {
471 ladder: &Precedence::ALL,
472 legacy_ranges: false,
473 };
474
475 pub const LEGACY: OperatorGrammar = OperatorGrammar {
476 ladder: &[
477 Precedence::Pipe,
478 Precedence::Range,
479 Precedence::Ternary,
480 Precedence::LogicalOr,
481 Precedence::LogicalAnd,
482 Precedence::Equality,
483 Precedence::Comparison,
484 Precedence::Additive,
485 Precedence::NilCoalescing,
486 Precedence::Multiplicative,
487 Precedence::Prefix,
488 Precedence::Exponent,
489 Precedence::Postfix,
490 ],
491 legacy_ranges: true,
492 };
493
494 #[must_use]
495 pub fn loosest(self) -> Precedence {
496 self.ladder[0]
497 }
498
499 #[must_use]
501 pub fn tighter(self, group: Precedence) -> Precedence {
502 self.ladder
503 .iter()
504 .position(|candidate| *candidate == group)
505 .and_then(|index| self.ladder.get(index + 1))
506 .copied()
507 .unwrap_or(Precedence::Postfix)
508 }
509
510 #[must_use]
512 pub fn legacy_ranges(self) -> bool {
513 self.legacy_ranges
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn wire_names_round_trip() {
523 for op in BinaryOperator::ALL {
524 assert_eq!(BinaryOperator::from_wire_name(op.wire_name()), Some(op));
525 }
526 assert_eq!(BinaryOperator::from_wire_name("not in"), None);
527 }
528
529 #[test]
530 fn nil_coalescing_sits_between_comparison_and_range() {
531 assert!(Precedence::Comparison < Precedence::NilCoalescing);
532 assert!(Precedence::NilCoalescing < Precedence::Range);
533 assert!(Precedence::Range < Precedence::Additive);
534 }
535
536 #[test]
537 fn parenthesization_follows_associativity() {
538 let add = Precedence::Additive;
539 assert!(!operand_needs_parens(add, add, OperandSide::Left));
540 assert!(operand_needs_parens(add, add, OperandSide::Right));
541 assert!(operand_needs_parens(
542 Precedence::Range,
543 Precedence::Range,
544 OperandSide::Left
545 ));
546 assert!(!operand_needs_parens(
547 Precedence::Exponent,
548 Precedence::Prefix,
549 OperandSide::Right
550 ));
551 assert!(operand_needs_parens(
552 Precedence::Exponent,
553 Precedence::Prefix,
554 OperandSide::Left
555 ));
556 assert!(operand_needs_parens(
557 Precedence::Additive,
558 Precedence::NilCoalescing,
559 OperandSide::Left
560 ));
561 assert!(!operand_needs_parens(
562 Precedence::NilCoalescing,
563 Precedence::Additive,
564 OperandSide::Right
565 ));
566 }
567
568 #[test]
569 fn legacy_ladder_is_a_permutation_of_the_current_groups() {
570 let mut legacy = OperatorGrammar::LEGACY.ladder.to_vec();
571 legacy.sort();
572 assert_eq!(legacy, Precedence::ALL.to_vec());
573 }
574}