tex2typst 0.2.16 → 0.3.0-beta-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generic.d.ts +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1235 -442
- package/dist/map.d.ts +2 -1
- package/dist/tex-parser.d.ts +1 -7
- package/dist/tex-writer.d.ts +11 -0
- package/dist/tex2typst.min.js +19 -1
- package/dist/types.d.ts +15 -5
- package/dist/typst-parser.d.ts +21 -0
- package/dist/util.d.ts +3 -0
- package/package.json +1 -1
- package/src/generic.ts +37 -0
- package/src/index.ts +10 -0
- package/src/map.ts +28 -2
- package/src/tex-parser.ts +24 -101
- package/src/tex-writer.ts +249 -0
- package/src/tex2typst.ts +2 -1
- package/src/types.ts +206 -14
- package/src/typst-parser.ts +507 -0
- package/src/util.ts +14 -0
- package/src/writer.ts +51 -47
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"],
|
|
@@ -42,7 +42,7 @@ var symbolMap = new Map([
|
|
|
42
42
|
["approx", "approx"],
|
|
43
43
|
["cong", "tilde.equiv"],
|
|
44
44
|
["simeq", "tilde.eq"],
|
|
45
|
-
["asymp", "
|
|
45
|
+
["asymp", "≍"],
|
|
46
46
|
["equiv", "equiv"],
|
|
47
47
|
["propto", "prop"],
|
|
48
48
|
["gets", "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
|
+
class TexToken {
|
|
1031
|
+
type;
|
|
1032
|
+
value;
|
|
1033
|
+
constructor(type, value) {
|
|
1034
|
+
this.type = type;
|
|
1035
|
+
this.value = value;
|
|
1036
|
+
}
|
|
1037
|
+
eq(token) {
|
|
1038
|
+
return this.type === token.type && this.value === token.value;
|
|
1039
|
+
}
|
|
1040
|
+
toString() {
|
|
1041
|
+
switch (this.type) {
|
|
1042
|
+
case 2 /* TEXT */:
|
|
1043
|
+
return `\\text{${this.value}}`;
|
|
1044
|
+
case 3 /* COMMENT */:
|
|
1045
|
+
return `%${this.value}`;
|
|
1046
|
+
default:
|
|
1047
|
+
return this.value;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
function apply_escape_if_needed(c) {
|
|
1052
|
+
if (["{", "}", "%"].includes(c)) {
|
|
1053
|
+
return "\\" + c;
|
|
1054
|
+
}
|
|
1055
|
+
return c;
|
|
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
|
-
|
|
1069
|
+
eq(other) {
|
|
1050
1070
|
return this.type === other.type && this.content === other.content;
|
|
1051
1071
|
}
|
|
1052
1072
|
toString() {
|
|
@@ -1057,26 +1077,174 @@ 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 */, `
|
|
1170
|
+
`));
|
|
1171
|
+
for (let i = 0;i < matrix.length; i++) {
|
|
1172
|
+
const row = matrix[i];
|
|
1173
|
+
for (let j = 0;j < row.length; j++) {
|
|
1174
|
+
const cell = row[j];
|
|
1175
|
+
tokens = tokens.concat(cell.serialize());
|
|
1176
|
+
if (j !== row.length - 1) {
|
|
1177
|
+
tokens.push(new TexToken(6 /* CONTROL */, "&"));
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (i !== matrix.length - 1) {
|
|
1181
|
+
tokens.push(new TexToken(6 /* CONTROL */, "\\\\"));
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
tokens.push(new TexToken(5 /* NEWLINE */, `
|
|
1185
|
+
`));
|
|
1186
|
+
tokens.push(new TexToken(1 /* COMMAND */, `\\end{${this.content}}`));
|
|
1187
|
+
return tokens;
|
|
1188
|
+
}
|
|
1189
|
+
default:
|
|
1190
|
+
throw new Error("[TexNode.serialize] Unimplemented type: " + this.type);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1060
1193
|
}
|
|
1061
1194
|
class TypstToken {
|
|
1062
1195
|
type;
|
|
1063
|
-
|
|
1196
|
+
value;
|
|
1064
1197
|
constructor(type, content) {
|
|
1065
1198
|
this.type = type;
|
|
1066
|
-
this.
|
|
1199
|
+
this.value = content;
|
|
1067
1200
|
}
|
|
1068
1201
|
eq(other) {
|
|
1069
|
-
return this.type === other.type && this.
|
|
1202
|
+
return this.type === other.type && this.value === other.value;
|
|
1070
1203
|
}
|
|
1071
1204
|
isOneOf(tokens) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1205
|
+
return array_includes(tokens, this);
|
|
1206
|
+
}
|
|
1207
|
+
toNode() {
|
|
1208
|
+
switch (this.type) {
|
|
1209
|
+
case 2 /* TEXT */:
|
|
1210
|
+
return new TypstNode("text", this.value);
|
|
1211
|
+
case 3 /* COMMENT */:
|
|
1212
|
+
return new TypstNode("comment", this.value);
|
|
1213
|
+
case 4 /* SPACE */:
|
|
1214
|
+
case 7 /* NEWLINE */:
|
|
1215
|
+
return new TypstNode("whitespace", this.value);
|
|
1216
|
+
case 1 /* ELEMENT */:
|
|
1217
|
+
return new TypstNode("atom", this.value);
|
|
1218
|
+
case 0 /* SYMBOL */:
|
|
1219
|
+
return new TypstNode("symbol", this.value);
|
|
1220
|
+
case 6 /* CONTROL */: {
|
|
1221
|
+
const controlChar = this.value;
|
|
1222
|
+
switch (controlChar) {
|
|
1223
|
+
case "":
|
|
1224
|
+
case "_":
|
|
1225
|
+
case "^":
|
|
1226
|
+
return new TypstNode("empty", "");
|
|
1227
|
+
case "&":
|
|
1228
|
+
return new TypstNode("control", "&");
|
|
1229
|
+
case "\\":
|
|
1230
|
+
return new TypstNode("control", "\\");
|
|
1231
|
+
default:
|
|
1232
|
+
throw new Error(`Unexpected control character ${controlChar}`);
|
|
1233
|
+
}
|
|
1077
1234
|
}
|
|
1235
|
+
default:
|
|
1236
|
+
throw new Error(`Unexpected token type ${this.type}`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
toString() {
|
|
1240
|
+
switch (this.type) {
|
|
1241
|
+
case 2 /* TEXT */:
|
|
1242
|
+
return `"${this.value}"`;
|
|
1243
|
+
case 3 /* COMMENT */:
|
|
1244
|
+
return `//${this.value}`;
|
|
1245
|
+
default:
|
|
1246
|
+
return this.value;
|
|
1078
1247
|
}
|
|
1079
|
-
return found;
|
|
1080
1248
|
}
|
|
1081
1249
|
}
|
|
1082
1250
|
|
|
@@ -1095,17 +1263,65 @@ class TypstNode {
|
|
|
1095
1263
|
setOptions(options) {
|
|
1096
1264
|
this.options = options;
|
|
1097
1265
|
}
|
|
1098
|
-
|
|
1266
|
+
eq(other) {
|
|
1099
1267
|
return this.type === other.type && this.content === other.content;
|
|
1100
1268
|
}
|
|
1101
1269
|
}
|
|
1102
1270
|
|
|
1103
|
-
// src/
|
|
1271
|
+
// src/util.ts
|
|
1272
|
+
function isalpha(char) {
|
|
1273
|
+
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(char);
|
|
1274
|
+
}
|
|
1275
|
+
function isdigit(char) {
|
|
1276
|
+
return "0123456789".includes(char);
|
|
1277
|
+
}
|
|
1104
1278
|
function assert(condition, message = "") {
|
|
1105
1279
|
if (!condition) {
|
|
1106
|
-
throw new
|
|
1280
|
+
throw new Error(message);
|
|
1107
1281
|
}
|
|
1108
1282
|
}
|
|
1283
|
+
|
|
1284
|
+
// src/tex-parser.ts
|
|
1285
|
+
var UNARY_COMMANDS = [
|
|
1286
|
+
"sqrt",
|
|
1287
|
+
"text",
|
|
1288
|
+
"bar",
|
|
1289
|
+
"bold",
|
|
1290
|
+
"boldsymbol",
|
|
1291
|
+
"ddot",
|
|
1292
|
+
"dot",
|
|
1293
|
+
"hat",
|
|
1294
|
+
"mathbb",
|
|
1295
|
+
"mathbf",
|
|
1296
|
+
"mathcal",
|
|
1297
|
+
"mathfrak",
|
|
1298
|
+
"mathit",
|
|
1299
|
+
"mathrm",
|
|
1300
|
+
"mathscr",
|
|
1301
|
+
"mathsf",
|
|
1302
|
+
"mathtt",
|
|
1303
|
+
"operatorname",
|
|
1304
|
+
"overbrace",
|
|
1305
|
+
"overline",
|
|
1306
|
+
"pmb",
|
|
1307
|
+
"rm",
|
|
1308
|
+
"tilde",
|
|
1309
|
+
"underbrace",
|
|
1310
|
+
"underline",
|
|
1311
|
+
"vec",
|
|
1312
|
+
"widehat",
|
|
1313
|
+
"widetilde"
|
|
1314
|
+
];
|
|
1315
|
+
var BINARY_COMMANDS = [
|
|
1316
|
+
"frac",
|
|
1317
|
+
"tfrac",
|
|
1318
|
+
"binom",
|
|
1319
|
+
"dbinom",
|
|
1320
|
+
"dfrac",
|
|
1321
|
+
"tbinom",
|
|
1322
|
+
"overset"
|
|
1323
|
+
];
|
|
1324
|
+
var EMPTY_NODE = new TexNode("empty", "");
|
|
1109
1325
|
function get_command_param_num(command) {
|
|
1110
1326
|
if (UNARY_COMMANDS.includes(command)) {
|
|
1111
1327
|
return 1;
|
|
@@ -1115,46 +1331,10 @@ function get_command_param_num(command) {
|
|
|
1115
1331
|
return 0;
|
|
1116
1332
|
}
|
|
1117
1333
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
-
}
|
|
1334
|
+
var LEFT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "{");
|
|
1335
|
+
var RIGHT_CURLY_BRACKET = new TexToken(6 /* CONTROL */, "}");
|
|
1336
|
+
var LEFT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "[");
|
|
1337
|
+
var RIGHT_SQUARE_BRACKET = new TexToken(0 /* ELEMENT */, "]");
|
|
1158
1338
|
function eat_whitespaces(tokens, start) {
|
|
1159
1339
|
let pos = start;
|
|
1160
1340
|
while (pos < tokens.length && [4 /* SPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) {
|
|
@@ -1164,7 +1344,7 @@ function eat_whitespaces(tokens, start) {
|
|
|
1164
1344
|
}
|
|
1165
1345
|
function eat_parenthesis(tokens, start) {
|
|
1166
1346
|
const firstToken = tokens[start];
|
|
1167
|
-
if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}"].includes(firstToken.value)) {
|
|
1347
|
+
if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}", "."].includes(firstToken.value)) {
|
|
1168
1348
|
return firstToken;
|
|
1169
1349
|
} else if (firstToken.type === 1 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) {
|
|
1170
1350
|
return firstToken;
|
|
@@ -1186,37 +1366,32 @@ function eat_command_name(latex, start) {
|
|
|
1186
1366
|
}
|
|
1187
1367
|
return latex.substring(start, pos);
|
|
1188
1368
|
}
|
|
1189
|
-
function
|
|
1369
|
+
function find_closing_match(tokens, start, leftToken, rightToken) {
|
|
1370
|
+
assert(tokens[start].eq(leftToken));
|
|
1190
1371
|
let count = 1;
|
|
1191
|
-
let pos = start;
|
|
1372
|
+
let pos = start + 1;
|
|
1192
1373
|
while (count > 0) {
|
|
1193
1374
|
if (pos >= tokens.length) {
|
|
1194
1375
|
return -1;
|
|
1195
1376
|
}
|
|
1196
|
-
if (tokens[pos].eq(
|
|
1377
|
+
if (tokens[pos].eq(leftToken)) {
|
|
1197
1378
|
count += 1;
|
|
1198
|
-
} else if (tokens[pos].eq(
|
|
1379
|
+
} else if (tokens[pos].eq(rightToken)) {
|
|
1199
1380
|
count -= 1;
|
|
1200
1381
|
}
|
|
1201
1382
|
pos += 1;
|
|
1202
1383
|
}
|
|
1203
1384
|
return pos - 1;
|
|
1204
1385
|
}
|
|
1386
|
+
var LEFT_COMMAND = new TexToken(1 /* COMMAND */, "\\left");
|
|
1387
|
+
var RIGHT_COMMAND = new TexToken(1 /* COMMAND */, "\\right");
|
|
1388
|
+
function find_closing_right_command(tokens, start) {
|
|
1389
|
+
return find_closing_match(tokens, start, LEFT_COMMAND, RIGHT_COMMAND);
|
|
1390
|
+
}
|
|
1391
|
+
var BEGIN_COMMAND = new TexToken(1 /* COMMAND */, "\\begin");
|
|
1392
|
+
var END_COMMAND = new TexToken(1 /* COMMAND */, "\\end");
|
|
1205
1393
|
function find_closing_end_command(tokens, start) {
|
|
1206
|
-
|
|
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;
|
|
1394
|
+
return find_closing_match(tokens, start, BEGIN_COMMAND, END_COMMAND);
|
|
1220
1395
|
}
|
|
1221
1396
|
function find_closing_curly_bracket_char(latex, start) {
|
|
1222
1397
|
assert(latex[start] === "{");
|
|
@@ -1248,7 +1423,8 @@ function tokenize(latex) {
|
|
|
1248
1423
|
switch (firstChar) {
|
|
1249
1424
|
case "%": {
|
|
1250
1425
|
let newPos = pos + 1;
|
|
1251
|
-
while (newPos < latex.length && latex[newPos] !==
|
|
1426
|
+
while (newPos < latex.length && latex[newPos] !== `
|
|
1427
|
+
`) {
|
|
1252
1428
|
newPos += 1;
|
|
1253
1429
|
}
|
|
1254
1430
|
token = new TexToken(3 /* COMMENT */, latex.slice(pos + 1, newPos));
|
|
@@ -1263,16 +1439,20 @@ function tokenize(latex) {
|
|
|
1263
1439
|
token = new TexToken(6 /* CONTROL */, firstChar);
|
|
1264
1440
|
pos++;
|
|
1265
1441
|
break;
|
|
1266
|
-
case
|
|
1442
|
+
case `
|
|
1443
|
+
`:
|
|
1267
1444
|
token = new TexToken(5 /* NEWLINE */, firstChar);
|
|
1268
1445
|
pos++;
|
|
1269
1446
|
break;
|
|
1270
1447
|
case "\r": {
|
|
1271
|
-
if (pos + 1 < latex.length && latex[pos + 1] ===
|
|
1272
|
-
|
|
1448
|
+
if (pos + 1 < latex.length && latex[pos + 1] === `
|
|
1449
|
+
`) {
|
|
1450
|
+
token = new TexToken(5 /* NEWLINE */, `
|
|
1451
|
+
`);
|
|
1273
1452
|
pos += 2;
|
|
1274
1453
|
} else {
|
|
1275
|
-
token = new TexToken(5 /* NEWLINE */,
|
|
1454
|
+
token = new TexToken(5 /* NEWLINE */, `
|
|
1455
|
+
`);
|
|
1276
1456
|
pos++;
|
|
1277
1457
|
}
|
|
1278
1458
|
break;
|
|
@@ -1311,7 +1491,7 @@ function tokenize(latex) {
|
|
|
1311
1491
|
token = new TexToken(0 /* ELEMENT */, latex.slice(pos, newPos));
|
|
1312
1492
|
} else if (isalpha(firstChar)) {
|
|
1313
1493
|
token = new TexToken(0 /* ELEMENT */, firstChar);
|
|
1314
|
-
} else if ("
|
|
1494
|
+
} else if ("+-*/='<>!.,;?()[]|".includes(firstChar)) {
|
|
1315
1495
|
token = new TexToken(0 /* ELEMENT */, firstChar);
|
|
1316
1496
|
} else {
|
|
1317
1497
|
token = new TexToken(7 /* UNKNOWN */, firstChar);
|
|
@@ -1339,115 +1519,22 @@ function tokenize(latex) {
|
|
|
1339
1519
|
}
|
|
1340
1520
|
return tokens;
|
|
1341
1521
|
}
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
continue;
|
|
1348
|
-
}
|
|
1349
|
-
if (tokens[i].type === 4 /* SPACE */ && i - 1 >= 0 && is_script_mark(tokens[i - 1])) {
|
|
1350
|
-
continue;
|
|
1351
|
-
}
|
|
1352
|
-
out_tokens.push(tokens[i]);
|
|
1522
|
+
|
|
1523
|
+
class LatexParserError extends Error {
|
|
1524
|
+
constructor(message) {
|
|
1525
|
+
super(message);
|
|
1526
|
+
this.name = "LatexParserError";
|
|
1353
1527
|
}
|
|
1354
|
-
return out_tokens;
|
|
1355
1528
|
}
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
return out_tokens;
|
|
1367
|
-
}
|
|
1368
|
-
function parseTex(tex, customTexMacros) {
|
|
1369
|
-
const parser = new LatexParser;
|
|
1370
|
-
let tokens = tokenize(tex);
|
|
1371
|
-
tokens = passIgnoreWhitespaceBeforeScriptMark(tokens);
|
|
1372
|
-
tokens = passExpandCustomTexMacros(tokens, customTexMacros);
|
|
1373
|
-
return parser.parse(tokens);
|
|
1374
|
-
}
|
|
1375
|
-
var UNARY_COMMANDS = [
|
|
1376
|
-
"sqrt",
|
|
1377
|
-
"text",
|
|
1378
|
-
"bar",
|
|
1379
|
-
"bold",
|
|
1380
|
-
"boldsymbol",
|
|
1381
|
-
"ddot",
|
|
1382
|
-
"dot",
|
|
1383
|
-
"hat",
|
|
1384
|
-
"mathbb",
|
|
1385
|
-
"mathbf",
|
|
1386
|
-
"mathcal",
|
|
1387
|
-
"mathfrak",
|
|
1388
|
-
"mathit",
|
|
1389
|
-
"mathrm",
|
|
1390
|
-
"mathscr",
|
|
1391
|
-
"mathsf",
|
|
1392
|
-
"mathtt",
|
|
1393
|
-
"operatorname",
|
|
1394
|
-
"overbrace",
|
|
1395
|
-
"overline",
|
|
1396
|
-
"pmb",
|
|
1397
|
-
"rm",
|
|
1398
|
-
"tilde",
|
|
1399
|
-
"underbrace",
|
|
1400
|
-
"underline",
|
|
1401
|
-
"vec",
|
|
1402
|
-
"widehat",
|
|
1403
|
-
"widetilde"
|
|
1404
|
-
];
|
|
1405
|
-
var BINARY_COMMANDS = [
|
|
1406
|
-
"frac",
|
|
1407
|
-
"tfrac",
|
|
1408
|
-
"binom",
|
|
1409
|
-
"dbinom",
|
|
1410
|
-
"dfrac",
|
|
1411
|
-
"tbinom",
|
|
1412
|
-
"overset"
|
|
1413
|
-
];
|
|
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
|
-
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");
|
|
1435
|
-
|
|
1436
|
-
class LatexParserError extends Error {
|
|
1437
|
-
constructor(message) {
|
|
1438
|
-
super(message);
|
|
1439
|
-
this.name = "LatexParserError";
|
|
1440
|
-
}
|
|
1441
|
-
}
|
|
1442
|
-
var SUB_SYMBOL = new TexToken(6 /* CONTROL */, "_");
|
|
1443
|
-
var SUP_SYMBOL = new TexToken(6 /* CONTROL */, "^");
|
|
1444
|
-
|
|
1445
|
-
class LatexParser {
|
|
1446
|
-
space_sensitive;
|
|
1447
|
-
newline_sensitive;
|
|
1448
|
-
constructor(space_sensitive = false, newline_sensitive = true) {
|
|
1449
|
-
this.space_sensitive = space_sensitive;
|
|
1450
|
-
this.newline_sensitive = newline_sensitive;
|
|
1529
|
+
var SUB_SYMBOL = new TexToken(6 /* CONTROL */, "_");
|
|
1530
|
+
var SUP_SYMBOL = new TexToken(6 /* CONTROL */, "^");
|
|
1531
|
+
|
|
1532
|
+
class LatexParser {
|
|
1533
|
+
space_sensitive;
|
|
1534
|
+
newline_sensitive;
|
|
1535
|
+
constructor(space_sensitive = false, newline_sensitive = true) {
|
|
1536
|
+
this.space_sensitive = space_sensitive;
|
|
1537
|
+
this.newline_sensitive = newline_sensitive;
|
|
1451
1538
|
}
|
|
1452
1539
|
parse(tokens) {
|
|
1453
1540
|
const results = [];
|
|
@@ -1462,7 +1549,8 @@ class LatexParser {
|
|
|
1462
1549
|
if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
|
|
1463
1550
|
continue;
|
|
1464
1551
|
}
|
|
1465
|
-
if (!this.newline_sensitive && res.content ===
|
|
1552
|
+
if (!this.newline_sensitive && res.content === `
|
|
1553
|
+
`) {
|
|
1466
1554
|
continue;
|
|
1467
1555
|
}
|
|
1468
1556
|
}
|
|
@@ -1565,7 +1653,7 @@ class LatexParser {
|
|
|
1565
1653
|
const controlChar = firstToken.value;
|
|
1566
1654
|
switch (controlChar) {
|
|
1567
1655
|
case "{":
|
|
1568
|
-
const posClosingBracket =
|
|
1656
|
+
const posClosingBracket = find_closing_match(tokens, start, LEFT_CURLY_BRACKET, RIGHT_CURLY_BRACKET);
|
|
1569
1657
|
const exprInside = tokens.slice(start + 1, posClosingBracket);
|
|
1570
1658
|
return [this.parse(exprInside), posClosingBracket + 1];
|
|
1571
1659
|
case "}":
|
|
@@ -1574,12 +1662,9 @@ class LatexParser {
|
|
|
1574
1662
|
return [new TexNode("control", "\\\\"), start + 1];
|
|
1575
1663
|
case "\\,":
|
|
1576
1664
|
return [new TexNode("control", "\\,"), start + 1];
|
|
1577
|
-
case "_":
|
|
1578
|
-
|
|
1579
|
-
}
|
|
1580
|
-
case "^": {
|
|
1665
|
+
case "_":
|
|
1666
|
+
case "^":
|
|
1581
1667
|
return [EMPTY_NODE, start];
|
|
1582
|
-
}
|
|
1583
1668
|
case "&":
|
|
1584
1669
|
return [new TexNode("control", "&"), start + 1];
|
|
1585
1670
|
default:
|
|
@@ -1606,7 +1691,7 @@ class LatexParser {
|
|
|
1606
1691
|
case 1: {
|
|
1607
1692
|
if (command === "\\sqrt" && pos < tokens.length && tokens[pos].eq(LEFT_SQUARE_BRACKET)) {
|
|
1608
1693
|
const posLeftSquareBracket = pos;
|
|
1609
|
-
const posRightSquareBracket =
|
|
1694
|
+
const posRightSquareBracket = find_closing_match(tokens, pos, LEFT_SQUARE_BRACKET, RIGHT_SQUARE_BRACKET);
|
|
1610
1695
|
const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket);
|
|
1611
1696
|
const exponent = this.parse(exprInside);
|
|
1612
1697
|
const [arg12, newPos2] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1);
|
|
@@ -1646,7 +1731,7 @@ class LatexParser {
|
|
|
1646
1731
|
}
|
|
1647
1732
|
pos++;
|
|
1648
1733
|
const exprInsideStart = pos;
|
|
1649
|
-
const idx = find_closing_right_command(tokens,
|
|
1734
|
+
const idx = find_closing_right_command(tokens, start);
|
|
1650
1735
|
if (idx === -1) {
|
|
1651
1736
|
throw new LatexParserError("No matching \\right");
|
|
1652
1737
|
}
|
|
@@ -1681,7 +1766,7 @@ class LatexParser {
|
|
|
1681
1766
|
pos += 3;
|
|
1682
1767
|
pos += eat_whitespaces(tokens, pos).length;
|
|
1683
1768
|
const exprInsideStart = pos;
|
|
1684
|
-
const endIdx = find_closing_end_command(tokens,
|
|
1769
|
+
const endIdx = find_closing_end_command(tokens, start);
|
|
1685
1770
|
if (endIdx === -1) {
|
|
1686
1771
|
throw new LatexParserError("No matching \\end");
|
|
1687
1772
|
}
|
|
@@ -1716,7 +1801,8 @@ class LatexParser {
|
|
|
1716
1801
|
if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
|
|
1717
1802
|
continue;
|
|
1718
1803
|
}
|
|
1719
|
-
if (!this.newline_sensitive && res.content ===
|
|
1804
|
+
if (!this.newline_sensitive && res.content === `
|
|
1805
|
+
`) {
|
|
1720
1806
|
continue;
|
|
1721
1807
|
}
|
|
1722
1808
|
}
|
|
@@ -1735,15 +1821,57 @@ class LatexParser {
|
|
|
1735
1821
|
return allRows;
|
|
1736
1822
|
}
|
|
1737
1823
|
}
|
|
1824
|
+
function passIgnoreWhitespaceBeforeScriptMark(tokens) {
|
|
1825
|
+
const is_script_mark = (token) => token.eq(SUB_SYMBOL) || token.eq(SUP_SYMBOL);
|
|
1826
|
+
let out_tokens = [];
|
|
1827
|
+
for (let i = 0;i < tokens.length; i++) {
|
|
1828
|
+
if (tokens[i].type === 4 /* SPACE */ && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) {
|
|
1829
|
+
continue;
|
|
1830
|
+
}
|
|
1831
|
+
if (tokens[i].type === 4 /* SPACE */ && i - 1 >= 0 && is_script_mark(tokens[i - 1])) {
|
|
1832
|
+
continue;
|
|
1833
|
+
}
|
|
1834
|
+
out_tokens.push(tokens[i]);
|
|
1835
|
+
}
|
|
1836
|
+
return out_tokens;
|
|
1837
|
+
}
|
|
1838
|
+
function passExpandCustomTexMacros(tokens, customTexMacros) {
|
|
1839
|
+
let out_tokens = [];
|
|
1840
|
+
for (const token of tokens) {
|
|
1841
|
+
if (token.type === 1 /* COMMAND */ && customTexMacros[token.value]) {
|
|
1842
|
+
const expanded_tokens = tokenize(customTexMacros[token.value]);
|
|
1843
|
+
out_tokens = out_tokens.concat(expanded_tokens);
|
|
1844
|
+
} else {
|
|
1845
|
+
out_tokens.push(token);
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return out_tokens;
|
|
1849
|
+
}
|
|
1850
|
+
function parseTex(tex, customTexMacros) {
|
|
1851
|
+
const parser = new LatexParser;
|
|
1852
|
+
let tokens = tokenize(tex);
|
|
1853
|
+
tokens = passIgnoreWhitespaceBeforeScriptMark(tokens);
|
|
1854
|
+
tokens = passExpandCustomTexMacros(tokens, customTexMacros);
|
|
1855
|
+
return parser.parse(tokens);
|
|
1856
|
+
}
|
|
1738
1857
|
|
|
1739
1858
|
// src/writer.ts
|
|
1859
|
+
var TYPST_INTRINSIC_SYMBOLS = [
|
|
1860
|
+
"dim",
|
|
1861
|
+
"id",
|
|
1862
|
+
"im",
|
|
1863
|
+
"mod",
|
|
1864
|
+
"Pr",
|
|
1865
|
+
"sech",
|
|
1866
|
+
"csch"
|
|
1867
|
+
];
|
|
1740
1868
|
function is_delimiter(c) {
|
|
1741
|
-
return c.type === "atom" && ["(", ")", "[", "]", "{", "}", "|", "
|
|
1869
|
+
return c.type === "atom" && ["(", ")", "[", "]", "{", "}", "|", "⌊", "⌋", "⌈", "⌉"].includes(c.content);
|
|
1742
1870
|
}
|
|
1743
1871
|
function convert_overset(node) {
|
|
1744
1872
|
const [sup, base] = node.args;
|
|
1745
1873
|
const is_def = (n) => {
|
|
1746
|
-
if (n.
|
|
1874
|
+
if (n.eq(new TexNode("text", "def"))) {
|
|
1747
1875
|
return true;
|
|
1748
1876
|
}
|
|
1749
1877
|
if (n.type === "ordgroup" && n.args.length === 3) {
|
|
@@ -1751,169 +1879,28 @@ function convert_overset(node) {
|
|
|
1751
1879
|
const d = new TexNode("element", "d");
|
|
1752
1880
|
const e = new TexNode("element", "e");
|
|
1753
1881
|
const f = new TexNode("element", "f");
|
|
1754
|
-
if (a1.
|
|
1882
|
+
if (a1.eq(d) && a2.eq(e) && a3.eq(f)) {
|
|
1755
1883
|
return true;
|
|
1756
1884
|
}
|
|
1757
1885
|
}
|
|
1758
1886
|
return false;
|
|
1759
1887
|
};
|
|
1760
|
-
const is_eq = (n) => n.
|
|
1888
|
+
const is_eq = (n) => n.eq(new TexNode("element", "="));
|
|
1761
1889
|
if (is_def(sup) && is_eq(base)) {
|
|
1762
1890
|
return new TypstNode("symbol", "eq.def");
|
|
1763
1891
|
}
|
|
1764
|
-
const op_call = new TypstNode("
|
|
1892
|
+
const op_call = new TypstNode("funcCall", "op", [convertTree(base)]);
|
|
1765
1893
|
op_call.setOptions({ limits: "#true" });
|
|
1766
1894
|
return new TypstNode("supsub", "", [], {
|
|
1767
1895
|
base: op_call,
|
|
1768
1896
|
sup: convertTree(sup)
|
|
1769
1897
|
});
|
|
1770
1898
|
}
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
return new TypstNode("whitespace", node.content);
|
|
1777
|
-
case "ordgroup":
|
|
1778
|
-
return new TypstNode("group", "", node.args.map(convertTree));
|
|
1779
|
-
case "element":
|
|
1780
|
-
return new TypstNode("atom", convertToken(node.content));
|
|
1781
|
-
case "symbol":
|
|
1782
|
-
return new TypstNode("symbol", convertToken(node.content));
|
|
1783
|
-
case "text":
|
|
1784
|
-
return new TypstNode("text", node.content);
|
|
1785
|
-
case "comment":
|
|
1786
|
-
return new TypstNode("comment", node.content);
|
|
1787
|
-
case "supsub": {
|
|
1788
|
-
let { base, sup, sub } = node.data;
|
|
1789
|
-
if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) {
|
|
1790
|
-
return new TypstNode("binaryFunc", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
|
|
1791
|
-
} else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) {
|
|
1792
|
-
return new TypstNode("binaryFunc", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
|
|
1793
|
-
}
|
|
1794
|
-
const data = {
|
|
1795
|
-
base: convertTree(base)
|
|
1796
|
-
};
|
|
1797
|
-
if (data.base.type === "empty") {
|
|
1798
|
-
data.base = new TypstNode("text", "");
|
|
1799
|
-
}
|
|
1800
|
-
if (sup) {
|
|
1801
|
-
data.sup = convertTree(sup);
|
|
1802
|
-
}
|
|
1803
|
-
if (sub) {
|
|
1804
|
-
data.sub = convertTree(sub);
|
|
1805
|
-
}
|
|
1806
|
-
return new TypstNode("supsub", "", [], data);
|
|
1807
|
-
}
|
|
1808
|
-
case "leftright": {
|
|
1809
|
-
const [left, body, right] = node.args;
|
|
1810
|
-
const group = new TypstNode("group", "", node.args.map(convertTree));
|
|
1811
|
-
if ([
|
|
1812
|
-
"[]",
|
|
1813
|
-
"()",
|
|
1814
|
-
"\\{\\}",
|
|
1815
|
-
"\\lfloor\\rfloor",
|
|
1816
|
-
"\\lceil\\rceil",
|
|
1817
|
-
"\\lfloor\\rceil"
|
|
1818
|
-
].includes(left.content + right.content)) {
|
|
1819
|
-
return group;
|
|
1820
|
-
}
|
|
1821
|
-
return new TypstNode("unaryFunc", "lr", [group]);
|
|
1822
|
-
}
|
|
1823
|
-
case "binaryFunc": {
|
|
1824
|
-
if (node.content === "\\overset") {
|
|
1825
|
-
return convert_overset(node);
|
|
1826
|
-
}
|
|
1827
|
-
return new TypstNode("binaryFunc", convertToken(node.content), node.args.map(convertTree));
|
|
1828
|
-
}
|
|
1829
|
-
case "unaryFunc": {
|
|
1830
|
-
const arg0 = convertTree(node.args[0]);
|
|
1831
|
-
if (node.content === "\\sqrt" && node.data) {
|
|
1832
|
-
const data = convertTree(node.data);
|
|
1833
|
-
return new TypstNode("binaryFunc", "root", [data, arg0]);
|
|
1834
|
-
}
|
|
1835
|
-
if (node.content === "\\mathbf") {
|
|
1836
|
-
const inner = new TypstNode("unaryFunc", "bold", [arg0]);
|
|
1837
|
-
return new TypstNode("unaryFunc", "upright", [inner]);
|
|
1838
|
-
}
|
|
1839
|
-
if (node.content === "\\mathbb" && arg0.type === "atom" && /^[A-Z]$/.test(arg0.content)) {
|
|
1840
|
-
return new TypstNode("symbol", arg0.content + arg0.content);
|
|
1841
|
-
}
|
|
1842
|
-
if (node.content === "\\operatorname") {
|
|
1843
|
-
const body = node.args;
|
|
1844
|
-
if (body.length !== 1 || body[0].type !== "text") {
|
|
1845
|
-
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node);
|
|
1846
|
-
}
|
|
1847
|
-
const text = body[0].content;
|
|
1848
|
-
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
1849
|
-
return new TypstNode("symbol", text);
|
|
1850
|
-
} else {
|
|
1851
|
-
return new TypstNode("unaryFunc", "op", [new TypstNode("text", text)]);
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
return new TypstNode("unaryFunc", convertToken(node.content), node.args.map(convertTree));
|
|
1855
|
-
}
|
|
1856
|
-
case "beginend": {
|
|
1857
|
-
const matrix = node.data;
|
|
1858
|
-
const data = matrix.map((row) => row.map(convertTree));
|
|
1859
|
-
if (node.content.startsWith("align")) {
|
|
1860
|
-
return new TypstNode("align", "", [], data);
|
|
1861
|
-
} else {
|
|
1862
|
-
const res = new TypstNode("matrix", "", [], data);
|
|
1863
|
-
res.setOptions({ delim: "#none" });
|
|
1864
|
-
return res;
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
case "unknownMacro":
|
|
1868
|
-
return new TypstNode("unknown", convertToken(node.content));
|
|
1869
|
-
case "control":
|
|
1870
|
-
if (node.content === "\\\\") {
|
|
1871
|
-
return new TypstNode("symbol", "\\");
|
|
1872
|
-
} else if (node.content === "\\,") {
|
|
1873
|
-
return new TypstNode("symbol", "thin");
|
|
1874
|
-
} else {
|
|
1875
|
-
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
1876
|
-
}
|
|
1877
|
-
default:
|
|
1878
|
-
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
function convertToken(token) {
|
|
1882
|
-
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
1883
|
-
return token;
|
|
1884
|
-
} else if (token === "/") {
|
|
1885
|
-
return "\\/";
|
|
1886
|
-
} else if (token === "\\|") {
|
|
1887
|
-
return "parallel";
|
|
1888
|
-
} else if (token === "\\colon") {
|
|
1889
|
-
return ":";
|
|
1890
|
-
} else if (token === "\\\\") {
|
|
1891
|
-
return "\\";
|
|
1892
|
-
} else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
|
|
1893
|
-
return token;
|
|
1894
|
-
} else if (token.startsWith("\\")) {
|
|
1895
|
-
const symbol = token.slice(1);
|
|
1896
|
-
if (symbolMap.has(symbol)) {
|
|
1897
|
-
return symbolMap.get(symbol);
|
|
1898
|
-
} else {
|
|
1899
|
-
return symbol;
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
return token;
|
|
1903
|
-
}
|
|
1904
|
-
var TYPST_INTRINSIC_SYMBOLS = [
|
|
1905
|
-
"dim",
|
|
1906
|
-
"id",
|
|
1907
|
-
"im",
|
|
1908
|
-
"mod",
|
|
1909
|
-
"Pr",
|
|
1910
|
-
"sech",
|
|
1911
|
-
"csch"
|
|
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");
|
|
1899
|
+
var TYPST_LEFT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, "(");
|
|
1900
|
+
var TYPST_RIGHT_PARENTHESIS = new TypstToken(1 /* ELEMENT */, ")");
|
|
1901
|
+
var TYPST_COMMA = new TypstToken(1 /* ELEMENT */, ",");
|
|
1902
|
+
var TYPST_NEWLINE = new TypstToken(0 /* SYMBOL */, `
|
|
1903
|
+
`);
|
|
1917
1904
|
|
|
1918
1905
|
class TypstWriterError extends Error {
|
|
1919
1906
|
node;
|
|
@@ -1937,19 +1924,22 @@ class TypstWriter {
|
|
|
1937
1924
|
this.keepSpaces = keepSpaces;
|
|
1938
1925
|
}
|
|
1939
1926
|
writeBuffer(token) {
|
|
1940
|
-
const str = token.
|
|
1927
|
+
const str = token.toString();
|
|
1941
1928
|
if (str === "") {
|
|
1942
1929
|
return;
|
|
1943
1930
|
}
|
|
1944
1931
|
let no_need_space = false;
|
|
1945
|
-
no_need_space ||= /[\(\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
1946
|
-
no_need_space ||= /^[}
|
|
1932
|
+
no_need_space ||= /[\(\[\|]$/.test(this.buffer) && /^\w/.test(str);
|
|
1933
|
+
no_need_space ||= /^[})\]\|]$/.test(str);
|
|
1934
|
+
no_need_space ||= /^[(_^,;!]$/.test(str);
|
|
1947
1935
|
no_need_space ||= str === "'";
|
|
1948
1936
|
no_need_space ||= /[0-9]$/.test(this.buffer) && /^[0-9]/.test(str);
|
|
1949
1937
|
no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
|
|
1950
|
-
no_need_space ||= str.startsWith(
|
|
1938
|
+
no_need_space ||= str.startsWith(`
|
|
1939
|
+
`);
|
|
1951
1940
|
no_need_space ||= this.buffer === "";
|
|
1952
1941
|
no_need_space ||= /^\s/.test(str);
|
|
1942
|
+
no_need_space ||= this.buffer.endsWith("&") && str === "=";
|
|
1953
1943
|
no_need_space ||= /[\s_^{\(]$/.test(this.buffer);
|
|
1954
1944
|
if (!no_need_space) {
|
|
1955
1945
|
this.buffer += " ";
|
|
@@ -1964,7 +1954,7 @@ class TypstWriter {
|
|
|
1964
1954
|
if (node.content === "," && this.insideFunctionDepth > 0) {
|
|
1965
1955
|
this.queue.push(new TypstToken(0 /* SYMBOL */, "comma"));
|
|
1966
1956
|
} else {
|
|
1967
|
-
this.queue.push(new TypstToken(1 /*
|
|
1957
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, node.content));
|
|
1968
1958
|
}
|
|
1969
1959
|
break;
|
|
1970
1960
|
}
|
|
@@ -1972,10 +1962,10 @@ class TypstWriter {
|
|
|
1972
1962
|
this.queue.push(new TypstToken(0 /* SYMBOL */, node.content));
|
|
1973
1963
|
break;
|
|
1974
1964
|
case "text":
|
|
1975
|
-
this.queue.push(new TypstToken(2 /* TEXT */,
|
|
1965
|
+
this.queue.push(new TypstToken(2 /* TEXT */, node.content));
|
|
1976
1966
|
break;
|
|
1977
1967
|
case "comment":
|
|
1978
|
-
this.queue.push(new TypstToken(3 /* COMMENT */,
|
|
1968
|
+
this.queue.push(new TypstToken(3 /* COMMENT */, node.content));
|
|
1979
1969
|
break;
|
|
1980
1970
|
case "whitespace":
|
|
1981
1971
|
for (const c of node.content) {
|
|
@@ -1983,7 +1973,8 @@ class TypstWriter {
|
|
|
1983
1973
|
if (this.keepSpaces) {
|
|
1984
1974
|
this.queue.push(new TypstToken(4 /* SPACE */, c));
|
|
1985
1975
|
}
|
|
1986
|
-
} else if (c ===
|
|
1976
|
+
} else if (c === `
|
|
1977
|
+
`) {
|
|
1987
1978
|
this.queue.push(new TypstToken(0 /* SYMBOL */, c));
|
|
1988
1979
|
} else {
|
|
1989
1980
|
throw new TypstWriterError(`Unexpected whitespace character: ${c}`, node);
|
|
@@ -1999,17 +1990,17 @@ class TypstWriter {
|
|
|
1999
1990
|
let { base, sup, sub } = node.data;
|
|
2000
1991
|
this.appendWithBracketsIfNeeded(base);
|
|
2001
1992
|
let trailing_space_needed = false;
|
|
2002
|
-
const has_prime = sup && sup.type === "atom" && sup.content === "
|
|
1993
|
+
const has_prime = sup && sup.type === "atom" && sup.content === "'";
|
|
2003
1994
|
if (has_prime) {
|
|
2004
|
-
this.queue.push(new TypstToken(1 /*
|
|
1995
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, "'"));
|
|
2005
1996
|
trailing_space_needed = false;
|
|
2006
1997
|
}
|
|
2007
1998
|
if (sub) {
|
|
2008
|
-
this.queue.push(new TypstToken(1 /*
|
|
1999
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, "_"));
|
|
2009
2000
|
trailing_space_needed = this.appendWithBracketsIfNeeded(sub);
|
|
2010
2001
|
}
|
|
2011
2002
|
if (sup && !has_prime) {
|
|
2012
|
-
this.queue.push(new TypstToken(1 /*
|
|
2003
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, "^"));
|
|
2013
2004
|
trailing_space_needed = this.appendWithBracketsIfNeeded(sup);
|
|
2014
2005
|
}
|
|
2015
2006
|
if (trailing_space_needed) {
|
|
@@ -2017,26 +2008,17 @@ class TypstWriter {
|
|
|
2017
2008
|
}
|
|
2018
2009
|
break;
|
|
2019
2010
|
}
|
|
2020
|
-
case "
|
|
2021
|
-
const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
|
|
2022
|
-
const [arg0, arg1] = node.args;
|
|
2023
|
-
this.queue.push(func_symbol);
|
|
2024
|
-
this.insideFunctionDepth++;
|
|
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);
|
|
2030
|
-
this.insideFunctionDepth--;
|
|
2031
|
-
break;
|
|
2032
|
-
}
|
|
2033
|
-
case "unaryFunc": {
|
|
2011
|
+
case "funcCall": {
|
|
2034
2012
|
const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
|
|
2035
|
-
const arg0 = node.args[0];
|
|
2036
2013
|
this.queue.push(func_symbol);
|
|
2037
2014
|
this.insideFunctionDepth++;
|
|
2038
2015
|
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
2039
|
-
|
|
2016
|
+
for (let i = 0;i < node.args.length; i++) {
|
|
2017
|
+
this.serialize(node.args[i]);
|
|
2018
|
+
if (i < node.args.length - 1) {
|
|
2019
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, ","));
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2040
2022
|
if (node.options) {
|
|
2041
2023
|
for (const [key, value] of Object.entries(node.options)) {
|
|
2042
2024
|
this.queue.push(new TypstToken(0 /* SYMBOL */, `, ${key}: ${value}`));
|
|
@@ -2051,7 +2033,7 @@ class TypstWriter {
|
|
|
2051
2033
|
matrix.forEach((row, i) => {
|
|
2052
2034
|
row.forEach((cell, j) => {
|
|
2053
2035
|
if (j > 0) {
|
|
2054
|
-
this.queue.push(new TypstToken(1 /*
|
|
2036
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, "&"));
|
|
2055
2037
|
}
|
|
2056
2038
|
this.serialize(cell);
|
|
2057
2039
|
});
|
|
@@ -2075,10 +2057,10 @@ class TypstWriter {
|
|
|
2075
2057
|
row.forEach((cell, j) => {
|
|
2076
2058
|
this.serialize(cell);
|
|
2077
2059
|
if (j < row.length - 1) {
|
|
2078
|
-
this.queue.push(new TypstToken(1 /*
|
|
2060
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, ","));
|
|
2079
2061
|
} else {
|
|
2080
2062
|
if (i < matrix.length - 1) {
|
|
2081
|
-
this.queue.push(new TypstToken(1 /*
|
|
2063
|
+
this.queue.push(new TypstToken(1 /* ELEMENT */, ";"));
|
|
2082
2064
|
}
|
|
2083
2065
|
}
|
|
2084
2066
|
});
|
|
@@ -2123,9 +2105,9 @@ class TypstWriter {
|
|
|
2123
2105
|
let token = this.queue[i];
|
|
2124
2106
|
if (token.eq(SOFT_SPACE)) {
|
|
2125
2107
|
if (i === this.queue.length - 1) {
|
|
2126
|
-
this.queue[i].
|
|
2108
|
+
this.queue[i].value = "";
|
|
2127
2109
|
} else if (this.queue[i + 1].isOneOf([TYPST_RIGHT_PARENTHESIS, TYPST_COMMA, TYPST_NEWLINE])) {
|
|
2128
|
-
this.queue[i].
|
|
2110
|
+
this.queue[i].value = "";
|
|
2129
2111
|
}
|
|
2130
2112
|
}
|
|
2131
2113
|
}
|
|
@@ -2158,7 +2140,810 @@ class TypstWriter {
|
|
|
2158
2140
|
return this.buffer;
|
|
2159
2141
|
}
|
|
2160
2142
|
}
|
|
2161
|
-
|
|
2143
|
+
function convertTree(node) {
|
|
2144
|
+
switch (node.type) {
|
|
2145
|
+
case "empty":
|
|
2146
|
+
return new TypstNode("empty", "");
|
|
2147
|
+
case "whitespace":
|
|
2148
|
+
return new TypstNode("whitespace", node.content);
|
|
2149
|
+
case "ordgroup":
|
|
2150
|
+
return new TypstNode("group", "", node.args.map(convertTree));
|
|
2151
|
+
case "element":
|
|
2152
|
+
return new TypstNode("atom", convertToken(node.content));
|
|
2153
|
+
case "symbol":
|
|
2154
|
+
return new TypstNode("symbol", convertToken(node.content));
|
|
2155
|
+
case "text":
|
|
2156
|
+
return new TypstNode("text", node.content);
|
|
2157
|
+
case "comment":
|
|
2158
|
+
return new TypstNode("comment", node.content);
|
|
2159
|
+
case "supsub": {
|
|
2160
|
+
let { base, sup, sub } = node.data;
|
|
2161
|
+
if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) {
|
|
2162
|
+
return new TypstNode("funcCall", "overbrace", [convertTree(base.args[0]), convertTree(sup)]);
|
|
2163
|
+
} else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) {
|
|
2164
|
+
return new TypstNode("funcCall", "underbrace", [convertTree(base.args[0]), convertTree(sub)]);
|
|
2165
|
+
}
|
|
2166
|
+
const data = {
|
|
2167
|
+
base: convertTree(base)
|
|
2168
|
+
};
|
|
2169
|
+
if (data.base.type === "empty") {
|
|
2170
|
+
data.base = new TypstNode("text", "");
|
|
2171
|
+
}
|
|
2172
|
+
if (sup) {
|
|
2173
|
+
data.sup = convertTree(sup);
|
|
2174
|
+
}
|
|
2175
|
+
if (sub) {
|
|
2176
|
+
data.sub = convertTree(sub);
|
|
2177
|
+
}
|
|
2178
|
+
return new TypstNode("supsub", "", [], data);
|
|
2179
|
+
}
|
|
2180
|
+
case "leftright": {
|
|
2181
|
+
const [left, body, right] = node.args;
|
|
2182
|
+
const group = new TypstNode("group", "", node.args.map(convertTree));
|
|
2183
|
+
if ([
|
|
2184
|
+
"[]",
|
|
2185
|
+
"()",
|
|
2186
|
+
"\\{\\}",
|
|
2187
|
+
"\\lfloor\\rfloor",
|
|
2188
|
+
"\\lceil\\rceil",
|
|
2189
|
+
"\\lfloor\\rceil"
|
|
2190
|
+
].includes(left.content + right.content)) {
|
|
2191
|
+
return group;
|
|
2192
|
+
}
|
|
2193
|
+
if (right.content === ".") {
|
|
2194
|
+
group.args.pop();
|
|
2195
|
+
return group;
|
|
2196
|
+
} else if (left.content === ".") {
|
|
2197
|
+
group.args.shift();
|
|
2198
|
+
return new TypstNode("funcCall", "lr", [group]);
|
|
2199
|
+
}
|
|
2200
|
+
return new TypstNode("funcCall", "lr", [group]);
|
|
2201
|
+
}
|
|
2202
|
+
case "binaryFunc": {
|
|
2203
|
+
if (node.content === "\\overset") {
|
|
2204
|
+
return convert_overset(node);
|
|
2205
|
+
}
|
|
2206
|
+
return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
|
|
2207
|
+
}
|
|
2208
|
+
case "unaryFunc": {
|
|
2209
|
+
const arg0 = convertTree(node.args[0]);
|
|
2210
|
+
if (node.content === "\\sqrt" && node.data) {
|
|
2211
|
+
const data = convertTree(node.data);
|
|
2212
|
+
return new TypstNode("funcCall", "root", [data, arg0]);
|
|
2213
|
+
}
|
|
2214
|
+
if (node.content === "\\mathbf") {
|
|
2215
|
+
const inner = new TypstNode("funcCall", "bold", [arg0]);
|
|
2216
|
+
return new TypstNode("funcCall", "upright", [inner]);
|
|
2217
|
+
}
|
|
2218
|
+
if (node.content === "\\mathbb" && arg0.type === "atom" && /^[A-Z]$/.test(arg0.content)) {
|
|
2219
|
+
return new TypstNode("symbol", arg0.content + arg0.content);
|
|
2220
|
+
}
|
|
2221
|
+
if (node.content === "\\operatorname") {
|
|
2222
|
+
const body = node.args;
|
|
2223
|
+
if (body.length !== 1 || body[0].type !== "text") {
|
|
2224
|
+
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node);
|
|
2225
|
+
}
|
|
2226
|
+
const text = body[0].content;
|
|
2227
|
+
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) {
|
|
2228
|
+
return new TypstNode("symbol", text);
|
|
2229
|
+
} else {
|
|
2230
|
+
return new TypstNode("funcCall", "op", [new TypstNode("text", text)]);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
return new TypstNode("funcCall", convertToken(node.content), node.args.map(convertTree));
|
|
2234
|
+
}
|
|
2235
|
+
case "beginend": {
|
|
2236
|
+
const matrix = node.data;
|
|
2237
|
+
const data = matrix.map((row) => row.map(convertTree));
|
|
2238
|
+
if (node.content.startsWith("align")) {
|
|
2239
|
+
return new TypstNode("align", "", [], data);
|
|
2240
|
+
} else {
|
|
2241
|
+
const res = new TypstNode("matrix", "", [], data);
|
|
2242
|
+
res.setOptions({ delim: "#none" });
|
|
2243
|
+
return res;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
case "unknownMacro":
|
|
2247
|
+
return new TypstNode("unknown", convertToken(node.content));
|
|
2248
|
+
case "control":
|
|
2249
|
+
if (node.content === "\\\\") {
|
|
2250
|
+
return new TypstNode("symbol", "\\");
|
|
2251
|
+
} else if (node.content === "\\,") {
|
|
2252
|
+
return new TypstNode("symbol", "thin");
|
|
2253
|
+
} else {
|
|
2254
|
+
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node);
|
|
2255
|
+
}
|
|
2256
|
+
default:
|
|
2257
|
+
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
function convertToken(token) {
|
|
2261
|
+
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
2262
|
+
return token;
|
|
2263
|
+
} else if (token === "/") {
|
|
2264
|
+
return "\\/";
|
|
2265
|
+
} else if (token === "\\|") {
|
|
2266
|
+
return "parallel";
|
|
2267
|
+
} else if (token === "\\colon") {
|
|
2268
|
+
return ":";
|
|
2269
|
+
} else if (token === "\\\\") {
|
|
2270
|
+
return "\\";
|
|
2271
|
+
} else if (["\\$", "\\#", "\\&", "\\_"].includes(token)) {
|
|
2272
|
+
return token;
|
|
2273
|
+
} else if (token.startsWith("\\")) {
|
|
2274
|
+
const symbol = token.slice(1);
|
|
2275
|
+
if (symbolMap.has(symbol)) {
|
|
2276
|
+
return symbolMap.get(symbol);
|
|
2277
|
+
} else {
|
|
2278
|
+
return symbol;
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return token;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// src/typst-parser.ts
|
|
2285
|
+
function eat_primes2(tokens, start) {
|
|
2286
|
+
let pos = start;
|
|
2287
|
+
while (pos < tokens.length && tokens[pos].eq(new TypstToken(1 /* ELEMENT */, "'"))) {
|
|
2288
|
+
pos += 1;
|
|
2289
|
+
}
|
|
2290
|
+
return pos - start;
|
|
2291
|
+
}
|
|
2292
|
+
function eat_identifier_name(typst, start) {
|
|
2293
|
+
let pos = start;
|
|
2294
|
+
while (pos < typst.length && (isalpha(typst[pos]) || typst[pos] === ".")) {
|
|
2295
|
+
pos += 1;
|
|
2296
|
+
}
|
|
2297
|
+
return typst.substring(start, pos);
|
|
2298
|
+
}
|
|
2299
|
+
var TYPST_EMPTY_NODE = new TypstNode("empty", "");
|
|
2300
|
+
function tokenize_typst(typst) {
|
|
2301
|
+
const tokens = [];
|
|
2302
|
+
let pos = 0;
|
|
2303
|
+
while (pos < typst.length) {
|
|
2304
|
+
const firstChar = typst[pos];
|
|
2305
|
+
let token;
|
|
2306
|
+
switch (firstChar) {
|
|
2307
|
+
case "_":
|
|
2308
|
+
case "^":
|
|
2309
|
+
case "&":
|
|
2310
|
+
token = new TypstToken(6 /* CONTROL */, firstChar);
|
|
2311
|
+
pos++;
|
|
2312
|
+
break;
|
|
2313
|
+
case `
|
|
2314
|
+
`:
|
|
2315
|
+
token = new TypstToken(7 /* NEWLINE */, firstChar);
|
|
2316
|
+
pos++;
|
|
2317
|
+
break;
|
|
2318
|
+
case "\r": {
|
|
2319
|
+
if (pos + 1 < typst.length && typst[pos + 1] === `
|
|
2320
|
+
`) {
|
|
2321
|
+
token = new TypstToken(7 /* NEWLINE */, `
|
|
2322
|
+
`);
|
|
2323
|
+
pos += 2;
|
|
2324
|
+
} else {
|
|
2325
|
+
token = new TypstToken(7 /* NEWLINE */, `
|
|
2326
|
+
`);
|
|
2327
|
+
pos++;
|
|
2328
|
+
}
|
|
2329
|
+
break;
|
|
2330
|
+
}
|
|
2331
|
+
case " ": {
|
|
2332
|
+
let newPos = pos;
|
|
2333
|
+
while (newPos < typst.length && typst[newPos] === " ") {
|
|
2334
|
+
newPos++;
|
|
2335
|
+
}
|
|
2336
|
+
token = new TypstToken(4 /* SPACE */, typst.substring(pos, newPos));
|
|
2337
|
+
pos = newPos;
|
|
2338
|
+
break;
|
|
2339
|
+
}
|
|
2340
|
+
case "/": {
|
|
2341
|
+
if (pos < typst.length && typst[pos + 1] === "/") {
|
|
2342
|
+
let newPos = pos + 2;
|
|
2343
|
+
while (newPos < typst.length && typst[newPos] !== `
|
|
2344
|
+
`) {
|
|
2345
|
+
newPos++;
|
|
2346
|
+
}
|
|
2347
|
+
token = new TypstToken(3 /* COMMENT */, typst.slice(pos + 2, newPos));
|
|
2348
|
+
pos = newPos;
|
|
2349
|
+
} else {
|
|
2350
|
+
token = new TypstToken(1 /* ELEMENT */, "/");
|
|
2351
|
+
pos++;
|
|
2352
|
+
}
|
|
2353
|
+
break;
|
|
2354
|
+
}
|
|
2355
|
+
case "\\": {
|
|
2356
|
+
if (pos + 1 >= typst.length) {
|
|
2357
|
+
throw new Error("Expecting a character after \\");
|
|
2358
|
+
}
|
|
2359
|
+
const firstTwoChars = typst.substring(pos, pos + 2);
|
|
2360
|
+
if (["\\$", "\\&", "\\#", "\\_"].includes(firstTwoChars)) {
|
|
2361
|
+
token = new TypstToken(1 /* ELEMENT */, firstTwoChars);
|
|
2362
|
+
pos += 2;
|
|
2363
|
+
} else if (firstTwoChars === "\\\n") {
|
|
2364
|
+
token = new TypstToken(6 /* CONTROL */, "\\");
|
|
2365
|
+
pos += 1;
|
|
2366
|
+
} else {
|
|
2367
|
+
token = new TypstToken(6 /* CONTROL */, "");
|
|
2368
|
+
pos++;
|
|
2369
|
+
}
|
|
2370
|
+
break;
|
|
2371
|
+
}
|
|
2372
|
+
case '"': {
|
|
2373
|
+
let newPos = pos + 1;
|
|
2374
|
+
while (newPos < typst.length) {
|
|
2375
|
+
if (typst[newPos] === '"' && typst[newPos - 1] !== "\\") {
|
|
2376
|
+
break;
|
|
2377
|
+
}
|
|
2378
|
+
newPos++;
|
|
2379
|
+
}
|
|
2380
|
+
let text = typst.substring(pos + 1, newPos);
|
|
2381
|
+
const chars = ['"', "\\"];
|
|
2382
|
+
for (const char of chars) {
|
|
2383
|
+
text = text.replaceAll("\\" + char, char);
|
|
2384
|
+
}
|
|
2385
|
+
token = new TypstToken(2 /* TEXT */, text);
|
|
2386
|
+
pos = newPos + 1;
|
|
2387
|
+
break;
|
|
2388
|
+
}
|
|
2389
|
+
default: {
|
|
2390
|
+
if (isdigit(firstChar)) {
|
|
2391
|
+
let newPos = pos;
|
|
2392
|
+
while (newPos < typst.length && isdigit(typst[newPos])) {
|
|
2393
|
+
newPos += 1;
|
|
2394
|
+
}
|
|
2395
|
+
token = new TypstToken(1 /* ELEMENT */, typst.slice(pos, newPos));
|
|
2396
|
+
} else if ("+-*/='<>!.,;?()[]|".includes(firstChar)) {
|
|
2397
|
+
token = new TypstToken(1 /* ELEMENT */, firstChar);
|
|
2398
|
+
} else if (isalpha(firstChar)) {
|
|
2399
|
+
const identifier = eat_identifier_name(typst, pos);
|
|
2400
|
+
const _type = identifier.length === 1 ? 1 /* ELEMENT */ : 0 /* SYMBOL */;
|
|
2401
|
+
token = new TypstToken(_type, identifier);
|
|
2402
|
+
} else {
|
|
2403
|
+
token = new TypstToken(1 /* ELEMENT */, firstChar);
|
|
2404
|
+
}
|
|
2405
|
+
pos += token.value.length;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
tokens.push(token);
|
|
2409
|
+
}
|
|
2410
|
+
return tokens;
|
|
2411
|
+
}
|
|
2412
|
+
function find_closing_match2(tokens, start) {
|
|
2413
|
+
assert(tokens[start].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2]));
|
|
2414
|
+
let count = 1;
|
|
2415
|
+
let pos = start + 1;
|
|
2416
|
+
while (count > 0) {
|
|
2417
|
+
if (pos >= tokens.length) {
|
|
2418
|
+
throw new Error("Unmatched brackets");
|
|
2419
|
+
}
|
|
2420
|
+
if (tokens[pos].isOneOf([LEFT_PARENTHESES, LEFT_BRACKET, LEFT_CURLY_BRACKET2])) {
|
|
2421
|
+
count += 1;
|
|
2422
|
+
} else if (tokens[pos].isOneOf([RIGHT_PARENTHESES, RIGHT_BRACKET, RIGHT_CURLY_BRACKET2])) {
|
|
2423
|
+
count -= 1;
|
|
2424
|
+
}
|
|
2425
|
+
pos += 1;
|
|
2426
|
+
}
|
|
2427
|
+
return pos - 1;
|
|
2428
|
+
}
|
|
2429
|
+
function find_closing_parenthesis(nodes, start) {
|
|
2430
|
+
const left_parenthesis = new TypstNode("atom", "(");
|
|
2431
|
+
const right_parenthesis = new TypstNode("atom", ")");
|
|
2432
|
+
assert(nodes[start].eq(left_parenthesis));
|
|
2433
|
+
let count = 1;
|
|
2434
|
+
let pos = start + 1;
|
|
2435
|
+
while (count > 0) {
|
|
2436
|
+
if (pos >= nodes.length) {
|
|
2437
|
+
throw new Error("Unmatched brackets");
|
|
2438
|
+
}
|
|
2439
|
+
if (nodes[pos].eq(left_parenthesis)) {
|
|
2440
|
+
count += 1;
|
|
2441
|
+
} else if (nodes[pos].eq(right_parenthesis)) {
|
|
2442
|
+
count -= 1;
|
|
2443
|
+
}
|
|
2444
|
+
pos += 1;
|
|
2445
|
+
}
|
|
2446
|
+
return pos - 1;
|
|
2447
|
+
}
|
|
2448
|
+
function primes(num) {
|
|
2449
|
+
const res = [];
|
|
2450
|
+
for (let i = 0;i < num; i++) {
|
|
2451
|
+
res.push(new TypstNode("atom", "'"));
|
|
2452
|
+
}
|
|
2453
|
+
return res;
|
|
2454
|
+
}
|
|
2455
|
+
var DIV = new TypstNode("atom", "/");
|
|
2456
|
+
function next_non_whitespace(nodes, start) {
|
|
2457
|
+
let pos = start;
|
|
2458
|
+
while (pos < nodes.length && nodes[pos].type === "whitespace") {
|
|
2459
|
+
pos++;
|
|
2460
|
+
}
|
|
2461
|
+
return pos === nodes.length ? TYPST_EMPTY_NODE : nodes[pos];
|
|
2462
|
+
}
|
|
2463
|
+
function trim_whitespace_around_operators(nodes) {
|
|
2464
|
+
let after_operator = false;
|
|
2465
|
+
const res = [];
|
|
2466
|
+
for (let i = 0;i < nodes.length; i++) {
|
|
2467
|
+
const current = nodes[i];
|
|
2468
|
+
if (current.type === "whitespace") {
|
|
2469
|
+
if (after_operator) {
|
|
2470
|
+
continue;
|
|
2471
|
+
}
|
|
2472
|
+
if (next_non_whitespace(nodes, i + 1).eq(DIV)) {
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
if (current.eq(DIV)) {
|
|
2477
|
+
after_operator = true;
|
|
2478
|
+
} else {
|
|
2479
|
+
after_operator = false;
|
|
2480
|
+
}
|
|
2481
|
+
res.push(current);
|
|
2482
|
+
}
|
|
2483
|
+
return res;
|
|
2484
|
+
}
|
|
2485
|
+
function process_operators(nodes, parenthesis = false) {
|
|
2486
|
+
nodes = trim_whitespace_around_operators(nodes);
|
|
2487
|
+
const opening_bracket = new TypstNode("atom", "(");
|
|
2488
|
+
const closing_bracket = new TypstNode("atom", ")");
|
|
2489
|
+
const stack = [];
|
|
2490
|
+
const args = [];
|
|
2491
|
+
let pos = 0;
|
|
2492
|
+
while (pos < nodes.length) {
|
|
2493
|
+
const current = nodes[pos];
|
|
2494
|
+
if (current.eq(closing_bracket)) {
|
|
2495
|
+
throw new TypstParserError("Unexpected ')'");
|
|
2496
|
+
} else if (current.eq(DIV)) {
|
|
2497
|
+
stack.push(current);
|
|
2498
|
+
pos++;
|
|
2499
|
+
} else {
|
|
2500
|
+
let current_tree;
|
|
2501
|
+
if (current.eq(opening_bracket)) {
|
|
2502
|
+
const pos_closing = find_closing_parenthesis(nodes, pos);
|
|
2503
|
+
current_tree = process_operators(nodes.slice(pos + 1, pos_closing), true);
|
|
2504
|
+
pos = pos_closing + 1;
|
|
2505
|
+
} else {
|
|
2506
|
+
current_tree = current;
|
|
2507
|
+
pos++;
|
|
2508
|
+
}
|
|
2509
|
+
if (stack.length > 0 && stack[stack.length - 1].eq(DIV)) {
|
|
2510
|
+
const denominator = current_tree;
|
|
2511
|
+
if (args.length === 0) {
|
|
2512
|
+
throw new TypstParserError("Unexpected '/' operator, no numerator before it");
|
|
2513
|
+
}
|
|
2514
|
+
const numerator = args.pop();
|
|
2515
|
+
if (denominator.type === "group" && denominator.content === "parenthesis") {
|
|
2516
|
+
denominator.content = "";
|
|
2517
|
+
}
|
|
2518
|
+
if (numerator.type === "group" && numerator.content === "parenthesis") {
|
|
2519
|
+
numerator.content = "";
|
|
2520
|
+
}
|
|
2521
|
+
args.push(new TypstNode("fraction", "", [numerator, denominator]));
|
|
2522
|
+
stack.pop();
|
|
2523
|
+
} else {
|
|
2524
|
+
args.push(current_tree);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
if (parenthesis) {
|
|
2529
|
+
return new TypstNode("group", "parenthesis", args);
|
|
2530
|
+
} else {
|
|
2531
|
+
if (args.length === 0) {
|
|
2532
|
+
return TYPST_EMPTY_NODE;
|
|
2533
|
+
} else if (args.length === 1) {
|
|
2534
|
+
return args[0];
|
|
2535
|
+
} else {
|
|
2536
|
+
return new TypstNode("group", "", args);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
class TypstParserError extends Error {
|
|
2542
|
+
constructor(message) {
|
|
2543
|
+
super(message);
|
|
2544
|
+
this.name = "TypstParserError";
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
var SUB_SYMBOL2 = new TypstToken(6 /* CONTROL */, "_");
|
|
2548
|
+
var SUP_SYMBOL2 = new TypstToken(6 /* CONTROL */, "^");
|
|
2549
|
+
var LEFT_PARENTHESES = new TypstToken(1 /* ELEMENT */, "(");
|
|
2550
|
+
var RIGHT_PARENTHESES = new TypstToken(1 /* ELEMENT */, ")");
|
|
2551
|
+
var LEFT_BRACKET = new TypstToken(1 /* ELEMENT */, "[");
|
|
2552
|
+
var RIGHT_BRACKET = new TypstToken(1 /* ELEMENT */, "]");
|
|
2553
|
+
var LEFT_CURLY_BRACKET2 = new TypstToken(1 /* ELEMENT */, "{");
|
|
2554
|
+
var RIGHT_CURLY_BRACKET2 = new TypstToken(1 /* ELEMENT */, "}");
|
|
2555
|
+
var COMMA = new TypstToken(1 /* ELEMENT */, ",");
|
|
2556
|
+
var SEMICOLON = new TypstToken(1 /* ELEMENT */, ";");
|
|
2557
|
+
var SINGLE_SPACE = new TypstToken(4 /* SPACE */, " ");
|
|
2558
|
+
|
|
2559
|
+
class TypstParser {
|
|
2560
|
+
space_sensitive;
|
|
2561
|
+
newline_sensitive;
|
|
2562
|
+
constructor(space_sensitive = true, newline_sensitive = true) {
|
|
2563
|
+
this.space_sensitive = space_sensitive;
|
|
2564
|
+
this.newline_sensitive = newline_sensitive;
|
|
2565
|
+
}
|
|
2566
|
+
parse(tokens) {
|
|
2567
|
+
const [tree, _] = this.parseGroup(tokens, 0, tokens.length);
|
|
2568
|
+
return tree;
|
|
2569
|
+
}
|
|
2570
|
+
parseGroup(tokens, start, end, parentheses = false) {
|
|
2571
|
+
const results = [];
|
|
2572
|
+
let pos = start;
|
|
2573
|
+
while (pos < end) {
|
|
2574
|
+
const [res, newPos] = this.parseNextExpr(tokens, pos);
|
|
2575
|
+
pos = newPos;
|
|
2576
|
+
if (res.type === "whitespace") {
|
|
2577
|
+
if (!this.space_sensitive && res.content.replace(/ /g, "").length === 0) {
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
if (!this.newline_sensitive && res.content === `
|
|
2581
|
+
`) {
|
|
2582
|
+
continue;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
results.push(res);
|
|
2586
|
+
}
|
|
2587
|
+
let node;
|
|
2588
|
+
if (parentheses) {
|
|
2589
|
+
node = process_operators(results, true);
|
|
2590
|
+
} else {
|
|
2591
|
+
if (results.length === 0) {
|
|
2592
|
+
node = TYPST_EMPTY_NODE;
|
|
2593
|
+
} else if (results.length === 1) {
|
|
2594
|
+
node = results[0];
|
|
2595
|
+
} else {
|
|
2596
|
+
node = process_operators(results);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
return [node, end + 1];
|
|
2600
|
+
}
|
|
2601
|
+
parseNextExpr(tokens, start) {
|
|
2602
|
+
let [base, pos] = this.parseNextExprWithoutSupSub(tokens, start);
|
|
2603
|
+
let sub = null;
|
|
2604
|
+
let sup = null;
|
|
2605
|
+
const num_base_prime = eat_primes2(tokens, pos);
|
|
2606
|
+
if (num_base_prime > 0) {
|
|
2607
|
+
base = new TypstNode("group", "", [base].concat(primes(num_base_prime)));
|
|
2608
|
+
pos += num_base_prime;
|
|
2609
|
+
}
|
|
2610
|
+
if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL2)) {
|
|
2611
|
+
[sub, pos] = this.parseSupOrSub(tokens, pos + 1);
|
|
2612
|
+
if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL2)) {
|
|
2613
|
+
[sup, pos] = this.parseSupOrSub(tokens, pos + 1);
|
|
2614
|
+
}
|
|
2615
|
+
} else if (pos < tokens.length && tokens[pos].eq(SUP_SYMBOL2)) {
|
|
2616
|
+
[sup, pos] = this.parseSupOrSub(tokens, pos + 1);
|
|
2617
|
+
if (pos < tokens.length && tokens[pos].eq(SUB_SYMBOL2)) {
|
|
2618
|
+
[sub, pos] = this.parseSupOrSub(tokens, pos + 1);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
if (sub !== null || sup !== null) {
|
|
2622
|
+
const res = { base };
|
|
2623
|
+
if (sub) {
|
|
2624
|
+
res.sub = sub;
|
|
2625
|
+
}
|
|
2626
|
+
if (sup) {
|
|
2627
|
+
res.sup = sup;
|
|
2628
|
+
}
|
|
2629
|
+
return [new TypstNode("supsub", "", [], res), pos];
|
|
2630
|
+
} else {
|
|
2631
|
+
return [base, pos];
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
parseSupOrSub(tokens, start) {
|
|
2635
|
+
let node;
|
|
2636
|
+
let end;
|
|
2637
|
+
if (tokens[start].eq(LEFT_PARENTHESES)) {
|
|
2638
|
+
const pos_closing = find_closing_match2(tokens, start);
|
|
2639
|
+
[node, end] = this.parseGroup(tokens, start + 1, pos_closing);
|
|
2640
|
+
} else {
|
|
2641
|
+
[node, end] = this.parseNextExprWithoutSupSub(tokens, start);
|
|
2642
|
+
}
|
|
2643
|
+
const num_prime = eat_primes2(tokens, end);
|
|
2644
|
+
if (num_prime > 0) {
|
|
2645
|
+
node = new TypstNode("group", "", [node].concat(primes(num_prime)));
|
|
2646
|
+
end += num_prime;
|
|
2647
|
+
}
|
|
2648
|
+
return [node, end];
|
|
2649
|
+
}
|
|
2650
|
+
parseNextExprWithoutSupSub(tokens, start) {
|
|
2651
|
+
const firstToken = tokens[start];
|
|
2652
|
+
const node = firstToken.toNode();
|
|
2653
|
+
if (firstToken.eq(LEFT_PARENTHESES)) {
|
|
2654
|
+
const pos_closing = find_closing_match2(tokens, start);
|
|
2655
|
+
return this.parseGroup(tokens, start + 1, pos_closing, true);
|
|
2656
|
+
}
|
|
2657
|
+
if (firstToken.type === 1 /* ELEMENT */ && !isalpha(firstToken.value[0])) {
|
|
2658
|
+
return [node, start + 1];
|
|
2659
|
+
}
|
|
2660
|
+
if ([1 /* ELEMENT */, 0 /* SYMBOL */].includes(firstToken.type)) {
|
|
2661
|
+
if (start + 1 < tokens.length && tokens[start + 1].eq(LEFT_PARENTHESES)) {
|
|
2662
|
+
if (firstToken.value === "mat") {
|
|
2663
|
+
const [matrix, newPos2] = this.parseGroupsOfArguments(tokens, start + 1);
|
|
2664
|
+
const mat = new TypstNode("matrix", "", [], matrix);
|
|
2665
|
+
return [mat, newPos2];
|
|
2666
|
+
}
|
|
2667
|
+
const [args, newPos] = this.parseArguments(tokens, start + 1);
|
|
2668
|
+
const func_call = new TypstNode("funcCall", firstToken.value);
|
|
2669
|
+
func_call.args = args;
|
|
2670
|
+
return [func_call, newPos];
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return [node, start + 1];
|
|
2674
|
+
}
|
|
2675
|
+
parseArguments(tokens, start) {
|
|
2676
|
+
const end = find_closing_match2(tokens, start);
|
|
2677
|
+
return [this.parseCommaSeparatedArguments(tokens, start + 1, end), end + 1];
|
|
2678
|
+
}
|
|
2679
|
+
parseGroupsOfArguments(tokens, start) {
|
|
2680
|
+
const end = find_closing_match2(tokens, start);
|
|
2681
|
+
const matrix = [];
|
|
2682
|
+
let pos = start + 1;
|
|
2683
|
+
while (pos < end) {
|
|
2684
|
+
while (pos < end) {
|
|
2685
|
+
let next_stop = array_find(tokens, SEMICOLON, pos);
|
|
2686
|
+
if (next_stop === -1) {
|
|
2687
|
+
next_stop = end;
|
|
2688
|
+
}
|
|
2689
|
+
const row = this.parseCommaSeparatedArguments(tokens, pos, next_stop);
|
|
2690
|
+
matrix.push(row);
|
|
2691
|
+
pos = next_stop + 1;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return [matrix, end + 1];
|
|
2695
|
+
}
|
|
2696
|
+
parseCommaSeparatedArguments(tokens, start, end) {
|
|
2697
|
+
const args = [];
|
|
2698
|
+
let pos = start;
|
|
2699
|
+
while (pos < end) {
|
|
2700
|
+
let arg = new TypstNode("group", "", []);
|
|
2701
|
+
while (pos < end) {
|
|
2702
|
+
if (tokens[pos].eq(COMMA)) {
|
|
2703
|
+
pos += 1;
|
|
2704
|
+
break;
|
|
2705
|
+
} else if (tokens[pos].eq(SINGLE_SPACE)) {
|
|
2706
|
+
pos += 1;
|
|
2707
|
+
continue;
|
|
2708
|
+
}
|
|
2709
|
+
const [argItem, newPos] = this.parseNextExpr(tokens, pos);
|
|
2710
|
+
pos = newPos;
|
|
2711
|
+
arg.args.push(argItem);
|
|
2712
|
+
}
|
|
2713
|
+
if (arg.args.length === 0) {
|
|
2714
|
+
arg = TYPST_EMPTY_NODE;
|
|
2715
|
+
} else if (arg.args.length === 1) {
|
|
2716
|
+
arg = arg.args[0];
|
|
2717
|
+
}
|
|
2718
|
+
args.push(arg);
|
|
2719
|
+
}
|
|
2720
|
+
return args;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
function parseTypst(typst) {
|
|
2724
|
+
const parser = new TypstParser;
|
|
2725
|
+
let tokens = tokenize_typst(typst);
|
|
2726
|
+
return parser.parse(tokens);
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// src/tex-writer.ts
|
|
2730
|
+
var TYPST_UNARY_FUNCTIONS = [
|
|
2731
|
+
"sqrt",
|
|
2732
|
+
"bold",
|
|
2733
|
+
"arrow",
|
|
2734
|
+
"upright",
|
|
2735
|
+
"lr",
|
|
2736
|
+
"op",
|
|
2737
|
+
"macron",
|
|
2738
|
+
"dot",
|
|
2739
|
+
"dot.double",
|
|
2740
|
+
"hat",
|
|
2741
|
+
"tilde",
|
|
2742
|
+
"overline",
|
|
2743
|
+
"underline",
|
|
2744
|
+
"bb",
|
|
2745
|
+
"cal",
|
|
2746
|
+
"frak"
|
|
2747
|
+
];
|
|
2748
|
+
var TYPST_BINARY_FUNCTIONS = [
|
|
2749
|
+
"frac",
|
|
2750
|
+
"root",
|
|
2751
|
+
"overbrace",
|
|
2752
|
+
"underbrace"
|
|
2753
|
+
];
|
|
2754
|
+
function apply_escape_if_needed2(c) {
|
|
2755
|
+
if (["{", "}", "%"].includes(c)) {
|
|
2756
|
+
return "\\" + c;
|
|
2757
|
+
}
|
|
2758
|
+
return c;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
class TexWriter {
|
|
2762
|
+
buffer = "";
|
|
2763
|
+
queue = [];
|
|
2764
|
+
writeBuffer(token) {
|
|
2765
|
+
const str = token.toString();
|
|
2766
|
+
let no_need_space = false;
|
|
2767
|
+
if (token.type === 4 /* SPACE */) {
|
|
2768
|
+
no_need_space = true;
|
|
2769
|
+
} else {
|
|
2770
|
+
no_need_space ||= /[{\(\[\|]$/.test(this.buffer);
|
|
2771
|
+
no_need_space ||= /\\\w+$/.test(this.buffer) && str === "[";
|
|
2772
|
+
no_need_space ||= /^[\.,;:!\?\(\)\]{}_^]$/.test(str);
|
|
2773
|
+
no_need_space ||= ["\\{", "\\}"].includes(str);
|
|
2774
|
+
no_need_space ||= str === "'";
|
|
2775
|
+
no_need_space ||= this.buffer.endsWith("_") || this.buffer.endsWith("^");
|
|
2776
|
+
no_need_space ||= /\s$/.test(this.buffer);
|
|
2777
|
+
no_need_space ||= /^\s/.test(str);
|
|
2778
|
+
no_need_space ||= this.buffer === "";
|
|
2779
|
+
no_need_space ||= /[\(\[{]\s*(-|\+)$/.test(this.buffer) || this.buffer === "-" || this.buffer === "+";
|
|
2780
|
+
no_need_space ||= this.buffer.endsWith("&") && str === "=";
|
|
2781
|
+
}
|
|
2782
|
+
if (!no_need_space) {
|
|
2783
|
+
this.buffer += " ";
|
|
2784
|
+
}
|
|
2785
|
+
this.buffer += str;
|
|
2786
|
+
}
|
|
2787
|
+
append(node) {
|
|
2788
|
+
const alignment_char = new TexNode("control", "&");
|
|
2789
|
+
const newline_char = new TexNode("control", "\\\\");
|
|
2790
|
+
if (node.type === "ordgroup" && array_includes(node.args, alignment_char)) {
|
|
2791
|
+
const rows = array_split(node.args, newline_char);
|
|
2792
|
+
const data = [];
|
|
2793
|
+
for (const row of rows) {
|
|
2794
|
+
const cells = array_split(row, alignment_char);
|
|
2795
|
+
data.push(cells.map((cell) => new TexNode("ordgroup", "", cell)));
|
|
2796
|
+
}
|
|
2797
|
+
node = new TexNode("beginend", "aligned", [], data);
|
|
2798
|
+
}
|
|
2799
|
+
this.queue = this.queue.concat(node.serialize());
|
|
2800
|
+
}
|
|
2801
|
+
flushQueue() {
|
|
2802
|
+
for (let i = 0;i < this.queue.length; i++) {
|
|
2803
|
+
this.writeBuffer(this.queue[i]);
|
|
2804
|
+
}
|
|
2805
|
+
this.queue = [];
|
|
2806
|
+
}
|
|
2807
|
+
finalize() {
|
|
2808
|
+
this.flushQueue();
|
|
2809
|
+
return this.buffer;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
function convert_typst_node_to_tex(node) {
|
|
2813
|
+
if (node.eq(new TypstNode("symbol", "eq.def"))) {
|
|
2814
|
+
return new TexNode("binaryFunc", "\\overset", [
|
|
2815
|
+
new TexNode("text", "def"),
|
|
2816
|
+
new TexNode("element", "=")
|
|
2817
|
+
]);
|
|
2818
|
+
}
|
|
2819
|
+
switch (node.type) {
|
|
2820
|
+
case "empty":
|
|
2821
|
+
return new TexNode("empty", "");
|
|
2822
|
+
case "whitespace":
|
|
2823
|
+
return new TexNode("whitespace", node.content);
|
|
2824
|
+
case "atom":
|
|
2825
|
+
if (node.content === ":") {
|
|
2826
|
+
return new TexNode("symbol", "\\colon");
|
|
2827
|
+
}
|
|
2828
|
+
return new TexNode("element", node.content);
|
|
2829
|
+
case "symbol":
|
|
2830
|
+
if (node.content === "comma") {
|
|
2831
|
+
return new TexNode("element", ",");
|
|
2832
|
+
}
|
|
2833
|
+
return new TexNode("symbol", typst_token_to_tex(node.content));
|
|
2834
|
+
case "text":
|
|
2835
|
+
return new TexNode("text", node.content);
|
|
2836
|
+
case "comment":
|
|
2837
|
+
return new TexNode("comment", node.content);
|
|
2838
|
+
case "group": {
|
|
2839
|
+
const args = node.args.map(convert_typst_node_to_tex);
|
|
2840
|
+
if (node.content === "parenthesis") {
|
|
2841
|
+
args.unshift(new TexNode("element", "("));
|
|
2842
|
+
args.push(new TexNode("element", ")"));
|
|
2843
|
+
}
|
|
2844
|
+
return new TexNode("ordgroup", "", args);
|
|
2845
|
+
}
|
|
2846
|
+
case "funcCall": {
|
|
2847
|
+
if (TYPST_UNARY_FUNCTIONS.includes(node.content)) {
|
|
2848
|
+
if (node.content === "lr") {
|
|
2849
|
+
const body = node.args[0];
|
|
2850
|
+
if (body.type === "group") {
|
|
2851
|
+
let left_delim = body.args[0].content;
|
|
2852
|
+
let right_delim = body.args[body.args.length - 1].content;
|
|
2853
|
+
left_delim = apply_escape_if_needed2(left_delim);
|
|
2854
|
+
right_delim = apply_escape_if_needed2(right_delim);
|
|
2855
|
+
return new TexNode("ordgroup", "", [
|
|
2856
|
+
new TexNode("element", "\\left" + left_delim),
|
|
2857
|
+
...body.args.slice(1, body.args.length - 1).map(convert_typst_node_to_tex),
|
|
2858
|
+
new TexNode("element", "\\right" + right_delim)
|
|
2859
|
+
]);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
const command = typst_token_to_tex(node.content);
|
|
2863
|
+
return new TexNode("unaryFunc", command, node.args.map(convert_typst_node_to_tex));
|
|
2864
|
+
} else if (TYPST_BINARY_FUNCTIONS.includes(node.content)) {
|
|
2865
|
+
if (node.content === "root") {
|
|
2866
|
+
const [degree, radicand] = node.args;
|
|
2867
|
+
const data = convert_typst_node_to_tex(degree);
|
|
2868
|
+
return new TexNode("unaryFunc", "\\sqrt", [convert_typst_node_to_tex(radicand)], data);
|
|
2869
|
+
}
|
|
2870
|
+
if (node.content === "overbrace" || node.content === "underbrace") {
|
|
2871
|
+
const [body, label] = node.args;
|
|
2872
|
+
const base = new TexNode("unaryFunc", "\\" + node.content, [convert_typst_node_to_tex(body)]);
|
|
2873
|
+
const script = convert_typst_node_to_tex(label);
|
|
2874
|
+
const data = node.content === "overbrace" ? { base, sup: script } : { base, sub: script };
|
|
2875
|
+
return new TexNode("supsub", "", [], data);
|
|
2876
|
+
}
|
|
2877
|
+
const command = typst_token_to_tex(node.content);
|
|
2878
|
+
return new TexNode("binaryFunc", command, node.args.map(convert_typst_node_to_tex));
|
|
2879
|
+
} else {
|
|
2880
|
+
return new TexNode("ordgroup", "", [
|
|
2881
|
+
new TexNode("symbol", typst_token_to_tex(node.content)),
|
|
2882
|
+
new TexNode("element", "("),
|
|
2883
|
+
...node.args.map(convert_typst_node_to_tex),
|
|
2884
|
+
new TexNode("element", ")")
|
|
2885
|
+
]);
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
case "supsub": {
|
|
2889
|
+
const { base, sup, sub } = node.data;
|
|
2890
|
+
const base_tex = convert_typst_node_to_tex(base);
|
|
2891
|
+
let sup_tex;
|
|
2892
|
+
let sub_tex;
|
|
2893
|
+
if (sup) {
|
|
2894
|
+
sup_tex = convert_typst_node_to_tex(sup);
|
|
2895
|
+
}
|
|
2896
|
+
if (sub) {
|
|
2897
|
+
sub_tex = convert_typst_node_to_tex(sub);
|
|
2898
|
+
}
|
|
2899
|
+
const res = new TexNode("supsub", "", [], {
|
|
2900
|
+
base: base_tex,
|
|
2901
|
+
sup: sup_tex,
|
|
2902
|
+
sub: sub_tex
|
|
2903
|
+
});
|
|
2904
|
+
return res;
|
|
2905
|
+
}
|
|
2906
|
+
case "matrix": {
|
|
2907
|
+
const typst_data = node.data;
|
|
2908
|
+
const tex_data = typst_data.map((row) => row.map(convert_typst_node_to_tex));
|
|
2909
|
+
const matrix = new TexNode("beginend", "matrix", [], tex_data);
|
|
2910
|
+
return new TexNode("ordgroup", "", [
|
|
2911
|
+
new TexNode("element", "\\left("),
|
|
2912
|
+
matrix,
|
|
2913
|
+
new TexNode("element", "\\right)")
|
|
2914
|
+
]);
|
|
2915
|
+
}
|
|
2916
|
+
case "control": {
|
|
2917
|
+
switch (node.content) {
|
|
2918
|
+
case "\\":
|
|
2919
|
+
return new TexNode("control", "\\\\");
|
|
2920
|
+
case "&":
|
|
2921
|
+
return new TexNode("control", "&");
|
|
2922
|
+
default:
|
|
2923
|
+
throw new Error("[convert_typst_node_to_tex] Unimplemented control: " + node.content);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
case "fraction": {
|
|
2927
|
+
const [numerator, denominator] = node.args;
|
|
2928
|
+
const num_tex = convert_typst_node_to_tex(numerator);
|
|
2929
|
+
const den_tex = convert_typst_node_to_tex(denominator);
|
|
2930
|
+
return new TexNode("binaryFunc", "\\frac", [num_tex, den_tex]);
|
|
2931
|
+
}
|
|
2932
|
+
default:
|
|
2933
|
+
throw new Error("[convert_typst_node_to_tex] Unimplemented type: " + node.type);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
function typst_token_to_tex(token) {
|
|
2937
|
+
if (/^[a-zA-Z0-9]$/.test(token)) {
|
|
2938
|
+
return token;
|
|
2939
|
+
} else if (token === "thin") {
|
|
2940
|
+
return "\\,";
|
|
2941
|
+
} else if (reverseSymbolMap.has(token)) {
|
|
2942
|
+
return "\\" + reverseSymbolMap.get(token);
|
|
2943
|
+
}
|
|
2944
|
+
return "\\" + token;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2162
2947
|
// src/index.ts
|
|
2163
2948
|
function tex2typst(tex, options) {
|
|
2164
2949
|
const opt = {
|
|
@@ -2180,11 +2965,19 @@ function tex2typst(tex, options) {
|
|
|
2180
2965
|
}
|
|
2181
2966
|
const texTree = parseTex(tex, opt.customTexMacros);
|
|
2182
2967
|
const typstTree = convertTree(texTree);
|
|
2183
|
-
const
|
|
2184
|
-
|
|
2185
|
-
return
|
|
2968
|
+
const writer = new TypstWriter(opt.nonStrict, opt.preferTypstIntrinsic, opt.keepSpaces);
|
|
2969
|
+
writer.serialize(typstTree);
|
|
2970
|
+
return writer.finalize();
|
|
2971
|
+
}
|
|
2972
|
+
function typst2tex(typst) {
|
|
2973
|
+
const typstTree = parseTypst(typst);
|
|
2974
|
+
const texTree = convert_typst_node_to_tex(typstTree);
|
|
2975
|
+
const writer = new TexWriter;
|
|
2976
|
+
writer.append(texTree);
|
|
2977
|
+
return writer.finalize();
|
|
2186
2978
|
}
|
|
2187
2979
|
export {
|
|
2980
|
+
typst2tex,
|
|
2188
2981
|
tex2typst,
|
|
2189
2982
|
symbolMap
|
|
2190
2983
|
};
|