Skip to content

Commit f149447

Browse files
committed
feat: added \mleft...\mright support, parse \color
1 parent 9c63ddb commit f149447

6 files changed

Lines changed: 77 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
### [Unreleased]
22

3+
- **Parse `\mleft`/`\mright` delimiters**: These alternative delimiters from the
4+
`mleftright` LaTeX package are now recognized and behave identically to
5+
`\left`/`\right`.
6+
7+
- **Parse `\color` in math mode**: The `\color{...}` command is now recognized
8+
in math mode. The color argument is consumed and discarded, allowing the
9+
subsequent expression to parse normally. Previously, `\color{red}3` produced
10+
an `unexpected-command` error.
11+
12+
- **Parse `:` and `\colon` as infix operators**: A bare `:` or `\colon` outside
13+
of quantifier contexts now parses as a `Colon` infix operator, useful for type
14+
annotations and mapping notation (e.g., `f:[a,b]\to\R`). The `:=` assignment
15+
operator and quantifier colon syntax are unaffected.
16+
317
- **`expand()` now returns the input expression instead of `null`**: The
418
`expand()` free function and the internal `expand()` / `expandAll()` functions
519
now return the original expression when no expansion is possible, instead of

src/compute-engine/latex-syntax/dictionary/definitions-core.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,30 @@ export const DEFINITIONS_CORE: LatexDictionary = [
366366
parse: parseAssign,
367367
},
368368

369+
// General colon operator (type annotation, mapping notation)
370+
// Precedence below assignment (260) so `:=` takes priority,
371+
// and below arrows (270) so `f: A \to B` parses as `Colon(f, To(A, B))`
372+
{
373+
name: 'Colon',
374+
latexTrigger: ':',
375+
kind: 'infix',
376+
associativity: 'right',
377+
precedence: 250,
378+
serialize: (serializer: Serializer, expr: MathJsonExpression): string =>
379+
joinLatex([
380+
serializer.serialize(operand(expr, 1)),
381+
'\\colon',
382+
serializer.serialize(operand(expr, 2)),
383+
]),
384+
},
385+
{
386+
latexTrigger: '\\colon',
387+
kind: 'infix',
388+
associativity: 'right',
389+
precedence: 250,
390+
parse: 'Colon',
391+
},
392+
369393
{
370394
name: 'BaseForm',
371395
serialize: (serializer, expr) => {

src/compute-engine/latex-syntax/dictionary/definitions-logic.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,15 @@ function parseQuantifier(
552552
// 2. If we didn't find a standalone symbol, we look for a condition
553553
//
554554
parser.index = index;
555-
const condition = parser.parseExpression(terminator);
555+
// Stop at colon separators so the quantifier can consume them
556+
const condTerminator = {
557+
...terminator,
558+
condition: (p: Parser) =>
559+
p.peek === ':' ||
560+
p.peek === '\\colon' ||
561+
(terminator.condition?.(p) ?? false),
562+
};
563+
const condition = parser.parseExpression(condTerminator);
556564
if (condition === null) return null;
557565

558566
// Either a separator or a parenthesis

src/compute-engine/latex-syntax/dictionary/definitions-other.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,14 @@ export const DEFINITIONS_OTHERS: LatexDictionary = [
278278
latexTrigger: ['\\scriptscriptstyle'],
279279
parse: () => 'Nothing', // @todo: parse as ['Annotated'...]
280280
},
281+
{
282+
latexTrigger: ['\\color'],
283+
parse: (parser: Parser) => {
284+
// Consume the {color} argument and discard it
285+
parser.parseGroup();
286+
return 'Nothing';
287+
},
288+
},
281289

282290
{
283291
latexTrigger: ['\\tiny'],

src/compute-engine/latex-syntax/parse.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const OPEN_DELIMITER_PREFIX = {
9999
'\\bigg': '\\bigg',
100100
'\\Bigg': '\\Bigg',
101101
'\\mathopen': '\\mathclose',
102+
'\\mleft': '\\mright',
102103
};
103104

104105
/** Commands that can be used with a middle delimiter */
@@ -1032,6 +1033,7 @@ export class _Parser implements Parser {
10321033
...'!"#$%&(),/;:?@[]\\`|~'.split(''),
10331034
'\\left',
10341035
'\\bigl',
1036+
'\\mleft',
10351037
];
10361038
if (excluding.includes(this.peek)) return null;
10371039

test/compute-engine/latex-syntax/stefnotch.test.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,28 @@ describe('STEFNOTCH #10', () => {
4646
});
4747

4848
test('4/ \\color{red}3', () => {
49-
expect(parse('\\color{red}3')).toMatchInlineSnapshot(`
50-
[
51-
"Tuple",
52-
["Error", "unexpected-command", ["LatexString", "\\color"]],
53-
"r",
54-
"ExponentialE",
55-
"d",
56-
3
57-
]
58-
`);
49+
expect(parse('\\color{red}3')).toMatchInlineSnapshot(`3`);
50+
});
51+
52+
test('4b/ \\color{blue}{x+y}', () => {
53+
expect(parse('\\color{blue}{x+y}')).toMatchInlineSnapshot(
54+
`["Add", "x", "y"]`
55+
);
5956
});
6057

6158
test('5/ \\ln(3)', () => {
6259
expect(parse('\\ln(3)')).toMatchInlineSnapshot(`["Ln", 3]`);
6360
});
6461

65-
test('6/ f:[a,b]\\to R', () => {
66-
expect(parse('f:[a,b]\\to R ')).toMatchInlineSnapshot(
67-
`["Sequence", "f", ["Error", ["ErrorCode", "unexpected-token", ":"]]]`
62+
test('6/ f:[a,b]\\to\\R', () => {
63+
expect(parse('f:[a,b]\\to\\R')).toMatchInlineSnapshot(
64+
`["Colon", "f", ["To", ["List", "a", "b"], "RealNumbers"]]`
65+
);
66+
});
67+
68+
test('6b/ x:\\R', () => {
69+
expect(parse('x:\\R')).toMatchInlineSnapshot(
70+
`["Colon", "x", "RealNumbers"]`
6871
);
6972
});
7073

@@ -74,6 +77,10 @@ describe('STEFNOTCH #10', () => {
7477
);
7578
});
7679

80+
test('8a/ \\mleft(x\\mright) parses like \\left(x\\right)', () => {
81+
expect(parse('\\mleft(x\\mright)')).toMatchInlineSnapshot(`x`);
82+
});
83+
7784
test('8/ \\begin{cases} 3 & x < 5 \\\\ 7 & \\text{else} \\end{cases}', () => {
7885
expect(
7986
parse('\\begin{cases} 3 & x < 5 \\\\ 7 & \\text{else} \\end{cases}')

0 commit comments

Comments
 (0)