Skip to content

Spacing commands — kern, hspace, skip (item 4)#243

Merged
kostub merged 3 commits into
masterfrom
feature/box-spacing-pr2
Jun 29, 2026
Merged

Spacing commands — kern, hspace, skip (item 4)#243
kostub merged 3 commits into
masterfrom
feature/box-spacing-pr2

Conversation

@kostub

@kostub kostub commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Goal

Recognize \kern, \mkern, \hspace, \hspace*, \hskip, \mskip, \mspace via a new em/mu length scanner that emits the existing MTMathSpace atom. No new atom, no new display — touches only MTMathListBuilder.m (plus tests).

This is PR 2 of the box-and-spacing layer. It is independent of PR 1 and based directly on master.

Commits

  • [item 4] Add em/mu dimension scanner and spacing commands emitting MTMathSpace

Implementation

  • +spacingCommands — dispatch table: kern/hspace/hskip accept em or mu; mkern/mskip/mspace are mu-only.
  • -readDimensionIntoMu:allowEm:command: — scanner handling optional {…}, sign, decimal mantissa, surrounding whitespace, and em/mu units. Malformed/unsupported input sets MTParseErrorInvalidCommand.
  • Spacing branch in -atomForCommand: — consumes the optional * for \hspace*, calls the scanner, returns [[MTMathSpace alloc] initWithSpace:mu].

Tests

  • MTMathListBuilderTest: testParseSpacingDimensions, testParseSpacingAliases, testParseGlueTailIgnored, + 5 error cases in getTestDataParseErrors().
  • MTTypesetterTest: testSpacingAdvances.
  • Full suite: 301 tests, 0 failures. swift build clean.

References

  • Plan: docs/plans/2026-06-28-box-spacing-layer.md (PR 2, Task 4)
  • LLD: docs/lld/2026-06-28-box-spacing-layer.md

🤖 Generated with Claude Code

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for TeX spacing commands (such as \kern, \hspace, \hskip, \mkern, \mskip, and \mspace) by implementing a dimension parser and mapping commands to their allowed units (em or mu). It also adds comprehensive unit tests to verify parsing errors, spacing dimensions, and typesetter advances. The review feedback suggests defensive programming improvements in MTMathListBuilder.m, specifically guarding against a potential nil key when looking up spacing commands in the dictionary and checking for a NULL pointer before dereferencing outMu in readDimensionIntoMu:allowEm:command:.

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.

Comment thread iosMath/lib/MTMathListBuilder.m Outdated
} else {
// Spacing commands: \kern, \hspace[*], \hskip, \mkern, \mskip, \mspace.
// The table maps each command name to @YES (em or mu allowed) or @NO (mu only).
NSNumber* allowEm = [MTMathListBuilder spacingCommands][command];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Always guard against nil keys before accessing an NSDictionary in Objective-C to prevent NSInvalidArgumentException crashes. If command is nil, the lookup [MTMathListBuilder spacingCommands][command] will crash. Although command is expected to be non-nil in the current parser flow, adding a defensive check ensures safety against future refactorings or unexpected inputs.

        NSNumber* allowEm = command ? [MTMathListBuilder spacingCommands][command] : nil;
References
  1. Always guard against nil keys before accessing or modifying an NSMutableDictionary (or NSDictionary) in Objective-C to prevent NSInvalidArgumentException crashes.

}
}

*outMu = sign * num.doubleValue * factor;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

It is a good defensive programming practice to check if the pointer outMu is not NULL before dereferencing it. Dereferencing a NULL pointer will cause a crash. Although the current call site always passes a valid address of a local variable, adding this check prevents potential crashes if the method is reused or refactored in the future.

    if (outMu) {
        *outMu = sign * num.doubleValue * factor;
    }

@kostub kostub left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review — Spacing commands (item 4)

Reviewed against the plan (docs/plans/2026-06-28-box-spacing-layer.md, PR 2 / Task 4) and LLD. Built with swift build and ran the new tests plus targeted edge-case probes off ffcfb3a. All green; the implementation is faithful to the plan and the scanner behaves correctly. Not approving — leaving the call to the maintainer. No blocking issues found.

Strengths

  • Faithful to the design: reuses the existing MTMathSpace atom (mu units), no new atom/display/export, em→mu factor 18 matches the existing quad/qquad table in MTMathAtomFactory.m.
  • Sensible deviation from the plan's @"hspace*" table key: keys on hspace and consumes the trailing * in the dispatch, which is correct given readString stops at the non-alphabetic *. The inline comment explains it.
  • Upgraded the +spacingCommands table to dispatch_once (the plan sketch used a non-thread-safe if (!commands)); consistent with the file's other lazy tables.
  • Malformed/unsupported input fails loud with MTParseErrorInvalidCommand (matches the LLD's error-code decision and the \cfrac[zzz] precedent). Verified \kern1pt, \kern1xx, \kern1e all set the error and produce no atom.
  • Round-trips: \kern1em (space 18) re-serializes via appendLaTeXToString: as \quad/\mkernX.Xmu, both re-parseable through this new path.

Minor (non-blocking)

1. Test gap — the "valid number, bad unit" error branch is uncovered.
All five new error cases hit either the no-digit branch (\kernabc, \hspace{abc}, \hspace{}) or the em-disallowed branch (\mkern{1em}). The else branch that emits "expects em or mu units, got: %@" (e.g. \kern1pt, \kern1xx) has no test, though I confirmed it works. Suggest adding to getTestDataParseErrors():

@[@"\\kern1pt", @(MTParseErrorInvalidCommand)],   // valid number, unsupported unit

2. \hspace *{1em} (whitespace between command and *) is rejected.
The * is only consumed when it is the immediate next character; with intervening space the scanner then sees * and errors. Real TeX tolerates the space. Acceptable given the documented em/mu-only scope, but worth a one-line note or a follow-up if broader TeX compatibility is desired.

3. Deviation from TeX semantics (intentional, per design).
\kern/\hspace/\hskip accept mu here, which real TeX does not (mu is only valid in math glue / \mkern/\mskip); and only em/mu are supported (no pt/cm/ex/…). This matches the LLD ("kern accepts em or mu") and the fail-loud principle, so not a bug — flagging only so the deviation is a conscious record.

Verification

  • swift build: clean.
  • New tests testParseSpacingDimensions, testParseSpacingAliases, testParseGlueTailIgnored, testSpacingAdvances, and the 5 added error cases: all pass.
  • Edge probes confirmed: \kern1.em→1 atom, \kern1emx→space + ordinary x (glue-tail behavior), invalid units→error.

kostub and others added 2 commits June 29, 2026 12:38
# Conflicts:
#	iosMath/lib/MTMathListBuilder.m
#	iosMathTests/MTMathListBuilderTest.m
#	iosMathTests/MTTypesetterTest.m
Owner review on PR #243 raised two minor items:

1. Test gap: the "valid number, unsupported unit" error branch in
   readDimensionIntoMu:allowEm:command: (the else that emits "expects em or
   mu units, got: %@") had no coverage. Add \kern1pt and \kern1xx to
   getTestDataParseErrors().

2. \hspace *{1em} (whitespace between the command and the '*') was rejected
   where real TeX tolerates it. Skip spaces before consuming the optional
   '*'; a skipped run is harmless when no '*' follows since the dimension
   scanner also skips leading whitespace. Locked in with a new alias test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@kostub kostub merged commit f644371 into master Jun 29, 2026
1 check passed
@kostub-goldcast kostub-goldcast mentioned this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant