tex2typst 0.3.23 → 0.3.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/convert.d.ts +9 -3
- package/dist/generic.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2092 -2009
- package/dist/tex-parser.d.ts +1 -1
- package/dist/tex-tokenizer.d.ts +1 -1
- package/dist/tex-types.d.ts +103 -0
- package/dist/tex-writer.d.ts +1 -2
- package/dist/tex2typst.min.js +13 -13
- package/dist/typst-parser.d.ts +6 -4
- package/dist/typst-tokenizer.d.ts +1 -1
- package/dist/typst-types.d.ts +95 -0
- package/dist/typst-writer.d.ts +4 -2
- package/package.json +1 -1
- package/src/convert.ts +455 -393
- package/src/generic.ts +28 -2
- package/src/index.ts +1 -1
- package/src/map.ts +5 -0
- package/src/tex-parser.ts +45 -54
- package/src/tex-tokenizer.ts +1 -1
- package/src/tex-types.ts +358 -0
- package/src/tex-writer.ts +3 -51
- package/src/typst-parser.ts +53 -55
- package/src/typst-tokenizer.ts +86 -85
- package/src/typst-types.ts +221 -0
- package/src/typst-writer.ts +79 -65
- package/src/util.ts +1 -1
- package/dist/types.d.ts +0 -109
- package/src/types.ts +0 -414
package/src/generic.ts
CHANGED
|
@@ -2,25 +2,48 @@ interface IEquatable {
|
|
|
2
2
|
eq(other: IEquatable): boolean;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
|
|
6
|
+
/*
|
|
7
|
+
if (a.length !== b.length) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
for (let i = 0; i < a.length; i++) {
|
|
11
|
+
if (!a[i].eq(b[i])) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
*/
|
|
17
|
+
return a.length === b.length && a.every((x, i) => x.eq(b[i]));
|
|
18
|
+
}
|
|
5
19
|
|
|
6
20
|
export function array_find<T extends IEquatable>(array: T[], item: T, start: number = 0): number {
|
|
21
|
+
/*
|
|
7
22
|
for (let i = start; i < array.length; i++) {
|
|
8
23
|
if (array[i].eq(item)) {
|
|
9
24
|
return i;
|
|
10
25
|
}
|
|
11
26
|
}
|
|
12
27
|
return -1;
|
|
28
|
+
*/
|
|
29
|
+
const index = array.slice(start).findIndex((x) => x.eq(item));
|
|
30
|
+
return index === -1 ? -1 : index + start;
|
|
13
31
|
}
|
|
14
32
|
|
|
15
33
|
export function array_includes<T extends IEquatable>(array: T[], item: T): boolean {
|
|
16
|
-
|
|
17
|
-
|
|
34
|
+
/*
|
|
35
|
+
for (const x of array) {
|
|
36
|
+
if (x.eq(item)) {
|
|
18
37
|
return true;
|
|
19
38
|
}
|
|
20
39
|
}
|
|
21
40
|
return false;
|
|
41
|
+
*/
|
|
42
|
+
return array.some((x) => x.eq(item));
|
|
22
43
|
}
|
|
23
44
|
|
|
45
|
+
// e.g. input array=['a', 'b', '+', 'c', '+', 'd', 'e'], sep = '+'
|
|
46
|
+
// return [['a', 'b'], ['c'], ['d', 'e']]
|
|
24
47
|
export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
25
48
|
const res: T[][] = [];
|
|
26
49
|
let current_slice: T[] = [];
|
|
@@ -39,6 +62,7 @@ export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
|
|
|
39
62
|
// e.g. input array=['a', 'b', 'c'], sep = '+'
|
|
40
63
|
// return ['a','+', 'b', '+','c']
|
|
41
64
|
export function array_intersperse<T>(array: T[], sep: T): T[] {
|
|
65
|
+
/*
|
|
42
66
|
const res: T[] = [];
|
|
43
67
|
for (let i = 0; i < array.length; i++) {
|
|
44
68
|
res.push(array[i]);
|
|
@@ -47,4 +71,6 @@ export function array_intersperse<T>(array: T[], sep: T): T[] {
|
|
|
47
71
|
}
|
|
48
72
|
}
|
|
49
73
|
return res;
|
|
74
|
+
*/
|
|
75
|
+
return array.flatMap((x, i) => i !== array.length - 1? [x, sep]: [x]);
|
|
50
76
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseTex } from "./tex-parser";
|
|
2
|
-
import type { Tex2TypstOptions } from "./types";
|
|
2
|
+
import type { Tex2TypstOptions } from "./tex-types";
|
|
3
3
|
import { TypstWriter, type TypstWriterOptions } from "./typst-writer";
|
|
4
4
|
import { convert_tex_node_to_typst, convert_typst_node_to_tex } from "./convert";
|
|
5
5
|
import { symbolMap } from "./map";
|
package/src/map.ts
CHANGED
|
@@ -5,6 +5,7 @@ const symbolMap = new Map<string, string>([
|
|
|
5
5
|
['|', 'bar.v.double'],
|
|
6
6
|
[',', 'thin'],
|
|
7
7
|
[':', 'med'],
|
|
8
|
+
[' ', 'med'],
|
|
8
9
|
[';', 'thick'],
|
|
9
10
|
|
|
10
11
|
/* textual operators */
|
|
@@ -21,6 +22,10 @@ const symbolMap = new Map<string, string>([
|
|
|
21
22
|
['Xi', 'Xi'],
|
|
22
23
|
['Upsilon', 'Upsilon'],
|
|
23
24
|
['lim', 'lim'],
|
|
25
|
+
['binom', 'binom'],
|
|
26
|
+
['tilde', 'tilde'],
|
|
27
|
+
['hat', 'hat'],
|
|
28
|
+
['sqrt', 'sqrt'],
|
|
24
29
|
|
|
25
30
|
['nonumber', ''],
|
|
26
31
|
['vec', 'arrow'],
|
package/src/tex-parser.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { TexNode, TexSupsubData, TexToken, TexTokenType } from "./types";
|
|
1
|
+
import { TexBeginEnd, TexFuncCall, TexLeftRight, TexNode, TexGroup, TexSupSub, TexSupsubData, TexText, TexToken, TexTokenType } from "./tex-types";
|
|
3
2
|
import { assert } from "./util";
|
|
4
3
|
import { array_find } from "./generic";
|
|
5
4
|
import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS, tokenize_tex } from "./tex-tokenizer";
|
|
@@ -11,7 +10,7 @@ const IGNORED_COMMANDS = [
|
|
|
11
10
|
'Biggl', 'Biggr',
|
|
12
11
|
];
|
|
13
12
|
|
|
14
|
-
const EMPTY_NODE: TexNode =
|
|
13
|
+
const EMPTY_NODE: TexNode = TexToken.EMPTY.toNode();
|
|
15
14
|
|
|
16
15
|
function get_command_param_num(command: string): number {
|
|
17
16
|
if (TEX_UNARY_COMMANDS.includes(command)) {
|
|
@@ -120,7 +119,8 @@ export class LatexParser {
|
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
parse(tokens: TexToken[]): TexNode {
|
|
123
|
-
const
|
|
122
|
+
const token_displaystyle = new TexToken(TexTokenType.COMMAND, '\\displaystyle');
|
|
123
|
+
const idx = array_find(tokens, token_displaystyle);
|
|
124
124
|
if (idx === -1) {
|
|
125
125
|
// no \displaystyle, normal execution path
|
|
126
126
|
const [tree, _] = this.parseGroup(tokens, 0, tokens.length);
|
|
@@ -128,13 +128,13 @@ export class LatexParser {
|
|
|
128
128
|
} else if (idx === 0) {
|
|
129
129
|
// \displaystyle at the beginning. Wrap the whole thing in \displaystyle
|
|
130
130
|
const [tree, _] = this.parseGroup(tokens, 1, tokens.length);
|
|
131
|
-
return new
|
|
131
|
+
return new TexFuncCall(token_displaystyle, [tree]);
|
|
132
132
|
} else {
|
|
133
133
|
// \displaystyle somewhere in the middle. Split the expression to two parts
|
|
134
134
|
const [tree1, _1] = this.parseGroup(tokens, 0, idx);
|
|
135
135
|
const [tree2, _2] = this.parseGroup(tokens, idx + 1, tokens.length);
|
|
136
|
-
const display = new
|
|
137
|
-
return new
|
|
136
|
+
const display = new TexFuncCall(token_displaystyle, [tree2]);
|
|
137
|
+
return new TexGroup([tree1, display]);
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -144,15 +144,15 @@ export class LatexParser {
|
|
|
144
144
|
while (pos < end) {
|
|
145
145
|
const [res, newPos] = this.parseNextExpr(tokens, pos);
|
|
146
146
|
pos = newPos;
|
|
147
|
-
if(res.type ===
|
|
148
|
-
if (!this.space_sensitive && res.
|
|
147
|
+
if(res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
|
|
148
|
+
if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
|
|
149
149
|
continue;
|
|
150
150
|
}
|
|
151
|
-
if (!this.newline_sensitive && res.
|
|
151
|
+
if (!this.newline_sensitive && res.head.value === '\n') {
|
|
152
152
|
continue;
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
if (res.
|
|
155
|
+
if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
|
|
156
156
|
throw new LatexParserError('Unexpected & outside of an alignment');
|
|
157
157
|
}
|
|
158
158
|
results.push(res);
|
|
@@ -162,7 +162,7 @@ export class LatexParser {
|
|
|
162
162
|
if (results.length === 1) {
|
|
163
163
|
node = results[0];
|
|
164
164
|
} else {
|
|
165
|
-
node = new
|
|
165
|
+
node = new TexGroup(results);
|
|
166
166
|
}
|
|
167
167
|
return [node, end + 1];
|
|
168
168
|
}
|
|
@@ -199,14 +199,14 @@ export class LatexParser {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
if (sub !== null || sup !== null || num_prime > 0) {
|
|
202
|
-
const res: TexSupsubData = { base };
|
|
202
|
+
const res: TexSupsubData = { base, sup: null, sub: null };
|
|
203
203
|
if (sub) {
|
|
204
204
|
res.sub = sub;
|
|
205
205
|
}
|
|
206
206
|
if (num_prime > 0) {
|
|
207
|
-
res.sup = new
|
|
207
|
+
res.sup = new TexGroup([]);
|
|
208
208
|
for (let i = 0; i < num_prime; i++) {
|
|
209
|
-
res.sup.args!.push(new
|
|
209
|
+
res.sup.args!.push(new TexToken(TexTokenType.ELEMENT, "'").toNode());
|
|
210
210
|
}
|
|
211
211
|
if (sup) {
|
|
212
212
|
res.sup.args!.push(sup);
|
|
@@ -217,7 +217,7 @@ export class LatexParser {
|
|
|
217
217
|
} else if (sup) {
|
|
218
218
|
res.sup = sup;
|
|
219
219
|
}
|
|
220
|
-
return [new
|
|
220
|
+
return [new TexSupSub(res), pos];
|
|
221
221
|
} else {
|
|
222
222
|
return [base, pos];
|
|
223
223
|
}
|
|
@@ -230,14 +230,11 @@ export class LatexParser {
|
|
|
230
230
|
const firstToken = tokens[start];
|
|
231
231
|
switch (firstToken.type) {
|
|
232
232
|
case TexTokenType.ELEMENT:
|
|
233
|
-
return [new TexNode('element', firstToken.value), start + 1];
|
|
234
233
|
case TexTokenType.LITERAL:
|
|
235
|
-
return [new TexNode('literal', firstToken.value), start + 1];
|
|
236
234
|
case TexTokenType.COMMENT:
|
|
237
|
-
return [new TexNode('comment', firstToken.value), start + 1];
|
|
238
235
|
case TexTokenType.SPACE:
|
|
239
236
|
case TexTokenType.NEWLINE:
|
|
240
|
-
return [
|
|
237
|
+
return [firstToken.toNode(), start + 1];
|
|
241
238
|
case TexTokenType.COMMAND:
|
|
242
239
|
const commandName = firstToken.value.slice(1);
|
|
243
240
|
if (IGNORED_COMMANDS.includes(commandName)) {
|
|
@@ -266,14 +263,14 @@ export class LatexParser {
|
|
|
266
263
|
case '\\,':
|
|
267
264
|
case '\\:':
|
|
268
265
|
case '\\;':
|
|
269
|
-
return [
|
|
266
|
+
return [firstToken.toNode(), start + 1];
|
|
270
267
|
case '\\ ':
|
|
271
|
-
return [
|
|
268
|
+
return [firstToken.toNode(), start + 1];
|
|
272
269
|
case '_':
|
|
273
270
|
case '^':
|
|
274
271
|
return [ EMPTY_NODE, start];
|
|
275
272
|
case '&':
|
|
276
|
-
return [
|
|
273
|
+
return [firstToken.toNode(), start + 1];
|
|
277
274
|
default:
|
|
278
275
|
throw new LatexParserError('Unknown control sequence');
|
|
279
276
|
}
|
|
@@ -285,7 +282,8 @@ export class LatexParser {
|
|
|
285
282
|
parseCommandExpr(tokens: TexToken[], start: number): ParseResult {
|
|
286
283
|
assert(tokens[start].type === TexTokenType.COMMAND);
|
|
287
284
|
|
|
288
|
-
const
|
|
285
|
+
const command_token = tokens[start];
|
|
286
|
+
const command = command_token.value; // command name starts with a \
|
|
289
287
|
|
|
290
288
|
let pos = start + 1;
|
|
291
289
|
|
|
@@ -297,10 +295,7 @@ export class LatexParser {
|
|
|
297
295
|
const paramNum = get_command_param_num(command.slice(1));
|
|
298
296
|
switch (paramNum) {
|
|
299
297
|
case 0:
|
|
300
|
-
|
|
301
|
-
return [new TexNode('unknownMacro', command), pos];
|
|
302
|
-
}
|
|
303
|
-
return [new TexNode('symbol', command), pos];
|
|
298
|
+
return [command_token.toNode(), pos];
|
|
304
299
|
case 1: {
|
|
305
300
|
// TODO: JavaScript gives undefined instead of throwing an error when accessing an index out of bounds,
|
|
306
301
|
// so index checking like this should be everywhere. This is rough.
|
|
@@ -315,7 +310,7 @@ export class LatexParser {
|
|
|
315
310
|
}
|
|
316
311
|
const [exponent, _] = this.parseGroup(tokens, posLeftSquareBracket + 1, posRightSquareBracket);
|
|
317
312
|
const [arg1, newPos] = this.parseNextArg(tokens, posRightSquareBracket + 1);
|
|
318
|
-
return [new
|
|
313
|
+
return [new TexFuncCall(command_token, [arg1], exponent), newPos];
|
|
319
314
|
} else if (command === '\\text') {
|
|
320
315
|
if (pos + 2 >= tokens.length) {
|
|
321
316
|
throw new LatexParserError('Expecting content for \\text command');
|
|
@@ -323,16 +318,16 @@ export class LatexParser {
|
|
|
323
318
|
assert(tokens[pos].eq(LEFT_CURLY_BRACKET));
|
|
324
319
|
assert(tokens[pos + 1].type === TexTokenType.LITERAL);
|
|
325
320
|
assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
|
|
326
|
-
const
|
|
327
|
-
return [new
|
|
321
|
+
const literal = tokens[pos + 1];
|
|
322
|
+
return [new TexText(literal), pos + 3];
|
|
328
323
|
}
|
|
329
324
|
let [arg1, newPos] = this.parseNextArg(tokens, pos);
|
|
330
|
-
return [new
|
|
325
|
+
return [new TexFuncCall(command_token, [arg1]), newPos];
|
|
331
326
|
}
|
|
332
327
|
case 2: {
|
|
333
328
|
const [arg1, pos1] = this.parseNextArg(tokens, pos);
|
|
334
329
|
const [arg2, pos2] = this.parseNextArg(tokens, pos1);
|
|
335
|
-
return [new
|
|
330
|
+
return [new TexFuncCall(command_token, [arg1, arg2]), pos2];
|
|
336
331
|
}
|
|
337
332
|
default:
|
|
338
333
|
throw new Error( 'Invalid number of parameters');
|
|
@@ -342,9 +337,9 @@ export class LatexParser {
|
|
|
342
337
|
/*
|
|
343
338
|
Extract a non-space argument from the token stream.
|
|
344
339
|
So that `\frac{12} 3` is parsed as
|
|
345
|
-
|
|
340
|
+
TypstFuncCall{ head: '\frac', args: [ELEMENT_12, ELEMENT_3] }
|
|
346
341
|
rather than
|
|
347
|
-
|
|
342
|
+
TypstFuncCall{ head: '\frac', args: [ELEMENT_12, SPACE] }, ELEMENT_3
|
|
348
343
|
*/
|
|
349
344
|
parseNextArg(tokens: TexToken[], start: number): ParseResult {
|
|
350
345
|
let pos = start;
|
|
@@ -352,7 +347,7 @@ export class LatexParser {
|
|
|
352
347
|
while (pos < tokens.length) {
|
|
353
348
|
let node: TexNode;
|
|
354
349
|
[node, pos] = this.parseNextExprWithoutSupSub(tokens, pos);
|
|
355
|
-
if (node.type
|
|
350
|
+
if (!(node.head.type === TexTokenType.SPACE || node.head.type === TexTokenType.NEWLINE)) {
|
|
356
351
|
arg = node;
|
|
357
352
|
break;
|
|
358
353
|
}
|
|
@@ -398,12 +393,10 @@ export class LatexParser {
|
|
|
398
393
|
pos++;
|
|
399
394
|
|
|
400
395
|
const [body, _] = this.parseGroup(tokens, exprInsideStart, exprInsideEnd);
|
|
401
|
-
const args
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
]
|
|
406
|
-
const res = new TexNode('leftright', '', args);
|
|
396
|
+
const args = [ body ];
|
|
397
|
+
const left = leftDelimiter.value === '.'? null: leftDelimiter;
|
|
398
|
+
const right = rightDelimiter.value === '.'? null: rightDelimiter;
|
|
399
|
+
const res = new TexLeftRight(args, {left: left, right: right});
|
|
407
400
|
return [res, pos];
|
|
408
401
|
}
|
|
409
402
|
|
|
@@ -420,9 +413,7 @@ export class LatexParser {
|
|
|
420
413
|
|
|
421
414
|
const args: TexNode[] = [];
|
|
422
415
|
if(['array', 'subarray'].includes(envName)) {
|
|
423
|
-
|
|
424
|
-
throw new LatexParserError(`Missing arg for \\begin{${envName}}`);
|
|
425
|
-
}
|
|
416
|
+
pos += eat_whitespaces(tokens, pos).length;
|
|
426
417
|
const [arg, newPos] = this.parseNextArg(tokens, pos);
|
|
427
418
|
args.push(arg);
|
|
428
419
|
pos = newPos;
|
|
@@ -454,7 +445,7 @@ export class LatexParser {
|
|
|
454
445
|
exprInside.pop();
|
|
455
446
|
}
|
|
456
447
|
const body = this.parseAligned(exprInside);
|
|
457
|
-
const res = new
|
|
448
|
+
const res = new TexBeginEnd(new TexToken(TexTokenType.LITERAL, envName), args, body);
|
|
458
449
|
return [res, pos];
|
|
459
450
|
}
|
|
460
451
|
|
|
@@ -463,29 +454,29 @@ export class LatexParser {
|
|
|
463
454
|
const allRows: TexNode[][] = [];
|
|
464
455
|
let row: TexNode[] = [];
|
|
465
456
|
allRows.push(row);
|
|
466
|
-
let group = new
|
|
457
|
+
let group = new TexGroup([]);
|
|
467
458
|
row.push(group);
|
|
468
459
|
|
|
469
460
|
while (pos < tokens.length) {
|
|
470
461
|
const [res, newPos] = this.parseNextExpr(tokens, pos);
|
|
471
462
|
pos = newPos;
|
|
472
463
|
|
|
473
|
-
if (res.type ===
|
|
474
|
-
if (!this.space_sensitive && res.
|
|
464
|
+
if (res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
|
|
465
|
+
if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
|
|
475
466
|
continue;
|
|
476
467
|
}
|
|
477
|
-
if (!this.newline_sensitive && res.
|
|
468
|
+
if (!this.newline_sensitive && res.head.value === '\n') {
|
|
478
469
|
continue;
|
|
479
470
|
}
|
|
480
471
|
}
|
|
481
472
|
|
|
482
|
-
if (res.
|
|
473
|
+
if (res.head.eq(new TexToken(TexTokenType.CONTROL, '\\\\'))) {
|
|
483
474
|
row = [];
|
|
484
|
-
group = new
|
|
475
|
+
group = new TexGroup([]);
|
|
485
476
|
row.push(group);
|
|
486
477
|
allRows.push(row);
|
|
487
|
-
} else if (res.
|
|
488
|
-
group = new
|
|
478
|
+
} else if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
|
|
479
|
+
group = new TexGroup([]);
|
|
489
480
|
row.push(group);
|
|
490
481
|
} else {
|
|
491
482
|
group.args!.push(res);
|
package/src/tex-tokenizer.ts
CHANGED