tex2typst 0.3.26 → 0.3.27

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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +21 -4
  3. package/dist/index.js +409 -340
  4. package/dist/parser.js +23 -0
  5. package/dist/tex2typst.min.js +12 -12
  6. package/package.json +6 -7
  7. package/src/convert.ts +56 -6
  8. package/src/exposed-types.ts +23 -0
  9. package/src/index.ts +9 -5
  10. package/src/jslex.ts +1 -1
  11. package/src/tex-tokenizer.ts +1 -0
  12. package/src/tex-types.ts +1 -17
  13. package/src/tex2typst.ts +2 -4
  14. package/src/typst-parser.ts +9 -10
  15. package/src/typst-types.ts +484 -230
  16. package/src/typst-writer.ts +28 -274
  17. package/tests/cheat-sheet.test.ts +42 -0
  18. package/tests/cheat-sheet.toml +304 -0
  19. package/tests/example.ts +15 -0
  20. package/tests/general-symbols.test.ts +22 -0
  21. package/tests/general-symbols.toml +755 -0
  22. package/tests/integration-tex2typst.yaml +89 -0
  23. package/tests/struct-bidirection.yaml +179 -0
  24. package/tests/struct-tex2typst.yaml +443 -0
  25. package/tests/struct-typst2tex.yaml +412 -0
  26. package/tests/symbol.yml +123 -0
  27. package/tests/test-common.ts +26 -0
  28. package/tests/tex-parser.test.ts +57 -0
  29. package/tests/tex-to-typst.test.ts +143 -0
  30. package/tests/typst-parser.test.ts +134 -0
  31. package/tests/typst-to-tex.test.ts +76 -0
  32. package/tsconfig.json +11 -16
  33. package/dist/convert.d.ts +0 -9
  34. package/dist/generic.d.ts +0 -9
  35. package/dist/jslex.d.ts +0 -107
  36. package/dist/map.d.ts +0 -5
  37. package/dist/tex-parser.d.ts +0 -23
  38. package/dist/tex-tokenizer.d.ts +0 -4
  39. package/dist/tex-types.d.ts +0 -107
  40. package/dist/tex-writer.d.ts +0 -8
  41. package/dist/typst-parser.d.ts +0 -23
  42. package/dist/typst-shorthands.d.ts +0 -3
  43. package/dist/typst-tokenizer.d.ts +0 -2
  44. package/dist/typst-types.d.ts +0 -98
  45. package/dist/typst-writer.d.ts +0 -30
  46. package/dist/util.d.ts +0 -3
package/dist/index.js CHANGED
@@ -10,6 +10,8 @@ function assert(condition, message = "Assertion failed.") {
10
10
 
11
11
  // src/tex-types.ts
12
12
  var TexToken = class _TexToken {
13
+ type;
14
+ value;
13
15
  constructor(type, value) {
14
16
  this.type = type;
15
17
  this.value = value;
@@ -28,11 +30,11 @@ var TexToken = class _TexToken {
28
30
  toNode() {
29
31
  return new TexTerminal(this);
30
32
  }
31
- static {
32
- this.EMPTY = new _TexToken(0 /* EMPTY */, "");
33
- }
33
+ static EMPTY = new _TexToken(0 /* EMPTY */, "");
34
34
  };
35
35
  var TexNode = class {
36
+ type;
37
+ head;
36
38
  constructor(type, head) {
37
39
  this.type = type;
38
40
  this.head = head ? head : TexToken.EMPTY;
@@ -90,6 +92,7 @@ var TexText = class extends TexNode {
90
92
  }
91
93
  };
92
94
  var TexGroup = class extends TexNode {
95
+ items;
93
96
  constructor(items) {
94
97
  super("ordgroup", TexToken.EMPTY);
95
98
  this.items = items;
@@ -99,6 +102,9 @@ var TexGroup = class extends TexNode {
99
102
  }
100
103
  };
101
104
  var TexSupSub = class extends TexNode {
105
+ base;
106
+ sup;
107
+ sub;
102
108
  constructor(data) {
103
109
  super("supsub", TexToken.EMPTY);
104
110
  this.base = data.base;
@@ -142,6 +148,9 @@ var TexSupSub = class extends TexNode {
142
148
  }
143
149
  };
144
150
  var TexFuncCall = class extends TexNode {
151
+ args;
152
+ // For type="sqrt", it's additional argument wrapped square bracket. e.g. 3 in \sqrt[3]{x}
153
+ data;
145
154
  constructor(head, args, data = null) {
146
155
  super("funcCall", head);
147
156
  this.args = args;
@@ -164,6 +173,9 @@ var TexFuncCall = class extends TexNode {
164
173
  }
165
174
  };
166
175
  var TexLeftRight = class extends TexNode {
176
+ body;
177
+ left;
178
+ right;
167
179
  constructor(data) {
168
180
  super("leftright", TexToken.EMPTY);
169
181
  this.body = data.body;
@@ -181,6 +193,9 @@ var TexLeftRight = class extends TexNode {
181
193
  }
182
194
  };
183
195
  var TexBeginEnd = class extends TexNode {
196
+ matrix;
197
+ // for environment="array" or "subarray", there's additional data like {c|c} right after \begin{env}
198
+ data;
184
199
  constructor(head, matrix, data = null) {
185
200
  assert(head.type === 3 /* LITERAL */);
186
201
  super("beginend", head);
@@ -279,20 +294,23 @@ function matchcompare(m1, m2) {
279
294
  }
280
295
  }
281
296
  var Scanner = class {
297
+ _input;
298
+ _lexer;
299
+ // position within input stream
300
+ _pos = 0;
301
+ // current line number
302
+ _line = 0;
303
+ // current column number
304
+ _col = 0;
305
+ _offset = 0;
306
+ _less = null;
307
+ _go = false;
308
+ _newstate = null;
309
+ _state;
310
+ _text = null;
311
+ _leng = null;
312
+ _reMatchArray = null;
282
313
  constructor(input, lexer) {
283
- // position within input stream
284
- this._pos = 0;
285
- // current line number
286
- this._line = 0;
287
- // current column number
288
- this._col = 0;
289
- this._offset = 0;
290
- this._less = null;
291
- this._go = false;
292
- this._newstate = null;
293
- this._text = null;
294
- this._leng = null;
295
- this._reMatchArray = null;
296
314
  this._input = input;
297
315
  this._lexer = lexer;
298
316
  this._state = lexer.states[0];
@@ -443,6 +461,8 @@ var Scanner = class {
443
461
  }
444
462
  };
445
463
  var JSLex = class {
464
+ states;
465
+ specification;
446
466
  constructor(spec3) {
447
467
  this.states = Object.keys(spec3);
448
468
  this.specification = {};
@@ -543,7 +563,8 @@ var TEX_UNARY_COMMANDS = [
543
563
  "overleftarrow",
544
564
  "overrightarrow",
545
565
  "hspace",
546
- "substack"
566
+ "substack",
567
+ "set"
547
568
  ];
548
569
  var TEX_BINARY_COMMANDS = [
549
570
  "frac",
@@ -736,6 +757,8 @@ var LatexParserError = class extends Error {
736
757
  var SUB_SYMBOL = new TexToken(7 /* CONTROL */, "_");
737
758
  var SUP_SYMBOL = new TexToken(7 /* CONTROL */, "^");
738
759
  var LatexParser = class {
760
+ space_sensitive;
761
+ newline_sensitive;
739
762
  constructor(space_sensitive = false, newline_sensitive = true) {
740
763
  this.space_sensitive = space_sensitive;
741
764
  this.newline_sensitive = newline_sensitive;
@@ -1096,8 +1119,58 @@ function parseTex(tex, customTexMacros) {
1096
1119
  return parser.parse(tokens);
1097
1120
  }
1098
1121
 
1122
+ // src/typst-shorthands.ts
1123
+ var shorthandMap = /* @__PURE__ */ new Map([
1124
+ ["arrow.l.r.double.long", "<==>"],
1125
+ ["arrow.l.r.long", "<-->"],
1126
+ ["arrow.r.bar", "|->"],
1127
+ ["arrow.r.double.bar", "|=>"],
1128
+ ["arrow.r.double.long", "==>"],
1129
+ ["arrow.r.long", "-->"],
1130
+ ["arrow.r.long.squiggly", "~~>"],
1131
+ ["arrow.r.tail", ">->"],
1132
+ ["arrow.r.twohead", "->>"],
1133
+ ["arrow.l.double.long", "<=="],
1134
+ ["arrow.l.long", "<--"],
1135
+ ["arrow.l.long.squiggly", "<~~"],
1136
+ ["arrow.l.tail", "<-<"],
1137
+ ["arrow.l.twohead", "<<-"],
1138
+ ["arrow.l.r", "<->"],
1139
+ ["arrow.l.r.double", "<=>"],
1140
+ ["colon.double.eq", "::="],
1141
+ ["dots.h", "..."],
1142
+ ["gt.triple", ">>>"],
1143
+ ["lt.triple", "<<<"],
1144
+ ["arrow.r", "->"],
1145
+ ["arrow.r.double", "=>"],
1146
+ ["arrow.r.squiggly", "~>"],
1147
+ ["arrow.l", "<-"],
1148
+ ["arrow.l.squiggly", "<~"],
1149
+ ["bar.v.double", "||"],
1150
+ ["bracket.l.double", "[|"],
1151
+ ["bracket.r.double", "|]"],
1152
+ ["colon.eq", ":="],
1153
+ ["eq.colon", "=:"],
1154
+ ["eq.not", "!="],
1155
+ ["gt.double", ">>"],
1156
+ ["gt.eq", ">="],
1157
+ ["lt.double", "<<"],
1158
+ ["lt.eq", "<="],
1159
+ ["ast.op", "*"],
1160
+ ["minus", "-"],
1161
+ ["tilde.op", "~"]
1162
+ ]);
1163
+ var reverseShorthandMap = /* @__PURE__ */ new Map();
1164
+ for (const [key, value] of shorthandMap.entries()) {
1165
+ if (value.length > 1) {
1166
+ reverseShorthandMap.set(value, key);
1167
+ }
1168
+ }
1169
+
1099
1170
  // src/typst-types.ts
1100
1171
  var TypstToken = class _TypstToken {
1172
+ type;
1173
+ value;
1101
1174
  constructor(type, content) {
1102
1175
  this.type = type;
1103
1176
  this.value = content;
@@ -1121,14 +1194,39 @@ var TypstToken = class _TypstToken {
1121
1194
  return this.value;
1122
1195
  }
1123
1196
  }
1124
- static {
1125
- this.NONE = new _TypstToken(0 /* NONE */, "#none");
1126
- }
1127
- static {
1128
- this.EMPTY = new _TypstToken(2 /* ELEMENT */, "");
1197
+ static NONE = new _TypstToken(0 /* NONE */, "#none");
1198
+ static EMPTY = new _TypstToken(2 /* ELEMENT */, "");
1199
+ static LEFT_BRACE = new _TypstToken(2 /* ELEMENT */, "{");
1200
+ static RIGHT_BRACE = new _TypstToken(2 /* ELEMENT */, "}");
1201
+ static LEFT_DELIMITERS = [
1202
+ new _TypstToken(2 /* ELEMENT */, "("),
1203
+ new _TypstToken(2 /* ELEMENT */, "["),
1204
+ new _TypstToken(2 /* ELEMENT */, "{"),
1205
+ new _TypstToken(2 /* ELEMENT */, "|"),
1206
+ new _TypstToken(1 /* SYMBOL */, "angle.l")
1207
+ ];
1208
+ static RIGHT_DELIMITERS = [
1209
+ new _TypstToken(2 /* ELEMENT */, ")"),
1210
+ new _TypstToken(2 /* ELEMENT */, "]"),
1211
+ new _TypstToken(2 /* ELEMENT */, "}"),
1212
+ new _TypstToken(2 /* ELEMENT */, "|"),
1213
+ new _TypstToken(1 /* SYMBOL */, "angle.r")
1214
+ ];
1215
+ };
1216
+ var TypstWriterError = class extends Error {
1217
+ node;
1218
+ constructor(message, node) {
1219
+ super(message);
1220
+ this.name = "TypstWriterError";
1221
+ this.node = node;
1129
1222
  }
1130
1223
  };
1224
+ var SOFT_SPACE = new TypstToken(7 /* CONTROL */, " ");
1131
1225
  var TypstNode = class {
1226
+ type;
1227
+ head;
1228
+ // Some Typst functions accept additional options. e.g. mat() has option "delim", op() has option "limits"
1229
+ options;
1132
1230
  constructor(type, head) {
1133
1231
  this.type = type;
1134
1232
  this.head = head ? head : TypstToken.NONE;
@@ -1154,8 +1252,42 @@ var TypstTerminal = class extends TypstNode {
1154
1252
  toString() {
1155
1253
  return this.head.toString();
1156
1254
  }
1255
+ serialize(env, options) {
1256
+ if (this.head.type === 2 /* ELEMENT */) {
1257
+ if (this.head.value === "," && env.insideFunctionDepth > 0) {
1258
+ return [new TypstToken(1 /* SYMBOL */, "comma")];
1259
+ }
1260
+ } else if (this.head.type === 1 /* SYMBOL */) {
1261
+ let symbol_name = this.head.value;
1262
+ if (options.preferShorthands) {
1263
+ if (shorthandMap.has(symbol_name)) {
1264
+ symbol_name = shorthandMap.get(symbol_name);
1265
+ }
1266
+ }
1267
+ if (options.inftyToOo && symbol_name === "infinity") {
1268
+ symbol_name = "oo";
1269
+ }
1270
+ return [new TypstToken(1 /* SYMBOL */, symbol_name)];
1271
+ } else if (this.head.type === 6 /* SPACE */ || this.head.type === 8 /* NEWLINE */) {
1272
+ const queue = [];
1273
+ for (const c of this.head.value) {
1274
+ if (c === " ") {
1275
+ if (options.keepSpaces) {
1276
+ queue.push(new TypstToken(6 /* SPACE */, c));
1277
+ }
1278
+ } else if (c === "\n") {
1279
+ queue.push(new TypstToken(1 /* SYMBOL */, c));
1280
+ } else {
1281
+ throw new TypstWriterError(`Unexpected whitespace character: ${c}`, this);
1282
+ }
1283
+ }
1284
+ return queue;
1285
+ }
1286
+ return [this.head];
1287
+ }
1157
1288
  };
1158
1289
  var TypstGroup = class extends TypstNode {
1290
+ items;
1159
1291
  constructor(items) {
1160
1292
  super("group", TypstToken.NONE);
1161
1293
  this.items = items;
@@ -1163,8 +1295,21 @@ var TypstGroup = class extends TypstNode {
1163
1295
  isOverHigh() {
1164
1296
  return this.items.some((n) => n.isOverHigh());
1165
1297
  }
1298
+ serialize(env, options) {
1299
+ const queue = this.items.flatMap((n) => n.serialize(env, options));
1300
+ if (queue.length > 0 && queue[0].eq(SOFT_SPACE)) {
1301
+ queue.shift();
1302
+ }
1303
+ if (queue.length > 0 && queue[queue.length - 1].eq(SOFT_SPACE)) {
1304
+ queue.pop();
1305
+ }
1306
+ return queue;
1307
+ }
1166
1308
  };
1167
1309
  var TypstSupsub = class extends TypstNode {
1310
+ base;
1311
+ sup;
1312
+ sub;
1168
1313
  constructor(data) {
1169
1314
  super("supsub", TypstToken.NONE);
1170
1315
  this.base = data.base;
@@ -1174,8 +1319,28 @@ var TypstSupsub = class extends TypstNode {
1174
1319
  isOverHigh() {
1175
1320
  return this.base.isOverHigh();
1176
1321
  }
1322
+ serialize(env, options) {
1323
+ const queue = [];
1324
+ let { base, sup, sub } = this;
1325
+ queue.push(...base.serialize(env, options));
1326
+ const has_prime = sup && sup.head.eq(new TypstToken(2 /* ELEMENT */, "'"));
1327
+ if (has_prime) {
1328
+ queue.push(new TypstToken(2 /* ELEMENT */, "'"));
1329
+ }
1330
+ if (sub) {
1331
+ queue.push(new TypstToken(2 /* ELEMENT */, "_"));
1332
+ queue.push(...sub.serialize(env, options));
1333
+ }
1334
+ if (sup && !has_prime) {
1335
+ queue.push(new TypstToken(2 /* ELEMENT */, "^"));
1336
+ queue.push(...sup.serialize(env, options));
1337
+ }
1338
+ queue.push(SOFT_SPACE);
1339
+ return queue;
1340
+ }
1177
1341
  };
1178
1342
  var TypstFuncCall = class extends TypstNode {
1343
+ args;
1179
1344
  constructor(head, args) {
1180
1345
  super("funcCall", head);
1181
1346
  this.args = args;
@@ -1186,8 +1351,30 @@ var TypstFuncCall = class extends TypstNode {
1186
1351
  }
1187
1352
  return this.args.some((n) => n.isOverHigh());
1188
1353
  }
1354
+ serialize(env, options) {
1355
+ const queue = [];
1356
+ const func_symbol = this.head;
1357
+ queue.push(func_symbol);
1358
+ env.insideFunctionDepth++;
1359
+ queue.push(TYPST_LEFT_PARENTHESIS);
1360
+ for (let i = 0; i < this.args.length; i++) {
1361
+ queue.push(...this.args[i].serialize(env, options));
1362
+ if (i < this.args.length - 1) {
1363
+ queue.push(new TypstToken(2 /* ELEMENT */, ","));
1364
+ }
1365
+ }
1366
+ if (this.options) {
1367
+ for (const [key, value] of Object.entries(this.options)) {
1368
+ queue.push(new TypstToken(3 /* LITERAL */, `, ${key}: ${value.toString()}`));
1369
+ }
1370
+ }
1371
+ queue.push(TYPST_RIGHT_PARENTHESIS);
1372
+ env.insideFunctionDepth--;
1373
+ return queue;
1374
+ }
1189
1375
  };
1190
1376
  var TypstFraction = class extends TypstNode {
1377
+ args;
1191
1378
  constructor(args) {
1192
1379
  super("fraction", TypstToken.NONE);
1193
1380
  this.args = args;
@@ -1195,8 +1382,23 @@ var TypstFraction = class extends TypstNode {
1195
1382
  isOverHigh() {
1196
1383
  return true;
1197
1384
  }
1385
+ serialize(env, options) {
1386
+ const queue = [];
1387
+ const [numerator, denominator] = this.args;
1388
+ queue.push(SOFT_SPACE);
1389
+ queue.push(...numerator.serialize(env, options));
1390
+ queue.push(new TypstToken(2 /* ELEMENT */, "/"));
1391
+ queue.push(...denominator.serialize(env, options));
1392
+ queue.push(SOFT_SPACE);
1393
+ return queue;
1394
+ }
1198
1395
  };
1396
+ var TYPST_LEFT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, "(");
1397
+ var TYPST_RIGHT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, ")");
1199
1398
  var TypstLeftright = class extends TypstNode {
1399
+ body;
1400
+ left;
1401
+ right;
1200
1402
  // head is either null or 'lr'
1201
1403
  constructor(head, data) {
1202
1404
  super("leftright", head);
@@ -1207,8 +1409,29 @@ var TypstLeftright = class extends TypstNode {
1207
1409
  isOverHigh() {
1208
1410
  return this.body.isOverHigh();
1209
1411
  }
1412
+ serialize(env, options) {
1413
+ const queue = [];
1414
+ const LR = new TypstToken(1 /* SYMBOL */, "lr");
1415
+ const { left, right } = this;
1416
+ if (this.head.eq(LR)) {
1417
+ queue.push(LR);
1418
+ queue.push(TYPST_LEFT_PARENTHESIS);
1419
+ }
1420
+ if (left) {
1421
+ queue.push(left);
1422
+ }
1423
+ queue.push(...this.body.serialize(env, options));
1424
+ if (right) {
1425
+ queue.push(right);
1426
+ }
1427
+ if (this.head.eq(LR)) {
1428
+ queue.push(TYPST_RIGHT_PARENTHESIS);
1429
+ }
1430
+ return queue;
1431
+ }
1210
1432
  };
1211
- var TypstMatrixLike = class extends TypstNode {
1433
+ var TypstMatrixLike = class _TypstMatrixLike extends TypstNode {
1434
+ matrix;
1212
1435
  // head is 'mat', 'cases' or null
1213
1436
  constructor(head, data) {
1214
1437
  super("matrixLike", head);
@@ -1217,14 +1440,60 @@ var TypstMatrixLike = class extends TypstNode {
1217
1440
  isOverHigh() {
1218
1441
  return true;
1219
1442
  }
1220
- static {
1221
- this.MAT = new TypstToken(1 /* SYMBOL */, "mat");
1222
- }
1223
- static {
1224
- this.CASES = new TypstToken(1 /* SYMBOL */, "cases");
1443
+ serialize(env, options) {
1444
+ const queue = [];
1445
+ let cell_sep;
1446
+ let row_sep;
1447
+ if (this.head.eq(_TypstMatrixLike.MAT)) {
1448
+ cell_sep = new TypstToken(2 /* ELEMENT */, ",");
1449
+ row_sep = new TypstToken(2 /* ELEMENT */, ";");
1450
+ } else if (this.head.eq(_TypstMatrixLike.CASES)) {
1451
+ cell_sep = new TypstToken(2 /* ELEMENT */, "&");
1452
+ row_sep = new TypstToken(2 /* ELEMENT */, ",");
1453
+ } else if (this.head.eq(TypstToken.NONE)) {
1454
+ cell_sep = new TypstToken(2 /* ELEMENT */, "&");
1455
+ row_sep = new TypstToken(1 /* SYMBOL */, "\\");
1456
+ }
1457
+ if (!this.head.eq(TypstToken.NONE)) {
1458
+ queue.push(this.head);
1459
+ env.insideFunctionDepth++;
1460
+ queue.push(TYPST_LEFT_PARENTHESIS);
1461
+ if (this.options) {
1462
+ for (const [key, value] of Object.entries(this.options)) {
1463
+ queue.push(new TypstToken(3 /* LITERAL */, `${key}: ${value.toString()}, `));
1464
+ }
1465
+ }
1466
+ }
1467
+ this.matrix.forEach((row, i) => {
1468
+ row.forEach((cell, j) => {
1469
+ queue.push(...cell.serialize(env, options));
1470
+ if (j < row.length - 1) {
1471
+ queue.push(cell_sep);
1472
+ } else {
1473
+ if (i < this.matrix.length - 1) {
1474
+ queue.push(row_sep);
1475
+ }
1476
+ }
1477
+ });
1478
+ });
1479
+ if (!this.head.eq(TypstToken.NONE)) {
1480
+ queue.push(TYPST_RIGHT_PARENTHESIS);
1481
+ env.insideFunctionDepth--;
1482
+ }
1483
+ return queue;
1225
1484
  }
1485
+ static MAT = new TypstToken(1 /* SYMBOL */, "mat");
1486
+ static CASES = new TypstToken(1 /* SYMBOL */, "cases");
1226
1487
  };
1227
1488
  var TypstMarkupFunc = class extends TypstNode {
1489
+ /*
1490
+ In idealized situations, for `#heading([some text and math $x + y$ example])`,
1491
+ fragments would be [TypstMarkup{"some text and math "}, TypstNode{"x + y"}, TypstMarkup{" example"}]
1492
+ At present, we haven't implemented anything about TypstMarkup.
1493
+ So only pattens like `#heading(level: 2)[$x+y$]`, `#text(fill: red)[$x + y$]` are supported.
1494
+ Therefore, fragments is always a list containing exactly 1 TypstNode in well-working situations.
1495
+ */
1496
+ fragments;
1228
1497
  constructor(head, fragments) {
1229
1498
  super("markupFunc", head);
1230
1499
  this.fragments = fragments;
@@ -1232,82 +1501,45 @@ var TypstMarkupFunc = class extends TypstNode {
1232
1501
  isOverHigh() {
1233
1502
  return this.fragments.some((n) => n.isOverHigh());
1234
1503
  }
1235
- };
1236
-
1237
- // src/typst-shorthands.ts
1238
- var shorthandMap = /* @__PURE__ */ new Map([
1239
- ["arrow.l.r.double.long", "<==>"],
1240
- ["arrow.l.r.long", "<-->"],
1241
- ["arrow.r.bar", "|->"],
1242
- ["arrow.r.double.bar", "|=>"],
1243
- ["arrow.r.double.long", "==>"],
1244
- ["arrow.r.long", "-->"],
1245
- ["arrow.r.long.squiggly", "~~>"],
1246
- ["arrow.r.tail", ">->"],
1247
- ["arrow.r.twohead", "->>"],
1248
- ["arrow.l.double.long", "<=="],
1249
- ["arrow.l.long", "<--"],
1250
- ["arrow.l.long.squiggly", "<~~"],
1251
- ["arrow.l.tail", "<-<"],
1252
- ["arrow.l.twohead", "<<-"],
1253
- ["arrow.l.r", "<->"],
1254
- ["arrow.l.r.double", "<=>"],
1255
- ["colon.double.eq", "::="],
1256
- ["dots.h", "..."],
1257
- ["gt.triple", ">>>"],
1258
- ["lt.triple", "<<<"],
1259
- ["arrow.r", "->"],
1260
- ["arrow.r.double", "=>"],
1261
- ["arrow.r.squiggly", "~>"],
1262
- ["arrow.l", "<-"],
1263
- ["arrow.l.squiggly", "<~"],
1264
- ["bar.v.double", "||"],
1265
- ["bracket.l.double", "[|"],
1266
- ["bracket.r.double", "|]"],
1267
- ["colon.eq", ":="],
1268
- ["eq.colon", "=:"],
1269
- ["eq.not", "!="],
1270
- ["gt.double", ">>"],
1271
- ["gt.eq", ">="],
1272
- ["lt.double", "<<"],
1273
- ["lt.eq", "<="],
1274
- ["ast.op", "*"],
1275
- ["minus", "-"],
1276
- ["tilde.op", "~"]
1277
- ]);
1278
- var reverseShorthandMap = /* @__PURE__ */ new Map();
1279
- for (const [key, value] of shorthandMap.entries()) {
1280
- if (value.length > 1) {
1281
- reverseShorthandMap.set(value, key);
1504
+ serialize(env, options) {
1505
+ const queue = [];
1506
+ queue.push(this.head);
1507
+ env.insideFunctionDepth++;
1508
+ queue.push(TYPST_LEFT_PARENTHESIS);
1509
+ if (this.options) {
1510
+ const entries = Object.entries(this.options);
1511
+ for (let i = 0; i < entries.length; i++) {
1512
+ const [key, value] = entries[i];
1513
+ queue.push(new TypstToken(3 /* LITERAL */, `${key}: ${value.toString()}`));
1514
+ if (i < entries.length - 1) {
1515
+ queue.push(new TypstToken(2 /* ELEMENT */, ","));
1516
+ }
1517
+ }
1518
+ }
1519
+ queue.push(TYPST_RIGHT_PARENTHESIS);
1520
+ queue.push(new TypstToken(3 /* LITERAL */, "["));
1521
+ for (const frag of this.fragments) {
1522
+ queue.push(new TypstToken(3 /* LITERAL */, "$"));
1523
+ queue.push(...frag.serialize(env, options));
1524
+ queue.push(new TypstToken(3 /* LITERAL */, "$"));
1525
+ }
1526
+ queue.push(new TypstToken(3 /* LITERAL */, "]"));
1527
+ return queue;
1282
1528
  }
1283
- }
1529
+ };
1284
1530
 
1285
1531
  // src/typst-writer.ts
1286
- function is_delimiter(c) {
1287
- return c.head.type === 2 /* ELEMENT */ && ["(", ")", "[", "]", "{", "}", "|", "\u230A", "\u230B", "\u2308", "\u2309"].includes(c.head.value);
1288
- }
1289
- var TYPST_LEFT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, "(");
1290
- var TYPST_RIGHT_PARENTHESIS = new TypstToken(2 /* ELEMENT */, ")");
1532
+ var TYPST_LEFT_PARENTHESIS2 = new TypstToken(2 /* ELEMENT */, "(");
1533
+ var TYPST_RIGHT_PARENTHESIS2 = new TypstToken(2 /* ELEMENT */, ")");
1291
1534
  var TYPST_COMMA = new TypstToken(2 /* ELEMENT */, ",");
1292
1535
  var TYPST_NEWLINE = new TypstToken(1 /* SYMBOL */, "\n");
1293
- var SOFT_SPACE = new TypstToken(7 /* CONTROL */, " ");
1294
- var TypstWriterError = class extends Error {
1295
- constructor(message, node) {
1296
- super(message);
1297
- this.name = "TypstWriterError";
1298
- this.node = node;
1299
- }
1300
- };
1536
+ var SOFT_SPACE2 = new TypstToken(7 /* CONTROL */, " ");
1301
1537
  var TypstWriter = class {
1538
+ buffer = "";
1539
+ queue = [];
1540
+ options;
1302
1541
  constructor(options) {
1303
- this.buffer = "";
1304
- this.queue = [];
1305
- this.insideFunctionDepth = 0;
1306
- this.nonStrict = options.nonStrict;
1307
- this.preferShorthands = options.preferShorthands;
1308
- this.keepSpaces = options.keepSpaces;
1309
- this.inftyToOo = options.inftyToOo;
1310
- this.optimize = options.optimize;
1542
+ this.options = options;
1311
1543
  }
1312
1544
  writeBuffer(previousToken, token) {
1313
1545
  const str = token.toString();
@@ -1323,7 +1555,7 @@ var TypstWriter = class {
1323
1555
  no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
1324
1556
  no_need_space ||= str.startsWith("\n");
1325
1557
  no_need_space ||= this.buffer === "";
1326
- no_need_space ||= /^\s/.test(str);
1558
+ no_need_space ||= /\s$/.test(this.buffer) || /^\s/.test(str);
1327
1559
  no_need_space ||= this.buffer.endsWith("&") && str === "=";
1328
1560
  no_need_space ||= this.buffer.endsWith("/") || str === "/";
1329
1561
  no_need_space ||= token.type === 3 /* LITERAL */;
@@ -1338,239 +1570,31 @@ var TypstWriter = class {
1338
1570
  }
1339
1571
  // Serialize a tree of TypstNode into a list of TypstToken
1340
1572
  serialize(abstractNode) {
1341
- switch (abstractNode.type) {
1342
- case "terminal": {
1343
- const node = abstractNode;
1344
- if (node.head.type === 2 /* ELEMENT */) {
1345
- if (node.head.value === "," && this.insideFunctionDepth > 0) {
1346
- this.queue.push(new TypstToken(1 /* SYMBOL */, "comma"));
1347
- } else {
1348
- this.queue.push(node.head);
1349
- }
1350
- break;
1351
- } else if (node.head.type === 1 /* SYMBOL */) {
1352
- let symbol_name = node.head.value;
1353
- if (this.preferShorthands) {
1354
- if (shorthandMap.has(symbol_name)) {
1355
- symbol_name = shorthandMap.get(symbol_name);
1356
- }
1357
- }
1358
- if (this.inftyToOo && symbol_name === "infinity") {
1359
- symbol_name = "oo";
1360
- }
1361
- this.queue.push(new TypstToken(1 /* SYMBOL */, symbol_name));
1362
- break;
1363
- } else if (node.head.type === 6 /* SPACE */ || node.head.type === 8 /* NEWLINE */) {
1364
- for (const c of node.head.value) {
1365
- if (c === " ") {
1366
- if (this.keepSpaces) {
1367
- this.queue.push(new TypstToken(6 /* SPACE */, c));
1368
- }
1369
- } else if (c === "\n") {
1370
- this.queue.push(new TypstToken(1 /* SYMBOL */, c));
1371
- } else {
1372
- throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
1373
- }
1374
- }
1375
- break;
1376
- } else {
1377
- this.queue.push(node.head);
1378
- break;
1379
- }
1380
- }
1381
- case "group": {
1382
- const node = abstractNode;
1383
- for (const item of node.items) {
1384
- this.serialize(item);
1385
- }
1386
- break;
1387
- }
1388
- case "leftright": {
1389
- const node = abstractNode;
1390
- const LR = new TypstToken(1 /* SYMBOL */, "lr");
1391
- const { left, right } = node;
1392
- if (node.head.eq(LR)) {
1393
- this.queue.push(LR);
1394
- this.queue.push(TYPST_LEFT_PARENTHESIS);
1395
- }
1396
- if (left) {
1397
- this.queue.push(left);
1398
- }
1399
- this.serialize(node.body);
1400
- if (right) {
1401
- this.queue.push(right);
1402
- }
1403
- if (node.head.eq(LR)) {
1404
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
1405
- }
1406
- break;
1407
- }
1408
- case "supsub": {
1409
- const node = abstractNode;
1410
- let { base, sup, sub } = node;
1411
- this.appendWithBracketsIfNeeded(base);
1412
- let trailing_space_needed = false;
1413
- const has_prime = sup && sup.head.eq(new TypstToken(2 /* ELEMENT */, "'"));
1414
- if (has_prime) {
1415
- this.queue.push(new TypstToken(2 /* ELEMENT */, "'"));
1416
- trailing_space_needed = false;
1417
- }
1418
- if (sub) {
1419
- this.queue.push(new TypstToken(2 /* ELEMENT */, "_"));
1420
- trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
1421
- }
1422
- if (sup && !has_prime) {
1423
- this.queue.push(new TypstToken(2 /* ELEMENT */, "^"));
1424
- trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
1425
- }
1426
- if (trailing_space_needed) {
1427
- this.queue.push(SOFT_SPACE);
1428
- }
1429
- break;
1430
- }
1431
- case "funcCall": {
1432
- const node = abstractNode;
1433
- const func_symbol = node.head;
1434
- this.queue.push(func_symbol);
1435
- this.insideFunctionDepth++;
1436
- this.queue.push(TYPST_LEFT_PARENTHESIS);
1437
- for (let i = 0; i < node.args.length; i++) {
1438
- this.serialize(node.args[i]);
1439
- if (i < node.args.length - 1) {
1440
- this.queue.push(new TypstToken(2 /* ELEMENT */, ","));
1441
- }
1442
- }
1443
- if (node.options) {
1444
- for (const [key, value] of Object.entries(node.options)) {
1445
- this.queue.push(new TypstToken(3 /* LITERAL */, `, ${key}: ${value.toString()}`));
1446
- }
1447
- }
1448
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
1449
- this.insideFunctionDepth--;
1450
- break;
1451
- }
1452
- case "fraction": {
1453
- const node = abstractNode;
1454
- const [numerator, denominator] = node.args;
1455
- const pos = this.queue.length;
1456
- const no_wrap = this.appendWithBracketsIfNeeded(numerator);
1457
- const wrapped = !no_wrap;
1458
- if (wrapped) {
1459
- this.queue.splice(pos, 0, SOFT_SPACE);
1460
- }
1461
- this.queue.push(new TypstToken(2 /* ELEMENT */, "/"));
1462
- this.appendWithBracketsIfNeeded(denominator);
1463
- break;
1464
- }
1465
- case "matrixLike": {
1466
- const node = abstractNode;
1467
- const matrix = node.matrix;
1468
- let cell_sep;
1469
- let row_sep;
1470
- if (node.head.eq(TypstMatrixLike.MAT)) {
1471
- cell_sep = new TypstToken(2 /* ELEMENT */, ",");
1472
- row_sep = new TypstToken(2 /* ELEMENT */, ";");
1473
- } else if (node.head.eq(TypstMatrixLike.CASES)) {
1474
- cell_sep = new TypstToken(2 /* ELEMENT */, "&");
1475
- row_sep = new TypstToken(2 /* ELEMENT */, ",");
1476
- } else if (node.head.eq(TypstToken.NONE)) {
1477
- cell_sep = new TypstToken(2 /* ELEMENT */, "&");
1478
- row_sep = new TypstToken(1 /* SYMBOL */, "\\");
1479
- }
1480
- if (!node.head.eq(TypstToken.NONE)) {
1481
- this.queue.push(node.head);
1482
- this.insideFunctionDepth++;
1483
- this.queue.push(TYPST_LEFT_PARENTHESIS);
1484
- if (node.options) {
1485
- for (const [key, value] of Object.entries(node.options)) {
1486
- this.queue.push(new TypstToken(3 /* LITERAL */, `${key}: ${value.toString()}, `));
1487
- }
1488
- }
1489
- }
1490
- matrix.forEach((row, i) => {
1491
- row.forEach((cell, j) => {
1492
- this.serialize(cell);
1493
- if (j < row.length - 1) {
1494
- this.queue.push(cell_sep);
1495
- } else {
1496
- if (i < matrix.length - 1) {
1497
- this.queue.push(row_sep);
1498
- }
1499
- }
1500
- });
1501
- });
1502
- if (!node.head.eq(TypstToken.NONE)) {
1503
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
1504
- this.insideFunctionDepth--;
1505
- }
1506
- break;
1507
- }
1508
- case "markupFunc": {
1509
- const node = abstractNode;
1510
- this.queue.push(node.head);
1511
- this.queue.push(TYPST_LEFT_PARENTHESIS);
1512
- if (node.options) {
1513
- const entries = Object.entries(node.options);
1514
- for (let i = 0; i < entries.length; i++) {
1515
- const [key, value] = entries[i];
1516
- this.queue.push(new TypstToken(3 /* LITERAL */, `${key}: ${value.toString()}`));
1517
- if (i < entries.length - 1) {
1518
- this.queue.push(new TypstToken(2 /* ELEMENT */, ","));
1519
- }
1520
- }
1521
- }
1522
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
1523
- this.queue.push(new TypstToken(3 /* LITERAL */, "["));
1524
- for (const frag of node.fragments) {
1525
- this.queue.push(new TypstToken(3 /* LITERAL */, "$"));
1526
- this.serialize(frag);
1527
- this.queue.push(new TypstToken(3 /* LITERAL */, "$"));
1528
- }
1529
- this.queue.push(new TypstToken(3 /* LITERAL */, "]"));
1530
- break;
1531
- }
1532
- default:
1533
- throw new TypstWriterError(`Unimplemented node type to append: ${abstractNode.type}`, abstractNode);
1534
- }
1573
+ const env = { insideFunctionDepth: 0 };
1574
+ this.queue.push(...abstractNode.serialize(env, this.options));
1535
1575
  }
1536
- appendWithBracketsIfNeeded(node) {
1537
- let need_to_wrap = ["group", "supsub", "matrixLike", "fraction", "empty"].includes(node.type);
1538
- if (node.type === "group") {
1539
- const group = node;
1540
- if (group.items.length === 0) {
1541
- need_to_wrap = true;
1542
- } else {
1543
- const first = group.items[0];
1544
- const last = group.items[group.items.length - 1];
1545
- if (is_delimiter(first) && is_delimiter(last)) {
1546
- need_to_wrap = false;
1547
- }
1576
+ flushQueue() {
1577
+ let qu = [];
1578
+ for (const token of this.queue) {
1579
+ if (token.eq(SOFT_SPACE2) && qu.length > 0 && qu[qu.length - 1].eq(SOFT_SPACE2)) {
1580
+ continue;
1548
1581
  }
1582
+ qu.push(token);
1549
1583
  }
1550
- if (need_to_wrap) {
1551
- this.queue.push(TYPST_LEFT_PARENTHESIS);
1552
- this.serialize(node);
1553
- this.queue.push(TYPST_RIGHT_PARENTHESIS);
1554
- } else {
1555
- this.serialize(node);
1556
- }
1557
- return !need_to_wrap;
1558
- }
1559
- flushQueue() {
1560
1584
  const dummy_token = new TypstToken(1 /* SYMBOL */, "");
1561
- for (let i = 0; i < this.queue.length; i++) {
1562
- let token = this.queue[i];
1563
- if (token.eq(SOFT_SPACE)) {
1564
- const to_delete = i === 0 || i === this.queue.length - 1 || this.queue[i - 1].type === 6 /* SPACE */ || this.queue[i - 1].isOneOf([TYPST_LEFT_PARENTHESIS, TYPST_NEWLINE]) || this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE]);
1585
+ for (let i = 0; i < qu.length; i++) {
1586
+ let token = qu[i];
1587
+ if (token.eq(SOFT_SPACE2)) {
1588
+ const to_delete = i === 0 || i === qu.length - 1 || qu[i - 1].type === 6 /* SPACE */ || qu[i - 1].isOneOf([TYPST_LEFT_PARENTHESIS2, TYPST_NEWLINE]) || qu[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS2, TYPST_COMMA, TYPST_NEWLINE]);
1565
1589
  if (to_delete) {
1566
- this.queue[i] = dummy_token;
1590
+ qu[i] = dummy_token;
1567
1591
  }
1568
1592
  }
1569
1593
  }
1570
- this.queue = this.queue.filter((token) => !token.eq(dummy_token));
1571
- for (let i = 0; i < this.queue.length; i++) {
1572
- let token = this.queue[i];
1573
- let previous_token = i === 0 ? null : this.queue[i - 1];
1594
+ qu = qu.filter((token) => !token.eq(dummy_token));
1595
+ for (let i = 0; i < qu.length; i++) {
1596
+ let token = qu[i];
1597
+ let previous_token = i === 0 ? null : qu[i - 1];
1574
1598
  this.writeBuffer(previous_token, token);
1575
1599
  }
1576
1600
  this.queue = [];
@@ -1592,7 +1616,7 @@ var TypstWriter = class {
1592
1616
  res = res.replace(/round\(\)/g, 'round("")');
1593
1617
  return res;
1594
1618
  };
1595
- if (this.optimize) {
1619
+ if (this.options.optimize) {
1596
1620
  const all_passes = [smartFloorPass, smartCeilPass, smartRoundPass];
1597
1621
  for (const pass of all_passes) {
1598
1622
  this.buffer = pass(this.buffer);
@@ -2687,6 +2711,7 @@ for (const [key, value] of texAliasMap) {
2687
2711
 
2688
2712
  // src/convert.ts
2689
2713
  var ConverterError = class extends Error {
2714
+ node;
2690
2715
  constructor(message, node = null) {
2691
2716
  super(message);
2692
2717
  this.name = "ConverterError";
@@ -2829,6 +2854,35 @@ function convert_tex_array_align_literal(alignLiteral) {
2829
2854
  }
2830
2855
  return np;
2831
2856
  }
2857
+ var TYPST_LEFT_PARENTHESIS3 = new TypstToken(2 /* ELEMENT */, "(");
2858
+ var TYPST_RIGHT_PARENTHESIS3 = new TypstToken(2 /* ELEMENT */, ")");
2859
+ function is_delimiter(c) {
2860
+ return c.head.type === 2 /* ELEMENT */ && ["(", ")", "[", "]", "{", "}", "|", "\u230A", "\u230B", "\u2308", "\u2309"].includes(c.head.value);
2861
+ }
2862
+ function appendWithBracketsIfNeeded(node) {
2863
+ let need_to_wrap = ["group", "supsub", "matrixLike", "fraction", "empty"].includes(node.type);
2864
+ if (node.type === "group") {
2865
+ const group = node;
2866
+ if (group.items.length === 0) {
2867
+ need_to_wrap = true;
2868
+ } else {
2869
+ const first = group.items[0];
2870
+ const last = group.items[group.items.length - 1];
2871
+ if (is_delimiter(first) && is_delimiter(last)) {
2872
+ need_to_wrap = false;
2873
+ }
2874
+ }
2875
+ }
2876
+ if (need_to_wrap) {
2877
+ return new TypstLeftright(null, {
2878
+ left: TYPST_LEFT_PARENTHESIS3,
2879
+ right: TYPST_RIGHT_PARENTHESIS3,
2880
+ body: node
2881
+ });
2882
+ } else {
2883
+ return node;
2884
+ }
2885
+ }
2832
2886
  function convert_tex_node_to_typst(abstractNode, options = {}) {
2833
2887
  switch (abstractNode.type) {
2834
2888
  case "terminal": {
@@ -2869,6 +2923,13 @@ function convert_tex_node_to_typst(abstractNode, options = {}) {
2869
2923
  sup: sup ? convert_tex_node_to_typst(sup, options) : null,
2870
2924
  sub: sub ? convert_tex_node_to_typst(sub, options) : null
2871
2925
  };
2926
+ data.base = appendWithBracketsIfNeeded(data.base);
2927
+ if (data.sup) {
2928
+ data.sup = appendWithBracketsIfNeeded(data.sup);
2929
+ }
2930
+ if (data.sub) {
2931
+ data.sub = appendWithBracketsIfNeeded(data.sub);
2932
+ }
2872
2933
  return new TypstSupsub(data);
2873
2934
  }
2874
2935
  case "leftright": {
@@ -2965,6 +3026,12 @@ function convert_tex_node_to_typst(abstractNode, options = {}) {
2965
3026
  if (node2.head.value === "\\substack") {
2966
3027
  return arg0;
2967
3028
  }
3029
+ if (node2.head.value === "\\set") {
3030
+ return new TypstLeftright(
3031
+ null,
3032
+ { body: arg0, left: TypstToken.LEFT_BRACE, right: TypstToken.RIGHT_BRACE }
3033
+ );
3034
+ }
2968
3035
  if (node2.head.value === "\\overset") {
2969
3036
  return convert_overset(node2, options);
2970
3037
  }
@@ -2973,7 +3040,7 @@ function convert_tex_node_to_typst(abstractNode, options = {}) {
2973
3040
  }
2974
3041
  if (node2.head.value === "\\frac") {
2975
3042
  if (options.fracToSlash) {
2976
- return new TypstFraction(node2.args.map((n) => convert_tex_node_to_typst(n, options)));
3043
+ return new TypstFraction(node2.args.map((n) => convert_tex_node_to_typst(n, options)).map(appendWithBracketsIfNeeded));
2977
3044
  }
2978
3045
  }
2979
3046
  if (options.optimize) {
@@ -3466,8 +3533,8 @@ function find_closing_delim(tokens, start) {
3466
3533
  return _find_closing_match(
3467
3534
  tokens,
3468
3535
  start,
3469
- [LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2, VERTICAL_BAR],
3470
- [RIGHT_PARENTHESES, RIGHT_BRACKET, RIGHT_CURLY_BRACKET2, VERTICAL_BAR]
3536
+ TypstToken.LEFT_DELIMITERS,
3537
+ TypstToken.RIGHT_DELIMITERS
3471
3538
  );
3472
3539
  }
3473
3540
  function find_closing_parenthesis(nodes, start) {
@@ -3600,12 +3667,13 @@ var LEFT_BRACKET = new TypstToken(2 /* ELEMENT */, "[");
3600
3667
  var RIGHT_BRACKET = new TypstToken(2 /* ELEMENT */, "]");
3601
3668
  var LEFT_CURLY_BRACKET2 = new TypstToken(2 /* ELEMENT */, "{");
3602
3669
  var RIGHT_CURLY_BRACKET2 = new TypstToken(2 /* ELEMENT */, "}");
3603
- var VERTICAL_BAR = new TypstToken(2 /* ELEMENT */, "|");
3604
3670
  var COMMA = new TypstToken(2 /* ELEMENT */, ",");
3605
3671
  var SEMICOLON = new TypstToken(2 /* ELEMENT */, ";");
3606
3672
  var SINGLE_SPACE = new TypstToken(6 /* SPACE */, " ");
3607
3673
  var CONTROL_AND = new TypstToken(7 /* CONTROL */, "&");
3608
3674
  var TypstParser = class {
3675
+ space_sensitive;
3676
+ newline_sensitive;
3609
3677
  constructor(space_sensitive = true, newline_sensitive = true) {
3610
3678
  this.space_sensitive = space_sensitive;
3611
3679
  this.newline_sensitive = newline_sensitive;
@@ -3739,20 +3807,20 @@ var TypstParser = class {
3739
3807
  // start: the position of the left parentheses
3740
3808
  parseLrArguments(tokens, start) {
3741
3809
  const lr_token = tokens[start];
3742
- if (tokens[start + 1].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2, VERTICAL_BAR])) {
3743
- const end = find_closing_match2(tokens, start);
3810
+ const end = find_closing_match2(tokens, start);
3811
+ if (tokens[start + 1].isOneOf(TypstToken.LEFT_DELIMITERS)) {
3744
3812
  const inner_start = start + 1;
3745
3813
  const inner_end = find_closing_delim(tokens, inner_start);
3746
- const inner_args = this.parseArgumentsWithSeparator(tokens, inner_start + 1, inner_end, COMMA);
3814
+ const [inner_args, _] = this.parseGroup(tokens, inner_start + 1, inner_end);
3747
3815
  return [
3748
- new TypstLeftright(lr_token, { body: new TypstGroup(inner_args), left: tokens[inner_start], right: tokens[inner_end] }),
3816
+ new TypstLeftright(lr_token, { body: inner_args, left: tokens[inner_start], right: tokens[inner_end] }),
3749
3817
  end + 1
3750
3818
  ];
3751
3819
  } else {
3752
- const [args, end] = this.parseArguments(tokens, start);
3820
+ const [inner_args, _] = this.parseGroup(tokens, start + 1, end - 1);
3753
3821
  return [
3754
- new TypstLeftright(lr_token, { body: new TypstGroup(args), left: null, right: null }),
3755
- end
3822
+ new TypstLeftright(lr_token, { body: inner_args, left: null, right: null }),
3823
+ end + 1
3756
3824
  ];
3757
3825
  }
3758
3826
  }
@@ -3846,10 +3914,8 @@ function parseTypst(typst) {
3846
3914
 
3847
3915
  // src/tex-writer.ts
3848
3916
  var TexWriter = class {
3849
- constructor() {
3850
- this.buffer = "";
3851
- this.queue = [];
3852
- }
3917
+ buffer = "";
3918
+ queue = [];
3853
3919
  append(node) {
3854
3920
  this.queue = this.queue.concat(node.serialize());
3855
3921
  }
@@ -3878,8 +3944,11 @@ function tex2typst(tex, options) {
3878
3944
  customTexMacros: {}
3879
3945
  };
3880
3946
  if (options !== void 0) {
3947
+ if (typeof options !== "object") {
3948
+ throw new Error("options must be an object");
3949
+ }
3881
3950
  for (const key in opt) {
3882
- if (options[key] !== void 0) {
3951
+ if (key in options) {
3883
3952
  opt[key] = options[key];
3884
3953
  }
3885
3954
  }