Skip to content

Commit 0fa75ed

Browse files
committed
Fix infinite loop when comment follows /; operator
The Operator and ConditionOp grammar rules were non-atomic, causing pest to consume WHITESPACE/COMMENT tokens within the operator span. This made as_str() return e.g. "/; (*foo*) " instead of "/;", which wasn't recognized as a valid operator and produced a Raw expression that was re-evaluated infinitely. Fix: make both rules atomic (@{ }) so comments are not included in the operator span.
1 parent 7fe7cc4 commit 0fa75ed

File tree

2 files changed

+24
-2
lines changed

2 files changed

+24
-2
lines changed

src/wolfram.pest

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ RuleAnonymousFunction = { ReplacementRule ~ "&" ~ !"&" }
108108
// BaseFunctionCall is a function call that doesn't include anonymous functions to avoid recursion
109109
BaseFunctionCall = { Identifier ~ DerivativePrime? ~ ("[" ~ (FunctionArg ~ ("," ~ FunctionArg)*)? ~ "]")+ }
110110

111-
Operator = {
111+
// Atomic so that WHITESPACE/COMMENT tokens are not consumed within
112+
// the operator span — otherwise as_str() would include e.g. comments
113+
// between "/;" and the following negative lookahead, breaking operator
114+
// recognition in the AST builder.
115+
Operator = @{
112116
"\\[NotElement]" | "\u{2209}" // Named character operator: NotElement (∉)
113117
| "\\[ReverseElement]" | "\u{220B}" // Named character operator: ReverseElement (∋)
114118
| "\\[Element]" | "\u{2208}" // Named character operator: Element (∈)
@@ -339,7 +343,8 @@ ConditionExpr = {
339343
(LeadingNot ~ Term ~ FactorialSuffix? | LeadingMinus ~ Term ~ FactorialSuffix? | Term ~ FactorialSuffix?) ~ ((ConditionOp | TildeInfix) ~ (LeadingMinus ~ Term ~ FactorialSuffix? | Term ~ FactorialSuffix?))*
340344
}
341345
// Operators allowed in conditions - excludes -> and :> which mark rule boundaries
342-
ConditionOp = {
346+
// Atomic so that WHITESPACE/COMMENT tokens are not consumed within the operator span.
347+
ConditionOp = @{
343348
"\\[NotElement]" | "\u{2209}" // Named character operator: NotElement (∉)
344349
| "\\[ReverseElement]" | "\u{220B}" // Named character operator: ReverseElement (∋)
345350
| "\\[Element]" | "\u{2208}" // Named character operator: Element (∈)

tests/interpreter_tests.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,23 @@ mod interpreter_tests {
156156
assert_eq!(interpret("5 + (* inline *) 3").unwrap(), "8");
157157
}
158158

159+
#[test]
160+
fn test_comment_after_condition_operator() {
161+
// A comment after /; should not cause an infinite loop
162+
clear_state();
163+
assert_eq!(interpret("x /; (* foo *) True").unwrap(), "x /; True");
164+
}
165+
166+
#[test]
167+
fn test_comment_after_condition_in_set_delayed() {
168+
// SetDelayed with Condition and inline comment should work
169+
clear_state();
170+
assert_eq!(
171+
interpret("f[x_] := x^2 /; (* positive *) True; f[3]").unwrap(),
172+
"9"
173+
);
174+
}
175+
159176
#[test]
160177
fn test_nested_comment() {
161178
clear_state();

0 commit comments

Comments
 (0)