From 10699405d31e14f4b06b1b86c6c0c4054fce0102 Mon Sep 17 00:00:00 2001 From: Manoel Brunnen Date: Sat, 7 Nov 2026 23:30:39 +0100 Subject: [PATCH] Let a unary operator take a unary expression KerML 8.2.5.8.1 gives the operand of a UnaryOperatorExpression as an ArgumentMember, so any expression, and `- -x` and `not not b` are valid. The parser followed the Pilot's Xtext, which narrows the operand to an ExtentExpression. The formatter prints `-(-x)` as `--x`, so it refused such a file. --- CHANGELOG.md | 6 ++++++ crates/sysmlv2-parser/tests/parser.rs | 30 +++++++++++++++++++++++++++ crates/sysmlv2-syntax/src/parser.rs | 5 ++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d370d5e..bc1fb3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixed + +- A unary operator takes another unary expression as its operand (KerML 8.2.5.8.1: the operand is an `ArgumentMember`, so any expression), so `- -x`, `~~x` and `not not b` parse; the Pilot's Xtext narrows the operand to an `ExtentExpression`. `fmt`, which prints `-(-x)` as `--x`, no longer refuses such a file. + ## v0.11.3 ### Added diff --git a/crates/sysmlv2-parser/tests/parser.rs b/crates/sysmlv2-parser/tests/parser.rs index 3b7bd34..b6ef695 100644 --- a/crates/sysmlv2-parser/tests/parser.rs +++ b/crates/sysmlv2-parser/tests/parser.rs @@ -558,6 +558,36 @@ fn unary_binds_tighter_than_exponentiation() { )); } +#[test] +fn unary_operators_nest() { + // KerML 8.2.5.8.1: the operand of a unary operator is any expression. + for (src, outer, inner) in [ + ("- -x", UnaryOp::Minus, UnaryOp::Minus), + ("~~x", UnaryOp::Tilde, UnaryOp::Tilde), + ("not not x", UnaryOp::Not, UnaryOp::Not), + ("-~x", UnaryOp::Minus, UnaryOp::Tilde), + ] { + let ExprKind::Unary { op, operand } = value_expr(src).kind else { + panic!("{src}") + }; + assert_eq!(op, outer, "{src}"); + assert!( + matches!(operand.kind, ExprKind::Unary { op, .. } if op == inner), + "{src}" + ); + } + // - -2 ** 3 parses as (-(-2)) ** 3, as -2 ** 3 does. + let ExprKind::Binary { + op: BinaryOp::Pow, + lhs, + .. + } = value_expr("- -2 ** 3").kind + else { + panic!() + }; + assert!(matches!(lhs.kind, ExprKind::Unary { .. })); +} + #[test] fn logical_chain() { // a or b and c => or(a, and(b, c)) — `and` binds tighter. diff --git a/crates/sysmlv2-syntax/src/parser.rs b/crates/sysmlv2-syntax/src/parser.rs index 64990f9..109a6ec 100644 --- a/crates/sysmlv2-syntax/src/parser.rs +++ b/crates/sysmlv2-syntax/src/parser.rs @@ -5262,7 +5262,10 @@ impl<'s> Parser<'s> { if let Some(op) = op { let start = self.cur().span; self.bump(); - let operand = self.parse_extent()?; + // KerML 8.2.5.8.1: the operand is an ArgumentMember, so any + // expression, and `- -x` and `not not b` nest. The recursion + // is charged as one level deeper. + let operand = self.branch(|p| p.descend(Self::parse_unary))?; let span = start.join(operand.span); return Some(Expr { kind: ExprKind::Unary {