Fix crash on lone \sqrt at end of input (#188)#207
Conversation
The sqrt branch in MTMathListBuilder read the next character
unconditionally. When \sqrt was the last token in the input, this hit
the NSAssert in getNextCharacter and crashed with an
NSInternalInconsistencyException.
Guard the lookahead with hasCharacters; when no argument follows, build
an empty radicand, matching the existing behavior of \sqrt{}.
Adds testSqrtAtEnd covering the regression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request fixes a crash that occurs when a lone \sqrt command is parsed at the end of the input by ensuring characters are available before reading. A unit test was also added to prevent regression. The reviewer suggested simplifying the nested conditional logic in MTMathListBuilder.m by pulling the common rad.radicand assignment out of the conditional branches to reduce code duplication.
| if ([self hasCharacters]) { | ||
| unichar ch = [self getNextCharacter]; | ||
| if (ch == '[') { | ||
| // special handling for sqrt[degree]{radicand} | ||
| rad.degree = [self buildInternal:false stopChar:']']; | ||
| rad.radicand = [self buildInternal:true]; | ||
| } else { | ||
| [self unlookCharacter]; | ||
| rad.radicand = [self buildInternal:true]; | ||
| } | ||
| } else { | ||
| [self unlookCharacter]; | ||
| // No argument follows (e.g. a lone "\sqrt" at the end of input). | ||
| // Build an empty radicand rather than reading past the end. | ||
| rad.radicand = [self buildInternal:true]; | ||
| } |
There was a problem hiding this comment.
The nested conditional logic can be simplified by pulling the common rad.radicand = [self buildInternal:true]; call out of the conditional branches. This eliminates redundant code duplication and improves readability.
if ([self hasCharacters]) {
unichar ch = [self getNextCharacter];
if (ch == '[') {
// special handling for sqrt[degree]{radicand}
rad.degree = [self buildInternal:false stopChar:']'];
} else {
[self unlookCharacter];
}
}
rad.radicand = [self buildInternal:true];There was a problem hiding this comment.
Fixed. Pulled the common rad.radicand = [self buildInternal:true] out of all three branches into a single call after the optional degree parse. Kept a brief comment noting the lone-\sqrt guard. All 119 tests pass.
Covers a lone \sqrt inside a group ({\sqrt}), which reaches an empty
radicand via the oneCharOnly stop-char guard — a different path than the
end-of-input case. Suggested in code review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pull the common radicand build out of all three branches into a single call after the optional degree parse, per review on #207. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
Fixes #188. A lone
\sqrtat the end of the input crashed the parser with anNSInternalInconsistencyException.The
sqrtbranch inMTMathListBuildercalledgetNextCharacterunconditionally to peek for an optional[degree]argument. When\sqrtwas the last token, that call tripped theNSAssert([self hasCharacters])ingetNextCharacterand crashed.Fix
Guard the lookahead with
[self hasCharacters]. When no argument follows, build an empty radicand — the same result\sqrt{}already produces. This matches the fix proposed by the issue author.Testing
testSqrtAtEnd: asserts\sqrtparses to a radical with an empty radicand and round-trips to\sqrt{}. Confirmed it crashed before the fix and passes after.🤖 Generated with Claude Code