Add LaTeX array environment (PR 2/3): parser + serialization#253
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for the LaTeX array environment in iosMath, enabling the parsing and serialization of column alignments, vertical borders, and horizontal lines (\hline). It also adds comprehensive unit tests to verify the new parsing logic and error handling. The review feedback suggests two important improvements: adding a nil-guard for _currentEnv.envName to prevent potential crashes when checking environment capabilities, and ignoring whitespace characters within the array column specification to align with standard LaTeX parsing behavior.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (_currentEnv && [[MTMathListBuilder environmentsAllowingHorizontalLines] | ||
| containsObject:_currentEnv.envName]) { |
There was a problem hiding this comment.
If _currentEnv.envName is nil (which occurs when parsing default tables), calling containsObject: with nil can lead to unexpected behavior or crashes depending on the Foundation version. It is safer to explicitly guard against nil before checking set membership.
if (_currentEnv && _currentEnv.envName && [[MTMathListBuilder environmentsAllowingHorizontalLines]
containsObject:_currentEnv.envName]) {There was a problem hiding this comment.
Added the _currentEnv.envName guard in 741ee63 for consistency with the existing nil-envName handling (e.g. line 1473) and to avoid relying on containsObject:nil semantics. Note: on Apple Foundation [NSSet containsObject:nil] returns NO rather than crashing, so behavior was already correct here (falls through to the loud error) — this just makes the nil case explicit.
| for (NSUInteger i = 0; i < raw.length; i++) { | ||
| unichar c = [raw characterAtIndex:i]; | ||
| switch (c) { | ||
| case 'l': [alignments addObject:@(kMTColumnAlignmentLeft)]; [vLines addObject:@0]; break; | ||
| case 'c': [alignments addObject:@(kMTColumnAlignmentCenter)]; [vLines addObject:@0]; break; | ||
| case 'r': [alignments addObject:@(kMTColumnAlignmentRight)]; [vLines addObject:@0]; break; | ||
| case '|': { | ||
| NSUInteger idx = vLines.count - 1; | ||
| vLines[idx] = @(vLines[idx].integerValue + 1); | ||
| break; | ||
| } | ||
| default: { | ||
| NSString* message = [NSString stringWithFormat: | ||
| @"Unsupported array column specifier: %C", c]; | ||
| [self setError:MTParseErrorInvalidColumnSpec message:message]; | ||
| return NO; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
In LaTeX, whitespace characters inside the column specification (e.g., \begin{array}{c | c}) are ignored. Currently, any whitespace character in the column specifier will trigger the default case and raise an MTParseErrorInvalidColumnSpec error. We should explicitly ignore whitespace characters during parsing.
for (NSUInteger i = 0; i < raw.length; i++) {
unichar c = [raw characterAtIndex:i];
switch (c) {
case ' ':
case '\t':
case '\n':
case '\r':
break;
case 'l': [alignments addObject:@(kMTColumnAlignmentLeft)]; [vLines addObject:@0]; break;
case 'c': [alignments addObject:@(kMTColumnAlignmentCenter)]; [vLines addObject:@0]; break;
case 'r': [alignments addObject:@(kMTColumnAlignmentRight)]; [vLines addObject:@0]; break;
case '|': {
NSUInteger idx = vLines.count - 1;
vLines[idx] = @(vLines[idx].integerValue + 1);
break;
}
default: {
NSString* message = [NSString stringWithFormat:
@"Unsupported array column specifier: %C", c];
[self setError:MTParseErrorInvalidColumnSpec message:message];
return NO;
}
}
}There was a problem hiding this comment.
Fixed in 741ee63. interpretArrayColumnSpec now skips space/tab/newline, so {c | c} parses as two columns. Added an assertion to testArrayColumnSpecInterpretation covering it.
Code review — PR 2/3 (array parser + serialization)Reviewed the delta on top of PR #251 ( The core work is solid and matches the plan/LLD:
Four non-blocking fidelity/edge findings (all verified empirically against the head), none critical: 1. (Minor) 2. (Minor) The array-scoped trim can drop an intentional all-empty final row. 3. (Minor) Internal whitespace in the column spec is rejected. 4. (Nit) Coverage gap. Tests cover top/middle/bottom Not approving (comment-only per request). Nothing here blocks merge; items 1–3 are worth a brief look since the LLD frames round-trip fidelity as a goal. |
7570801 to
843a1c8
Compare
Adds MTParseErrorMissingColumnSpec/MTParseErrorInvalidColumnSpec, registers "array" in +environmentsTakingArgument, and makes -readEnvironmentArgument: env-aware so array gets its own loud, specific error messages instead of alignedat's numeric-argument message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H5hd85BBs6ExZVyqevdpqu
Also trims a wholly-empty trailing array row: a trailing \\ is needed so a bottom \hline is recognized before \end (matching real LaTeX array semantics), but if nothing but \hline/whitespace followed it, the row it opened has no content and must not count as an extra row.
843a1c8 to
7972159
Compare
…vName
- interpretArrayColumnSpec now skips space/tab/newline chars so specs like
{c | c} parse as two columns (LaTeX ignores whitespace in column specs)
instead of raising MTParseErrorInvalidColumnSpec.
- recordHorizontalLine explicitly guards _currentEnv.envName before the set
membership check, matching the existing nil-envName handling elsewhere and
removing reliance on containsObject:nil semantics.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Goal
Parse
\begin{array}{<spec>} … \end{array}end-to-end: registerarrayin the merged#248argument hook, interpret the raw column spec into structuredalignments/verticalLines, count\hlineper boundary (array-only, loud error otherwise), build the table via PR 1's constructor, and round-trip viaappendLaTeXToString:. No renderer changes yet — the produced atom tree is validated structurally.Commits
[item 4]Register array in argument hook + column-spec error codes[item 5]Interpret array column spec into alignments + verticalLines[item 6]Count\hlineper boundary; reject outside array[item 7]End-to-end array parse + error-table coverage[item 8]Round-trip array spec +\hlinein appendLaTeXToStringNotes
\hlineis intercepted at thebuildInternal\\-command dispatch site (via-recordHorizontalLine+continue), not inside-stopCommand:.array-scoped trim in-buildTable:that drops a trailing all-empty row (so a bottom\hlinewith a trailing\\yields the correctnumRows). Scoped toarrayonly; matrix/eqalign/split/aligned behavior is unchanged. Full suite green (397/397).Stack
master)feature/array-environment-pr1)References
docs/plans/2026-07-03-array-environment.md(PR 2, items 4-8)docs/lld/2026-06-29-array-environment.md🤖 Generated with Claude Code