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