tex2typst 0.2.12 → 0.2.15

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/index.js CHANGED
@@ -1034,7 +1034,73 @@ for (const [key, value] of map_from_official_docs) {
1034
1034
  }
1035
1035
  }
1036
1036
 
1037
- // src/parser.ts
1037
+ // src/types.ts
1038
+ class TexNode {
1039
+ type;
1040
+ content;
1041
+ args;
1042
+ data;
1043
+ constructor(type, content, args, data) {
1044
+ this.type = type;
1045
+ this.content = content;
1046
+ this.args = args;
1047
+ this.data = data;
1048
+ }
1049
+ eq_shallow(other) {
1050
+ return this.type === other.type && this.content === other.content;
1051
+ }
1052
+ toString() {
1053
+ switch (this.type) {
1054
+ case "text":
1055
+ return `\\text{${this.content}}`;
1056
+ default:
1057
+ throw new Error(`toString() is not implemented for type ${this.type}`);
1058
+ }
1059
+ }
1060
+ }
1061
+ class TypstToken {
1062
+ type;
1063
+ content;
1064
+ constructor(type, content) {
1065
+ this.type = type;
1066
+ this.content = content;
1067
+ }
1068
+ eq(other) {
1069
+ return this.type === other.type && this.content === other.content;
1070
+ }
1071
+ isOneOf(tokens) {
1072
+ let found = false;
1073
+ for (const token of tokens) {
1074
+ if (this.eq(token)) {
1075
+ found = true;
1076
+ break;
1077
+ }
1078
+ }
1079
+ return found;
1080
+ }
1081
+ }
1082
+
1083
+ class TypstNode {
1084
+ type;
1085
+ content;
1086
+ args;
1087
+ data;
1088
+ options;
1089
+ constructor(type, content, args, data) {
1090
+ this.type = type;
1091
+ this.content = content;
1092
+ this.args = args;
1093
+ this.data = data;
1094
+ }
1095
+ setOptions(options) {
1096
+ this.options = options;
1097
+ }
1098
+ eq_shallow(other) {
1099
+ return this.type === other.type && this.content === other.content;
1100
+ }
1101
+ }
1102
+
1103
+ // src/tex-parser.ts
1038
1104
  function assert(condition, message = "") {
1039
1105
  if (!condition) {
1040
1106
  throw new LatexParserError(message);
@@ -1091,7 +1157,7 @@ function isdigit(char) {
1091
1157
  }
1092
1158
  function eat_whitespaces(tokens, start) {
1093
1159
  let pos = start;
1094
- while (pos < tokens.length && [4 /* WHITESPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
1160
+ while (pos < tokens.length && [4 /* SPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
1095
1161
  pos++;
1096
1162
  }
1097
1163
  return tokens.slice(start, pos);
@@ -1108,7 +1174,7 @@ function eat_parenthesis(tokens, start) {
1108
1174
  }
1109
1175
  function eat_primes(tokens, start) {
1110
1176
  let pos = start;
1111
- while (pos < tokens.length && tokens[pos].eq(new Token(0 /* ELEMENT */, "'"))) {
1177
+ while (pos < tokens.length && tokens[pos].eq(new TexToken(0 /* ELEMENT */, "'"))) {
1112
1178
  pos += 1;
1113
1179
  }
1114
1180
  return pos - start;
@@ -1185,7 +1251,7 @@ function tokenize(latex) {
1185
1251
  while (newPos < latex.length && latex[newPos] !== "\n") {
1186
1252
  newPos += 1;
1187
1253
  }
1188
- token = new Token(3 /* COMMENT */, latex.slice(pos + 1, newPos));
1254
+ token = new TexToken(3 /* COMMENT */, latex.slice(pos + 1, newPos));
1189
1255
  pos = newPos;
1190
1256
  break;
1191
1257
  }
@@ -1194,19 +1260,19 @@ function tokenize(latex) {
1194
1260
  case "_":
1195
1261
  case "^":
1196
1262
  case "&":
1197
- token = new Token(6 /* CONTROL */, firstChar);
1263
+ token = new TexToken(6 /* CONTROL */, firstChar);
1198
1264
  pos++;
1199
1265
  break;
1200
1266
  case "\n":
1201
- token = new Token(5 /* NEWLINE */, firstChar);
1267
+ token = new TexToken(5 /* NEWLINE */, firstChar);
1202
1268
  pos++;
1203
1269
  break;
1204
1270
  case "\r": {
1205
1271
  if (pos + 1 < latex.length && latex[pos + 1] === "\n") {
1206
- token = new Token(5 /* NEWLINE */, "\n");
1272
+ token = new TexToken(5 /* NEWLINE */, "\n");
1207
1273
  pos += 2;
1208
1274
  } else {
1209
- token = new Token(5 /* NEWLINE */, "\n");
1275
+ token = new TexToken(5 /* NEWLINE */, "\n");
1210
1276
  pos++;
1211
1277
  }
1212
1278
  break;
@@ -1216,7 +1282,7 @@ function tokenize(latex) {
1216
1282
  while (newPos < latex.length && latex[newPos] === " ") {
1217
1283
  newPos += 1;
1218
1284
  }
1219
- token = new Token(4 /* WHITESPACE */, latex.slice(pos, newPos));
1285
+ token = new TexToken(4 /* SPACE */, latex.slice(pos, newPos));
1220
1286
  pos = newPos;
1221
1287
  break;
1222
1288
  }
@@ -1226,12 +1292,12 @@ function tokenize(latex) {
1226
1292
  }
1227
1293
  const firstTwoChars = latex.slice(pos, pos + 2);
1228
1294
  if (["\\\\", "\\,"].includes(firstTwoChars)) {
1229
- token = new Token(6 /* CONTROL */, firstTwoChars);
1230
- } else if (["\\{", "\\}", "\\%", "\\$", "\\&", "\\#", "\\_"].includes(firstTwoChars)) {
1231
- token = new Token(0 /* ELEMENT */, firstTwoChars);
1295
+ token = new TexToken(6 /* CONTROL */, firstTwoChars);
1296
+ } else if (["\\{", "\\}", "\\%", "\\$", "\\&", "\\#", "\\_", "\\|"].includes(firstTwoChars)) {
1297
+ token = new TexToken(0 /* ELEMENT */, firstTwoChars);
1232
1298
  } else {
1233
1299
  const command = eat_command_name(latex, pos + 1);
1234
- token = new Token(1 /* COMMAND */, "\\" + command);
1300
+ token = new TexToken(1 /* COMMAND */, "\\" + command);
1235
1301
  }
1236
1302
  pos += token.value.length;
1237
1303
  break;
@@ -1242,13 +1308,13 @@ function tokenize(latex) {
1242
1308
  while (newPos < latex.length && isdigit(latex[newPos])) {
1243
1309
  newPos += 1;
1244
1310
  }
1245
- token = new Token(0 /* ELEMENT */, latex.slice(pos, newPos));
1311
+ token = new TexToken(0 /* ELEMENT */, latex.slice(pos, newPos));
1246
1312
  } else if (isalpha(firstChar)) {
1247
- token = new Token(0 /* ELEMENT */, firstChar);
1313
+ token = new TexToken(0 /* ELEMENT */, firstChar);
1248
1314
  } else if ("+-*/=\'<>!.,;?()[]|".includes(firstChar)) {
1249
- token = new Token(0 /* ELEMENT */, firstChar);
1315
+ token = new TexToken(0 /* ELEMENT */, firstChar);
1250
1316
  } else {
1251
- token = new Token(7 /* UNKNOWN */, firstChar);
1317
+ token = new TexToken(7 /* UNKNOWN */, firstChar);
1252
1318
  }
1253
1319
  pos += token.value.length;
1254
1320
  }
@@ -1258,7 +1324,7 @@ function tokenize(latex) {
1258
1324
  if (pos >= latex.length || latex[pos] !== "{") {
1259
1325
  throw new LatexParserError(`No content for ${token.value} command`);
1260
1326
  }
1261
- tokens.push(new Token(6 /* CONTROL */, "{"));
1327
+ tokens.push(new TexToken(6 /* CONTROL */, "{"));
1262
1328
  const posClosingBracket = find_closing_curly_bracket_char(latex, pos);
1263
1329
  pos++;
1264
1330
  let textInside = latex.slice(pos, posClosingBracket);
@@ -1266,8 +1332,8 @@ function tokenize(latex) {
1266
1332
  for (const char of chars) {
1267
1333
  textInside = textInside.replaceAll("\\" + char, char);
1268
1334
  }
1269
- tokens.push(new Token(2 /* TEXT */, textInside));
1270
- tokens.push(new Token(6 /* CONTROL */, "}"));
1335
+ tokens.push(new TexToken(2 /* TEXT */, textInside));
1336
+ tokens.push(new TexToken(6 /* CONTROL */, "}"));
1271
1337
  pos = posClosingBracket + 1;
1272
1338
  }
1273
1339
  }
@@ -1277,10 +1343,10 @@ function passIgnoreWhitespaceBeforeScriptMark(tokens) {
1277
1343
  const is_script_mark = (token) => token.eq(SUB_SYMBOL) || token.eq(SUP_SYMBOL);
1278
1344
  let out_tokens = [];
1279
1345
  for (let i = 0;i < tokens.length; i++) {
1280
- if (tokens[i].type === 4 /* WHITESPACE */ && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) {
1346
+ if (tokens[i].type === 4 /* SPACE */ && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) {
1281
1347
  continue;
1282
1348
  }
1283
- if (tokens[i].type === 4 /* WHITESPACE */ && i - 1 >= 0 && is_script_mark(tokens[i - 1])) {
1349
+ if (tokens[i].type === 4 /* SPACE */ && i - 1 >= 0 && is_script_mark(tokens[i - 1])) {
1284
1350
  continue;
1285
1351
  }
1286
1352
  out_tokens.push(tokens[i]);
@@ -1342,10 +1408,11 @@ var BINARY_COMMANDS = [
1342
1408
  "binom",
1343
1409
  "dbinom",
1344
1410
  "dfrac",
1345
- "tbinom"
1411
+ "tbinom",
1412
+ "overset"
1346
1413
  ];
1347
1414
 
1348
- class Token {
1415
+ class TexToken {
1349
1416
  type;
1350
1417
  value;
1351
1418
  constructor(type, value) {
@@ -1356,15 +1423,15 @@ class Token {
1356
1423
  return this.type === token.type && this.value === token.value;
1357
1424
  }
1358
1425
  }
1359
- var EMPTY_NODE = { type: "empty", content: "" };
1360
- var LEFT_CURLY_BRACKET = new Token(6 /* CONTROL */, "{");
1361
- var RIGHT_CURLY_BRACKET = new Token(6 /* CONTROL */, "}");
1362
- var LEFT_SQUARE_BRACKET = new Token(0 /* ELEMENT */, "[");
1363
- var RIGHT_SQUARE_BRACKET = new Token(0 /* ELEMENT */, "]");
1364
- var LEFT_COMMAND = new Token(1 /* COMMAND */, "\\left");
1365
- var RIGHT_COMMAND = new Token(1 /* COMMAND */, "\\right");
1366
- var BEGIN_COMMAND = new Token(1 /* COMMAND */, "\\begin");
1367
- var END_COMMAND = new Token(1 /* COMMAND */, "\\end");
1426
+ var EMPTY_NODE = new TexNode("empty", "");
1427
+ var LEFT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "{");
1428
+ var RIGHT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "}");
1429
+ var LEFT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "[");
1430
+ var RIGHT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "]");
1431
+ var LEFT_COMMAND = new TexToken(1 /* COMMAND */, "\\left");
1432
+ var RIGHT_COMMAND = new TexToken(1 /* COMMAND */, "\\right");
1433
+ var BEGIN_COMMAND = new TexToken(1 /* COMMAND */, "\\begin");
1434
+ var END_COMMAND = new TexToken(1 /* COMMAND */, "\\end");
1368
1435
 
1369
1436
  class LatexParserError extends Error {
1370
1437
  constructor(message) {
@@ -1372,8 +1439,8 @@ class LatexParserError extends Error {
1372
1439
  this.name = "LatexParserError";
1373
1440
  }
1374
1441
  }
1375
- var SUB_SYMBOL = new Token(6 /* CONTROL */, "_");
1376
- var SUP_SYMBOL = new Token(6 /* CONTROL */, "^");
1442
+ var SUB_SYMBOL = new TexToken(6 /* CONTROL */, "_");
1443
+ var SUP_SYMBOL = new TexToken(6 /* CONTROL */, "^");
1377
1444
 
1378
1445
  class LatexParser {
1379
1446
  space_sensitive;
@@ -1391,11 +1458,13 @@ class LatexParser {
1391
1458
  while (pos2 < tokens.length) {
1392
1459
  const [res, newPos] = this.parseNextExpr(tokens, pos2);
1393
1460
  pos2 = newPos;
1394
- if (!this.space_sensitive && res.type === "whitespace") {
1395
- continue;
1396
- }
1397
- if (!this.newline_sensitive && res.type === "newline") {
1398
- continue;
1461
+ if (res.type === "whitespace") {
1462
+ if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
1463
+ continue;
1464
+ }
1465
+ if (!this.newline_sensitive && res.content === "\n") {
1466
+ continue;
1467
+ }
1399
1468
  }
1400
1469
  if (res.type === "control" && res.content === "&") {
1401
1470
  throw new LatexParserError("Unexpected & outside of an alignment");
@@ -1407,7 +1476,7 @@ class LatexParser {
1407
1476
  } else if (results2.length === 1) {
1408
1477
  return results2[0];
1409
1478
  } else {
1410
- return { type: "ordgroup", content: "", args: results2 };
1479
+ return new TexNode("ordgroup", "", results2);
1411
1480
  }
1412
1481
  }
1413
1482
  if (results.length === 0) {
@@ -1415,7 +1484,7 @@ class LatexParser {
1415
1484
  } else if (results.length === 1) {
1416
1485
  return results[0];
1417
1486
  } else {
1418
- return { type: "ordgroup", content: "", args: results };
1487
+ return new TexNode("ordgroup", "", results);
1419
1488
  }
1420
1489
  }
1421
1490
  parseNextExpr(tokens, start) {
@@ -1453,9 +1522,9 @@ class LatexParser {
1453
1522
  res.sub = sub;
1454
1523
  }
1455
1524
  if (num_prime > 0) {
1456
- res.sup = { type: "ordgroup", content: "", args: [] };
1525
+ res.sup = new TexNode("ordgroup", "", []);
1457
1526
  for (let i = 0;i < num_prime; i++) {
1458
- res.sup.args.push({ type: "element", content: "'" });
1527
+ res.sup.args.push(new TexNode("element", "'"));
1459
1528
  }
1460
1529
  if (sup) {
1461
1530
  res.sup.args.push(sup);
@@ -1466,7 +1535,7 @@ class LatexParser {
1466
1535
  } else if (sup) {
1467
1536
  res.sup = sup;
1468
1537
  }
1469
- return [{ type: "supsub", content: "", data: res }, pos];
1538
+ return [new TexNode("supsub", "", [], res), pos];
1470
1539
  } else {
1471
1540
  return [base, pos];
1472
1541
  }
@@ -1476,15 +1545,14 @@ class LatexParser {
1476
1545
  const tokenType = firstToken.type;
1477
1546
  switch (tokenType) {
1478
1547
  case 0 /* ELEMENT */:
1479
- return [{ type: "element", content: firstToken.value }, start + 1];
1548
+ return [new TexNode("element", firstToken.value), start + 1];
1480
1549
  case 2 /* TEXT */:
1481
- return [{ type: "text", content: firstToken.value }, start + 1];
1550
+ return [new TexNode("text", firstToken.value), start + 1];
1482
1551
  case 3 /* COMMENT */:
1483
- return [{ type: "comment", content: firstToken.value }, start + 1];
1484
- case 4 /* WHITESPACE */:
1485
- return [{ type: "whitespace", content: firstToken.value }, start + 1];
1552
+ return [new TexNode("comment", firstToken.value), start + 1];
1553
+ case 4 /* SPACE */:
1486
1554
  case 5 /* NEWLINE */:
1487
- return [{ type: "newline", content: firstToken.value }, start + 1];
1555
+ return [new TexNode("whitespace", firstToken.value), start + 1];
1488
1556
  case 1 /* COMMAND */:
1489
1557
  if (firstToken.eq(BEGIN_COMMAND)) {
1490
1558
  return this.parseBeginEndExpr(tokens, start);
@@ -1503,9 +1571,9 @@ class LatexParser {
1503
1571
  case "}":
1504
1572
  throw new LatexParserError("Unmatched '}'");
1505
1573
  case "\\\\":
1506
- return [{ type: "control", content: "\\\\" }, start + 1];
1574
+ return [new TexNode("control", "\\\\"), start + 1];
1507
1575
  case "\\,":
1508
- return [{ type: "control", content: "\\," }, start + 1];
1576
+ return [new TexNode("control", "\\,"), start + 1];
1509
1577
  case "_": {
1510
1578
  return [EMPTY_NODE, start];
1511
1579
  }
@@ -1513,7 +1581,7 @@ class LatexParser {
1513
1581
  return [EMPTY_NODE, start];
1514
1582
  }
1515
1583
  case "&":
1516
- return [{ type: "control", content: "&" }, start + 1];
1584
+ return [new TexNode("control", "&"), start + 1];
1517
1585
  default:
1518
1586
  throw new LatexParserError("Unknown control sequence");
1519
1587
  }
@@ -1532,9 +1600,9 @@ class LatexParser {
1532
1600
  switch (paramNum) {
1533
1601
  case 0:
1534
1602
  if (!symbolMap.has(command.slice(1))) {
1535
- return [{ type: "unknownMacro", content: command }, pos];
1603
+ return [new TexNode("unknownMacro", command), pos];
1536
1604
  }
1537
- return [{ type: "symbol", content: command }, pos];
1605
+ return [new TexNode("symbol", command), pos];
1538
1606
  case 1: {
1539
1607
  if (command === "\\sqrt" && pos < tokens.length && tokens[pos].eq(LEFT_SQUARE_BRACKET)) {
1540
1608
  const posLeftSquareBracket = pos;
@@ -1542,7 +1610,7 @@ class LatexParser {
1542
1610
  const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket);
1543
1611
  const exponent = this.parse(exprInside);
1544
1612
  const [arg12, newPos2] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1);
1545
- return [{ type: "unaryFunc", content: command, args: [arg12], data: exponent }, newPos2];
1613
+ return [new TexNode("unaryFunc", command, [arg12], exponent), newPos2];
1546
1614
  } else if (command === "\\text") {
1547
1615
  if (pos + 2 >= tokens.length) {
1548
1616
  throw new LatexParserError("Expecting content for \\text command");
@@ -1551,15 +1619,15 @@ class LatexParser {
1551
1619
  assert(tokens[pos + 1].type === 2 /* TEXT */);
1552
1620
  assert(tokens[pos + 2].eq(RIGHT_CURLY_BRACKET));
1553
1621
  const text = tokens[pos + 1].value;
1554
- return [{ type: "text", content: text }, pos + 3];
1622
+ return [new TexNode("text", text), pos + 3];
1555
1623
  }
1556
1624
  let [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, pos);
1557
- return [{ type: "unaryFunc", content: command, args: [arg1] }, newPos];
1625
+ return [new TexNode("unaryFunc", command, [arg1]), newPos];
1558
1626
  }
1559
1627
  case 2: {
1560
1628
  const [arg1, pos1] = this.parseNextExprWithoutSupSub(tokens, pos);
1561
1629
  const [arg2, pos2] = this.parseNextExprWithoutSupSub(tokens, pos1);
1562
- return [{ type: "binaryFunc", content: command, args: [arg1, arg2] }, pos2];
1630
+ return [new TexNode("binaryFunc", command, [arg1, arg2]), pos2];
1563
1631
  }
1564
1632
  default:
1565
1633
  throw new Error("Invalid number of parameters");
@@ -1596,11 +1664,11 @@ class LatexParser {
1596
1664
  const exprInside = tokens.slice(exprInsideStart, exprInsideEnd);
1597
1665
  const body = this.parse(exprInside);
1598
1666
  const args = [
1599
- { type: "element", content: leftDelimiter.value },
1667
+ new TexNode("element", leftDelimiter.value),
1600
1668
  body,
1601
- { type: "element", content: rightDelimiter.value }
1669
+ new TexNode("element", rightDelimiter.value)
1602
1670
  ];
1603
- const res = { type: "leftright", content: "", args };
1671
+ const res = new TexNode("leftright", "", args);
1604
1672
  return [res, pos];
1605
1673
  }
1606
1674
  parseBeginEndExpr(tokens, start) {
@@ -1627,11 +1695,11 @@ class LatexParser {
1627
1695
  }
1628
1696
  pos += 3;
1629
1697
  const exprInside = tokens.slice(exprInsideStart, exprInsideEnd);
1630
- while (exprInside.length > 0 && [4 /* WHITESPACE */, 5 /* NEWLINE */].includes(exprInside[exprInside.length - 1].type)) {
1698
+ while (exprInside.length > 0 && [4 /* SPACE */, 5 /* NEWLINE */].includes(exprInside[exprInside.length - 1].type)) {
1631
1699
  exprInside.pop();
1632
1700
  }
1633
1701
  const body = this.parseAligned(exprInside);
1634
- const res = { type: "beginend", content: envName, data: body };
1702
+ const res = new TexNode("beginend", envName, [], body);
1635
1703
  return [res, pos];
1636
1704
  }
1637
1705
  parseAligned(tokens) {
@@ -1639,22 +1707,26 @@ class LatexParser {
1639
1707
  const allRows = [];
1640
1708
  let row = [];
1641
1709
  allRows.push(row);
1642
- let group = { type: "ordgroup", content: "", args: [] };
1710
+ let group = new TexNode("ordgroup", "", []);
1643
1711
  row.push(group);
1644
1712
  while (pos < tokens.length) {
1645
1713
  const [res, newPos] = this.parseNextExpr(tokens, pos);
1646
1714
  pos = newPos;
1647
1715
  if (res.type === "whitespace") {
1648
- continue;
1649
- } else if (res.type === "newline" && !this.newline_sensitive) {
1650
- continue;
1651
- } else if (res.type === "control" && res.content === "\\\\") {
1716
+ if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
1717
+ continue;
1718
+ }
1719
+ if (!this.newline_sensitive && res.content === "\n") {
1720
+ continue;
1721
+ }
1722
+ }
1723
+ if (res.type === "control" && res.content === "\\\\") {
1652
1724
  row = [];
1653
- group = { type: "ordgroup", content: "", args: [] };
1725
+ group = new TexNode("ordgroup", "", []);
1654
1726
  row.push(group);
1655
1727
  allRows.push(row);
1656
1728
  } else if (res.type === "control" && res.content === "&") {
1657
- group = { type: "ordgroup", content: "", args: [] };
1729
+ group = new TexNode("ordgroup", "", []);
1658
1730
  row.push(group);
1659
1731
  } else {
1660
1732
  group.args.push(res);
@@ -1668,45 +1740,62 @@ class LatexParser {
1668
1740
  function is_delimiter(c) {
1669
1741
  return c.type === "atom" && ["(", ")", "[", "]", "{", "}", "|", "\u230A", "\u230B", "\u2308", "\u2309"].includes(c.content);
1670
1742
  }
1743
+ function convert_overset(node) {
1744
+ const [sup, base] = node.args;
1745
+ const is_def = (n) => {
1746
+ if (n.eq_shallow(new TexNode("text", "def"))) {
1747
+ return true;
1748
+ }
1749
+ if (n.type === "ordgroup" && n.args.length === 3) {
1750
+ const [a1, a2, a3] = n.args;
1751
+ const d = new TexNode("element", "d");
1752
+ const e = new TexNode("element", "e");
1753
+ const f = new TexNode("element", "f");
1754
+ if (a1.eq_shallow(d) && a2.eq_shallow(e) && a3.eq_shallow(f)) {
1755
+ return true;
1756
+ }
1757
+ }
1758
+ return false;
1759
+ };
1760
+ const is_eq = (n) => n.eq_shallow(new TexNode("element", "="));
1761
+ if (is_def(sup) && is_eq(base)) {
1762
+ return new TypstNode("symbol", "eq.def");
1763
+ }
1764
+ const op_call = new TypstNode("unaryFunc", "op", [convertTree(base)]);
1765
+ op_call.setOptions({ limits: "#true" });
1766
+ return new TypstNode("supsub", "", [], {
1767
+ base: op_call,
1768
+ sup: convertTree(sup)
1769
+ });
1770
+ }
1671
1771
  function convertTree(node) {
1672
1772
  switch (node.type) {
1673
1773
  case "empty":
1774
+ return new TypstNode("empty", "");
1674
1775
  case "whitespace":
1675
- return { type: "empty", content: "" };
1776
+ return new TypstNode("whitespace", node.content);
1676
1777
  case "ordgroup":
1677
- return {
1678
- type: "group",
1679
- content: "",
1680
- args: node.args.map(convertTree)
1681
- };
1778
+ return new TypstNode("group", "", node.args.map(convertTree));
1682
1779
  case "element":
1683
- return { type: "atom", content: convertToken(node.content) };
1780
+ return new TypstNode("atom", convertToken(node.content));
1684
1781
  case "symbol":
1685
- return { type: "symbol", content: convertToken(node.content) };
1782
+ return new TypstNode("symbol", convertToken(node.content));
1686
1783
  case "text":
1687
- return { type: "text", content: node.content };
1784
+ return new TypstNode("text", node.content);
1688
1785
  case "comment":
1689
- return { type: "comment", content: node.content };
1786
+ return new TypstNode("comment", node.content);
1690
1787
  case "supsub": {
1691
1788
  let { base, sup, sub } = node.data;
1692
1789
  if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) {
1693
- return {
1694
- type: "binaryFunc",
1695
- content: "overbrace",
1696
- args: [convertTree(base.args[0]), convertTree(sup)]
1697
- };
1790
+ return new TypstNode("binaryFunc", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
1698
1791
  } else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) {
1699
- return {
1700
- type: "binaryFunc",
1701
- content: "underbrace",
1702
- args: [convertTree(base.args[0]), convertTree(sub)]
1703
- };
1792
+ return new TypstNode("binaryFunc", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
1704
1793
  }
1705
1794
  const data = {
1706
1795
  base: convertTree(base)
1707
1796
  };
1708
1797
  if (data.base.type === "empty") {
1709
- data.base = { type: "text", content: "" };
1798
+ data.base = new TypstNode("text", "");
1710
1799
  }
1711
1800
  if (sup) {
1712
1801
  data.sup = convertTree(sup);
@@ -1714,19 +1803,11 @@ function convertTree(node) {
1714
1803
  if (sub) {
1715
1804
  data.sub = convertTree(sub);
1716
1805
  }
1717
- return {
1718
- type: "supsub",
1719
- content: "",
1720
- data
1721
- };
1806
+ return new TypstNode("supsub", "", [], data);
1722
1807
  }
1723
1808
  case "leftright": {
1724
1809
  const [left, body, right] = node.args;
1725
- const group = {
1726
- type: "group",
1727
- content: "",
1728
- args: node.args.map(convertTree)
1729
- };
1810
+ const group = new TypstNode("group", "", node.args.map(convertTree));
1730
1811
  if ([
1731
1812
  "[]",
1732
1813
  "()",
@@ -1737,46 +1818,26 @@ function convertTree(node) {
1737
1818
  ].includes(left.content + right.content)) {
1738
1819
  return group;
1739
1820
  }
1740
- return {
1741
- type: "unaryFunc",
1742
- content: "lr",
1743
- args: [group]
1744
- };
1821
+ return new TypstNode("unaryFunc", "lr", [group]);
1745
1822
  }
1746
1823
  case "binaryFunc": {
1747
- return {
1748
- type: "binaryFunc",
1749
- content: convertToken(node.content),
1750
- args: node.args.map(convertTree)
1751
- };
1824
+ if (node.content === "\\overset") {
1825
+ return convert_overset(node);
1826
+ }
1827
+ return new TypstNode("binaryFunc", convertToken(node.content), node.args.map(convertTree));
1752
1828
  }
1753
1829
  case "unaryFunc": {
1754
1830
  const arg0 = convertTree(node.args[0]);
1755
1831
  if (node.content === "\\sqrt" && node.data) {
1756
1832
  const data = convertTree(node.data);
1757
- return {
1758
- type: "binaryFunc",
1759
- content: "root",
1760
- args: [data, arg0]
1761
- };
1833
+ return new TypstNode("binaryFunc", "root", [data, arg0]);
1762
1834
  }
1763
1835
  if (node.content === "\\mathbf") {
1764
- const inner = {
1765
- type: "unaryFunc",
1766
- content: "bold",
1767
- args: [arg0]
1768
- };
1769
- return {
1770
- type: "unaryFunc",
1771
- content: "upright",
1772
- args: [inner]
1773
- };
1836
+ const inner = new TypstNode("unaryFunc", "bold", [arg0]);
1837
+ return new TypstNode("unaryFunc", "upright", [inner]);
1774
1838
  }
1775
1839
  if (node.content === "\\mathbb" && arg0.type === "atom" && /^[A-Z]$/.test(arg0.content)) {
1776
- return {
1777
- type: "symbol",
1778
- content: arg0.content + arg0.content
1779
- };
1840
+ return new TypstNode("symbol", arg0.content + arg0.content);
1780
1841
  }
1781
1842
  if (node.content === "\\operatorname") {
1782
1843
  const body = node.args;
@@ -1785,50 +1846,31 @@ function convertTree(node) {
1785
1846
  }
1786
1847
  const text = body[0].content;
1787
1848
  if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
1788
- return {
1789
- type: "symbol",
1790
- content: text
1791
- };
1849
+ return new TypstNode("symbol", text);
1792
1850
  } else {
1793
- return {
1794
- type: "unaryFunc",
1795
- content: "op",
1796
- args: [{ type: "text", content: text }]
1797
- };
1851
+ return new TypstNode("unaryFunc", "op", [new TypstNode("text", text)]);
1798
1852
  }
1799
1853
  }
1800
- return {
1801
- type: "unaryFunc",
1802
- content: convertToken(node.content),
1803
- args: node.args.map(convertTree)
1804
- };
1854
+ return new TypstNode("unaryFunc", convertToken(node.content), node.args.map(convertTree));
1805
1855
  }
1806
- case "newline":
1807
- return { type: "newline", content: "\n" };
1808
1856
  case "beginend": {
1809
1857
  const matrix = node.data;
1810
1858
  const data = matrix.map((row) => row.map(convertTree));
1811
1859
  if (node.content.startsWith("align")) {
1812
- return {
1813
- type: "align",
1814
- content: "",
1815
- data
1816
- };
1860
+ return new TypstNode("align", "", [], data);
1817
1861
  } else {
1818
- return {
1819
- type: "matrix",
1820
- content: "mat",
1821
- data
1822
- };
1862
+ const res = new TypstNode("matrix", "", [], data);
1863
+ res.setOptions({ delim: "#none" });
1864
+ return res;
1823
1865
  }
1824
1866
  }
1825
1867
  case "unknownMacro":
1826
- return { type: "unknown", content: convertToken(node.content) };
1868
+ return new TypstNode("unknown", convertToken(node.content));
1827
1869
  case "control":
1828
1870
  if (node.content === "\\\\") {
1829
- return { type: "symbol", content: "\\" };
1871
+ return new TypstNode("symbol", "\\");
1830
1872
  } else if (node.content === "\\,") {
1831
- return { type: "symbol", content: "thin" };
1873
+ return new TypstNode("symbol", "thin");
1832
1874
  } else {
1833
1875
  throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
1834
1876
  }
@@ -1839,10 +1881,14 @@ function convertTree(node) {
1839
1881
  function convertToken(token) {
1840
1882
  if (/^[a-zA-Z0-9]$/.test(token)) {
1841
1883
  return token;
1884
+ } else if (token === "/") {
1885
+ return "\\/";
1886
+ } else if (token === "\\|") {
1887
+ return "parallel";
1888
+ } else if (token === "\\colon") {
1889
+ return ":";
1842
1890
  } else if (token === "\\\\") {
1843
1891
  return "\\";
1844
- } else if (token == "/") {
1845
- return "\\/";
1846
1892
  } else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
1847
1893
  return token;
1848
1894
  } else if (token.startsWith("\\")) {
@@ -1864,6 +1910,10 @@ var TYPST_INTRINSIC_SYMBOLS = [
1864
1910
  "sech",
1865
1911
  "csch"
1866
1912
  ];
1913
+ var TYPST_LEFT_PARENTHESIS = new TypstToken(1 /* ATOM */, "(");
1914
+ var TYPST_RIGHT_PARENTHESIS = new TypstToken(1 /* ATOM */, ")");
1915
+ var TYPST_COMMA = new TypstToken(1 /* ATOM */, ",");
1916
+ var TYPST_NEWLINE = new TypstToken(0 /* SYMBOL */, "\n");
1867
1917
 
1868
1918
  class TypstWriterError extends Error {
1869
1919
  node;
@@ -1877,57 +1927,72 @@ class TypstWriterError extends Error {
1877
1927
  class TypstWriter {
1878
1928
  nonStrict;
1879
1929
  preferTypstIntrinsic;
1930
+ keepSpaces;
1880
1931
  buffer = "";
1881
1932
  queue = [];
1882
- needSpaceAfterSingleItemScript = false;
1883
1933
  insideFunctionDepth = 0;
1884
- constructor(nonStrict, preferTypstIntrinsic) {
1934
+ constructor(nonStrict, preferTypstIntrinsic, keepSpaces) {
1885
1935
  this.nonStrict = nonStrict;
1886
1936
  this.preferTypstIntrinsic = preferTypstIntrinsic;
1937
+ this.keepSpaces = keepSpaces;
1887
1938
  }
1888
- writeBuffer(str) {
1889
- if (this.needSpaceAfterSingleItemScript && /^[0-9a-zA-Z\(]/.test(str)) {
1890
- this.buffer += " ";
1891
- } else {
1892
- let no_need_space = false;
1893
- no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
1894
- no_need_space ||= /^[}()_^,;!\|]$/.test(str);
1895
- no_need_space ||= str === "'";
1896
- no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
1897
- no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
1898
- no_need_space ||= str.startsWith("\n");
1899
- no_need_space ||= this.buffer === "";
1900
- no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
1901
- if (!no_need_space) {
1902
- this.buffer += " ";
1903
- }
1939
+ writeBuffer(token) {
1940
+ const str = token.content;
1941
+ if (str === "") {
1942
+ return;
1904
1943
  }
1905
- if (this.needSpaceAfterSingleItemScript) {
1906
- this.needSpaceAfterSingleItemScript = false;
1944
+ let no_need_space = false;
1945
+ no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
1946
+ no_need_space ||= /^[}()_^,;!\|]$/.test(str);
1947
+ no_need_space ||= str === "'";
1948
+ no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
1949
+ no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
1950
+ no_need_space ||= str.startsWith("\n");
1951
+ no_need_space ||= this.buffer === "";
1952
+ no_need_space ||= /^\s/.test(str);
1953
+ no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
1954
+ if (!no_need_space) {
1955
+ this.buffer += " ";
1907
1956
  }
1908
1957
  this.buffer += str;
1909
1958
  }
1910
- append(node) {
1959
+ serialize(node) {
1911
1960
  switch (node.type) {
1912
1961
  case "empty":
1913
1962
  break;
1914
1963
  case "atom": {
1915
1964
  if (node.content === "," && this.insideFunctionDepth > 0) {
1916
- this.queue.push({ type: "symbol", content: "comma" });
1965
+ this.queue.push(new TypstToken(0 /* SYMBOL */, "comma"));
1917
1966
  } else {
1918
- this.queue.push({ type: "atom", content: node.content });
1967
+ this.queue.push(new TypstToken(1 /* ATOM */, node.content));
1919
1968
  }
1920
1969
  break;
1921
1970
  }
1922
1971
  case "symbol":
1972
+ this.queue.push(new TypstToken(0 /* SYMBOL */, node.content));
1973
+ break;
1923
1974
  case "text":
1975
+ this.queue.push(new TypstToken(2 /* TEXT */, `"${node.content}"`));
1976
+ break;
1924
1977
  case "comment":
1925
- case "newline":
1926
- this.queue.push(node);
1978
+ this.queue.push(new TypstToken(3 /* COMMENT */, `//${node.content}`));
1979
+ break;
1980
+ case "whitespace":
1981
+ for (const c of node.content) {
1982
+ if (c === " ") {
1983
+ if (this.keepSpaces) {
1984
+ this.queue.push(new TypstToken(4 /* SPACE */, c));
1985
+ }
1986
+ } else if (c === "\n") {
1987
+ this.queue.push(new TypstToken(0 /* SYMBOL */, c));
1988
+ } else {
1989
+ throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
1990
+ }
1991
+ }
1927
1992
  break;
1928
1993
  case "group":
1929
1994
  for (const item of node.args) {
1930
- this.append(item);
1995
+ this.serialize(item);
1931
1996
  }
1932
1997
  break;
1933
1998
  case "supsub": {
@@ -1936,43 +2001,48 @@ class TypstWriter {
1936
2001
  let trailing_space_needed = false;
1937
2002
  const has_prime = sup && sup.type === "atom" && sup.content === "\'";
1938
2003
  if (has_prime) {
1939
- this.queue.push({ type: "atom", content: "\'" });
2004
+ this.queue.push(new TypstToken(1 /* ATOM */, "\'"));
1940
2005
  trailing_space_needed = false;
1941
2006
  }
1942
2007
  if (sub) {
1943
- this.queue.push({ type: "atom", content: "_" });
2008
+ this.queue.push(new TypstToken(1 /* ATOM */, "_"));
1944
2009
  trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
1945
2010
  }
1946
2011
  if (sup && !has_prime) {
1947
- this.queue.push({ type: "atom", content: "^" });
2012
+ this.queue.push(new TypstToken(1 /* ATOM */, "^"));
1948
2013
  trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
1949
2014
  }
1950
2015
  if (trailing_space_needed) {
1951
- this.queue.push({ type: "softSpace", content: "" });
2016
+ this.queue.push(new TypstToken(6 /* CONTROL */, " "));
1952
2017
  }
1953
2018
  break;
1954
2019
  }
1955
2020
  case "binaryFunc": {
1956
- const func_symbol = { type: "symbol", content: node.content };
2021
+ const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
1957
2022
  const [arg0, arg1] = node.args;
1958
2023
  this.queue.push(func_symbol);
1959
2024
  this.insideFunctionDepth++;
1960
- this.queue.push({ type: "atom", content: "(" });
1961
- this.append(arg0);
1962
- this.queue.push({ type: "atom", content: "," });
1963
- this.append(arg1);
1964
- this.queue.push({ type: "atom", content: ")" });
2025
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2026
+ this.serialize(arg0);
2027
+ this.queue.push(new TypstToken(1 /* ATOM */, ","));
2028
+ this.serialize(arg1);
2029
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
1965
2030
  this.insideFunctionDepth--;
1966
2031
  break;
1967
2032
  }
1968
2033
  case "unaryFunc": {
1969
- const func_symbol = { type: "symbol", content: node.content };
2034
+ const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
1970
2035
  const arg0 = node.args[0];
1971
2036
  this.queue.push(func_symbol);
1972
2037
  this.insideFunctionDepth++;
1973
- this.queue.push({ type: "atom", content: "(" });
1974
- this.append(arg0);
1975
- this.queue.push({ type: "atom", content: ")" });
2038
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2039
+ this.serialize(arg0);
2040
+ if (node.options) {
2041
+ for (const [key, value] of Object.entries(node.options)) {
2042
+ this.queue.push(new TypstToken(0 /* SYMBOL */, `, ${key}: ${value}`));
2043
+ }
2044
+ }
2045
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
1976
2046
  this.insideFunctionDepth--;
1977
2047
  break;
1978
2048
  }
@@ -1981,41 +2051,45 @@ class TypstWriter {
1981
2051
  matrix.forEach((row, i) => {
1982
2052
  row.forEach((cell, j) => {
1983
2053
  if (j > 0) {
1984
- this.queue.push({ type: "atom", content: "&" });
2054
+ this.queue.push(new TypstToken(1 /* ATOM */, "&"));
1985
2055
  }
1986
- this.append(cell);
2056
+ this.serialize(cell);
1987
2057
  });
1988
2058
  if (i < matrix.length - 1) {
1989
- this.queue.push({ type: "symbol", content: "\\" });
2059
+ this.queue.push(new TypstToken(0 /* SYMBOL */, "\\"));
1990
2060
  }
1991
2061
  });
1992
2062
  break;
1993
2063
  }
1994
2064
  case "matrix": {
1995
2065
  const matrix = node.data;
1996
- this.queue.push({ type: "symbol", content: "mat" });
2066
+ this.queue.push(new TypstToken(0 /* SYMBOL */, "mat"));
1997
2067
  this.insideFunctionDepth++;
1998
- this.queue.push({ type: "atom", content: "(" });
1999
- this.queue.push({ type: "symbol", content: "delim: #none, " });
2068
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2069
+ if (node.options) {
2070
+ for (const [key, value] of Object.entries(node.options)) {
2071
+ this.queue.push(new TypstToken(0 /* SYMBOL */, `${key}: ${value}, `));
2072
+ }
2073
+ }
2000
2074
  matrix.forEach((row, i) => {
2001
2075
  row.forEach((cell, j) => {
2002
- this.append(cell);
2076
+ this.serialize(cell);
2003
2077
  if (j < row.length - 1) {
2004
- this.queue.push({ type: "atom", content: "," });
2078
+ this.queue.push(new TypstToken(1 /* ATOM */, ","));
2005
2079
  } else {
2006
2080
  if (i < matrix.length - 1) {
2007
- this.queue.push({ type: "atom", content: ";" });
2081
+ this.queue.push(new TypstToken(1 /* ATOM */, ";"));
2008
2082
  }
2009
2083
  }
2010
2084
  });
2011
2085
  });
2012
- this.queue.push({ type: "atom", content: ")" });
2086
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
2013
2087
  this.insideFunctionDepth--;
2014
2088
  break;
2015
2089
  }
2016
2090
  case "unknown": {
2017
2091
  if (this.nonStrict) {
2018
- this.queue.push({ type: "symbol", content: node.content });
2092
+ this.queue.push(new TypstToken(0 /* SYMBOL */, node.content));
2019
2093
  } else {
2020
2094
  throw new TypstWriterError(`Unknown macro: ${node.content}`, node);
2021
2095
  }
@@ -2035,41 +2109,28 @@ class TypstWriter {
2035
2109
  }
2036
2110
  }
2037
2111
  if (need_to_wrap) {
2038
- this.queue.push({ type: "atom", content: "(" });
2039
- this.append(node);
2040
- this.queue.push({ type: "atom", content: ")" });
2112
+ this.queue.push(TYPST_LEFT_PARENTHESIS);
2113
+ this.serialize(node);
2114
+ this.queue.push(TYPST_RIGHT_PARENTHESIS);
2041
2115
  } else {
2042
- this.append(node);
2116
+ this.serialize(node);
2043
2117
  }
2044
2118
  return !need_to_wrap;
2045
2119
  }
2046
2120
  flushQueue() {
2047
- this.queue.forEach((node) => {
2048
- let str = "";
2049
- switch (node.type) {
2050
- case "atom":
2051
- case "symbol":
2052
- str = node.content;
2053
- break;
2054
- case "text":
2055
- str = `"${node.content}"`;
2056
- break;
2057
- case "softSpace":
2058
- this.needSpaceAfterSingleItemScript = true;
2059
- str = "";
2060
- break;
2061
- case "comment":
2062
- str = `//${node.content}`;
2063
- break;
2064
- case "newline":
2065
- str = "\n";
2066
- break;
2067
- default:
2068
- throw new TypstWriterError(`Unexpected node type to stringify: ${node.type}`, node);
2069
- }
2070
- if (str !== "") {
2071
- this.writeBuffer(str);
2121
+ const SOFT_SPACE = new TypstToken(6 /* CONTROL */, " ");
2122
+ for (let i = 0;i < this.queue.length; i++) {
2123
+ let token = this.queue[i];
2124
+ if (token.eq(SOFT_SPACE)) {
2125
+ if (i === this.queue.length - 1) {
2126
+ this.queue[i].content = "";
2127
+ } else if (this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE])) {
2128
+ this.queue[i].content = "";
2129
+ }
2072
2130
  }
2131
+ }
2132
+ this.queue.forEach((token) => {
2133
+ this.writeBuffer(token);
2073
2134
  });
2074
2135
  this.queue = [];
2075
2136
  }
@@ -2103,6 +2164,7 @@ function tex2typst(tex, options) {
2103
2164
  const opt = {
2104
2165
  nonStrict: true,
2105
2166
  preferTypstIntrinsic: true,
2167
+ keepSpaces: false,
2106
2168
  customTexMacros: {}
2107
2169
  };
2108
2170
  if (options) {
@@ -2118,8 +2180,8 @@ function tex2typst(tex, options) {
2118
2180
  }
2119
2181
  const texTree = parseTex(tex, opt.customTexMacros);
2120
2182
  const typstTree = convertTree(texTree);
2121
- const writer2 = new TypstWriter(opt.nonStrict, opt.preferTypstIntrinsic);
2122
- writer2.append(typstTree);
2183
+ const writer2 = new TypstWriter(opt.nonStrict, opt.preferTypstIntrinsic, opt.keepSpaces);
2184
+ writer2.serialize(typstTree);
2123
2185
  return writer2.finalize();
2124
2186
  }
2125
2187
  export {