tex2typst 0.4.0 → 0.4.1

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/src/generic.ts CHANGED
@@ -1,8 +1,8 @@
1
- interface IEquatable {
2
- eq(other: IEquatable): boolean;
1
+ interface IEquatable<T> {
2
+ eq(other: T): boolean;
3
3
  }
4
4
 
5
- export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
5
+ export function array_equal<T extends IEquatable<T>>(a: T[], b: T[]): boolean {
6
6
  /*
7
7
  if (a.length !== b.length) {
8
8
  return false;
@@ -17,7 +17,7 @@ export function array_equal<T extends IEquatable>(a: T[], b: T[]): boolean {
17
17
  return a.length === b.length && a.every((x, i) => x.eq(b[i]));
18
18
  }
19
19
 
20
- export function array_find<T extends IEquatable>(array: T[], item: T, start: number = 0): number {
20
+ export function array_find<T extends IEquatable<T>>(array: T[], item: T, start: number = 0): number {
21
21
  /*
22
22
  for (let i = start; i < array.length; i++) {
23
23
  if (array[i].eq(item)) {
@@ -30,7 +30,7 @@ export function array_find<T extends IEquatable>(array: T[], item: T, start: num
30
30
  return index === -1 ? -1 : index + start;
31
31
  }
32
32
 
33
- export function array_includes<T extends IEquatable>(array: T[], item: T): boolean {
33
+ export function array_includes<T extends IEquatable<T>>(array: T[], item: T): boolean {
34
34
  /*
35
35
  for (const x of array) {
36
36
  if (x.eq(item)) {
@@ -44,7 +44,7 @@ export function array_includes<T extends IEquatable>(array: T[], item: T): boole
44
44
 
45
45
  // e.g. input array=['a', 'b', '+', 'c', '+', 'd', 'e'], sep = '+'
46
46
  // return [['a', 'b'], ['c'], ['d', 'e']]
47
- export function array_split<T extends IEquatable>(array: T[], sep: T): T[][] {
47
+ export function array_split<T extends IEquatable<T>>(array: T[], sep: T): T[][] {
48
48
  const res: T[][] = [];
49
49
  let current_slice: T[] = [];
50
50
  for (const i of array) {
package/src/tex-parser.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { TexBeginEnd, TexFuncCall, TexLeftRight, TexNode, TexGroup, TexSupSub, TexSupsubData, TexText, TexToken, TexTokenType } from "./tex-types";
2
- import { assert } from "./util";
3
- import { array_find, array_join } from "./generic";
2
+ import { assert } from "./utils";
3
+ import { array_find, array_join, array_split } from "./generic";
4
4
  import { TEX_BINARY_COMMANDS, TEX_UNARY_COMMANDS, tokenize_tex } from "./tex-tokenizer";
5
5
 
6
6
  const IGNORED_COMMANDS = [
@@ -446,59 +446,31 @@ export class LatexParser {
446
446
  // ignore whitespaces and '\n' after \begin{envName}
447
447
  pos += eat_whitespaces(tokens, pos).length;
448
448
 
449
- const allRows: TexNode[][] = [];
450
- let row: TexNode[] = [];
451
- allRows.push(row);
452
- let group = new TexGroup([]);
453
- row.push(group);
449
+ let closure: TexNode;
450
+ [closure, pos] = this.parseClosure(tokens, pos, closingToken);
454
451
 
455
- while (pos < tokens.length) {
456
- if (tokens[pos].eq(closingToken)) {
457
- break;
458
- }
459
-
460
- const [res, newPos] = this.parseNextExpr(tokens, pos);
461
- pos = newPos;
462
-
463
- if (res.head.type === TexTokenType.SPACE || res.head.type === TexTokenType.NEWLINE) {
464
- if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
465
- continue;
466
- }
467
- if (!this.newline_sensitive && res.head.value === '\n') {
468
- continue;
469
- }
470
- }
471
-
472
- if (res.head.eq(new TexToken(TexTokenType.CONTROL, '\\\\'))) {
473
- row = [];
474
- group = new TexGroup([]);
475
- row.push(group);
476
- allRows.push(row);
477
- } else if (res.head.eq(new TexToken(TexTokenType.CONTROL, '&'))) {
478
- group = new TexGroup([]);
479
- row.push(group);
480
- } else {
481
- group.items.push(res);
482
- }
483
- }
484
-
485
- if (pos >= tokens.length) {
452
+ if (pos === -1) {
486
453
  return [[], -1];
487
454
  }
488
455
 
489
- // ignore spaces and '\n' before \end{envName}
490
- if (allRows.length > 0 && allRows[allRows.length - 1].length > 0) {
491
- const last_cell = allRows[allRows.length - 1][allRows[allRows.length - 1].length - 1];
492
- if (last_cell.type === 'ordgroup') {
493
- const last_cell_items = (last_cell as TexGroup).items;
494
- while(last_cell_items.length > 0 && [TexTokenType.SPACE, TexTokenType.NEWLINE].includes(last_cell_items[last_cell_items.length - 1].head.type)) {
495
- last_cell_items.pop();
496
- }
456
+ let allRows: TexNode[][];
457
+ if (closure.type === 'ordgroup') {
458
+ const elements = (closure as TexGroup).items;
459
+ // ignore spaces and '\n' before \end{envName}
460
+ while(elements.length > 0 && [TexTokenType.SPACE, TexTokenType.NEWLINE].includes(elements[elements.length - 1].head.type)) {
461
+ elements.pop();
497
462
  }
463
+ allRows = array_split(elements, new TexToken(TexTokenType.CONTROL, '\\\\').toNode())
464
+ .map(row => {
465
+ return array_split(row, new TexToken(TexTokenType.CONTROL, '&').toNode())
466
+ .map(arr => new TexGroup(arr));
467
+ });
468
+ } else {
469
+ allRows = [[closure]];
498
470
  }
499
471
 
500
472
  this.alignmentDepth--;
501
- return [allRows, pos + 1];
473
+ return [allRows, pos];
502
474
  }
503
475
  }
504
476
 
@@ -41,6 +41,7 @@ export const TEX_UNARY_COMMANDS = [
41
41
  'mathrel',
42
42
  'mathbin',
43
43
  'mathop',
44
+ 'not',
44
45
  ]
45
46
 
46
47
  export const TEX_BINARY_COMMANDS = [
package/src/tex-types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { assert } from "./util";
1
+ import { assert } from "./utils";
2
2
 
3
3
  /**
4
4
  * ELEMENT: 0-9, a-z, A-Z, punctuations such as +-/*,:; etc.
@@ -6,7 +6,7 @@ import { TypstSupsubData } from "./typst-types";
6
6
  import { TypstToken } from "./typst-types";
7
7
  import { TypstTokenType } from "./typst-types";
8
8
  import { tokenize_typst } from "./typst-tokenizer";
9
- import { assert, isalpha } from "./util";
9
+ import { assert, isalpha } from "./utils";
10
10
 
11
11
 
12
12
  // TODO: In Typst, y' ' is not the same as y''.
@@ -49,41 +49,37 @@ function find_closing_match(tokens: TypstToken[], start: number): number {
49
49
  );
50
50
  }
51
51
 
52
- function find_closing_delim(tokens: TypstToken[], start: number): number {
53
- return _find_closing_match(
54
- tokens,
55
- start,
56
- TypstToken.LEFT_DELIMITERS,
57
- TypstToken.RIGHT_DELIMITERS
58
- );
59
- }
60
-
61
52
 
53
+ function extract_named_params(arr: TypstNode[]): [TypstNode[], TypstNamedParams] {
54
+ const COLON = new TypstToken(TypstTokenType.ELEMENT, ':').toNode();
55
+ const np: TypstNamedParams = {};
62
56
 
63
- function find_closing_parenthesis(nodes: TypstNode[], start: number): number {
64
- const left_parenthesis = new TypstToken(TypstTokenType.ELEMENT, '(').toNode();
65
- const right_parenthesis = new TypstToken(TypstTokenType.ELEMENT, ')').toNode();
66
-
67
-
68
-
69
- assert(nodes[start].eq(left_parenthesis));
70
-
71
- let count = 1;
72
- let pos = start + 1;
57
+ const to_delete: number[] = [];
58
+ for(let i = 0; i < arr.length; i++) {
59
+ if(arr[i].type !== 'group') {
60
+ continue;
61
+ }
73
62
 
74
- while (count > 0) {
75
- if (pos >= nodes.length) {
76
- throw new Error("Unmatched '('");
63
+ const g = arr[i] as TypstGroup;
64
+ const pos_colon = array_find(g.items, COLON);
65
+ if(pos_colon === -1 || pos_colon === 0) {
66
+ continue;
77
67
  }
78
- if (nodes[pos].eq(left_parenthesis)) {
79
- count += 1;
80
- } else if (nodes[pos].eq(right_parenthesis)) {
81
- count -= 1;
68
+ to_delete.push(i);
69
+ const param_name = g.items[pos_colon - 1];
70
+ if(param_name.eq(new TypstToken(TypstTokenType.SYMBOL, 'delim').toNode())) {
71
+ if(g.items.length !== 3) {
72
+ throw new TypstParserError('Invalid number of arguments for delim');
73
+ }
74
+ np['delim'] = g.items[pos_colon + 1];
75
+ } else {
76
+ throw new TypstParserError('Not implemented for other named parameters');
82
77
  }
83
- pos += 1;
84
78
  }
85
-
86
- return pos - 1;
79
+ for(let i = to_delete.length - 1; i >= 0; i--) {
80
+ arr.splice(to_delete[i], 1);
81
+ }
82
+ return [arr, np];
87
83
  }
88
84
 
89
85
  function primes(num: number): TypstNode[] {
@@ -129,36 +125,18 @@ function trim_whitespace_around_operators(nodes: TypstNode[]): TypstNode[] {
129
125
  return res;
130
126
  }
131
127
 
132
- function process_operators(nodes: TypstNode[], parenthesis = false): TypstNode {
128
+ function process_operators(nodes: TypstNode[]): TypstNode {
133
129
  nodes = trim_whitespace_around_operators(nodes);
134
130
 
135
- const opening_bracket = LEFT_PARENTHESES.toNode();
136
- const closing_bracket = RIGHT_PARENTHESES.toNode();
137
-
138
131
  const stack: TypstNode[] = [];
139
132
 
140
133
  const args: TypstNode[] = [];
141
134
  let pos = 0;
142
135
  while (pos < nodes.length) {
143
- const current = nodes[pos];
144
- if (current.eq(closing_bracket)) {
145
- throw new TypstParserError("Unexpected ')'");
146
- } else if(current.eq(DIV)) {
147
- stack.push(current);
148
- pos++;
136
+ const current_tree = nodes[pos];
137
+ if(current_tree.eq(DIV)) {
138
+ stack.push(current_tree);
149
139
  } else {
150
- let current_tree: TypstNode;
151
- if(current.eq(opening_bracket)) {
152
- // the expression is a group wrapped in parenthesis
153
- const pos_closing = find_closing_parenthesis(nodes, pos);
154
- current_tree = process_operators(nodes.slice(pos + 1, pos_closing), true);
155
- pos = pos_closing + 1;
156
- } else {
157
- // the expression is just a single item
158
- current_tree = current;
159
- pos++;
160
- }
161
-
162
140
  if(stack.length > 0 && stack[stack.length-1].eq(DIV)) {
163
141
  let denominator = current_tree;
164
142
  if(args.length === 0) {
@@ -179,13 +157,9 @@ function process_operators(nodes: TypstNode[], parenthesis = false): TypstNode {
179
157
  args.push(current_tree);
180
158
  }
181
159
  }
160
+ pos++;
182
161
  }
183
- const body = args.length === 1? args[0]: new TypstGroup(args);
184
- if(parenthesis) {
185
- return new TypstLeftright(null, { body: body, left: LEFT_PARENTHESES, right: RIGHT_PARENTHESES } as TypstLeftRightData);
186
- } else {
187
- return body;
188
- }
162
+ return args.length === 1? args[0]: new TypstGroup(args);
189
163
  }
190
164
 
191
165
  function parse_named_params(groups: TypstGroup[]): TypstNamedParams {
@@ -220,9 +194,14 @@ const LEFT_CURLY_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '{
220
194
  const RIGHT_CURLY_BRACKET: TypstToken = new TypstToken(TypstTokenType.ELEMENT, '}');
221
195
  const COMMA = new TypstToken(TypstTokenType.ELEMENT, ',');
222
196
  const SEMICOLON = new TypstToken(TypstTokenType.ELEMENT, ';');
223
- const SINGLE_SPACE = new TypstToken(TypstTokenType.SPACE, ' ');
224
197
  const CONTROL_AND = new TypstToken(TypstTokenType.CONTROL, '&');
225
198
 
199
+
200
+ interface TexParseEnv {
201
+ spaceSensitive: boolean;
202
+ newlineSensitive: boolean;
203
+ }
204
+
226
205
  export class TypstParser {
227
206
  space_sensitive: boolean;
228
207
  newline_sensitive: boolean;
@@ -237,35 +216,8 @@ export class TypstParser {
237
216
  return tree;
238
217
  }
239
218
 
240
- parseGroup(tokens: TypstToken[], start: number, end: number, parentheses = false): TypstParseResult {
241
- const results: TypstNode[] = [];
242
- let pos = start;
243
-
244
- while (pos < end) {
245
- const [res, newPos] = this.parseNextExpr(tokens, pos);
246
- pos = newPos;
247
- if (res.head.type === TypstTokenType.SPACE || res.head.type === TypstTokenType.NEWLINE) {
248
- if (!this.space_sensitive && res.head.value.replace(/ /g, '').length === 0) {
249
- continue;
250
- }
251
- if (!this.newline_sensitive && res.head.value === '\n') {
252
- continue;
253
- }
254
- }
255
- results.push(res);
256
- }
257
-
258
- let node: TypstNode;
259
- if(parentheses) {
260
- node = process_operators(results, true);
261
- } else {
262
- if (results.length === 1) {
263
- node = results[0];
264
- } else {
265
- node = process_operators(results);
266
- }
267
- }
268
- return [node, end + 1];
219
+ parseGroup(tokens: TypstToken[], start: number, end: number): TypstParseResult {
220
+ return this.parseUntil(tokens.slice(start, end), 0, null);
269
221
  }
270
222
 
271
223
  parseNextExpr(tokens: TypstToken[], start: number): TypstParseResult {
@@ -298,12 +250,51 @@ export class TypstParser {
298
250
  }
299
251
  }
300
252
 
253
+ // return pos: (position of stopToken) + 1
254
+ // pos will be -1 if stopToken is not found
255
+ parseUntil(tokens: TypstToken[], start: number, stopToken: TypstToken | null, env: Partial<TexParseEnv> = {}): TypstParseResult {
256
+ if (env.spaceSensitive === undefined) {
257
+ env.spaceSensitive = this.space_sensitive;
258
+ }
259
+ if (env.newlineSensitive === undefined) {
260
+ env.newlineSensitive = this.newline_sensitive;
261
+ }
262
+
263
+ const results: TypstNode[] = [];
264
+ let pos = start;
265
+
266
+ while (pos < tokens.length) {
267
+ if (stopToken !== null && tokens[pos].eq(stopToken)) {
268
+ break;
269
+ }
270
+ const [res, newPos] = this.parseNextExpr(tokens, pos);
271
+ pos = newPos;
272
+ if (res.head.type === TypstTokenType.SPACE || res.head.type === TypstTokenType.NEWLINE) {
273
+ if (!env.spaceSensitive && res.head.value.replace(/ /g, '').length === 0) {
274
+ continue;
275
+ }
276
+ if (!env.newlineSensitive && res.head.value === '\n') {
277
+ continue;
278
+ }
279
+ }
280
+ results.push(res);
281
+ }
282
+ if (pos >= tokens.length && stopToken !== null) {
283
+ return [TypstToken.NONE.toNode(), -1];
284
+ }
285
+
286
+ const node = process_operators(results);
287
+ return [node, pos + 1];
288
+ }
289
+
301
290
  parseSupOrSub(tokens: TypstToken[], start: number): TypstParseResult {
302
291
  let node: TypstNode;
303
292
  let end: number;
304
293
  if(tokens[start].eq(LEFT_PARENTHESES)) {
305
- const pos_closing = find_closing_match(tokens, start);
306
- [node, end] = this.parseGroup(tokens, start + 1, pos_closing);
294
+ [node, end] = this.parseUntil(tokens, start + 1, RIGHT_PARENTHESES);
295
+ if (end === -1) {
296
+ throw new Error("Unmatched '('");
297
+ }
307
298
  } else {
308
299
  [node, end] = this.parseNextExprWithoutSupSub(tokens, start);
309
300
  }
@@ -319,8 +310,12 @@ export class TypstParser {
319
310
  const firstToken = tokens[start];
320
311
  const node = firstToken.toNode();
321
312
  if(firstToken.eq(LEFT_PARENTHESES)) {
322
- const pos_closing = find_closing_match(tokens, start);
323
- return this.parseGroup(tokens, start + 1, pos_closing, true);
313
+ const [body, end] = this.parseUntil(tokens, start + 1, RIGHT_PARENTHESES);
314
+ if (end === -1) {
315
+ throw new Error("Unmatched '('");
316
+ }
317
+ const res = new TypstLeftright(null, { body: body, left: LEFT_PARENTHESES, right: RIGHT_PARENTHESES } as TypstLeftRightData);
318
+ return [res, end];
324
319
  }
325
320
  if(firstToken.type === TypstTokenType.ELEMENT && !isalpha(firstToken.value[0])) {
326
321
  return [node, start + 1];
@@ -371,29 +366,32 @@ export class TypstParser {
371
366
 
372
367
  // start: the position of the left parentheses
373
368
  parseLrArguments(tokens: TypstToken[], start: number): [TypstNode, number] {
374
- const lr_token = tokens[start];
369
+ const lr_token = new TypstToken(TypstTokenType.SYMBOL, 'lr');
375
370
  const end = find_closing_match(tokens, start);
376
- if (tokens[start + 1].isOneOf(TypstToken.LEFT_DELIMITERS)) {
377
- const inner_start = start + 1;
378
- const inner_end = find_closing_delim(tokens, inner_start);
379
- const [inner_args, _]= this.parseGroup(tokens, inner_start + 1, inner_end);
380
- return [
381
- new TypstLeftright(lr_token, { body: inner_args, left: tokens[inner_start], right: tokens[inner_end]}),
382
- end + 1,
383
- ];
384
- } else {
385
- const [inner_args, _] = this.parseGroup(tokens, start + 1, end - 1);
386
- return [
387
- new TypstLeftright(lr_token, { body: inner_args, left: null, right: null }),
388
- end + 1,
389
- ];
371
+
372
+ let left: TypstToken | null = null;
373
+ let right: TypstToken | null = null;
374
+ let inner_start = start + 1;
375
+ let inner_end = end;
376
+ if (inner_end > inner_start && tokens[inner_start].isOneOf(TypstToken.LEFT_DELIMITERS)) {
377
+ left = tokens[inner_start];
378
+ inner_start += 1;
390
379
  }
380
+ if (inner_end - 1 > inner_start && tokens[inner_end - 1].isOneOf(TypstToken.RIGHT_DELIMITERS)) {
381
+ right = tokens[inner_end - 1];
382
+ inner_end -= 1;
383
+ }
384
+
385
+ const [inner_args, _] = this.parseGroup(tokens, inner_start, inner_end);
386
+ return [
387
+ new TypstLeftright(lr_token, { body: inner_args, left: left, right: right }),
388
+ end + 1,
389
+ ];
391
390
  }
392
391
 
393
392
  // start: the position of the left parentheses
394
393
  parseMatrix(tokens: TypstToken[], start: number, rowSepToken: TypstToken, cellSepToken: TypstToken): [TypstNode[][], TypstNamedParams, number] {
395
394
  const end = find_closing_match(tokens, start);
396
- tokens = tokens.slice(0, end);
397
395
 
398
396
  const matrix: TypstNode[][] = [];
399
397
  let named_params: TypstNamedParams = {};
@@ -402,45 +400,13 @@ export class TypstParser {
402
400
  while (pos < end) {
403
401
  while(pos < end) {
404
402
  let next_stop = array_find(tokens, rowSepToken, pos);
405
- if (next_stop === -1) {
403
+ if (next_stop === -1 || next_stop > end) {
406
404
  next_stop = end;
407
405
  }
408
406
 
409
407
  let row = this.parseArgumentsWithSeparator(tokens, pos, next_stop, cellSepToken);
410
408
  let np: TypstNamedParams = {};
411
409
 
412
- function extract_named_params(arr: TypstNode[]): [TypstNode[], TypstNamedParams] {
413
- const COLON = new TypstToken(TypstTokenType.ELEMENT, ':').toNode();
414
- const np: TypstNamedParams = {};
415
-
416
- const to_delete: number[] = [];
417
- for(let i = 0; i < arr.length; i++) {
418
- if(arr[i].type !== 'group') {
419
- continue;
420
- }
421
-
422
- const g = arr[i] as TypstGroup;
423
- const pos_colon = array_find(g.items, COLON);
424
- if(pos_colon === -1 || pos_colon === 0) {
425
- continue;
426
- }
427
- to_delete.push(i);
428
- const param_name = g.items[pos_colon - 1];
429
- if(param_name.eq(new TypstToken(TypstTokenType.SYMBOL, 'delim').toNode())) {
430
- if(g.items.length !== 3) {
431
- throw new TypstParserError('Invalid number of arguments for delim');
432
- }
433
- np['delim'] = g.items[pos_colon + 1];
434
- } else {
435
- throw new TypstParserError('Not implemented for other named parameters');
436
- }
437
- }
438
- for(let i = to_delete.length - 1; i >= 0; i--) {
439
- arr.splice(to_delete[i], 1);
440
- }
441
- return [arr, np];
442
- }
443
-
444
410
  [row, np] = extract_named_params(row);
445
411
  matrix.push(row);
446
412
  Object.assign(named_params, np);
@@ -455,29 +421,17 @@ export class TypstParser {
455
421
  parseArgumentsWithSeparator(tokens: TypstToken[], start: number, end: number, sepToken: TypstToken): TypstNode[] {
456
422
  const args: TypstNode[] = [];
457
423
  let pos = start;
458
- while (pos < end) {
459
- let nodes: TypstNode[] = [];
460
- while(pos < end) {
461
- if(tokens[pos].eq(sepToken)) {
462
- pos += 1;
463
- break;
464
- } else if(tokens[pos].eq(SINGLE_SPACE)) {
465
- pos += 1;
466
- continue;
467
- }
468
- const [argItem, newPos] = this.parseNextExpr(tokens, pos);
469
- pos = newPos;
470
- nodes.push(argItem);
471
- }
472
424
 
425
+ while (pos < end) {
473
426
  let arg: TypstNode;
474
- if (nodes.length === 1) {
475
- arg = nodes[0];
476
- } else {
477
- arg = process_operators(nodes);
427
+ let newPos: number;
428
+ const env = { spaceSensitive: false, newlineSensitive: true };
429
+ [arg, newPos] = this.parseUntil(tokens.slice(0, end), pos, sepToken, env);
430
+ if (newPos == -1) {
431
+ [arg, newPos] = this.parseUntil(tokens.slice(0, end), pos, null, env);
478
432
  }
479
-
480
433
  args.push(arg);
434
+ pos = newPos;
481
435
  }
482
436
  return args;
483
437
  }
@@ -1,42 +0,0 @@
1
- import path from 'path';
2
- import toml from 'toml';
3
- import fs from 'node:fs';
4
- import { describe, it, test, expect } from 'vitest';
5
- import { tex2typst, symbolMap } from '../src';
6
-
7
- interface CheatSheet {
8
- math_commands: { [key: string]: string };
9
- math_symbols: { [key: string]: string };
10
- }
11
-
12
- describe('cheat sheet', () => {
13
- const cheatSheetFile = path.join(__dirname, 'cheat-sheet.toml');
14
- const text_content = fs.readFileSync(cheatSheetFile, { encoding: 'utf-8' });
15
- const data = toml.parse(text_content) as CheatSheet;
16
-
17
- test('math_commands', () => {
18
- expect(data.math_commands).toBeDefined();
19
-
20
-
21
- for (const [key, value] of Object.entries(data.math_commands)) {
22
- const input = `\\${key}{x}{y}`;
23
- const expected1 = `${value} x y`;
24
- const expected2 = `${value}(x) y`;
25
- const expected3 = `${value}(x, y)`;
26
- const result = tex2typst(input, {preferShorthands: false});
27
- expect([expected1, expected2, expected3]).toContain(result);
28
- }
29
- });
30
-
31
- test('math_symbols', () => {
32
- expect(data.math_symbols).toBeDefined();
33
-
34
- for (const [key, value] of Object.entries(data.math_symbols)) {
35
- const input = `\\${key}`;
36
- const expected = value;
37
- const result = tex2typst(input, {preferShorthands: false});
38
- expect(result).toBe(expected);
39
- expect(symbolMap.get(key)).toBe(expected);
40
- }
41
- });
42
- });