Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Changelog

### v2.6.0 (2026-07-28)

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.

Do not add anything tot he changelog. There is no release.

* Add **modular-arithmetic notation**: `\bmod` as a binary operator, and the `\pmod`, `\mod`, and `\pod` macros with amsmath's exact inline gaps and upright "mod" (#264, #268). `a \equiv b \pmod{n}` now renders as it does in LaTeX. The macros expand through a new internal macro atom, so a macro invocation serializes back to the command the author wrote rather than to its expansion. amsmath's wider display-style gap (18mu instead of 8/12mu) is not reproduced: a macro expands at parse time, before the render style is known.
Comment on lines +3 to +4

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the release metadata and \bmod scope.

July 28, 2026 is future-dated relative to July 27, 2026; keep this section unreleased until the tag is cut. Also, \bmod is unchanged in this PR, so do not present it as newly added.

Proposed fix
-### v2.6.0 (2026-07-28)
-* Add **modular-arithmetic notation**: `\bmod` as a binary operator, and the `\pmod`, `\mod`, and `\pod` macros ...
+### Unreleased
+* Add the `\pmod`, `\mod`, and `\pod` modular-arithmetic macros ... Existing `\bmod` remains a binary operator.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### v2.6.0 (2026-07-28)
* Add **modular-arithmetic notation**: `\bmod` as a binary operator, and the `\pmod`, `\mod`, and `\pod` macros with amsmath's exact inline gaps and upright "mod" (#264, #268). `a \equiv b \pmod{n}` now renders as it does in LaTeX. The macros expand through a new internal macro atom, so a macro invocation serializes back to the command the author wrote rather than to its expansion. amsmath's wider display-style gap (18mu instead of 8/12mu) is not reproduced: a macro expands at parse time, before the render style is known.
### Unreleased
* Add the `\pmod`, `\mod`, and `\pod` modular-arithmetic macros with amsmath's exact inline gaps and upright "mod" (`#264`, `#268`). Existing `\bmod` remains a binary operator. `a \equiv b \pmod{n}` now renders as it does in LaTeX. The macros expand through a new internal macro atom, so a macro invocation serializes back to the command the author wrote rather than to its expansion. amsmath's wider display-style gap (18mu instead of 8/12mu) is not reproduced: a macro expands at parse time, before the render style is known.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 3 - 4, Update the v2.6.0 entry in the changelog to
remain unreleased rather than using the future date, and revise its feature
description to remove \bmod as a newly added item while retaining the newly
introduced \pmod, \mod, and \pod macro changes.


### v2.5.0 (2026-07-14)
* Add the LaTeX **`array` environment**: `\begin{array}{lcr}…\end{array}` with per-column alignment, `|` column rules, and `\hline` row rules (#251, #253, #254).
* Add more matrix and alignment environments: `smallmatrix`, `gathered`, and `alignedat` (#246, #248).
Expand Down
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ let package = Package(
cSettings: [
.headerSearchPath("."),
.headerSearchPath("lib"),
.headerSearchPath("lib/internal"),
.headerSearchPath("render"),
.headerSearchPath("render/internal"),
]
Expand All @@ -36,6 +37,7 @@ let package = Package(
cSettings: [
.headerSearchPath("../iosMath"),
.headerSearchPath("../iosMath/lib"),
.headerSearchPath("../iosMath/lib/internal"),
.headerSearchPath("../iosMath/render"),
.headerSearchPath("../iosMath/render/internal"),
]
Expand All @@ -47,6 +49,7 @@ let package = Package(
cSettings: [
.headerSearchPath("../iosMath"),
.headerSearchPath("../iosMath/lib"),
.headerSearchPath("../iosMath/lib/internal"),
.headerSearchPath("../iosMath/render"),
.headerSearchPath("../iosMath/render/internal"),
],
Expand Down
9 changes: 9 additions & 0 deletions iosMath.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

/* Begin PBXBuildFile section */
490465BF1D23DA8400F82033 /* MTTypesetterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 490465BE1D23DA8400F82033 /* MTTypesetterTest.m */; };
C01DEC0DE20260726000003 /* MTModularArithmeticTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C01DEC0DE20260726000002 /* MTModularArithmeticTest.m */; };
49A1B2C41D23DA8400F82033 /* MTInkWidthTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 49A1B2C31D23DA8400F82033 /* MTInkWidthTest.m */; };
490465C11D23DA8400F82033 /* MTFontManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 490465C01D23DA8400F82033 /* MTFontManagerTest.m */; };
492EED0817DAEDD200939107 /* MTFontManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 492EECFA17DAED9000939107 /* MTFontManager.m */; };
Expand Down Expand Up @@ -77,6 +78,7 @@

/* Begin PBXFileReference section */
490465BE1D23DA8400F82033 /* MTTypesetterTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTTypesetterTest.m; sourceTree = "<group>"; };
C01DEC0DE20260726000002 /* MTModularArithmeticTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTModularArithmeticTest.m; sourceTree = "<group>"; };
49A1B2C31D23DA8400F82033 /* MTInkWidthTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTInkWidthTest.m; sourceTree = "<group>"; };
490465C01D23DA8400F82033 /* MTFontManagerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTFontManagerTest.m; sourceTree = "<group>"; };
492EECF317DAED9000939107 /* MTMathListDisplay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTMathListDisplay.h; sourceTree = "<group>"; };
Expand All @@ -93,6 +95,7 @@
492EED0217DAEDB500939107 /* MTMathList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTMathList.h; sourceTree = "<group>"; };
492EED0317DAEDB500939107 /* MTMathList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MTMathList.m; sourceTree = "<group>"; };
492EED0417DAEDB500939107 /* MTMathListBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTMathListBuilder.h; sourceTree = "<group>"; };
C01DEC0DE20260726000001 /* MTMacroParameterAtom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MTMacroParameterAtom.h; sourceTree = "<group>"; };
4987307517D546800041B02B /* libIosMath.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libIosMath.a; sourceTree = BUILT_PRODUCTS_DIR; };
498730AB17D548DB0041B02B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
49965EFC17CBBA2700A555C5 /* iosMathExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosMathExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -258,6 +261,7 @@
C01DEC0DE20260722000104 /* MTMathUILabelSizingTest.m */,
49B83EF517CF046A0014B739 /* MTMathListTest.m */,
490465BE1D23DA8400F82033 /* MTTypesetterTest.m */,
C01DEC0DE20260726000002 /* MTModularArithmeticTest.m */,
49A1B2C31D23DA8400F82033 /* MTInkWidthTest.m */,
490465C01D23DA8400F82033 /* MTFontManagerTest.m */,
49965F2417CBBA2700A555C5 /* Supporting Files */,
Expand Down Expand Up @@ -287,10 +291,12 @@
492EECFF17DAEDB500939107 /* MTMathListBuilder.m */,
49DA6BC319A05F850086B19F /* MTUnicode.h */,
49DA6BC619A062A30086B19F /* MTUnicode.m */,
C01DEC0DE20260726000004 /* internal */,
);
path = lib;
sourceTree = "<group>";
};
C01DEC0DE20260726000004 /* internal */ = { isa = PBXGroup; children = ( C01DEC0DE20260726000001 /* MTMacroParameterAtom.h */, ); path = internal; sourceTree = "<group>"; };
49965F3917CBD02000A555C5 /* render */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -537,6 +543,7 @@
C01DEC0DE20260722000103 /* MTMathUILabelSizingTest.m in Sources */,
498730A817D548190041B02B /* MTMathListTest.m in Sources */,
490465BF1D23DA8400F82033 /* MTTypesetterTest.m in Sources */,
C01DEC0DE20260726000003 /* MTModularArithmeticTest.m in Sources */,
49A1B2C41D23DA8400F82033 /* MTInkWidthTest.m in Sources */,
490465C11D23DA8400F82033 /* MTFontManagerTest.m in Sources */,
);
Expand Down Expand Up @@ -635,6 +642,7 @@
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/iosMath",
"$(SRCROOT)/iosMath/lib",
"$(SRCROOT)/iosMath/lib/internal",
"$(SRCROOT)/iosMath/render",
"$(SRCROOT)/iosMath/render/internal",
);
Expand Down Expand Up @@ -687,6 +695,7 @@
HEADER_SEARCH_PATHS = (
"$(SRCROOT)/iosMath",
"$(SRCROOT)/iosMath/lib",
"$(SRCROOT)/iosMath/lib/internal",
"$(SRCROOT)/iosMath/render",
"$(SRCROOT)/iosMath/render/internal",
);
Expand Down
43 changes: 43 additions & 0 deletions iosMath/lib/MTMathList.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ typedef NS_ENUM(NSUInteger, MTMathAtomType)
/// sub-mlist (== TeX Ord noad with sub_mlist / KaTeX "ordgroup").
/// Script-capable (< kMTMathAtomBoundary); spaced as Ordinary.
kMTMathAtomOrdGroup = 21,
/// An unexpanded macro invocation (\pmod, \mod, \pod), expanded away by
/// -[MTMathList finalized] so it never reaches the typesetter.
/// Script-capable (< kMTMathAtomBoundary): ^/_ attaches at parse time and is
/// transferred onto the expansion.
kMTMathAtomMacro = 22,

// Atoms after this point do not support subscripts or superscripts

Expand Down Expand Up @@ -688,6 +693,44 @@ typedef NS_ENUM(NSUInteger, MTStrikeStyle) {

@end

/** An unexpanded macro invocation.

`\pmod{n}` parses to exactly one `MTMacroAtom` and expands by splicing a deep
copy of each argument into the `#N` placeholders of `templateExpression`. All
stored lists are raw (non-finalized), parsed at parse time; the expansion is
re-derived from them every time `-[MTMathList finalized]` runs.

`#N` substitution reaches only the top level of the template. A placeholder
nested inside a sub-list (`\frac{#1}{2}`, `{#1}`, `x^{#1}`) is not substituted
and renders as a literal `#N` — built-in templates are all flat; user-defined
templates (`\newcommand`) need substitution that descends into sub-lists, which
does not exist yet.

@note Only `-[MTMathList finalized]` expands. `-[MTMacroAtom finalized]` on a
lone atom returns another macro atom.
*/
@interface MTMacroAtom : MTMathAtom

/** The command name without the leading backslash, e.g. `@"pmod"`. */
@property (nonatomic, copy, readonly) NSString* command;

/** The parsed arguments in invocation order. The lists are mutable, and owned by
this atom (deep-copied at init). */
@property (nonatomic, copy, readonly) NSArray<MTMathList*>* arguments;

/** The golden expansion template: a raw, argument-free list whose `#N`
references are internal placeholder atoms. */
@property (nonatomic, strong, readonly) MTMathList* templateExpression;

- (instancetype)initWithCommand:(NSString*)command
arguments:(NSArray<MTMathList*>*)arguments
templateExpression:(MTMathList*)templateExpression NS_DESIGNATED_INITIALIZER;

/// The implementation additionally throws, to catch dynamic (`id`-typed) callers.
- (instancetype)initWithType:(MTMathAtomType)type value:(NSString*)value NS_UNAVAILABLE;

@end

/** An atom representing an table element. This atom is not like other
atoms and is not present in TeX. We use it to represent the `\halign` command
in TeX with some simplifications. This is used for matrices, equation
Expand Down
Loading
Loading