tex2typst 0.2.15 → 0.3.0-alpha

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
@@ -7,7 +7,6 @@ var symbolMap = new Map([
7
7
  ["ddot", "dot.double"],
8
8
  ["doteq", "dot(eq)"],
9
9
  ["dots", "dots.h"],
10
- ["ldots", "dots.h"],
11
10
  ["vdots", "dots.v"],
12
11
  ["ddots", "dots.down"],
13
12
  ["widehat", "hat"],
@@ -23,6 +22,7 @@ var symbolMap = new Map([
23
22
  ["tbinom", "binom"],
24
23
  ["dfrac", "frac"],
25
24
  ["tfrac", "frac"],
25
+ ["operatorname", "op"],
26
26
  ["boldsymbol", "bold"],
27
27
  ["mathbb", "bb"],
28
28
  ["mathbf", "bold"],
@@ -160,7 +160,7 @@ var symbolMap = new Map([
160
160
  ["langle", "angle.l"],
161
161
  ["lbrace", "brace.l"],
162
162
  ["lbrack", "bracket.l"],
163
- ["ldots", "dots.l"],
163
+ ["ldots", "dots.h"],
164
164
  ["le", "lt.eq"],
165
165
  ["leadsto", "arrow.squiggly"],
166
166
  ["leftarrow", "arrow.l"],
@@ -937,62 +937,6 @@ var map_from_official_docs = new Map([
937
937
  ["barV", "tack.b.double"],
938
938
  ["shortdowntack", "tack.b.short"],
939
939
  ["dashVdash", "tack.l.r"],
940
- ["mupalpha", "alpha"],
941
- ["mupbeta", "beta"],
942
- ["mupchi", "chi"],
943
- ["mupdelta", "delta"],
944
- ["mupvarepsilon", "epsilon"],
945
- ["mupepsilon", "epsilon.alt"],
946
- ["mupeta", "eta"],
947
- ["mupgamma", "gamma"],
948
- ["mupiota", "iota"],
949
- ["mupkappa", "kappa"],
950
- ["mupvarkappa", "kappa.alt"],
951
- ["muplambda", "lambda"],
952
- ["mupmu", "mu"],
953
- ["mupnu", "nu"],
954
- ["mho", "ohm.inv"],
955
- ["mupomega", "omega"],
956
- ["mupomicron", "omicron"],
957
- ["mupvarphi", "phi"],
958
- ["mupphi", "phi.alt"],
959
- ["muppi", "pi"],
960
- ["mupvarpi", "pi.alt"],
961
- ["muppsi", "psi"],
962
- ["muprho", "rho"],
963
- ["mupvarrho", "rho.alt"],
964
- ["mupsigma", "sigma"],
965
- ["mupvarsigma", "sigma.alt"],
966
- ["muptau", "tau"],
967
- ["muptheta", "theta"],
968
- ["mupvartheta", "theta.alt"],
969
- ["mupupsilon", "upsilon"],
970
- ["mupxi", "xi"],
971
- ["mupzeta", "zeta"],
972
- ["mupAlpha", "Alpha"],
973
- ["mupBeta", "Beta"],
974
- ["mupChi", "Chi"],
975
- ["mupDelta", "Delta"],
976
- ["mupEpsilon", "Epsilon"],
977
- ["mupEta", "Eta"],
978
- ["mupGamma", "Gamma"],
979
- ["mupIota", "Iota"],
980
- ["mupKappa", "Kappa"],
981
- ["mupLambda", "Lambda"],
982
- ["mupMu", "Mu"],
983
- ["mupNu", "Nu"],
984
- ["mupOmega", "Omega"],
985
- ["mupOmicron", "Omicron"],
986
- ["mupPhi", "Phi"],
987
- ["mupPi", "Pi"],
988
- ["mupPsi", "Psi"],
989
- ["mupRho", "Rho"],
990
- ["mupSigma", "Sigma"],
991
- ["mupTau", "Tau"],
992
- ["mupTheta", "Theta"],
993
- ["mupUpsilon", "Upsilon"],
994
- ["mupXi", "Xi"],
995
- ["mupZeta", "Zeta"],
996
940
  ["BbbA", "AA"],
997
941
  ["BbbB", "BB"],
998
942
  ["BbbC", "CC"],
@@ -1033,8 +977,84 @@ for (const [key, value] of map_from_official_docs) {
1033
977
  symbolMap.set(key, value);
1034
978
  }
1035
979
  }
980
+ var reverseSymbolMap = new Map;
981
+ for (const [key, value] of Array.from(symbolMap.entries()).reverse()) {
982
+ reverseSymbolMap.set(value, key);
983
+ }
984
+ reverseSymbolMap.set("dif", "mathrm{d}");
985
+ var typst_to_tex_map = new Map([
986
+ ["top", "top"],
987
+ ["frac", "frac"],
988
+ ["tilde", "tilde"],
989
+ ["hat", "hat"],
990
+ ["upright", "mathrm"],
991
+ ["bold", "boldsymbol"]
992
+ ]);
993
+ for (const [key, value] of typst_to_tex_map) {
994
+ reverseSymbolMap.set(key, value);
995
+ }
996
+
997
+ // src/generic.ts
998
+ function array_find(array, item, start = 0) {
999
+ for (let i = start;i < array.length; i++) {
1000
+ if (array[i].eq(item)) {
1001
+ return i;
1002
+ }
1003
+ }
1004
+ return -1;
1005
+ }
1006
+ function array_includes(array, item) {
1007
+ for (const i of array) {
1008
+ if (i.eq(item)) {
1009
+ return true;
1010
+ }
1011
+ }
1012
+ return false;
1013
+ }
1014
+ function array_split(array, sep) {
1015
+ const res = [];
1016
+ let current_slice = [];
1017
+ for (const i of array) {
1018
+ if (i.eq(sep)) {
1019
+ res.push(current_slice);
1020
+ current_slice = [];
1021
+ } else {
1022
+ current_slice.push(i);
1023
+ }
1024
+ }
1025
+ res.push(current_slice);
1026
+ return res;
1027
+ }
1036
1028
 
1037
1029
  // src/types.ts
1030
+ function apply_escape_if_needed(c) {
1031
+ if (["{", "}", "%"].includes(c)) {
1032
+ return "\\" + c;
1033
+ }
1034
+ return c;
1035
+ }
1036
+ class TexToken {
1037
+ type;
1038
+ value;
1039
+ constructor(type, value) {
1040
+ this.type = type;
1041
+ this.value = value;
1042
+ }
1043
+ eq(token) {
1044
+ return this.type === token.type && this.value === token.value;
1045
+ }
1046
+ toString() {
1047
+ switch (this.type) {
1048
+ case 2 /* TEXT */:
1049
+ return `\\text{${this.value}}`;
1050
+ case 3 /* COMMENT */:
1051
+ return `%${this.value}`;
1052
+ default:
1053
+ return this.value;
1054
+ }
1055
+ }
1056
+ }
1057
+
1038
1058
  class TexNode {
1039
1059
  type;
1040
1060
  content;
@@ -1046,7 +1066,7 @@ class TexNode {
1046
1066
  this.args = args;
1047
1067
  this.data = data;
1048
1068
  }
1049
- eq_shallow(other) {
1069
+ eq(other) {
1050
1070
  return this.type === other.type && this.content === other.content;
1051
1071
  }
1052
1072
  toString() {
@@ -1057,26 +1077,172 @@ class TexNode {
1057
1077
  throw new Error(`toString() is not implemented for type ${this.type}`);
1058
1078
  }
1059
1079
  }
1080
+ serialize() {
1081
+ switch (this.type) {
1082
+ case "empty":
1083
+ return [];
1084
+ case "element": {
1085
+ let c = this.content;
1086
+ c = apply_escape_if_needed(c);
1087
+ return [new TexToken(0 /* ELEMENT */, c)];
1088
+ }
1089
+ case "symbol":
1090
+ return [new TexToken(1 /* COMMAND */, this.content)];
1091
+ case "text":
1092
+ return [new TexToken(2 /* TEXT */, this.content)];
1093
+ case "comment":
1094
+ return [new TexToken(3 /* COMMENT */, this.content)];
1095
+ case "whitespace": {
1096
+ const tokens = [];
1097
+ for (const c of this.content) {
1098
+ const token_type = c === " " ? 4 /* SPACE */ : 5 /* NEWLINE */;
1099
+ tokens.push(new TexToken(token_type, c));
1100
+ }
1101
+ return tokens;
1102
+ }
1103
+ case "ordgroup":
1104
+ return this.args.map((n) => n.serialize()).flat();
1105
+ case "unaryFunc": {
1106
+ let tokens = [];
1107
+ tokens.push(new TexToken(1 /* COMMAND */, this.content));
1108
+ if (this.content === "\\sqrt" && this.data) {
1109
+ tokens.push(new TexToken(0 /* ELEMENT */, "["));
1110
+ tokens = tokens.concat(this.data.serialize());
1111
+ tokens.push(new TexToken(0 /* ELEMENT */, "]"));
1112
+ }
1113
+ if (this.content === "\\operatorname" && this.args.length === 1 && this.args[0].type === "text") {
1114
+ const text = this.args[0].content;
1115
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1116
+ tokens.push(new TexToken(1 /* COMMAND */, text));
1117
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1118
+ return tokens;
1119
+ }
1120
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1121
+ tokens = tokens.concat(this.args[0].serialize());
1122
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1123
+ return tokens;
1124
+ }
1125
+ case "binaryFunc": {
1126
+ let tokens = [];
1127
+ tokens.push(new TexToken(1 /* COMMAND */, this.content));
1128
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1129
+ tokens = tokens.concat(this.args[0].serialize());
1130
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1131
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1132
+ tokens = tokens.concat(this.args[1].serialize());
1133
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1134
+ return tokens;
1135
+ }
1136
+ case "supsub": {
1137
+ let tokens = [];
1138
+ const { base, sup, sub } = this.data;
1139
+ tokens = tokens.concat(base.serialize());
1140
+ if (sub) {
1141
+ tokens.push(new TexToken(6 /* CONTROL */, "_"));
1142
+ if (sub.type === "ordgroup" || sub.type === "supsub" || sub.type === "empty") {
1143
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1144
+ tokens = tokens.concat(sub.serialize());
1145
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1146
+ } else {
1147
+ tokens = tokens.concat(sub.serialize());
1148
+ }
1149
+ }
1150
+ if (sup) {
1151
+ tokens.push(new TexToken(6 /* CONTROL */, "^"));
1152
+ if (sup.type === "ordgroup" || sup.type === "supsub" || sup.type === "empty") {
1153
+ tokens.push(new TexToken(0 /* ELEMENT */, "{"));
1154
+ tokens = tokens.concat(sup.serialize());
1155
+ tokens.push(new TexToken(0 /* ELEMENT */, "}"));
1156
+ } else {
1157
+ tokens = tokens.concat(sup.serialize());
1158
+ }
1159
+ }
1160
+ return tokens;
1161
+ }
1162
+ case "control": {
1163
+ return [new TexToken(6 /* CONTROL */, this.content)];
1164
+ }
1165
+ case "beginend": {
1166
+ let tokens = [];
1167
+ const matrix = this.data;
1168
+ tokens.push(new TexToken(1 /* COMMAND */, `\\begin{${this.content}}`));
1169
+ tokens.push(new TexToken(5 /* NEWLINE */, "\n"));
1170
+ for (let i = 0;i < matrix.length; i++) {
1171
+ const row = matrix[i];
1172
+ for (let j = 0;j < row.length; j++) {
1173
+ const cell = row[j];
1174
+ tokens = tokens.concat(cell.serialize());
1175
+ if (j !== row.length - 1) {
1176
+ tokens.push(new TexToken(6 /* CONTROL */, "&"));
1177
+ }
1178
+ }
1179
+ if (i !== matrix.length - 1) {
1180
+ tokens.push(new TexToken(6 /* CONTROL */, "\\\\"));
1181
+ }
1182
+ }
1183
+ tokens.push(new TexToken(5 /* NEWLINE */, "\n"));
1184
+ tokens.push(new TexToken(1 /* COMMAND */, `\\end{${this.content}}`));
1185
+ return tokens;
1186
+ }
1187
+ default:
1188
+ throw new Error("[TexNode.serialize] Unimplemented type: " + this.type);
1189
+ }
1190
+ }
1060
1191
  }
1061
1192
  class TypstToken {
1062
1193
  type;
1063
- content;
1194
+ value;
1064
1195
  constructor(type, content) {
1065
1196
  this.type = type;
1066
- this.content = content;
1197
+ this.value = content;
1067
1198
  }
1068
1199
  eq(other) {
1069
- return this.type === other.type && this.content === other.content;
1200
+ return this.type === other.type && this.value === other.value;
1070
1201
  }
1071
1202
  isOneOf(tokens) {
1072
- let found = false;
1073
- for (const token of tokens) {
1074
- if (this.eq(token)) {
1075
- found = true;
1076
- break;
1203
+ return array_includes(tokens, this);
1204
+ }
1205
+ toNode() {
1206
+ switch (this.type) {
1207
+ case 2 /* TEXT */:
1208
+ return new TypstNode("text", this.value);
1209
+ case 3 /* COMMENT */:
1210
+ return new TypstNode("comment", this.value);
1211
+ case 4 /* SPACE */:
1212
+ case 7 /* NEWLINE */:
1213
+ return new TypstNode("whitespace", this.value);
1214
+ case 1 /* ELEMENT */:
1215
+ return new TypstNode("atom", this.value);
1216
+ case 0 /* SYMBOL */:
1217
+ return new TypstNode("symbol", this.value);
1218
+ case 6 /* CONTROL */: {
1219
+ const controlChar = this.value;
1220
+ switch (controlChar) {
1221
+ case "":
1222
+ case "_":
1223
+ case "^":
1224
+ return new TypstNode("empty", "");
1225
+ case "&":
1226
+ return new TypstNode("control", "&");
1227
+ case "\\":
1228
+ return new TypstNode("control", "\\");
1229
+ default:
1230
+ throw new Error(`Unexpected control character ${controlChar}`);
1231
+ }
1077
1232
  }
1233
+ default:
1234
+ throw new Error(`Unexpected token type ${this.type}`);
1235
+ }
1236
+ }
1237
+ toString() {
1238
+ switch (this.type) {
1239
+ case 2 /* TEXT */:
1240
+ return `"${this.value}"`;
1241
+ case 3 /* COMMENT */:
1242
+ return `//${this.value}`;
1243
+ default:
1244
+ return this.value;
1078
1245
  }
1079
- return found;
1080
1246
  }
1081
1247
  }
1082
1248
 
@@ -1095,17 +1261,25 @@ class TypstNode {
1095
1261
  setOptions(options) {
1096
1262
  this.options = options;
1097
1263
  }
1098
- eq_shallow(other) {
1264
+ eq(other) {
1099
1265
  return this.type === other.type && this.content === other.content;
1100
1266
  }
1101
1267
  }
1102
1268
 
1103
- // src/tex-parser.ts
1269
+ // src/util.ts
1270
+ function isalpha(char) {
1271
+ return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(char);
1272
+ }
1273
+ function isdigit(char) {
1274
+ return "0123456789".includes(char);
1275
+ }
1104
1276
  function assert(condition, message = "") {
1105
1277
  if (!condition) {
1106
- throw new LatexParserError(message);
1278
+ throw new Error(message);
1107
1279
  }
1108
1280
  }
1281
+
1282
+ // src/tex-parser.ts
1109
1283
  function get_command_param_num(command) {
1110
1284
  if (UNARY_COMMANDS.includes(command)) {
1111
1285
  return 1;
@@ -1115,46 +1289,6 @@ function get_command_param_num(command) {
1115
1289
  return 0;
1116
1290
  }
1117
1291
  }
1118
- function find_closing_curly_bracket(tokens, start) {
1119
- assert(tokens[start].eq(LEFT_CURLY_BRACKET));
1120
- let count = 1;
1121
- let pos = start + 1;
1122
- while (count > 0) {
1123
- if (pos >= tokens.length) {
1124
- throw new LatexParserError("Unmatched curly brackets");
1125
- }
1126
- if (tokens[pos].eq(LEFT_CURLY_BRACKET)) {
1127
- count += 1;
1128
- } else if (tokens[pos].eq(RIGHT_CURLY_BRACKET)) {
1129
- count -= 1;
1130
- }
1131
- pos += 1;
1132
- }
1133
- return pos - 1;
1134
- }
1135
- function find_closing_square_bracket(tokens, start) {
1136
- assert(tokens[start].eq(LEFT_SQUARE_BRACKET));
1137
- let count = 1;
1138
- let pos = start + 1;
1139
- while (count > 0) {
1140
- if (pos >= tokens.length) {
1141
- throw new LatexParserError("Unmatched square brackets");
1142
- }
1143
- if (tokens[pos].eq(LEFT_SQUARE_BRACKET)) {
1144
- count += 1;
1145
- } else if (tokens[pos].eq(RIGHT_SQUARE_BRACKET)) {
1146
- count -= 1;
1147
- }
1148
- pos += 1;
1149
- }
1150
- return pos - 1;
1151
- }
1152
- function isalpha(char) {
1153
- return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(char);
1154
- }
1155
- function isdigit(char) {
1156
- return "0123456789".includes(char);
1157
- }
1158
1292
  function eat_whitespaces(tokens, start) {
1159
1293
  let pos = start;
1160
1294
  while (pos < tokens.length && [4 /* SPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
@@ -1186,37 +1320,28 @@ function eat_command_name(latex, start) {
1186
1320
  }
1187
1321
  return latex.substring(start, pos);
1188
1322
  }
1189
- function find_closing_right_command(tokens, start) {
1323
+ function find_closing_match(tokens, start, leftToken, rightToken) {
1324
+ assert(tokens[start].eq(leftToken));
1190
1325
  let count = 1;
1191
- let pos = start;
1326
+ let pos = start + 1;
1192
1327
  while (count > 0) {
1193
1328
  if (pos >= tokens.length) {
1194
1329
  return -1;
1195
1330
  }
1196
- if (tokens[pos].eq(LEFT_COMMAND)) {
1331
+ if (tokens[pos].eq(leftToken)) {
1197
1332
  count += 1;
1198
- } else if (tokens[pos].eq(RIGHT_COMMAND)) {
1333
+ } else if (tokens[pos].eq(rightToken)) {
1199
1334
  count -= 1;
1200
1335
  }
1201
1336
  pos += 1;
1202
1337
  }
1203
1338
  return pos - 1;
1204
1339
  }
1340
+ function find_closing_right_command(tokens, start) {
1341
+ return find_closing_match(tokens, start, LEFT_COMMAND, RIGHT_COMMAND);
1342
+ }
1205
1343
  function find_closing_end_command(tokens, start) {
1206
- let count = 1;
1207
- let pos = start;
1208
- while (count > 0) {
1209
- if (pos >= tokens.length) {
1210
- return -1;
1211
- }
1212
- if (tokens[pos].eq(BEGIN_COMMAND)) {
1213
- count += 1;
1214
- } else if (tokens[pos].eq(END_COMMAND)) {
1215
- count -= 1;
1216
- }
1217
- pos += 1;
1218
- }
1219
- return pos - 1;
1344
+ return find_closing_match(tokens, start, BEGIN_COMMAND, END_COMMAND);
1220
1345
  }
1221
1346
  function find_closing_curly_bracket_char(latex, start) {
1222
1347
  assert(latex[start] === "{");
@@ -1411,18 +1536,6 @@ var BINARY_COMMANDS = [
1411
1536
  "tbinom",
1412
1537
  "overset"
1413
1538
  ];
1414
-
1415
- class TexToken {
1416
- type;
1417
- value;
1418
- constructor(type, value) {
1419
- this.type = type;
1420
- this.value = value;
1421
- }
1422
- eq(token) {
1423
- return this.type === token.type && this.value === token.value;
1424
- }
1425
- }
1426
1539
  var EMPTY_NODE = new TexNode("empty", "");
1427
1540
  var LEFT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "{");
1428
1541
  var RIGHT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "}");
@@ -1565,7 +1678,7 @@ class LatexParser {
1565
1678
  const controlChar = firstToken.value;
1566
1679
  switch (controlChar) {
1567
1680
  case "{":
1568
- const posClosingBracket = find_closing_curly_bracket(tokens, start);
1681
+ const posClosingBracket = find_closing_match(tokens, start, LEFT_CURLY_BRACKET, RIGHT_CURLY_BRACKET);
1569
1682
  const exprInside = tokens.slice(start + 1, posClosingBracket);
1570
1683
  return [this.parse(exprInside), posClosingBracket + 1];
1571
1684
  case "}":
@@ -1574,12 +1687,9 @@ class LatexParser {
1574
1687
  return [new TexNode("control", "\\\\"), start + 1];
1575
1688
  case "\\,":
1576
1689
  return [new TexNode("control", "\\,"), start + 1];
1577
- case "_": {
1578
- return [EMPTY_NODE, start];
1579
- }
1580
- case "^": {
1690
+ case "_":
1691
+ case "^":
1581
1692
  return [EMPTY_NODE, start];
1582
- }
1583
1693
  case "&":
1584
1694
  return [new TexNode("control", "&"), start + 1];
1585
1695
  default:
@@ -1606,7 +1716,7 @@ class LatexParser {
1606
1716
  case 1: {
1607
1717
  if (command === "\\sqrt" && pos < tokens.length && tokens[pos].eq(LEFT_SQUARE_BRACKET)) {
1608
1718
  const posLeftSquareBracket = pos;
1609
- const posRightSquareBracket = find_closing_square_bracket(tokens, pos);
1719
+ const posRightSquareBracket = find_closing_match(tokens, pos, LEFT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET);
1610
1720
  const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket);
1611
1721
  const exponent = this.parse(exprInside);
1612
1722
  const [arg12, newPos2] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1);
@@ -1646,7 +1756,7 @@ class LatexParser {
1646
1756
  }
1647
1757
  pos++;
1648
1758
  const exprInsideStart = pos;
1649
- const idx = find_closing_right_command(tokens, pos);
1759
+ const idx = find_closing_right_command(tokens, start);
1650
1760
  if (idx === -1) {
1651
1761
  throw new LatexParserError("No matching \\right");
1652
1762
  }
@@ -1681,7 +1791,7 @@ class LatexParser {
1681
1791
  pos += 3;
1682
1792
  pos += eat_whitespaces(tokens, pos).length;
1683
1793
  const exprInsideStart = pos;
1684
- const endIdx = find_closing_end_command(tokens, pos);
1794
+ const endIdx = find_closing_end_command(tokens, start);
1685
1795
  if (endIdx === -1) {
1686
1796
  throw new LatexParserError("No matching \\end");
1687
1797
  }
@@ -1743,7 +1853,7 @@ function is_delimiter(c) {
1743
1853
  function convert_overset(node) {
1744
1854
  const [sup, base] = node.args;
1745
1855
  const is_def = (n) => {
1746
- if (n.eq_shallow(new TexNode("text", "def"))) {
1856
+ if (n.eq(new TexNode("text", "def"))) {
1747
1857
  return true;
1748
1858
  }
1749
1859
  if (n.type === "ordgroup" && n.args.length === 3) {
@@ -1751,17 +1861,17 @@ function convert_overset(node) {
1751
1861
  const d = new TexNode("element", "d");
1752
1862
  const e = new TexNode("element", "e");
1753
1863
  const f = new TexNode("element", "f");
1754
- if (a1.eq_shallow(d) && a2.eq_shallow(e) && a3.eq_shallow(f)) {
1864
+ if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
1755
1865
  return true;
1756
1866
  }
1757
1867
  }
1758
1868
  return false;
1759
1869
  };
1760
- const is_eq = (n) => n.eq_shallow(new TexNode("element", "="));
1870
+ const is_eq = (n) => n.eq(new TexNode("element", "="));
1761
1871
  if (is_def(sup) && is_eq(base)) {
1762
1872
  return new TypstNode("symbol", "eq.def");
1763
1873
  }
1764
- const op_call = new TypstNode("unaryFunc", "op", [convertTree(base)]);
1874
+ const op_call = new TypstNode("funcCall", "op", [convertTree(base)]);
1765
1875
  op_call.setOptions({ limits: "#true" });
1766
1876
  return new TypstNode("supsub", "", [], {
1767
1877
  base: op_call,
@@ -1787,9 +1897,9 @@ function convertTree(node) {
1787
1897
  case "supsub": {
1788
1898
  let { base, sup, sub } = node.data;
1789
1899
  if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) {
1790
- return new TypstNode("binaryFunc", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
1900
+ return new TypstNode("funcCall", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
1791
1901
  } else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) {
1792
- return new TypstNode("binaryFunc", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
1902
+ return new TypstNode("funcCall", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
1793
1903
  }
1794
1904
  const data = {
1795
1905
  base: convertTree(base)
@@ -1818,23 +1928,23 @@ function convertTree(node) {
1818
1928
  ].includes(left.content + right.content)) {
1819
1929
  return group;
1820
1930
  }
1821
- return new TypstNode("unaryFunc", "lr", [group]);
1931
+ return new TypstNode("funcCall", "lr", [group]);
1822
1932
  }
1823
1933
  case "binaryFunc": {
1824
1934
  if (node.content === "\\overset") {
1825
1935
  return convert_overset(node);
1826
1936
  }
1827
- return new TypstNode("binaryFunc", convertToken(node.content), node.args.map(convertTree));
1937
+ return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
1828
1938
  }
1829
1939
  case "unaryFunc": {
1830
1940
  const arg0 = convertTree(node.args[0]);
1831
1941
  if (node.content === "\\sqrt" && node.data) {
1832
1942
  const data = convertTree(node.data);
1833
- return new TypstNode("binaryFunc", "root", [data, arg0]);
1943
+ return new TypstNode("funcCall", "root", [data, arg0]);
1834
1944
  }
1835
1945
  if (node.content === "\\mathbf") {
1836
- const inner = new TypstNode("unaryFunc", "bold", [arg0]);
1837
- return new TypstNode("unaryFunc", "upright", [inner]);
1946
+ const inner = new TypstNode("funcCall", "bold", [arg0]);
1947
+ return new TypstNode("funcCall", "upright", [inner]);
1838
1948
  }
1839
1949
  if (node.content === "\\mathbb" && arg0.type === "atom" && /^[A-Z]$/.test(arg0.content)) {
1840
1950
  return new TypstNode("symbol", arg0.content + arg0.content);
@@ -1848,10 +1958,10 @@ function convertTree(node) {
1848
1958
  if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
1849
1959
  return new TypstNode("symbol", text);
1850
1960
  } else {
1851
- return new TypstNode("unaryFunc", "op", [new TypstNode("text", text)]);
1961
+ return new TypstNode("funcCall", "op", [new TypstNode("text", text)]);
1852
1962
  }
1853
1963
  }
1854
- return new TypstNode("unaryFunc", convertToken(node.content), node.args.map(convertTree));
1964
+ return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
1855
1965
  }
1856
1966
  case "beginend": {
1857
1967
  const matrix = node.data;
@@ -1910,9 +2020,9 @@ var TYPST_INTRINSIC_SYMBOLS = [
1910
2020
  "sech",
1911
2021
  "csch"
1912
2022
  ];
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 */, ",");
2023
+ var TYPST_LEFT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, "(");
2024
+ var TYPST_RIGHT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, ")");
2025
+ var TYPST_COMMA = new TypstToken(1 /* ELEMENT */, ",");
1916
2026
  var TYPST_NEWLINE = new TypstToken(0 /* SYMBOL */, "\n");
1917
2027
 
1918
2028
  class TypstWriterError extends Error {
@@ -1937,19 +2047,21 @@ class TypstWriter {
1937
2047
  this.keepSpaces = keepSpaces;
1938
2048
  }
1939
2049
  writeBuffer(token) {
1940
- const str = token.content;
2050
+ const str = token.toString();
1941
2051
  if (str === "") {
1942
2052
  return;
1943
2053
  }
1944
2054
  let no_need_space = false;
1945
- no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
1946
- no_need_space ||= /^[}()_^,;!\|]$/.test(str);
2055
+ no_need_space ||= /[\(\[\|]$/.test(this.buffer) && /^\w/.test(str);
2056
+ no_need_space ||= /^[})\]\|]$/.test(str);
2057
+ no_need_space ||= /^[(_^,;!]$/.test(str);
1947
2058
  no_need_space ||= str === "'";
1948
2059
  no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
1949
2060
  no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
1950
2061
  no_need_space ||= str.startsWith("\n");
1951
2062
  no_need_space ||= this.buffer === "";
1952
2063
  no_need_space ||= /^\s/.test(str);
2064
+ no_need_space ||= this.buffer.endsWith("&") && str === "=";
1953
2065
  no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
1954
2066
  if (!no_need_space) {
1955
2067
  this.buffer += " ";
@@ -1964,7 +2076,7 @@ class TypstWriter {
1964
2076
  if (node.content === "," && this.insideFunctionDepth > 0) {
1965
2077
  this.queue.push(new TypstToken(0 /* SYMBOL */, "comma"));
1966
2078
  } else {
1967
- this.queue.push(new TypstToken(1 /* ATOM */, node.content));
2079
+ this.queue.push(new TypstToken(1 /* ELEMENT */, node.content));
1968
2080
  }
1969
2081
  break;
1970
2082
  }
@@ -1972,10 +2084,10 @@ class TypstWriter {
1972
2084
  this.queue.push(new TypstToken(0 /* SYMBOL */, node.content));
1973
2085
  break;
1974
2086
  case "text":
1975
- this.queue.push(new TypstToken(2 /* TEXT */, `"${node.content}"`));
2087
+ this.queue.push(new TypstToken(2 /* TEXT */, node.content));
1976
2088
  break;
1977
2089
  case "comment":
1978
- this.queue.push(new TypstToken(3 /* COMMENT */, `//${node.content}`));
2090
+ this.queue.push(new TypstToken(3 /* COMMENT */, node.content));
1979
2091
  break;
1980
2092
  case "whitespace":
1981
2093
  for (const c of node.content) {
@@ -2001,15 +2113,15 @@ class TypstWriter {
2001
2113
  let trailing_space_needed = false;
2002
2114
  const has_prime = sup && sup.type === "atom" && sup.content === "\'";
2003
2115
  if (has_prime) {
2004
- this.queue.push(new TypstToken(1 /* ATOM */, "\'"));
2116
+ this.queue.push(new TypstToken(1 /* ELEMENT */, "\'"));
2005
2117
  trailing_space_needed = false;
2006
2118
  }
2007
2119
  if (sub) {
2008
- this.queue.push(new TypstToken(1 /* ATOM */, "_"));
2120
+ this.queue.push(new TypstToken(1 /* ELEMENT */, "_"));
2009
2121
  trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
2010
2122
  }
2011
2123
  if (sup && !has_prime) {
2012
- this.queue.push(new TypstToken(1 /* ATOM */, "^"));
2124
+ this.queue.push(new TypstToken(1 /* ELEMENT */, "^"));
2013
2125
  trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
2014
2126
  }
2015
2127
  if (trailing_space_needed) {
@@ -2017,26 +2129,17 @@ class TypstWriter {
2017
2129
  }
2018
2130
  break;
2019
2131
  }
2020
- case "binaryFunc": {
2132
+ case "funcCall": {
2021
2133
  const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
2022
- const [arg0, arg1] = node.args;
2023
2134
  this.queue.push(func_symbol);
2024
2135
  this.insideFunctionDepth++;
2025
2136
  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);
2030
- this.insideFunctionDepth--;
2031
- break;
2032
- }
2033
- case "unaryFunc": {
2034
- const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
2035
- const arg0 = node.args[0];
2036
- this.queue.push(func_symbol);
2037
- this.insideFunctionDepth++;
2038
- this.queue.push(TYPST_LEFT_PARENTHESIS);
2039
- this.serialize(arg0);
2137
+ for (let i = 0;i < node.args.length; i++) {
2138
+ this.serialize(node.args[i]);
2139
+ if (i < node.args.length - 1) {
2140
+ this.queue.push(new TypstToken(1 /* ELEMENT */, ","));
2141
+ }
2142
+ }
2040
2143
  if (node.options) {
2041
2144
  for (const [key, value] of Object.entries(node.options)) {
2042
2145
  this.queue.push(new TypstToken(0 /* SYMBOL */, `, ${key}: ${value}`));
@@ -2051,7 +2154,7 @@ class TypstWriter {
2051
2154
  matrix.forEach((row, i) => {
2052
2155
  row.forEach((cell, j) => {
2053
2156
  if (j > 0) {
2054
- this.queue.push(new TypstToken(1 /* ATOM */, "&"));
2157
+ this.queue.push(new TypstToken(1 /* ELEMENT */, "&"));
2055
2158
  }
2056
2159
  this.serialize(cell);
2057
2160
  });
@@ -2075,10 +2178,10 @@ class TypstWriter {
2075
2178
  row.forEach((cell, j) => {
2076
2179
  this.serialize(cell);
2077
2180
  if (j < row.length - 1) {
2078
- this.queue.push(new TypstToken(1 /* ATOM */, ","));
2181
+ this.queue.push(new TypstToken(1 /* ELEMENT */, ","));
2079
2182
  } else {
2080
2183
  if (i < matrix.length - 1) {
2081
- this.queue.push(new TypstToken(1 /* ATOM */, ";"));
2184
+ this.queue.push(new TypstToken(1 /* ELEMENT */, ";"));
2082
2185
  }
2083
2186
  }
2084
2187
  });
@@ -2123,9 +2226,9 @@ class TypstWriter {
2123
2226
  let token = this.queue[i];
2124
2227
  if (token.eq(SOFT_SPACE)) {
2125
2228
  if (i === this.queue.length - 1) {
2126
- this.queue[i].content = "";
2229
+ this.queue[i].value = "";
2127
2230
  } else if (this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE])) {
2128
- this.queue[i].content = "";
2231
+ this.queue[i].value = "";
2129
2232
  }
2130
2233
  }
2131
2234
  }
@@ -2159,6 +2262,663 @@ class TypstWriter {
2159
2262
  }
2160
2263
  }
2161
2264
 
2265
+ // src/typst-parser.ts
2266
+ function eat_primes2(tokens, start) {
2267
+ let pos = start;
2268
+ while (pos < tokens.length && tokens[pos].eq(new TypstToken(1 /* ELEMENT */, "'"))) {
2269
+ pos += 1;
2270
+ }
2271
+ return pos - start;
2272
+ }
2273
+ function eat_identifier_name(typst, start) {
2274
+ let pos = start;
2275
+ while (pos < typst.length && (isalpha(typst[pos]) || typst[pos] === ".")) {
2276
+ pos += 1;
2277
+ }
2278
+ return typst.substring(start, pos);
2279
+ }
2280
+ function tokenize_typst(typst) {
2281
+ const tokens = [];
2282
+ let pos = 0;
2283
+ while (pos < typst.length) {
2284
+ const firstChar = typst[pos];
2285
+ let token;
2286
+ switch (firstChar) {
2287
+ case "_":
2288
+ case "^":
2289
+ case "&":
2290
+ token = new TypstToken(6 /* CONTROL */, firstChar);
2291
+ pos++;
2292
+ break;
2293
+ case "\n":
2294
+ token = new TypstToken(7 /* NEWLINE */, firstChar);
2295
+ pos++;
2296
+ break;
2297
+ case "\r": {
2298
+ if (pos + 1 < typst.length && typst[pos + 1] === "\n") {
2299
+ token = new TypstToken(7 /* NEWLINE */, "\n");
2300
+ pos += 2;
2301
+ } else {
2302
+ token = new TypstToken(7 /* NEWLINE */, "\n");
2303
+ pos++;
2304
+ }
2305
+ break;
2306
+ }
2307
+ case " ": {
2308
+ let newPos = pos;
2309
+ while (newPos < typst.length && typst[newPos] === " ") {
2310
+ newPos++;
2311
+ }
2312
+ token = new TypstToken(4 /* SPACE */, typst.substring(pos, newPos));
2313
+ pos = newPos;
2314
+ break;
2315
+ }
2316
+ case "/": {
2317
+ if (pos < typst.length && typst[pos + 1] === "/") {
2318
+ let newPos = pos + 2;
2319
+ while (newPos < typst.length && typst[newPos] !== "\n") {
2320
+ newPos++;
2321
+ }
2322
+ token = new TypstToken(3 /* COMMENT */, typst.slice(pos + 2, newPos));
2323
+ pos = newPos;
2324
+ } else {
2325
+ token = new TypstToken(1 /* ELEMENT */, "/");
2326
+ pos++;
2327
+ }
2328
+ break;
2329
+ }
2330
+ case "\\": {
2331
+ if (pos + 1 >= typst.length) {
2332
+ throw new Error("Expecting a character after \\");
2333
+ }
2334
+ const firstTwoChars = typst.substring(pos, pos + 2);
2335
+ if (["\\$", "\\&", "\\#", "\\_"].includes(firstTwoChars)) {
2336
+ token = new TypstToken(1 /* ELEMENT */, firstTwoChars);
2337
+ pos += 2;
2338
+ } else if (firstTwoChars === "\\\n") {
2339
+ token = new TypstToken(6 /* CONTROL */, "\\");
2340
+ pos += 1;
2341
+ } else {
2342
+ token = new TypstToken(6 /* CONTROL */, "");
2343
+ pos++;
2344
+ }
2345
+ break;
2346
+ }
2347
+ case '"': {
2348
+ let newPos = pos + 1;
2349
+ while (newPos < typst.length) {
2350
+ if (typst[newPos] === '"' && typst[newPos - 1] !== "\\") {
2351
+ break;
2352
+ }
2353
+ newPos++;
2354
+ }
2355
+ let text = typst.substring(pos + 1, newPos);
2356
+ const chars = ['"', "\\"];
2357
+ for (const char of chars) {
2358
+ text = text.replaceAll("\\" + char, char);
2359
+ }
2360
+ token = new TypstToken(2 /* TEXT */, text);
2361
+ pos = newPos + 1;
2362
+ break;
2363
+ }
2364
+ default: {
2365
+ if (isdigit(firstChar)) {
2366
+ let newPos = pos;
2367
+ while (newPos < typst.length && isdigit(typst[newPos])) {
2368
+ newPos += 1;
2369
+ }
2370
+ token = new TypstToken(1 /* ELEMENT */, typst.slice(pos, newPos));
2371
+ } else if ("+-*/=\'<>!.,;?()[]|".includes(firstChar)) {
2372
+ token = new TypstToken(1 /* ELEMENT */, firstChar);
2373
+ } else if (isalpha(firstChar)) {
2374
+ const identifier = eat_identifier_name(typst, pos);
2375
+ const _type = identifier.length === 1 ? 1 /* ELEMENT */ : 0 /* SYMBOL */;
2376
+ token = new TypstToken(_type, identifier);
2377
+ } else {
2378
+ token = new TypstToken(1 /* ELEMENT */, firstChar);
2379
+ }
2380
+ pos += token.value.length;
2381
+ }
2382
+ }
2383
+ tokens.push(token);
2384
+ }
2385
+ return tokens;
2386
+ }
2387
+ function find_closing_match2(tokens, start) {
2388
+ assert(tokens[start].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2]));
2389
+ let count = 1;
2390
+ let pos = start + 1;
2391
+ while (count > 0) {
2392
+ if (pos >= tokens.length) {
2393
+ throw new Error("Unmatched brackets");
2394
+ }
2395
+ if (tokens[pos].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2])) {
2396
+ count += 1;
2397
+ } else if (tokens[pos].isOneOf([RIGHT_PARENTHESES, RIGHT_BRACKET, RIGHT_CURLY_BRACKET2])) {
2398
+ count -= 1;
2399
+ }
2400
+ pos += 1;
2401
+ }
2402
+ return pos - 1;
2403
+ }
2404
+ function find_closing_parenthesis(nodes, start) {
2405
+ const left_parenthesis = new TypstNode("atom", "(");
2406
+ const right_parenthesis = new TypstNode("atom", ")");
2407
+ assert(nodes[start].eq(left_parenthesis));
2408
+ let count = 1;
2409
+ let pos = start + 1;
2410
+ while (count > 0) {
2411
+ if (pos >= nodes.length) {
2412
+ throw new Error("Unmatched brackets");
2413
+ }
2414
+ if (nodes[pos].eq(left_parenthesis)) {
2415
+ count += 1;
2416
+ } else if (nodes[pos].eq(right_parenthesis)) {
2417
+ count -= 1;
2418
+ }
2419
+ pos += 1;
2420
+ }
2421
+ return pos - 1;
2422
+ }
2423
+ function primes(num) {
2424
+ const res = [];
2425
+ for (let i = 0;i < num; i++) {
2426
+ res.push(new TypstNode("atom", "'"));
2427
+ }
2428
+ return res;
2429
+ }
2430
+ function next_non_whitespace(nodes, start) {
2431
+ let pos = start;
2432
+ while (pos < nodes.length && nodes[pos].type === "whitespace") {
2433
+ pos++;
2434
+ }
2435
+ return pos === nodes.length ? TYPST_EMPTY_NODE : nodes[pos];
2436
+ }
2437
+ function trim_whitespace_around_operators(nodes) {
2438
+ let after_operator = false;
2439
+ const res = [];
2440
+ for (let i = 0;i < nodes.length; i++) {
2441
+ const current = nodes[i];
2442
+ if (current.type === "whitespace") {
2443
+ if (after_operator) {
2444
+ continue;
2445
+ }
2446
+ if (next_non_whitespace(nodes, i + 1).eq(DIV)) {
2447
+ continue;
2448
+ }
2449
+ }
2450
+ if (current.eq(DIV)) {
2451
+ after_operator = true;
2452
+ } else {
2453
+ after_operator = false;
2454
+ }
2455
+ res.push(current);
2456
+ }
2457
+ return res;
2458
+ }
2459
+ function process_operators(nodes, parenthesis = false) {
2460
+ nodes = trim_whitespace_around_operators(nodes);
2461
+ const opening_bracket = new TypstNode("atom", "(");
2462
+ const closing_bracket = new TypstNode("atom", ")");
2463
+ const stack = [];
2464
+ const args = [];
2465
+ let pos = 0;
2466
+ while (pos < nodes.length) {
2467
+ const current = nodes[pos];
2468
+ if (current.eq(closing_bracket)) {
2469
+ throw new TypstParserError("Unexpected ')'");
2470
+ } else if (current.eq(DIV)) {
2471
+ stack.push(current);
2472
+ pos++;
2473
+ } else {
2474
+ let current_tree;
2475
+ if (current.eq(opening_bracket)) {
2476
+ const pos_closing = find_closing_parenthesis(nodes, pos);
2477
+ current_tree = process_operators(nodes.slice(pos + 1, pos_closing), true);
2478
+ pos = pos_closing + 1;
2479
+ } else {
2480
+ current_tree = current;
2481
+ pos++;
2482
+ }
2483
+ if (stack.length > 0 && stack[stack.length - 1].eq(DIV)) {
2484
+ const denominator = current_tree;
2485
+ if (args.length === 0) {
2486
+ throw new TypstParserError("Unexpected '/' operator, no numerator before it");
2487
+ }
2488
+ const numerator = args.pop();
2489
+ if (denominator.type === "group" && denominator.content === "parenthesis") {
2490
+ denominator.content = "";
2491
+ }
2492
+ if (numerator.type === "group" && numerator.content === "parenthesis") {
2493
+ numerator.content = "";
2494
+ }
2495
+ args.push(new TypstNode("fraction", "", [numerator, denominator]));
2496
+ stack.pop();
2497
+ } else {
2498
+ args.push(current_tree);
2499
+ }
2500
+ }
2501
+ }
2502
+ if (parenthesis) {
2503
+ return new TypstNode("group", "parenthesis", args);
2504
+ } else {
2505
+ if (args.length === 0) {
2506
+ return TYPST_EMPTY_NODE;
2507
+ } else if (args.length === 1) {
2508
+ return args[0];
2509
+ } else {
2510
+ return new TypstNode("group", "", args);
2511
+ }
2512
+ }
2513
+ }
2514
+ function parseTypst(typst) {
2515
+ const parser = new TypstParser;
2516
+ let tokens = tokenize_typst(typst);
2517
+ return parser.parse(tokens);
2518
+ }
2519
+ var TYPST_EMPTY_NODE = new TypstNode("empty", "");
2520
+ var DIV = new TypstNode("atom", "/");
2521
+
2522
+ class TypstParserError extends Error {
2523
+ constructor(message) {
2524
+ super(message);
2525
+ this.name = "TypstParserError";
2526
+ }
2527
+ }
2528
+ var SUB_SYMBOL2 = new TypstToken(6 /* CONTROL */, "_");
2529
+ var SUP_SYMBOL2 = new TypstToken(6 /* CONTROL */, "^");
2530
+ var LEFT_PARENTHESES = new TypstToken(1 /* ELEMENT */, "(");
2531
+ var RIGHT_PARENTHESES = new TypstToken(1 /* ELEMENT */, ")");
2532
+ var LEFT_BRACKET = new TypstToken(1 /* ELEMENT */, "[");
2533
+ var RIGHT_BRACKET = new TypstToken(1 /* ELEMENT */, "]");
2534
+ var LEFT_CURLY_BRACKET2 = new TypstToken(1 /* ELEMENT */, "{");
2535
+ var RIGHT_CURLY_BRACKET2 = new TypstToken(1 /* ELEMENT */, "}");
2536
+ var COMMA = new TypstToken(1 /* ELEMENT */, ",");
2537
+ var SEMICOLON = new TypstToken(1 /* ELEMENT */, ";");
2538
+ var SINGLE_SPACE = new TypstToken(4 /* SPACE */, " ");
2539
+
2540
+ class TypstParser {
2541
+ space_sensitive;
2542
+ newline_sensitive;
2543
+ constructor(space_sensitive = true, newline_sensitive = true) {
2544
+ this.space_sensitive = space_sensitive;
2545
+ this.newline_sensitive = newline_sensitive;
2546
+ }
2547
+ parse(tokens) {
2548
+ const [tree, _] = this.parseGroup(tokens, 0, tokens.length);
2549
+ return tree;
2550
+ }
2551
+ parseGroup(tokens, start, end, parentheses = false) {
2552
+ const results = [];
2553
+ let pos = start;
2554
+ while (pos < end) {
2555
+ const [res, newPos] = this.parseNextExpr(tokens, pos);
2556
+ pos = newPos;
2557
+ if (res.type === "whitespace") {
2558
+ if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
2559
+ continue;
2560
+ }
2561
+ if (!this.newline_sensitive && res.content === "\n") {
2562
+ continue;
2563
+ }
2564
+ }
2565
+ results.push(res);
2566
+ }
2567
+ let node;
2568
+ if (parentheses) {
2569
+ node = process_operators(results, true);
2570
+ } else {
2571
+ if (results.length === 0) {
2572
+ node = TYPST_EMPTY_NODE;
2573
+ } else if (results.length === 1) {
2574
+ node = results[0];
2575
+ } else {
2576
+ node = process_operators(results);
2577
+ }
2578
+ }
2579
+ return [node, end + 1];
2580
+ }
2581
+ parseNextExpr(tokens, start) {
2582
+ let [base, pos] = this.parseNextExprWithoutSupSub(tokens, start);
2583
+ let sub = null;
2584
+ let sup = null;
2585
+ const num_base_prime = eat_primes2(tokens, pos);
2586
+ if (num_base_prime > 0) {
2587
+ base = new TypstNode("group", "", [base].concat(primes(num_base_prime)));
2588
+ pos += num_base_prime;
2589
+ }
2590
+ if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL2)) {
2591
+ [sub, pos] = this.parseSupOrSub(tokens, pos + 1);
2592
+ if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL2)) {
2593
+ [sup, pos] = this.parseSupOrSub(tokens, pos + 1);
2594
+ }
2595
+ } else if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL2)) {
2596
+ [sup, pos] = this.parseSupOrSub(tokens, pos + 1);
2597
+ if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL2)) {
2598
+ [sub, pos] = this.parseSupOrSub(tokens, pos + 1);
2599
+ }
2600
+ }
2601
+ if (sub !== null || sup !== null) {
2602
+ const res = { base };
2603
+ if (sub) {
2604
+ res.sub = sub;
2605
+ }
2606
+ if (sup) {
2607
+ res.sup = sup;
2608
+ }
2609
+ return [new TypstNode("supsub", "", [], res), pos];
2610
+ } else {
2611
+ return [base, pos];
2612
+ }
2613
+ }
2614
+ parseSupOrSub(tokens, start) {
2615
+ let node;
2616
+ let end;
2617
+ if (tokens[start].eq(LEFT_PARENTHESES)) {
2618
+ const pos_closing = find_closing_match2(tokens, start);
2619
+ [node, end] = this.parseGroup(tokens, start + 1, pos_closing);
2620
+ } else {
2621
+ [node, end] = this.parseNextExprWithoutSupSub(tokens, start);
2622
+ }
2623
+ const num_prime = eat_primes2(tokens, end);
2624
+ if (num_prime > 0) {
2625
+ node = new TypstNode("group", "", [node].concat(primes(num_prime)));
2626
+ end += num_prime;
2627
+ }
2628
+ return [node, end];
2629
+ }
2630
+ parseNextExprWithoutSupSub(tokens, start) {
2631
+ const firstToken = tokens[start];
2632
+ const node = firstToken.toNode();
2633
+ if (firstToken.eq(LEFT_PARENTHESES)) {
2634
+ const pos_closing = find_closing_match2(tokens, start);
2635
+ return this.parseGroup(tokens, start + 1, pos_closing, true);
2636
+ }
2637
+ if (firstToken.type === 1 /* ELEMENT */ && !isalpha(firstToken.value[0])) {
2638
+ return [node, start + 1];
2639
+ }
2640
+ if ([1 /* ELEMENT */, 0 /* SYMBOL */].includes(firstToken.type)) {
2641
+ if (start + 1 < tokens.length && tokens[start + 1].eq(LEFT_PARENTHESES)) {
2642
+ if (firstToken.value === "mat") {
2643
+ const [matrix, newPos2] = this.parseGroupsOfArguments(tokens, start + 1);
2644
+ const mat = new TypstNode("matrix", "", [], matrix);
2645
+ return [mat, newPos2];
2646
+ }
2647
+ const [args, newPos] = this.parseArguments(tokens, start + 1);
2648
+ const func_call = new TypstNode("funcCall", firstToken.value);
2649
+ func_call.args = args;
2650
+ return [func_call, newPos];
2651
+ }
2652
+ }
2653
+ return [node, start + 1];
2654
+ }
2655
+ parseArguments(tokens, start) {
2656
+ const end = find_closing_match2(tokens, start);
2657
+ return [this.parseCommaSeparatedArguments(tokens, start + 1, end), end + 1];
2658
+ }
2659
+ parseGroupsOfArguments(tokens, start) {
2660
+ const end = find_closing_match2(tokens, start);
2661
+ const matrix = [];
2662
+ let pos = start + 1;
2663
+ while (pos < end) {
2664
+ while (pos < end) {
2665
+ let next_stop = array_find(tokens, SEMICOLON, pos);
2666
+ if (next_stop === -1) {
2667
+ next_stop = end;
2668
+ }
2669
+ const row = this.parseCommaSeparatedArguments(tokens, pos, next_stop);
2670
+ matrix.push(row);
2671
+ pos = next_stop + 1;
2672
+ }
2673
+ }
2674
+ return [matrix, end + 1];
2675
+ }
2676
+ parseCommaSeparatedArguments(tokens, start, end) {
2677
+ const args = [];
2678
+ let pos = start;
2679
+ while (pos < end) {
2680
+ let arg = new TypstNode("group", "", []);
2681
+ while (pos < end) {
2682
+ if (tokens[pos].eq(COMMA)) {
2683
+ pos += 1;
2684
+ break;
2685
+ } else if (tokens[pos].eq(SINGLE_SPACE)) {
2686
+ pos += 1;
2687
+ continue;
2688
+ }
2689
+ const [argItem, newPos] = this.parseNextExpr(tokens, pos);
2690
+ pos = newPos;
2691
+ arg.args.push(argItem);
2692
+ }
2693
+ if (arg.args.length === 0) {
2694
+ arg = TYPST_EMPTY_NODE;
2695
+ } else if (arg.args.length === 1) {
2696
+ arg = arg.args[0];
2697
+ }
2698
+ args.push(arg);
2699
+ }
2700
+ return args;
2701
+ }
2702
+ }
2703
+
2704
+ // src/tex-writer.ts
2705
+ function apply_escape_if_needed2(c) {
2706
+ if (["{", "}", "%"].includes(c)) {
2707
+ return "\\" + c;
2708
+ }
2709
+ return c;
2710
+ }
2711
+ function convert_typst_node_to_tex(node) {
2712
+ if (node.eq(new TypstNode("symbol", "eq.def"))) {
2713
+ return new TexNode("binaryFunc", "\\overset", [
2714
+ new TexNode("text", "def"),
2715
+ new TexNode("element", "=")
2716
+ ]);
2717
+ }
2718
+ switch (node.type) {
2719
+ case "empty":
2720
+ return new TexNode("empty", "");
2721
+ case "whitespace":
2722
+ return new TexNode("whitespace", node.content);
2723
+ case "atom":
2724
+ if (node.content === ":") {
2725
+ return new TexNode("symbol", "\\colon");
2726
+ }
2727
+ return new TexNode("element", node.content);
2728
+ case "symbol":
2729
+ if (node.content === "comma") {
2730
+ return new TexNode("element", ",");
2731
+ }
2732
+ return new TexNode("symbol", typst_token_to_tex(node.content));
2733
+ case "text":
2734
+ return new TexNode("text", node.content);
2735
+ case "comment":
2736
+ return new TexNode("comment", node.content);
2737
+ case "group": {
2738
+ const args = node.args.map(convert_typst_node_to_tex);
2739
+ if (node.content === "parenthesis") {
2740
+ args.unshift(new TexNode("element", "("));
2741
+ args.push(new TexNode("element", ")"));
2742
+ }
2743
+ return new TexNode("ordgroup", "", args);
2744
+ }
2745
+ case "funcCall": {
2746
+ if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
2747
+ if (node.content === "lr") {
2748
+ const body = node.args[0];
2749
+ if (body.type === "group") {
2750
+ let left_delim = body.args[0].content;
2751
+ let right_delim = body.args[body.args.length - 1].content;
2752
+ left_delim = apply_escape_if_needed2(left_delim);
2753
+ right_delim = apply_escape_if_needed2(right_delim);
2754
+ return new TexNode("ordgroup", "", [
2755
+ new TexNode("element", "\\left" + left_delim),
2756
+ ...body.args.slice(1, body.args.length - 1).map(convert_typst_node_to_tex),
2757
+ new TexNode("element", "\\right" + right_delim)
2758
+ ]);
2759
+ }
2760
+ }
2761
+ const command = typst_token_to_tex(node.content);
2762
+ return new TexNode("unaryFunc", command, node.args.map(convert_typst_node_to_tex));
2763
+ } else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
2764
+ if (node.content === "root") {
2765
+ const [degree, radicand] = node.args;
2766
+ const data = convert_typst_node_to_tex(degree);
2767
+ return new TexNode("unaryFunc", "\\sqrt", [convert_typst_node_to_tex(radicand)], data);
2768
+ }
2769
+ if (node.content === "overbrace" || node.content === "underbrace") {
2770
+ const [body, label] = node.args;
2771
+ const base = new TexNode("unaryFunc", "\\" + node.content, [convert_typst_node_to_tex(body)]);
2772
+ const script = convert_typst_node_to_tex(label);
2773
+ const data = node.content === "overbrace" ? { base, sup: script } : { base, sub: script };
2774
+ return new TexNode("supsub", "", [], data);
2775
+ }
2776
+ const command = typst_token_to_tex(node.content);
2777
+ return new TexNode("binaryFunc", command, node.args.map(convert_typst_node_to_tex));
2778
+ } else {
2779
+ return new TexNode("ordgroup", "", [
2780
+ new TexNode("symbol", typst_token_to_tex(node.content)),
2781
+ new TexNode("element", "("),
2782
+ ...node.args.map(convert_typst_node_to_tex),
2783
+ new TexNode("element", ")")
2784
+ ]);
2785
+ }
2786
+ }
2787
+ case "supsub": {
2788
+ const { base, sup, sub } = node.data;
2789
+ const base_tex = convert_typst_node_to_tex(base);
2790
+ let sup_tex;
2791
+ let sub_tex;
2792
+ if (sup) {
2793
+ sup_tex = convert_typst_node_to_tex(sup);
2794
+ }
2795
+ if (sub) {
2796
+ sub_tex = convert_typst_node_to_tex(sub);
2797
+ }
2798
+ const res = new TexNode("supsub", "", [], {
2799
+ base: base_tex,
2800
+ sup: sup_tex,
2801
+ sub: sub_tex
2802
+ });
2803
+ return res;
2804
+ }
2805
+ case "matrix": {
2806
+ const typst_data = node.data;
2807
+ const tex_data = typst_data.map((row) => row.map(convert_typst_node_to_tex));
2808
+ const matrix = new TexNode("beginend", "matrix", [], tex_data);
2809
+ return new TexNode("ordgroup", "", [
2810
+ new TexNode("element", "\\left("),
2811
+ matrix,
2812
+ new TexNode("element", "\\right)")
2813
+ ]);
2814
+ }
2815
+ case "control": {
2816
+ switch (node.content) {
2817
+ case "\\":
2818
+ return new TexNode("control", "\\\\");
2819
+ case "&":
2820
+ return new TexNode("control", "&");
2821
+ default:
2822
+ throw new Error("[convert_typst_node_to_tex] Unimplemented control: " + node.content);
2823
+ }
2824
+ }
2825
+ case "fraction": {
2826
+ const [numerator, denominator] = node.args;
2827
+ const num_tex = convert_typst_node_to_tex(numerator);
2828
+ const den_tex = convert_typst_node_to_tex(denominator);
2829
+ return new TexNode("binaryFunc", "\\frac", [num_tex, den_tex]);
2830
+ }
2831
+ default:
2832
+ throw new Error("[convert_typst_node_to_tex] Unimplemented type: " + node.type);
2833
+ }
2834
+ }
2835
+ function typst_token_to_tex(token) {
2836
+ if (/^[a-zA-Z0-9]$/.test(token)) {
2837
+ return token;
2838
+ } else if (token === "thin") {
2839
+ return "\\,";
2840
+ } else if (reverseSymbolMap.has(token)) {
2841
+ return "\\" + reverseSymbolMap.get(token);
2842
+ }
2843
+ return "\\" + token;
2844
+ }
2845
+ var TYPST_UNARY_FUNCTIONS = [
2846
+ "sqrt",
2847
+ "bold",
2848
+ "arrow",
2849
+ "upright",
2850
+ "lr",
2851
+ "op",
2852
+ "macron",
2853
+ "dot",
2854
+ "dot.double",
2855
+ "hat",
2856
+ "tilde",
2857
+ "overline",
2858
+ "underline",
2859
+ "bb",
2860
+ "cal",
2861
+ "frak"
2862
+ ];
2863
+ var TYPST_BINARY_FUNCTIONS = [
2864
+ "frac",
2865
+ "root",
2866
+ "overbrace",
2867
+ "underbrace"
2868
+ ];
2869
+
2870
+ class TexWriter {
2871
+ buffer = "";
2872
+ queue = [];
2873
+ writeBuffer(token) {
2874
+ const str = token.toString();
2875
+ let no_need_space = false;
2876
+ if (token.type === 4 /* SPACE */) {
2877
+ no_need_space = true;
2878
+ } else {
2879
+ no_need_space ||= /[{\(\[\|]$/.test(this.buffer);
2880
+ no_need_space ||= /\\\w+$/.test(this.buffer) && str === "[";
2881
+ no_need_space ||= /^[\.,;:!\?\(\)\]{}_^]$/.test(str);
2882
+ no_need_space ||= ["\\{", "\\}"].includes(str);
2883
+ no_need_space ||= str === "'";
2884
+ no_need_space ||= this.buffer.endsWith("_") || this.buffer.endsWith("^");
2885
+ no_need_space ||= /\s$/.test(this.buffer);
2886
+ no_need_space ||= /^\s/.test(str);
2887
+ no_need_space ||= this.buffer === "";
2888
+ no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
2889
+ no_need_space ||= this.buffer.endsWith("&") && str === "=";
2890
+ }
2891
+ if (!no_need_space) {
2892
+ this.buffer += " ";
2893
+ }
2894
+ this.buffer += str;
2895
+ }
2896
+ append(node) {
2897
+ const alignment_char = new TexNode("control", "&");
2898
+ const newline_char = new TexNode("control", "\\\\");
2899
+ if (node.type === "ordgroup" && array_includes(node.args, alignment_char)) {
2900
+ const rows = array_split(node.args, newline_char);
2901
+ const data = [];
2902
+ for (const row of rows) {
2903
+ const cells = array_split(row, alignment_char);
2904
+ data.push(cells.map((cell) => new TexNode("ordgroup", "", cell)));
2905
+ }
2906
+ node = new TexNode("beginend", "aligned", [], data);
2907
+ }
2908
+ this.queue = this.queue.concat(node.serialize());
2909
+ }
2910
+ flushQueue() {
2911
+ for (let i = 0;i < this.queue.length; i++) {
2912
+ this.writeBuffer(this.queue[i]);
2913
+ }
2914
+ this.queue = [];
2915
+ }
2916
+ finalize() {
2917
+ this.flushQueue();
2918
+ return this.buffer;
2919
+ }
2920
+ }
2921
+
2162
2922
  // src/index.ts
2163
2923
  function tex2typst(tex, options) {
2164
2924
  const opt = {
@@ -2184,7 +2944,15 @@ function tex2typst(tex, options) {
2184
2944
  writer2.serialize(typstTree);
2185
2945
  return writer2.finalize();
2186
2946
  }
2947
+ function typst2tex(typst) {
2948
+ const typstTree = parseTypst(typst);
2949
+ const texTree = convert_typst_node_to_tex(typstTree);
2950
+ const writer2 = new TexWriter;
2951
+ writer2.append(texTree);
2952
+ return writer2.finalize();
2953
+ }
2187
2954
  export {
2955
+ typst2tex,
2188
2956
  tex2typst,
2189
2957
  symbolMap
2190
2958
  };