tex2typst 0.2.16 → 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/generic.d.ts +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +963 -195
- 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 +1 -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 +23 -100
- 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 +42 -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"],
|
|
@@ -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
|
-
|
|
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
|
-
|
|
1194
|
+
value;
|
|
1064
1195
|
constructor(type, content) {
|
|
1065
1196
|
this.type = type;
|
|
1066
|
-
this.
|
|
1197
|
+
this.value = content;
|
|
1067
1198
|
}
|
|
1068
1199
|
eq(other) {
|
|
1069
|
-
return this.type === other.type && this.
|
|
1200
|
+
return this.type === other.type && this.value === other.value;
|
|
1070
1201
|
}
|
|
1071
1202
|
isOneOf(tokens) {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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
|
-
|
|
1264
|
+
eq(other) {
|
|
1099
1265
|
return this.type === other.type && this.content === other.content;
|
|
1100
1266
|
}
|
|
1101
1267
|
}
|
|
1102
1268
|
|
|
1103
|
-
// src/
|
|
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
|
|
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
|
|
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(
|
|
1331
|
+
if (tokens[pos].eq(leftToken)) {
|
|
1197
1332
|
count += 1;
|
|
1198
|
-
} else if (tokens[pos].eq(
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
1943
|
+
return new TypstNode("funcCall", "root", [data, arg0]);
|
|
1834
1944
|
}
|
|
1835
1945
|
if (node.content === "\\mathbf") {
|
|
1836
|
-
const inner = new TypstNode("
|
|
1837
|
-
return new TypstNode("
|
|
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("
|
|
1961
|
+
return new TypstNode("funcCall", "op", [new TypstNode("text", text)]);
|
|
1852
1962
|
}
|
|
1853
1963
|
}
|
|
1854
|
-
return new TypstNode("
|
|
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 /*
|
|
1914
|
-
var TYPST_RIGHT_PARENTHESIS = new TypstToken(1 /*
|
|
1915
|
-
var TYPST_COMMA = new TypstToken(1 /*
|
|
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.
|
|
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 ||= /^[}
|
|
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 /*
|
|
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 */,
|
|
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 */,
|
|
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 /*
|
|
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 /*
|
|
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 /*
|
|
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 "
|
|
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": {
|
|
2132
|
+
case "funcCall": {
|
|
2034
2133
|
const func_symbol = new TypstToken(0 /* SYMBOL */, node.content);
|
|
2035
|
-
const arg0 = node.args[0];
|
|
2036
2134
|
this.queue.push(func_symbol);
|
|
2037
2135
|
this.insideFunctionDepth++;
|
|
2038
2136
|
this.queue.push(TYPST_LEFT_PARENTHESIS);
|
|
2039
|
-
|
|
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 /*
|
|
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 /*
|
|
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 /*
|
|
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].
|
|
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].
|
|
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
|
};
|