Comparing version 0.2.7 to 0.2.8
1169
dist/index.js
@@ -0,1 +1,301 @@ | ||
// src/map.ts | ||
var symbolMap = new Map([ | ||
["nonumber", ""], | ||
["vec", "arrow"], | ||
["neq", "eq.not"], | ||
["dot", "dot"], | ||
["ddot", "dot.double"], | ||
["doteq", "dot(eq)"], | ||
["dots", "dots.h"], | ||
["ldots", "dots.h"], | ||
["vdots", "dots.v"], | ||
["ddots", "dots.down"], | ||
["widehat", "hat"], | ||
["widetilde", "tilde"], | ||
["quad", "quad"], | ||
["qquad", "wide"], | ||
["overbrace", "overbrace"], | ||
["underbrace", "underbrace"], | ||
["overline", "overline"], | ||
["underline", "underline"], | ||
["bar", "macron"], | ||
["dbinom", "binom"], | ||
["tbinom", "binom"], | ||
["dfrac", "frac"], | ||
["tfrac", "frac"], | ||
["boldsymbol", "bold"], | ||
["mathbb", "bb"], | ||
["mathbf", "bold"], | ||
["mathcal", "cal"], | ||
["mathit", "italic"], | ||
["mathfrak", "frak"], | ||
["mathrm", "upright"], | ||
["mathsf", "sans"], | ||
["mathtt", "mono"], | ||
["rm", "upright"], | ||
["pmb", "bold"], | ||
["pm", "plus.minus"], | ||
["mp", "minus.plus"], | ||
["oplus", "xor"], | ||
["boxplus", "plus.square"], | ||
["otimes", "times.circle"], | ||
["boxtimes", "times.square"], | ||
["sim", "tilde"], | ||
["approx", "approx"], | ||
["cong", "tilde.equiv"], | ||
["simeq", "tilde.eq"], | ||
["asymp", "\u224D"], | ||
["equiv", "equiv"], | ||
["propto", "prop"], | ||
["lfloor", "\u230A"], | ||
["rfloor", "\u230B"], | ||
["lceil", "\u2308"], | ||
["rceil", "\u2309"], | ||
["gets", "arrow.l"], | ||
["hookleftarrow", "arrow.l.hook"], | ||
["leftharpoonup", "harpoon.lt"], | ||
["leftharpoondown", "harpoon.lb"], | ||
["rightleftharpoons", "harpoons.rtlb"], | ||
["longleftarrow", "arrow.l.long"], | ||
["longrightarrow", "arrow.r.long"], | ||
["longleftrightarrow", "arrow.l.r.long"], | ||
["Longleftarrow", "arrow.l.double.long"], | ||
["Longrightarrow", "arrow.r.double.long"], | ||
["Longleftrightarrow", "arrow.l.r.double.long"], | ||
["longmapsto", "arrow.r.bar"], | ||
["hookrightarrow", "arrow.r.hook"], | ||
["rightharpoonup", "harpoon.rt"], | ||
["rightharpoondown", "harpoon.rb"], | ||
["iff", "arrow.l.r.double.long"], | ||
["implies", "arrow.r.double.long"], | ||
["uparrow", "arrow.t"], | ||
["downarrow", "arrow.b"], | ||
["updownarrow", "arrow.t.b"], | ||
["Uparrow", "arrow.t.double"], | ||
["Downarrow", "arrow.b.double"], | ||
["Updownarrow", "arrow.t.b.double"], | ||
["nearrow", "arrow.tr"], | ||
["searrow", "arrow.br"], | ||
["swarrow", "arrow.bl"], | ||
["nwarrow", "arrow.tl"], | ||
["leadsto", "arrow.squiggly"], | ||
["leftleftarrows", "arrows.ll"], | ||
["rightrightarrows", "arrows.rr"], | ||
["Cap", "sect.double"], | ||
["Cup", "union.double"], | ||
["Delta", "Delta"], | ||
["Gamma", "Gamma"], | ||
["Join", "join"], | ||
["Lambda", "Lambda"], | ||
["Leftarrow", "arrow.l.double"], | ||
["Leftrightarrow", "arrow.l.r.double"], | ||
["Longrightarrow", "arrow.r.double.long"], | ||
["Omega", "Omega"], | ||
["P", "pilcrow"], | ||
["Phi", "Phi"], | ||
["Pi", "Pi"], | ||
["Psi", "Psi"], | ||
["Rightarrow", "arrow.r.double"], | ||
["S", "section"], | ||
["Sigma", "Sigma"], | ||
["Theta", "Theta"], | ||
["aleph", "alef"], | ||
["alpha", "alpha"], | ||
["angle", "angle"], | ||
["approx", "approx"], | ||
["approxeq", "approx.eq"], | ||
["ast", "ast"], | ||
["beta", "beta"], | ||
["bigcap", "sect.big"], | ||
["bigcirc", "circle.big"], | ||
["bigcup", "union.big"], | ||
["bigodot", "dot.circle.big"], | ||
["bigoplus", "xor.big"], | ||
["bigotimes", "times.circle.big"], | ||
["bigsqcup", "union.sq.big"], | ||
["bigtriangledown", "triangle.b"], | ||
["bigtriangleup", "triangle.t"], | ||
["biguplus", "union.plus.big"], | ||
["bigvee", "or.big"], | ||
["bigwedge", "and.big"], | ||
["bullet", "bullet"], | ||
["cap", "sect"], | ||
["cdot", "dot.op"], | ||
["cdots", "dots.c"], | ||
["checkmark", "checkmark"], | ||
["chi", "chi"], | ||
["circ", "circle.small"], | ||
["colon", "colon"], | ||
["cong", "tilde.equiv"], | ||
["coprod", "product.co"], | ||
["copyright", "copyright"], | ||
["cup", "union"], | ||
["curlyvee", "or.curly"], | ||
["curlywedge", "and.curly"], | ||
["dagger", "dagger"], | ||
["dashv", "tack.l"], | ||
["ddagger", "dagger.double"], | ||
["delta", "delta"], | ||
["ddots", "dots.down"], | ||
["diamond", "diamond"], | ||
["div", "div"], | ||
["divideontimes", "times.div"], | ||
["dotplus", "plus.dot"], | ||
["downarrow", "arrow.b"], | ||
["ell", "ell"], | ||
["emptyset", "nothing"], | ||
["epsilon", "epsilon.alt"], | ||
["equiv", "equiv"], | ||
["eta", "eta"], | ||
["exists", "exists"], | ||
["forall", "forall"], | ||
["gamma", "gamma"], | ||
["ge", "gt.eq"], | ||
["geq", "gt.eq"], | ||
["geqslant", "gt.eq.slant"], | ||
["gg", "gt.double"], | ||
["hbar", "planck.reduce"], | ||
["imath", "dotless.i"], | ||
["iiiint", "intgral.quad"], | ||
["iiint", "integral.triple"], | ||
["iint", "integral.double"], | ||
["in", "in"], | ||
["infty", "infinity"], | ||
["int", "integral"], | ||
["intercal", "top"], | ||
["iota", "iota"], | ||
["jmath", "dotless.j"], | ||
["kappa", "kappa"], | ||
["lambda", "lambda"], | ||
["land", "and"], | ||
["langle", "angle.l"], | ||
["lbrace", "brace.l"], | ||
["lbrack", "bracket.l"], | ||
["ldots", "dots.l"], | ||
["le", "lt.eq"], | ||
["leadsto", "arrow.squiggly"], | ||
["leftarrow", "arrow.l"], | ||
["leftthreetimes", "times.three.l"], | ||
["leftrightarrow", "arrow.l.r"], | ||
["leq", "lt.eq"], | ||
["leqslant", "lt.eq.slant"], | ||
["lhd", "triangle.l"], | ||
["ll", "lt.double"], | ||
["longmapsto", "arrow.bar.long"], | ||
["longrightarrow", "arrow.long"], | ||
["lor", "or"], | ||
["ltimes", "times.l"], | ||
["mapsto", "arrow.bar"], | ||
["measuredangle", "angle.arc"], | ||
["mid", "divides"], | ||
["models", "models"], | ||
["mp", "minus.plus"], | ||
["mu", "mu"], | ||
["nRightarrow", "arrow.double.not"], | ||
["nabla", "nabla"], | ||
["ncong", "tilde.nequiv"], | ||
["ne", "eq.not"], | ||
["neg", "not"], | ||
["neq", "eq.not"], | ||
["nexists", "exists.not"], | ||
["ni", "in.rev"], | ||
["nleftarrow", "arrow.l.not"], | ||
["nleq", "lt.eq.not"], | ||
["nparallel", "parallel.not"], | ||
["ngeq", "gt.eq.not"], | ||
["nmid", "divides.not"], | ||
["notin", "in.not"], | ||
["nrightarrow", "arrow.not"], | ||
["nsim", "tilde.not"], | ||
["nsubseteq", "subset.eq.not"], | ||
["nu", "nu"], | ||
["ntriangleleft", "lt.tri.not"], | ||
["ntriangleright", "gt.tri.not"], | ||
["nwarrow", "arrow.tl"], | ||
["odot", "dot.circle"], | ||
["oint", "integral.cont"], | ||
["oiint", "integral.surf"], | ||
["oiiint", "integral.vol"], | ||
["omega", "omega"], | ||
["ominus", "minus.circle"], | ||
["oplus", "xor"], | ||
["otimes", "times.circle"], | ||
["parallel", "parallel"], | ||
["partial", "diff"], | ||
["perp", "perp"], | ||
["phi", "phi.alt"], | ||
["pi", "pi"], | ||
["pm", "plus.minus"], | ||
["pounds", "pound"], | ||
["prec", "prec"], | ||
["preceq", "prec.eq"], | ||
["prime", "prime"], | ||
["prod", "product"], | ||
["propto", "prop"], | ||
["psi", "psi"], | ||
["rangle", "angle.r"], | ||
["rbrace", "brace.r"], | ||
["rbrack", "bracket.r"], | ||
["rhd", "triangle"], | ||
["rho", "rho"], | ||
["rightarrow", "arrow.r"], | ||
["rightthreetimes", "times.three.r"], | ||
["rtimes", "times.r"], | ||
["setminus", "without"], | ||
["sigma", "sigma"], | ||
["sim", "tilde"], | ||
["simeq", "tilde.eq"], | ||
["slash", "slash"], | ||
["smallsetminus", "without"], | ||
["spadesuit", "suit.spade"], | ||
["sqcap", "sect.sq"], | ||
["sqcup", "union.sq"], | ||
["sqsubseteq", "subset.eq.sq"], | ||
["sqsupseteq", "supset.eq.sq"], | ||
["star", "star"], | ||
["subset", "subset"], | ||
["subseteq", "subset.eq"], | ||
["subsetneq", "subset.neq"], | ||
["succ", "succ"], | ||
["succeq", "succ.eq"], | ||
["sum", "sum"], | ||
["supset", "supset"], | ||
["supseteq", "supset.eq"], | ||
["supsetneq", "supset.neq"], | ||
["swarrow", "arrow.bl"], | ||
["tau", "tau"], | ||
["theta", "theta"], | ||
["times", "times"], | ||
["to", "arrow.r"], | ||
["top", "top"], | ||
["triangle", "triangle.t"], | ||
["triangledown", "triangle.b.small"], | ||
["triangleleft", "triangle.l.small"], | ||
["triangleright", "triangle.r.small"], | ||
["twoheadrightarrow", "arrow.r.twohead"], | ||
["uparrow", "arrow.t"], | ||
["updownarrow", "arrow.t.b"], | ||
["upharpoonright", "harpoon.tr"], | ||
["uplus", "union.plus"], | ||
["upsilon", "upsilon"], | ||
["varepsilon", "epsilon"], | ||
["varnothing", "diameter"], | ||
["varphi", "phi"], | ||
["varpi", "pi.alt"], | ||
["varrho", "rho.alt"], | ||
["varsigma", "sigma.alt"], | ||
["vartheta", "theta.alt"], | ||
["vdash", "tack.r"], | ||
["vdots", "dots.v"], | ||
["vee", "or"], | ||
["wedge", "and"], | ||
["wr", "wreath"], | ||
["xi", "xi"], | ||
["yen", "yen"], | ||
["zeta", "zeta"], | ||
["mathscr", "scr"], | ||
["LaTeX", "#LaTeX"], | ||
["TeX", "#TeX"] | ||
]); | ||
// src/parser.ts | ||
@@ -58,3 +358,3 @@ function assert(condition, message = "") { | ||
let pos = start; | ||
while (pos < tokens.length && ["whitespace", "newline"].includes(tokens[pos].type)) { | ||
while (pos < tokens.length && [4 /* WHITESPACE */, 5 /* NEWLINE */].includes(tokens[pos].type)) { | ||
pos++; | ||
@@ -66,5 +366,5 @@ } | ||
const firstToken = tokens[start]; | ||
if (firstToken.type === "element" && ["(", ")", "[", "]", "|", "\\{", "\\}"].includes(firstToken.value)) { | ||
if (firstToken.type === 0 /* ELEMENT */ && ["(", ")", "[", "]", "|", "\\{", "\\}"].includes(firstToken.value)) { | ||
return firstToken; | ||
} else if (firstToken.type === "command" && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) { | ||
} else if (firstToken.type === 1 /* COMMAND */ && ["lfloor", "rfloor", "lceil", "rceil", "langle", "rangle"].includes(firstToken.value.slice(1))) { | ||
return firstToken; | ||
@@ -77,3 +377,3 @@ } else { | ||
let pos = start; | ||
while (pos < tokens.length && token_eq(tokens[pos], { type: "element", value: "'" })) { | ||
while (pos < tokens.length && token_eq(tokens[pos], { type: 0 /* ELEMENT */, value: "'" })) { | ||
pos += 1; | ||
@@ -155,3 +455,3 @@ } | ||
} | ||
token = { type: "comment", value: latex.slice(pos + 1, newPos) }; | ||
token = { type: 3 /* COMMENT */, value: latex.slice(pos + 1, newPos) }; | ||
pos = newPos; | ||
@@ -165,7 +465,7 @@ break; | ||
case "&": | ||
token = { type: "control", value: firstChar }; | ||
token = { type: 6 /* CONTROL */, value: firstChar }; | ||
pos++; | ||
break; | ||
case "\n": | ||
token = { type: "newline", value: firstChar }; | ||
token = { type: 5 /* NEWLINE */, value: firstChar }; | ||
pos++; | ||
@@ -175,6 +475,6 @@ break; | ||
if (pos + 1 < latex.length && latex[pos + 1] === "\n") { | ||
token = { type: "newline", value: "\n" }; | ||
token = { type: 5 /* NEWLINE */, value: "\n" }; | ||
pos += 2; | ||
} else { | ||
token = { type: "newline", value: "\n" }; | ||
token = { type: 5 /* NEWLINE */, value: "\n" }; | ||
pos++; | ||
@@ -189,3 +489,3 @@ } | ||
} | ||
token = { type: "whitespace", value: latex.slice(pos, newPos) }; | ||
token = { type: 4 /* WHITESPACE */, value: latex.slice(pos, newPos) }; | ||
pos = newPos; | ||
@@ -200,8 +500,8 @@ break; | ||
if (["\\\\", "\\,"].includes(firstTwoChars)) { | ||
token = { type: "control", value: firstTwoChars }; | ||
token = { type: 6 /* CONTROL */, value: firstTwoChars }; | ||
} else if (["\\{", "\\}", "\\%", "\\$", "\\&", "\\#", "\\_"].includes(firstTwoChars)) { | ||
token = { type: "element", value: firstTwoChars }; | ||
token = { type: 0 /* ELEMENT */, value: firstTwoChars }; | ||
} else { | ||
const command = eat_command_name(latex, pos + 1); | ||
token = { type: "command", value: "\\" + command }; | ||
token = { type: 1 /* COMMAND */, value: "\\" + command }; | ||
} | ||
@@ -217,9 +517,9 @@ pos += token.value.length; | ||
} | ||
token = { type: "element", value: latex.slice(pos, newPos) }; | ||
token = { type: 0 /* ELEMENT */, value: latex.slice(pos, newPos) }; | ||
} else if (isalpha(firstChar)) { | ||
token = { type: "element", value: firstChar }; | ||
token = { type: 0 /* ELEMENT */, value: firstChar }; | ||
} else if ("+-*/=\'<>!.,;?()[]|".includes(firstChar)) { | ||
token = { type: "element", value: firstChar }; | ||
token = { type: 0 /* ELEMENT */, value: firstChar }; | ||
} else { | ||
token = { type: "unknown", value: firstChar }; | ||
token = { type: 7 /* UNKNOWN */, value: firstChar }; | ||
} | ||
@@ -230,7 +530,7 @@ pos += token.value.length; | ||
tokens.push(token); | ||
if (token.type === "command" && ["\\text", "\\begin", "\\end"].includes(token.value)) { | ||
if (token.type === 1 /* COMMAND */ && ["\\text", "\\operatorname", "\\begin", "\\end"].includes(token.value)) { | ||
if (pos >= latex.length || latex[pos] !== "{") { | ||
throw new LatexParserError(`No content for ${token.value} command`); | ||
} | ||
tokens.push({ type: "control", value: "{" }); | ||
tokens.push({ type: 6 /* CONTROL */, value: "{" }); | ||
const posClosingBracket = find_closing_curly_bracket_char(latex, pos); | ||
@@ -243,4 +543,4 @@ pos++; | ||
} | ||
tokens.push({ type: "text", value: textInside }); | ||
tokens.push({ type: "control", value: "}" }); | ||
tokens.push({ type: 2 /* TEXT */, value: textInside }); | ||
tokens.push({ type: 6 /* CONTROL */, value: "}" }); | ||
pos = posClosingBracket + 1; | ||
@@ -258,6 +558,6 @@ } | ||
for (let i = 0;i < tokens.length; i++) { | ||
if (tokens[i].type === "whitespace" && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) { | ||
if (tokens[i].type === 4 /* WHITESPACE */ && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) { | ||
continue; | ||
} | ||
if (tokens[i].type === "whitespace" && i - 1 >= 0 && is_script_mark(tokens[i - 1])) { | ||
if (tokens[i].type === 4 /* WHITESPACE */ && i - 1 >= 0 && is_script_mark(tokens[i - 1])) { | ||
continue; | ||
@@ -272,3 +572,3 @@ } | ||
for (const token of tokens) { | ||
if (token.type === "command" && customTexMacros[token.value]) { | ||
if (token.type === 1 /* COMMAND */ && customTexMacros[token.value]) { | ||
const expanded_tokens = tokenize(customTexMacros[token.value]); | ||
@@ -328,10 +628,10 @@ out_tokens = out_tokens.concat(expanded_tokens); | ||
var EMPTY_NODE = { type: "empty", content: "" }; | ||
var LEFT_CURLY_BRACKET = { type: "control", value: "{" }; | ||
var RIGHT_CURLY_BRACKET = { type: "control", value: "}" }; | ||
var LEFT_SQUARE_BRACKET = { type: "element", value: "[" }; | ||
var RIGHT_SQUARE_BRACKET = { type: "element", value: "]" }; | ||
var LEFT_COMMAND = { type: "command", value: "\\left" }; | ||
var RIGHT_COMMAND = { type: "command", value: "\\right" }; | ||
var BEGIN_COMMAND = { type: "command", value: "\\begin" }; | ||
var END_COMMAND = { type: "command", value: "\\end" }; | ||
var LEFT_CURLY_BRACKET = { type: 6 /* CONTROL */, value: "{" }; | ||
var RIGHT_CURLY_BRACKET = { type: 6 /* CONTROL */, value: "}" }; | ||
var LEFT_SQUARE_BRACKET = { type: 0 /* ELEMENT */, value: "[" }; | ||
var RIGHT_SQUARE_BRACKET = { type: 0 /* ELEMENT */, value: "]" }; | ||
var LEFT_COMMAND = { type: 1 /* COMMAND */, value: "\\left" }; | ||
var RIGHT_COMMAND = { type: 1 /* COMMAND */, value: "\\right" }; | ||
var BEGIN_COMMAND = { type: 1 /* COMMAND */, value: "\\begin" }; | ||
var END_COMMAND = { type: 1 /* COMMAND */, value: "\\end" }; | ||
@@ -344,4 +644,4 @@ class LatexParserError extends Error { | ||
} | ||
var SUB_SYMBOL = { type: "control", value: "_" }; | ||
var SUP_SYMBOL = { type: "control", value: "^" }; | ||
var SUB_SYMBOL = { type: 6 /* CONTROL */, value: "_" }; | ||
var SUP_SYMBOL = { type: 6 /* CONTROL */, value: "^" }; | ||
@@ -428,3 +728,3 @@ class LatexParser { | ||
for (let i = 0;i < num_prime; i++) { | ||
res.sup.args.push({ type: "symbol", content: "\\prime" }); | ||
res.sup.args.push({ type: "element", content: "'" }); | ||
} | ||
@@ -449,9 +749,13 @@ if (sup) { | ||
switch (tokenType) { | ||
case "element": | ||
case "text": | ||
case "comment": | ||
case "whitespace": | ||
case "newline": | ||
return [{ type: tokenType, content: firstToken.value }, start + 1]; | ||
case "command": | ||
case 0 /* ELEMENT */: | ||
return [{ type: "element", content: firstToken.value }, start + 1]; | ||
case 2 /* TEXT */: | ||
return [{ type: "text", content: firstToken.value }, start + 1]; | ||
case 3 /* COMMENT */: | ||
return [{ type: "comment", content: firstToken.value }, start + 1]; | ||
case 4 /* WHITESPACE */: | ||
return [{ type: "whitespace", content: firstToken.value }, start + 1]; | ||
case 5 /* NEWLINE */: | ||
return [{ type: "newline", content: firstToken.value }, start + 1]; | ||
case 1 /* COMMAND */: | ||
if (token_eq(firstToken, BEGIN_COMMAND)) { | ||
@@ -464,3 +768,3 @@ return this.parseBeginEndExpr(tokens, start); | ||
} | ||
case "control": | ||
case 6 /* CONTROL */: | ||
const controlChar = firstToken.value; | ||
@@ -494,3 +798,3 @@ switch (controlChar) { | ||
parseCommandExpr(tokens, start) { | ||
assert(tokens[start].type === "command"); | ||
assert(tokens[start].type === 1 /* COMMAND */); | ||
const command = tokens[start].value; | ||
@@ -502,30 +806,36 @@ let pos = start + 1; | ||
const paramNum = get_command_param_num(command.slice(1)); | ||
if (paramNum === 0) { | ||
return [{ type: "symbol", content: command }, pos]; | ||
} else if (paramNum === 1) { | ||
if (command === "\\sqrt" && pos < tokens.length && token_eq(tokens[pos], LEFT_SQUARE_BRACKET)) { | ||
const posLeftSquareBracket = pos; | ||
const posRightSquareBracket = find_closing_square_bracket(tokens, pos); | ||
const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket); | ||
const exponent = this.parse(exprInside); | ||
const [arg12, newPos2] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1); | ||
return [{ type: "unaryFunc", content: command, args: [arg12], data: exponent }, newPos2]; | ||
} else if (command === "\\text") { | ||
if (pos + 2 >= tokens.length) { | ||
throw new LatexParserError("Expecting content for \\text command"); | ||
switch (paramNum) { | ||
case 0: | ||
if (!symbolMap.has(command.slice(1))) { | ||
return [{ type: "unknownMacro", content: command }, pos]; | ||
} | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === "text"); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
const text = tokens[pos + 1].value; | ||
return [{ type: "text", content: text }, pos + 3]; | ||
return [{ type: "symbol", content: command }, pos]; | ||
case 1: { | ||
if (command === "\\sqrt" && pos < tokens.length && token_eq(tokens[pos], LEFT_SQUARE_BRACKET)) { | ||
const posLeftSquareBracket = pos; | ||
const posRightSquareBracket = find_closing_square_bracket(tokens, pos); | ||
const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket); | ||
const exponent = this.parse(exprInside); | ||
const [arg12, newPos2] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1); | ||
return [{ type: "unaryFunc", content: command, args: [arg12], data: exponent }, newPos2]; | ||
} else if (command === "\\text") { | ||
if (pos + 2 >= tokens.length) { | ||
throw new LatexParserError("Expecting content for \\text command"); | ||
} | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === 2 /* TEXT */); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
const text = tokens[pos + 1].value; | ||
return [{ type: "text", content: text }, pos + 3]; | ||
} | ||
let [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
return [{ type: "unaryFunc", content: command, args: [arg1] }, newPos]; | ||
} | ||
let [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
return [{ type: "unaryFunc", content: command, args: [arg1] }, newPos]; | ||
} else if (paramNum === 2) { | ||
const [arg1, pos1] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
const [arg2, pos2] = this.parseNextExprWithoutSupSub(tokens, pos1); | ||
return [{ type: "binaryFunc", content: command, args: [arg1, arg2] }, pos2]; | ||
} else { | ||
throw new Error("Invalid number of parameters"); | ||
case 2: { | ||
const [arg1, pos1] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
const [arg2, pos2] = this.parseNextExprWithoutSupSub(tokens, pos1); | ||
return [{ type: "binaryFunc", content: command, args: [arg1, arg2] }, pos2]; | ||
} | ||
default: | ||
throw new Error("Invalid number of parameters"); | ||
} | ||
@@ -575,3 +885,3 @@ } | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === "text"); | ||
assert(tokens[pos + 1].type === 2 /* TEXT */); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
@@ -589,3 +899,3 @@ const envName = tokens[pos + 1].value; | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === "text"); | ||
assert(tokens[pos + 1].type === 2 /* TEXT */); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
@@ -597,3 +907,3 @@ if (tokens[pos + 1].value !== envName) { | ||
const exprInside = tokens.slice(exprInsideStart, exprInsideEnd); | ||
while (exprInside.length > 0 && ["whitespace", "newline"].includes(exprInside[exprInside.length - 1].type)) { | ||
while (exprInside.length > 0 && [4 /* WHITESPACE */, 5 /* NEWLINE */].includes(exprInside[exprInside.length - 1].type)) { | ||
exprInside.pop(); | ||
@@ -635,303 +945,163 @@ } | ||
// src/map.ts | ||
var symbolMap = new Map([ | ||
["nonumber", ""], | ||
["vec", "arrow"], | ||
["neq", "eq.not"], | ||
["dot", "dot"], | ||
["ddot", "dot.double"], | ||
["doteq", "dot(eq)"], | ||
["dots", "dots.h"], | ||
["ldots", "dots.h"], | ||
["vdots", "dots.v"], | ||
["ddots", "dots.down"], | ||
["widehat", "hat"], | ||
["widetilde", "tilde"], | ||
["quad", "quad"], | ||
["qquad", "wide"], | ||
["overbrace", "overbrace"], | ||
["underbrace", "underbrace"], | ||
["overline", "overline"], | ||
["underline", "underline"], | ||
["bar", "macron"], | ||
["dbinom", "binom"], | ||
["tbinom", "binom"], | ||
["dfrac", "frac"], | ||
["tfrac", "frac"], | ||
["boldsymbol", "bold"], | ||
["mathbb", "bb"], | ||
["mathbf", "bold"], | ||
["mathcal", "cal"], | ||
["mathit", "italic"], | ||
["mathfrak", "frak"], | ||
["mathrm", "upright"], | ||
["mathsf", "sans"], | ||
["mathtt", "mono"], | ||
["rm", "upright"], | ||
["pmb", "bold"], | ||
["pm", "plus.minus"], | ||
["mp", "minus.plus"], | ||
["oplus", "xor"], | ||
["boxplus", "plus.square"], | ||
["otimes", "times.circle"], | ||
["boxtimes", "times.square"], | ||
["sim", "tilde"], | ||
["approx", "approx"], | ||
["cong", "tilde.equiv"], | ||
["simeq", "tilde.eq"], | ||
["asymp", "\u224D"], | ||
["equiv", "equiv"], | ||
["propto", "prop"], | ||
["lfloor", "\u230A"], | ||
["rfloor", "\u230B"], | ||
["lceil", "\u2308"], | ||
["rceil", "\u2309"], | ||
["gets", "arrow.l"], | ||
["hookleftarrow", "arrow.l.hook"], | ||
["leftharpoonup", "harpoon.lt"], | ||
["leftharpoondown", "harpoon.lb"], | ||
["rightleftharpoons", "harpoons.rtlb"], | ||
["longleftarrow", "arrow.l.long"], | ||
["longrightarrow", "arrow.r.long"], | ||
["longleftrightarrow", "arrow.l.r.long"], | ||
["Longleftarrow", "arrow.l.double.long"], | ||
["Longrightarrow", "arrow.r.double.long"], | ||
["Longleftrightarrow", "arrow.l.r.double.long"], | ||
["longmapsto", "arrow.r.bar"], | ||
["hookrightarrow", "arrow.r.hook"], | ||
["rightharpoonup", "harpoon.rt"], | ||
["rightharpoondown", "harpoon.rb"], | ||
["iff", "arrow.l.r.double.long"], | ||
["implies", "arrow.r.double.long"], | ||
["uparrow", "arrow.t"], | ||
["downarrow", "arrow.b"], | ||
["updownarrow", "arrow.t.b"], | ||
["Uparrow", "arrow.t.double"], | ||
["Downarrow", "arrow.b.double"], | ||
["Updownarrow", "arrow.t.b.double"], | ||
["nearrow", "arrow.tr"], | ||
["searrow", "arrow.br"], | ||
["swarrow", "arrow.bl"], | ||
["nwarrow", "arrow.tl"], | ||
["leadsto", "arrow.squiggly"], | ||
["leftleftarrows", "arrows.ll"], | ||
["rightrightarrows", "arrows.rr"], | ||
["Cap", "sect.double"], | ||
["Cup", "union.double"], | ||
["Delta", "Delta"], | ||
["Gamma", "Gamma"], | ||
["Join", "join"], | ||
["Lambda", "Lambda"], | ||
["Leftarrow", "arrow.l.double"], | ||
["Leftrightarrow", "arrow.l.r.double"], | ||
["Longrightarrow", "arrow.r.double.long"], | ||
["Omega", "Omega"], | ||
["P", "pilcrow"], | ||
["Phi", "Phi"], | ||
["Pi", "Pi"], | ||
["Psi", "Psi"], | ||
["Rightarrow", "arrow.r.double"], | ||
["S", "section"], | ||
["Sigma", "Sigma"], | ||
["Theta", "Theta"], | ||
["aleph", "alef"], | ||
["alpha", "alpha"], | ||
["angle", "angle"], | ||
["approx", "approx"], | ||
["approxeq", "approx.eq"], | ||
["ast", "ast"], | ||
["beta", "beta"], | ||
["bigcap", "sect.big"], | ||
["bigcirc", "circle.big"], | ||
["bigcup", "union.big"], | ||
["bigodot", "dot.circle.big"], | ||
["bigoplus", "xor.big"], | ||
["bigotimes", "times.circle.big"], | ||
["bigsqcup", "union.sq.big"], | ||
["bigtriangledown", "triangle.b"], | ||
["bigtriangleup", "triangle.t"], | ||
["biguplus", "union.plus.big"], | ||
["bigvee", "or.big"], | ||
["bigwedge", "and.big"], | ||
["bullet", "bullet"], | ||
["cap", "sect"], | ||
["cdot", "dot.op"], | ||
["cdots", "dots.c"], | ||
["checkmark", "checkmark"], | ||
["chi", "chi"], | ||
["circ", "circle.small"], | ||
["colon", "colon"], | ||
["cong", "tilde.equiv"], | ||
["coprod", "product.co"], | ||
["copyright", "copyright"], | ||
["cup", "union"], | ||
["curlyvee", "or.curly"], | ||
["curlywedge", "and.curly"], | ||
["dagger", "dagger"], | ||
["dashv", "tack.l"], | ||
["ddagger", "dagger.double"], | ||
["delta", "delta"], | ||
["ddots", "dots.down"], | ||
["diamond", "diamond"], | ||
["div", "div"], | ||
["divideontimes", "times.div"], | ||
["dotplus", "plus.dot"], | ||
["downarrow", "arrow.b"], | ||
["ell", "ell"], | ||
["emptyset", "nothing"], | ||
["epsilon", "epsilon.alt"], | ||
["equiv", "equiv"], | ||
["eta", "eta"], | ||
["exists", "exists"], | ||
["forall", "forall"], | ||
["gamma", "gamma"], | ||
["ge", "gt.eq"], | ||
["geq", "gt.eq"], | ||
["geqslant", "gt.eq.slant"], | ||
["gg", "gt.double"], | ||
["hbar", "planck.reduce"], | ||
["imath", "dotless.i"], | ||
["iiiint", "intgral.quad"], | ||
["iiint", "integral.triple"], | ||
["iint", "integral.double"], | ||
["in", "in"], | ||
["infty", "infinity"], | ||
["int", "integral"], | ||
["intercal", "top"], | ||
["iota", "iota"], | ||
["jmath", "dotless.j"], | ||
["kappa", "kappa"], | ||
["lambda", "lambda"], | ||
["land", "and"], | ||
["langle", "angle.l"], | ||
["lbrace", "brace.l"], | ||
["lbrack", "bracket.l"], | ||
["ldots", "dots.l"], | ||
["le", "lt.eq"], | ||
["leadsto", "arrow.squiggly"], | ||
["leftarrow", "arrow.l"], | ||
["leftthreetimes", "times.three.l"], | ||
["leftrightarrow", "arrow.l.r"], | ||
["leq", "lt.eq"], | ||
["leqslant", "lt.eq.slant"], | ||
["lhd", "triangle.l"], | ||
["ll", "lt.double"], | ||
["longmapsto", "arrow.bar.long"], | ||
["longrightarrow", "arrow.long"], | ||
["lor", "or"], | ||
["ltimes", "times.l"], | ||
["mapsto", "arrow.bar"], | ||
["measuredangle", "angle.arc"], | ||
["mid", "divides"], | ||
["models", "models"], | ||
["mp", "minus.plus"], | ||
["mu", "mu"], | ||
["nRightarrow", "arrow.double.not"], | ||
["nabla", "nabla"], | ||
["ncong", "tilde.nequiv"], | ||
["ne", "eq.not"], | ||
["neg", "not"], | ||
["neq", "eq.not"], | ||
["nexists", "exists.not"], | ||
["ni", "in.rev"], | ||
["nleftarrow", "arrow.l.not"], | ||
["nleq", "lt.eq.not"], | ||
["nparallel", "parallel.not"], | ||
["ngeq", "gt.eq.not"], | ||
["nmid", "divides.not"], | ||
["notin", "in.not"], | ||
["nrightarrow", "arrow.not"], | ||
["nsim", "tilde.not"], | ||
["nsubseteq", "subset.eq.not"], | ||
["nu", "nu"], | ||
["ntriangleleft", "lt.tri.not"], | ||
["ntriangleright", "gt.tri.not"], | ||
["nwarrow", "arrow.tl"], | ||
["odot", "dot.circle"], | ||
["oint", "integral.cont"], | ||
["oiint", "integral.surf"], | ||
["oiiint", "integral.vol"], | ||
["omega", "omega"], | ||
["ominus", "minus.circle"], | ||
["oplus", "xor"], | ||
["otimes", "times.circle"], | ||
["parallel", "parallel"], | ||
["partial", "diff"], | ||
["perp", "perp"], | ||
["phi", "phi.alt"], | ||
["pi", "pi"], | ||
["pm", "plus.minus"], | ||
["pounds", "pound"], | ||
["prec", "prec"], | ||
["preceq", "prec.eq"], | ||
["prime", "prime"], | ||
["prod", "product"], | ||
["propto", "prop"], | ||
["psi", "psi"], | ||
["rangle", "angle.r"], | ||
["rbrace", "brace.r"], | ||
["rbrack", "bracket.r"], | ||
["rhd", "triangle"], | ||
["rho", "rho"], | ||
["rightarrow", "arrow.r"], | ||
["rightthreetimes", "times.three.r"], | ||
["rtimes", "times.r"], | ||
["setminus", "without"], | ||
["sigma", "sigma"], | ||
["sim", "tilde"], | ||
["simeq", "tilde.eq"], | ||
["slash", "slash"], | ||
["smallsetminus", "without"], | ||
["spadesuit", "suit.spade"], | ||
["sqcap", "sect.sq"], | ||
["sqcup", "union.sq"], | ||
["sqsubseteq", "subset.eq.sq"], | ||
["sqsupseteq", "supset.eq.sq"], | ||
["star", "star"], | ||
["subset", "subset"], | ||
["subseteq", "subset.eq"], | ||
["subsetneq", "subset.neq"], | ||
["succ", "succ"], | ||
["succeq", "succ.eq"], | ||
["sum", "sum"], | ||
["supset", "supset"], | ||
["supseteq", "supset.eq"], | ||
["supsetneq", "supset.neq"], | ||
["swarrow", "arrow.bl"], | ||
["tau", "tau"], | ||
["theta", "theta"], | ||
["times", "times"], | ||
["to", "arrow.r"], | ||
["top", "top"], | ||
["triangle", "triangle.t"], | ||
["triangledown", "triangle.b.small"], | ||
["triangleleft", "triangle.l.small"], | ||
["triangleright", "triangle.r.small"], | ||
["twoheadrightarrow", "arrow.r.twohead"], | ||
["uparrow", "arrow.t"], | ||
["updownarrow", "arrow.t.b"], | ||
["upharpoonright", "harpoon.tr"], | ||
["uplus", "union.plus"], | ||
["upsilon", "upsilon"], | ||
["varepsilon", "epsilon"], | ||
["varnothing", "diameter"], | ||
["varphi", "phi"], | ||
["varpi", "pi.alt"], | ||
["varrho", "rho.alt"], | ||
["varsigma", "sigma.alt"], | ||
["vartheta", "theta.alt"], | ||
["vdash", "tack.r"], | ||
["vdots", "dots.v"], | ||
["vee", "or"], | ||
["wedge", "and"], | ||
["wr", "wreath"], | ||
["xi", "xi"], | ||
["yen", "yen"], | ||
["zeta", "zeta"], | ||
["mathscr", "scr"], | ||
["LaTeX", "#LaTeX"], | ||
["TeX", "#TeX"] | ||
]); | ||
// src/writer.ts | ||
function convertTree(node) { | ||
switch (node.type) { | ||
case "empty": | ||
case "whitespace": | ||
return { type: "empty", content: "" }; | ||
case "ordgroup": | ||
return { | ||
type: "group", | ||
content: "", | ||
args: node.args.map(convertTree) | ||
}; | ||
case "element": | ||
case "symbol": | ||
return { type: "symbol", content: convertToken(node.content) }; | ||
case "text": | ||
return { type: "text", content: node.content }; | ||
case "comment": | ||
return { type: "comment", content: node.content }; | ||
case "supsub": { | ||
let { base, sup, sub } = node.data; | ||
if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) { | ||
return { | ||
type: "binaryFunc", | ||
content: "overbrace", | ||
args: [convertTree(base.args[0]), convertTree(sup)] | ||
}; | ||
} else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) { | ||
return { | ||
type: "binaryFunc", | ||
content: "underbrace", | ||
args: [convertTree(base.args[0]), convertTree(sub)] | ||
}; | ||
} | ||
const data = { | ||
base: convertTree(base) | ||
}; | ||
if (data.base.type === "empty") { | ||
data.base = { type: "text", content: "" }; | ||
} | ||
if (sup) { | ||
data.sup = convertTree(sup); | ||
} | ||
if (sub) { | ||
data.sub = convertTree(sub); | ||
} | ||
return { | ||
type: "supsub", | ||
content: "", | ||
data | ||
}; | ||
} | ||
case "leftright": { | ||
const [left, body, right] = node.args; | ||
const group = { | ||
type: "group", | ||
content: "", | ||
args: node.args.map(convertTree) | ||
}; | ||
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) { | ||
return group; | ||
} | ||
return { | ||
type: "unaryFunc", | ||
content: "lr", | ||
args: [group] | ||
}; | ||
} | ||
case "binaryFunc": { | ||
return { | ||
type: "binaryFunc", | ||
content: convertToken(node.content), | ||
args: node.args.map(convertTree) | ||
}; | ||
} | ||
case "unaryFunc": { | ||
const arg0 = convertTree(node.args[0]); | ||
if (node.content === "\\sqrt" && node.data) { | ||
const data = convertTree(node.data); | ||
return { | ||
type: "binaryFunc", | ||
content: "root", | ||
args: [data, arg0] | ||
}; | ||
} | ||
if (node.content === "\\mathbf") { | ||
const inner = { | ||
type: "unaryFunc", | ||
content: "bold", | ||
args: [arg0] | ||
}; | ||
return { | ||
type: "unaryFunc", | ||
content: "upright", | ||
args: [inner] | ||
}; | ||
} | ||
if (node.content === "\\mathbb" && arg0.type === "symbol" && /^[A-Z]$/.test(arg0.content)) { | ||
return { | ||
type: "symbol", | ||
content: arg0.content + arg0.content | ||
}; | ||
} | ||
if (node.content === "\\operatorname") { | ||
const body = node.args; | ||
if (body.length !== 1 || body[0].type !== "text") { | ||
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node); | ||
} | ||
const text = body[0].content; | ||
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) { | ||
return { | ||
type: "symbol", | ||
content: text | ||
}; | ||
} else { | ||
return { | ||
type: "unaryFunc", | ||
content: "op", | ||
args: [{ type: "text", content: text }] | ||
}; | ||
} | ||
} | ||
return { | ||
type: "unaryFunc", | ||
content: convertToken(node.content), | ||
args: node.args.map(convertTree) | ||
}; | ||
} | ||
case "newline": | ||
return { type: "newline", content: "\n" }; | ||
case "beginend": { | ||
const matrix = node.data; | ||
const data = matrix.map((row) => row.map(convertTree)); | ||
if (node.content.startsWith("align")) { | ||
return { | ||
type: "align", | ||
content: "", | ||
data | ||
}; | ||
} else { | ||
return { | ||
type: "matrix", | ||
content: "mat", | ||
data | ||
}; | ||
} | ||
} | ||
case "unknownMacro": | ||
return { type: "unknown", content: convertToken(node.content) }; | ||
case "control": | ||
if (node.content === "\\\\") { | ||
return { type: "symbol", content: "\\" }; | ||
} else if (node.content === "\\,") { | ||
return { type: "symbol", content: "thin" }; | ||
} else { | ||
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node); | ||
} | ||
default: | ||
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node); | ||
} | ||
} | ||
function convertToken(token) { | ||
@@ -1009,93 +1179,61 @@ if (/^[a-zA-Z0-9]$/.test(token)) { | ||
append(node) { | ||
if (node.type === "empty" || node.type === "whitespace") { | ||
return; | ||
} else if (node.type === "ordgroup") { | ||
node.args.forEach((arg) => this.append(arg)); | ||
} else if (node.type === "element") { | ||
let content = node.content; | ||
if (node.content === "," && this.insideFunctionDepth > 0) { | ||
content = "comma"; | ||
switch (node.type) { | ||
case "empty": | ||
break; | ||
case "symbol": { | ||
let content = node.content; | ||
if (node.content === "," && this.insideFunctionDepth > 0) { | ||
content = "comma"; | ||
} | ||
this.queue.push({ type: "symbol", content }); | ||
break; | ||
} | ||
this.queue.push({ type: "symbol", content }); | ||
} else if (node.type === "symbol") { | ||
this.queue.push({ type: "symbol", content: node.content }); | ||
} else if (node.type === "text") { | ||
this.queue.push(node); | ||
} else if (node.type === "supsub") { | ||
let { base, sup, sub } = node.data; | ||
if (base && base.type === "unaryFunc" && base.content === "\\overbrace" && sup) { | ||
this.append({ type: "binaryFunc", content: "\\overbrace", args: [base.args[0], sup] }); | ||
return; | ||
} else if (base && base.type === "unaryFunc" && base.content === "\\underbrace" && sub) { | ||
this.append({ type: "binaryFunc", content: "\\underbrace", args: [base.args[0], sub] }); | ||
return; | ||
} | ||
if (base.type === "empty") { | ||
this.queue.push({ type: "text", content: "" }); | ||
} else { | ||
case "text": | ||
case "comment": | ||
case "newline": | ||
this.queue.push(node); | ||
break; | ||
case "group": | ||
for (const item of node.args) { | ||
this.append(item); | ||
} | ||
break; | ||
case "supsub": { | ||
let { base, sup, sub } = node.data; | ||
this.appendWithBracketsIfNeeded(base); | ||
let trailing_space_needed = false; | ||
const has_prime = sup && sup.type === "symbol" && sup.content === "\'"; | ||
if (has_prime) { | ||
this.queue.push({ type: "atom", content: "\'" }); | ||
trailing_space_needed = false; | ||
} | ||
if (sub) { | ||
this.queue.push({ type: "atom", content: "_" }); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sub); | ||
} | ||
if (sup && !has_prime) { | ||
this.queue.push({ type: "atom", content: "^" }); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sup); | ||
} | ||
if (trailing_space_needed) { | ||
this.queue.push({ type: "softSpace", content: "" }); | ||
} | ||
break; | ||
} | ||
let trailing_space_needed = false; | ||
const has_prime = sup && sup.type === "symbol" && sup.content === "\\prime"; | ||
if (has_prime) { | ||
this.queue.push({ type: "atom", content: "\'" }); | ||
trailing_space_needed = false; | ||
} | ||
if (sub) { | ||
this.queue.push({ type: "atom", content: "_" }); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sub); | ||
} | ||
if (sup && !has_prime) { | ||
this.queue.push({ type: "atom", content: "^" }); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sup); | ||
} | ||
if (trailing_space_needed) { | ||
this.queue.push({ type: "softSpace", content: "" }); | ||
} | ||
} else if (node.type === "leftright") { | ||
const [left, body, right] = node.args; | ||
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) { | ||
this.append(left); | ||
this.append(body); | ||
this.append(right); | ||
return; | ||
} | ||
const func_symbol = { type: "symbol", content: "lr" }; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth++; | ||
this.queue.push({ type: "atom", content: "(" }); | ||
this.append(left); | ||
this.append(body); | ||
this.append(right); | ||
this.queue.push({ type: "atom", content: ")" }); | ||
this.insideFunctionDepth--; | ||
} else if (node.type === "binaryFunc") { | ||
const func_symbol = { type: "symbol", content: node.content }; | ||
const [arg0, arg1] = node.args; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth++; | ||
this.queue.push({ type: "atom", content: "(" }); | ||
this.append(arg0); | ||
this.queue.push({ type: "atom", content: "," }); | ||
this.append(arg1); | ||
this.queue.push({ type: "atom", content: ")" }); | ||
this.insideFunctionDepth--; | ||
} else if (node.type === "unaryFunc") { | ||
const func_symbol = { type: "symbol", content: node.content }; | ||
const arg0 = node.args[0]; | ||
if (node.content === "\\sqrt" && node.data) { | ||
func_symbol.content = "root"; | ||
case "binaryFunc": { | ||
const func_symbol = { type: "symbol", content: node.content }; | ||
const [arg0, arg1] = node.args; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth++; | ||
this.queue.push({ type: "atom", content: "(" }); | ||
this.append(node.data); | ||
this.append(arg0); | ||
this.queue.push({ type: "atom", content: "," }); | ||
this.append(arg0); | ||
this.append(arg1); | ||
this.queue.push({ type: "atom", content: ")" }); | ||
this.insideFunctionDepth--; | ||
return; | ||
} else if (node.content === "\\mathbf") { | ||
this.append({ type: "symbol", content: "upright" }); | ||
this.insideFunctionDepth++; | ||
this.queue.push({ type: "atom", content: "(" }); | ||
break; | ||
} | ||
case "unaryFunc": { | ||
const func_symbol = { type: "symbol", content: node.content }; | ||
const arg0 = node.args[0]; | ||
this.queue.push(func_symbol); | ||
@@ -1107,41 +1245,5 @@ this.insideFunctionDepth++; | ||
this.insideFunctionDepth--; | ||
this.queue.push({ type: "atom", content: ")" }); | ||
this.insideFunctionDepth--; | ||
return; | ||
} else if (node.content === "\\mathbb") { | ||
const body = node.args[0]; | ||
if (body.type === "element" && /^[A-Z]$/.test(body.content)) { | ||
this.queue.push({ type: "symbol", content: body.content + body.content }); | ||
return; | ||
} | ||
} else if (node.content === "\\operatorname") { | ||
let body = node.args; | ||
if (body.length === 1 && body[0].type == "ordgroup") { | ||
body = body[0].args; | ||
} | ||
const text = body.reduce((buff, n) => { | ||
buff += convertToken(n.content); | ||
return buff; | ||
}, ""); | ||
if (this.preferTypstIntrinsic && TYPST_INTRINSIC_SYMBOLS.includes(text)) { | ||
this.queue.push({ type: "symbol", content: text }); | ||
} else { | ||
this.queue.push({ type: "symbol", content: "op" }); | ||
this.queue.push({ type: "atom", content: "(" }); | ||
this.queue.push({ type: "text", content: text }); | ||
this.queue.push({ type: "atom", content: ")" }); | ||
} | ||
return; | ||
break; | ||
} | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth++; | ||
this.queue.push({ type: "atom", content: "(" }); | ||
this.append(arg0); | ||
this.queue.push({ type: "atom", content: ")" }); | ||
this.insideFunctionDepth--; | ||
} else if (node.type === "newline") { | ||
this.queue.push({ type: "newline", content: "\n" }); | ||
return; | ||
} else if (node.type === "beginend") { | ||
if (node.content.startsWith("align")) { | ||
case "align": { | ||
const matrix = node.data; | ||
@@ -1156,6 +1258,8 @@ matrix.forEach((row, i) => { | ||
if (i < matrix.length - 1) { | ||
this.queue.push({ type: "symbol", content: "\\\\" }); | ||
this.queue.push({ type: "symbol", content: "\\" }); | ||
} | ||
}); | ||
} else { | ||
break; | ||
} | ||
case "matrix": { | ||
const matrix = node.data; | ||
@@ -1168,6 +1272,2 @@ this.queue.push({ type: "symbol", content: "mat" }); | ||
row.forEach((cell, j) => { | ||
if (cell.type === "ordgroup" && cell.args.length === 0) { | ||
this.queue.push({ type: "atom", content: "," }); | ||
return; | ||
} | ||
this.append(cell); | ||
@@ -1185,23 +1285,32 @@ if (j < row.length - 1) { | ||
this.insideFunctionDepth--; | ||
break; | ||
} | ||
} else if (node.type === "matrix") { | ||
} else if (node.type === "unknownMacro") { | ||
if (this.nonStrict) { | ||
this.queue.push({ type: "symbol", content: node.content }); | ||
} else { | ||
throw new TypstWriterError(`Unknown macro: ${node.content}`, node); | ||
case "unknown": { | ||
if (this.nonStrict) { | ||
this.queue.push({ type: "symbol", content: node.content }); | ||
} else { | ||
throw new TypstWriterError(`Unknown macro: ${node.content}`, node); | ||
} | ||
break; | ||
} | ||
} else if (node.type === "control") { | ||
if (node.content === "\\\\") { | ||
this.queue.push({ type: "symbol", content: node.content }); | ||
} else if (node.content === "\\,") { | ||
this.queue.push({ type: "symbol", content: "thin" }); | ||
} else { | ||
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node); | ||
} | ||
} else if (node.type === "comment") { | ||
this.queue.push({ type: "comment", content: node.content }); | ||
default: | ||
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node); | ||
} | ||
} | ||
appendWithBracketsIfNeeded(node) { | ||
const is_single = !["group", "supsub", "empty"].includes(node.type); | ||
if (is_single) { | ||
this.append(node); | ||
} else { | ||
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node); | ||
this.queue.push({ | ||
type: "atom", | ||
content: "(" | ||
}); | ||
this.append(node); | ||
this.queue.push({ | ||
type: "atom", | ||
content: ")" | ||
}); | ||
} | ||
return is_single; | ||
} | ||
@@ -1213,7 +1322,5 @@ flushQueue() { | ||
case "atom": | ||
case "symbol": | ||
str = node.content; | ||
break; | ||
case "symbol": | ||
str = convertToken(node.content); | ||
break; | ||
case "text": | ||
@@ -1241,19 +1348,2 @@ str = `"${node.content}"`; | ||
} | ||
appendWithBracketsIfNeeded(node) { | ||
const is_single = ["symbol", "element", "unaryFunc", "binaryFunc", "leftright"].includes(node.type); | ||
if (is_single) { | ||
this.append(node); | ||
} else { | ||
this.queue.push({ | ||
type: "atom", | ||
content: "(" | ||
}); | ||
this.append(node); | ||
this.queue.push({ | ||
type: "atom", | ||
content: ")" | ||
}); | ||
} | ||
return is_single; | ||
} | ||
finalize() { | ||
@@ -1280,3 +1370,3 @@ this.flushQueue(); | ||
const opt = { | ||
nonStrict: false, | ||
nonStrict: true, | ||
preferTypstIntrinsic: true, | ||
@@ -1296,5 +1386,6 @@ customTexMacros: {} | ||
} | ||
const t = parseTex(tex, opt.customTexMacros); | ||
const texTree = parseTex(tex, opt.customTexMacros); | ||
const typstTree = convertTree(texTree); | ||
const writer2 = new TypstWriter(opt.nonStrict, opt.preferTypstIntrinsic); | ||
writer2.append(t); | ||
writer2.append(typstTree); | ||
return writer2.finalize(); | ||
@@ -1301,0 +1392,0 @@ } |
@@ -1,6 +0,2 @@ | ||
import { TexNode } from "./types"; | ||
export interface Token { | ||
type: 'element' | 'command' | 'text' | 'comment' | 'whitespace' | 'newline' | 'control' | 'unknown'; | ||
value: string; | ||
} | ||
import { TexNode, Token } from "./types"; | ||
export declare function tokenize(latex: string): Token[]; | ||
@@ -7,0 +3,0 @@ export declare class LatexParserError extends Error { |
@@ -1,1 +0,1 @@ | ||
function v(z,Z=""){if(!z)throw new H(Z)}function c(z){if(u.includes(z))return 1;else if(m.includes(z))return 2;else return 0}function x(z,Z){v(q(z[Z],F));let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched curly brackets");if(q(z[V],F))J+=1;else if(q(z[V],D))J-=1;V+=1}return V-1}function l(z,Z){v(q(z[Z],y));let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched square brackets");if(q(z[V],y))J+=1;else if(q(z[V],_))J-=1;V+=1}return V-1}function g(z){return"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(z)}function R(z){return"0123456789".includes(z)}function S(z,Z){let J=Z;while(J<z.length&&["whitespace","newline"].includes(z[J].type))J++;return z.slice(Z,J)}function w(z,Z){const J=z[Z];if(J.type==="element"&&["(",")","[","]","|","\\{","\\}"].includes(J.value))return J;else if(J.type==="command"&&["lfloor","rfloor","lceil","rceil","langle","rangle"].includes(J.value.slice(1)))return J;else return null}function G(z,Z){let J=Z;while(J<z.length&&q(z[J],{type:"element",value:"'"}))J+=1;return J-Z}function i(z,Z){let J=Z;while(J<z.length&&g(z[J]))J+=1;return z.substring(Z,J)}function p(z,Z){let J=1,V=Z;while(J>0){if(V>=z.length)return-1;if(q(z[V],N))J+=1;else if(q(z[V],d))J-=1;V+=1}return V-1}function a(z,Z){let J=1,V=Z;while(J>0){if(V>=z.length)return-1;if(q(z[V],A))J+=1;else if(q(z[V],k))J-=1;V+=1}return V-1}function n(z,Z){v(z[Z]==="{");let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched curly brackets");if(V+1<z.length&&["\\{","\\}"].includes(z.substring(V,V+2))){V+=2;continue}if(z[V]==="{")J+=1;else if(z[V]==="}")J-=1;V+=1}return V-1}function C(z){const Z=[];let J=0;while(J<z.length){const V=z[J];let X;switch(V){case"%":{let $=J+1;while($<z.length&&z[$]!=="\n")$+=1;X={type:"comment",value:z.slice(J+1,$)},J=$;break}case"{":case"}":case"_":case"^":case"&":X={type:"control",value:V},J++;break;case"\n":X={type:"newline",value:V},J++;break;case"\r":{if(J+1<z.length&&z[J+1]==="\n")X={type:"newline",value:"\n"},J+=2;else X={type:"newline",value:"\n"},J++;break}case" ":{let $=J;while($<z.length&&z[$]===" ")$+=1;X={type:"whitespace",value:z.slice(J,$)},J=$;break}case"\\":{if(J+1>=z.length)throw new H("Expecting command name after \\");const $=z.slice(J,J+2);if(["\\\\","\\,"].includes($))X={type:"control",value:$};else if(["\\{","\\}","\\%","\\$","\\&","\\#","\\_"].includes($))X={type:"element",value:$};else X={type:"command",value:"\\"+i(z,J+1)};J+=X.value.length;break}default:{if(R(V)){let $=J;while($<z.length&&R(z[$]))$+=1;X={type:"element",value:z.slice(J,$)}}else if(g(V))X={type:"element",value:V};else if("+-*/=\'<>!.,;?()[]|".includes(V))X={type:"element",value:V};else X={type:"unknown",value:V};J+=X.value.length}}if(Z.push(X),X.type==="command"&&["\\text","\\begin","\\end"].includes(X.value)){if(J>=z.length||z[J]!=="{")throw new H(`No content for ${X.value} command`);Z.push({type:"control",value:"{"});const $=n(z,J);J++;let j=z.slice(J,$);const Q=["{","}","\\","$","&","#","_","%"];for(let W of Q)j=j.replaceAll("\\"+W,W);Z.push({type:"text",value:j}),Z.push({type:"control",value:"}"}),J=$+1}}return Z}function q(z,Z){return z.type==Z.type&&z.value==Z.value}function r(z){const Z=(V)=>q(V,I)||q(V,B);let J=[];for(let V=0;V<z.length;V++){if(z[V].type==="whitespace"&&V+1<z.length&&Z(z[V+1]))continue;if(z[V].type==="whitespace"&&V-1>=0&&Z(z[V-1]))continue;J.push(z[V])}return J}function t(z,Z){let J=[];for(let V of z)if(V.type==="command"&&Z[V.value]){const X=C(Z[V.value]);J=J.concat(X)}else J.push(V);return J}function T(z,Z){const J=new b;let V=C(z);return V=r(V),V=t(V,Z),J.parse(V)}var u=["sqrt","text","bar","bold","boldsymbol","ddot","dot","hat","mathbb","mathbf","mathcal","mathfrak","mathit","mathrm","mathscr","mathsf","mathtt","operatorname","overbrace","overline","pmb","rm","tilde","underbrace","underline","vec","widehat","widetilde"],m=["frac","tfrac","binom","dbinom","dfrac","tbinom"],U={type:"empty",content:""},F={type:"control",value:"{"},D={type:"control",value:"}"},y={type:"element",value:"["},_={type:"element",value:"]"},N={type:"command",value:"\\left"},d={type:"command",value:"\\right"},A={type:"command",value:"\\begin"},k={type:"command",value:"\\end"};class H extends Error{constructor(z){super(z);this.name="LatexParserError"}}var I={type:"control",value:"_"},B={type:"control",value:"^"};class b{space_sensitive;newline_sensitive;constructor(z=!1,Z=!0){this.space_sensitive=z,this.newline_sensitive=Z}parse(z){const Z=[];let J=0;while(J<z.length){const V=[];let X=0;while(X<z.length){const[$,j]=this.parseNextExpr(z,X);if(X=j,!this.space_sensitive&&$.type==="whitespace")continue;if(!this.newline_sensitive&&$.type==="newline")continue;if($.type==="control"&&$.content==="&")throw new H("Unexpected & outside of an alignment");V.push($)}if(V.length===0)return U;else if(V.length===1)return V[0];else return{type:"ordgroup",content:"",args:V}}if(Z.length===0)return U;else if(Z.length===1)return Z[0];else return{type:"ordgroup",content:"",args:Z}}parseNextExpr(z,Z){let[J,V]=this.parseNextExprWithoutSupSub(z,Z),X=null,$=null,j=0;if(j+=G(z,V),V+=j,V<z.length&&q(z[V],I)){if([X,V]=this.parseNextExprWithoutSupSub(z,V+1),j+=G(z,V),V+=j,V<z.length&&q(z[V],B)){if([$,V]=this.parseNextExprWithoutSupSub(z,V+1),G(z,V)>0)throw new H("Double superscript")}}else if(V<z.length&&q(z[V],B)){if([$,V]=this.parseNextExprWithoutSupSub(z,V+1),G(z,V)>0)throw new H("Double superscript");if(V<z.length&&q(z[V],I)){if([X,V]=this.parseNextExprWithoutSupSub(z,V+1),G(z,V)>0)throw new H("Double superscript")}}if(X!==null||$!==null||j>0){const Q={base:J};if(X)Q.sub=X;if(j>0){Q.sup={type:"ordgroup",content:"",args:[]};for(let W=0;W<j;W++)Q.sup.args.push({type:"symbol",content:"\\prime"});if($)Q.sup.args.push($);if(Q.sup.args.length===1)Q.sup=Q.sup.args[0]}else if($)Q.sup=$;return[{type:"supsub",content:"",data:Q},V]}else return[J,V]}parseNextExprWithoutSupSub(z,Z){const J=z[Z],V=J.type;switch(V){case"element":case"text":case"comment":case"whitespace":case"newline":return[{type:V,content:J.value},Z+1];case"command":if(q(J,A))return this.parseBeginEndExpr(z,Z);else if(q(J,N))return this.parseLeftRightExpr(z,Z);else return this.parseCommandExpr(z,Z);case"control":switch(J.value){case"{":const $=x(z,Z),j=z.slice(Z+1,$);return[this.parse(j),$+1];case"}":throw new H("Unmatched '}'");case"\\\\":return[{type:"control",content:"\\\\"},Z+1];case"\\,":return[{type:"control",content:"\\,"},Z+1];case"_":return[U,Z];case"^":return[U,Z];case"&":return[{type:"control",content:"&"},Z+1];default:throw new H("Unknown control sequence")}default:throw new H("Unknown token type")}}parseCommandExpr(z,Z){v(z[Z].type==="command");const J=z[Z].value;let V=Z+1;if(["left","right","begin","end"].includes(J.slice(1)))throw new H("Unexpected command: "+J);const X=c(J.slice(1));if(X===0)return[{type:"symbol",content:J},V];else if(X===1){if(J==="\\sqrt"&&V<z.length&&q(z[V],y)){const Q=V,W=l(z,V),O=z.slice(Q+1,W),h=this.parse(O),[f,E]=this.parseNextExprWithoutSupSub(z,W+1);return[{type:"unaryFunc",content:J,args:[f],data:h},E]}else if(J==="\\text"){if(V+2>=z.length)throw new H("Expecting content for \\text command");return v(q(z[V],F)),v(z[V+1].type==="text"),v(q(z[V+2],D)),[{type:"text",content:z[V+1].value},V+3]}let[$,j]=this.parseNextExprWithoutSupSub(z,V);return[{type:"unaryFunc",content:J,args:[$]},j]}else if(X===2){const[$,j]=this.parseNextExprWithoutSupSub(z,V),[Q,W]=this.parseNextExprWithoutSupSub(z,j);return[{type:"binaryFunc",content:J,args:[$,Q]},W]}else throw new Error("Invalid number of parameters")}parseLeftRightExpr(z,Z){v(q(z[Z],N));let J=Z+1;if(J+=S(z,J).length,J>=z.length)throw new H("Expecting delimiter after \\left");const V=w(z,J);if(V===null)throw new H("Invalid delimiter after \\left");J++;const X=J,$=p(z,J);if($===-1)throw new H("No matching \\right");const j=$;if(J=$+1,J+=S(z,J).length,J>=z.length)throw new H("Expecting \\right after \\left");const Q=w(z,J);if(Q===null)throw new H("Invalid delimiter after \\right");J++;const W=z.slice(X,j),O=this.parse(W);return[{type:"leftright",content:"",args:[{type:"element",content:V.value},O,{type:"element",content:Q.value}]},J]}parseBeginEndExpr(z,Z){v(q(z[Z],A));let J=Z+1;v(q(z[J],F)),v(z[J+1].type==="text"),v(q(z[J+2],D));const V=z[J+1].value;J+=3,J+=S(z,J).length;const X=J,$=a(z,J);if($===-1)throw new H("No matching \\end");const j=$;if(J=$+1,v(q(z[J],F)),v(z[J+1].type==="text"),v(q(z[J+2],D)),z[J+1].value!==V)throw new H("Mismatched \\begin and \\end environments");J+=3;const Q=z.slice(X,j);while(Q.length>0&&["whitespace","newline"].includes(Q[Q.length-1].type))Q.pop();const W=this.parseAligned(Q);return[{type:"beginend",content:V,data:W},J]}parseAligned(z){let Z=0;const J=[];let V=[];J.push(V);let X={type:"ordgroup",content:"",args:[]};V.push(X);while(Z<z.length){const[$,j]=this.parseNextExpr(z,Z);if(Z=j,$.type==="whitespace")continue;else if($.type==="newline"&&!this.newline_sensitive)continue;else if($.type==="control"&&$.content==="\\\\")V=[],X={type:"ordgroup",content:"",args:[]},V.push(X),J.push(V);else if($.type==="control"&&$.content==="&")X={type:"ordgroup",content:"",args:[]},V.push(X);else X.args.push($)}return J}}var Y=new Map([["nonumber",""],["vec","arrow"],["neq","eq.not"],["dot","dot"],["ddot","dot.double"],["doteq","dot(eq)"],["dots","dots.h"],["ldots","dots.h"],["vdots","dots.v"],["ddots","dots.down"],["widehat","hat"],["widetilde","tilde"],["quad","quad"],["qquad","wide"],["overbrace","overbrace"],["underbrace","underbrace"],["overline","overline"],["underline","underline"],["bar","macron"],["dbinom","binom"],["tbinom","binom"],["dfrac","frac"],["tfrac","frac"],["boldsymbol","bold"],["mathbb","bb"],["mathbf","bold"],["mathcal","cal"],["mathit","italic"],["mathfrak","frak"],["mathrm","upright"],["mathsf","sans"],["mathtt","mono"],["rm","upright"],["pmb","bold"],["pm","plus.minus"],["mp","minus.plus"],["oplus","xor"],["boxplus","plus.square"],["otimes","times.circle"],["boxtimes","times.square"],["sim","tilde"],["approx","approx"],["cong","tilde.equiv"],["simeq","tilde.eq"],["asymp","\u224D"],["equiv","equiv"],["propto","prop"],["lfloor","\u230A"],["rfloor","\u230B"],["lceil","\u2308"],["rceil","\u2309"],["gets","arrow.l"],["hookleftarrow","arrow.l.hook"],["leftharpoonup","harpoon.lt"],["leftharpoondown","harpoon.lb"],["rightleftharpoons","harpoons.rtlb"],["longleftarrow","arrow.l.long"],["longrightarrow","arrow.r.long"],["longleftrightarrow","arrow.l.r.long"],["Longleftarrow","arrow.l.double.long"],["Longrightarrow","arrow.r.double.long"],["Longleftrightarrow","arrow.l.r.double.long"],["longmapsto","arrow.r.bar"],["hookrightarrow","arrow.r.hook"],["rightharpoonup","harpoon.rt"],["rightharpoondown","harpoon.rb"],["iff","arrow.l.r.double.long"],["implies","arrow.r.double.long"],["uparrow","arrow.t"],["downarrow","arrow.b"],["updownarrow","arrow.t.b"],["Uparrow","arrow.t.double"],["Downarrow","arrow.b.double"],["Updownarrow","arrow.t.b.double"],["nearrow","arrow.tr"],["searrow","arrow.br"],["swarrow","arrow.bl"],["nwarrow","arrow.tl"],["leadsto","arrow.squiggly"],["leftleftarrows","arrows.ll"],["rightrightarrows","arrows.rr"],["Cap","sect.double"],["Cup","union.double"],["Delta","Delta"],["Gamma","Gamma"],["Join","join"],["Lambda","Lambda"],["Leftarrow","arrow.l.double"],["Leftrightarrow","arrow.l.r.double"],["Longrightarrow","arrow.r.double.long"],["Omega","Omega"],["P","pilcrow"],["Phi","Phi"],["Pi","Pi"],["Psi","Psi"],["Rightarrow","arrow.r.double"],["S","section"],["Sigma","Sigma"],["Theta","Theta"],["aleph","alef"],["alpha","alpha"],["angle","angle"],["approx","approx"],["approxeq","approx.eq"],["ast","ast"],["beta","beta"],["bigcap","sect.big"],["bigcirc","circle.big"],["bigcup","union.big"],["bigodot","dot.circle.big"],["bigoplus","xor.big"],["bigotimes","times.circle.big"],["bigsqcup","union.sq.big"],["bigtriangledown","triangle.b"],["bigtriangleup","triangle.t"],["biguplus","union.plus.big"],["bigvee","or.big"],["bigwedge","and.big"],["bullet","bullet"],["cap","sect"],["cdot","dot.op"],["cdots","dots.c"],["checkmark","checkmark"],["chi","chi"],["circ","circle.small"],["colon","colon"],["cong","tilde.equiv"],["coprod","product.co"],["copyright","copyright"],["cup","union"],["curlyvee","or.curly"],["curlywedge","and.curly"],["dagger","dagger"],["dashv","tack.l"],["ddagger","dagger.double"],["delta","delta"],["ddots","dots.down"],["diamond","diamond"],["div","div"],["divideontimes","times.div"],["dotplus","plus.dot"],["downarrow","arrow.b"],["ell","ell"],["emptyset","nothing"],["epsilon","epsilon.alt"],["equiv","equiv"],["eta","eta"],["exists","exists"],["forall","forall"],["gamma","gamma"],["ge","gt.eq"],["geq","gt.eq"],["geqslant","gt.eq.slant"],["gg","gt.double"],["hbar","planck.reduce"],["imath","dotless.i"],["iiiint","intgral.quad"],["iiint","integral.triple"],["iint","integral.double"],["in","in"],["infty","infinity"],["int","integral"],["intercal","top"],["iota","iota"],["jmath","dotless.j"],["kappa","kappa"],["lambda","lambda"],["land","and"],["langle","angle.l"],["lbrace","brace.l"],["lbrack","bracket.l"],["ldots","dots.l"],["le","lt.eq"],["leadsto","arrow.squiggly"],["leftarrow","arrow.l"],["leftthreetimes","times.three.l"],["leftrightarrow","arrow.l.r"],["leq","lt.eq"],["leqslant","lt.eq.slant"],["lhd","triangle.l"],["ll","lt.double"],["longmapsto","arrow.bar.long"],["longrightarrow","arrow.long"],["lor","or"],["ltimes","times.l"],["mapsto","arrow.bar"],["measuredangle","angle.arc"],["mid","divides"],["models","models"],["mp","minus.plus"],["mu","mu"],["nRightarrow","arrow.double.not"],["nabla","nabla"],["ncong","tilde.nequiv"],["ne","eq.not"],["neg","not"],["neq","eq.not"],["nexists","exists.not"],["ni","in.rev"],["nleftarrow","arrow.l.not"],["nleq","lt.eq.not"],["nparallel","parallel.not"],["ngeq","gt.eq.not"],["nmid","divides.not"],["notin","in.not"],["nrightarrow","arrow.not"],["nsim","tilde.not"],["nsubseteq","subset.eq.not"],["nu","nu"],["ntriangleleft","lt.tri.not"],["ntriangleright","gt.tri.not"],["nwarrow","arrow.tl"],["odot","dot.circle"],["oint","integral.cont"],["oiint","integral.surf"],["oiiint","integral.vol"],["omega","omega"],["ominus","minus.circle"],["oplus","xor"],["otimes","times.circle"],["parallel","parallel"],["partial","diff"],["perp","perp"],["phi","phi.alt"],["pi","pi"],["pm","plus.minus"],["pounds","pound"],["prec","prec"],["preceq","prec.eq"],["prime","prime"],["prod","product"],["propto","prop"],["psi","psi"],["rangle","angle.r"],["rbrace","brace.r"],["rbrack","bracket.r"],["rhd","triangle"],["rho","rho"],["rightarrow","arrow.r"],["rightthreetimes","times.three.r"],["rtimes","times.r"],["setminus","without"],["sigma","sigma"],["sim","tilde"],["simeq","tilde.eq"],["slash","slash"],["smallsetminus","without"],["spadesuit","suit.spade"],["sqcap","sect.sq"],["sqcup","union.sq"],["sqsubseteq","subset.eq.sq"],["sqsupseteq","supset.eq.sq"],["star","star"],["subset","subset"],["subseteq","subset.eq"],["subsetneq","subset.neq"],["succ","succ"],["succeq","succ.eq"],["sum","sum"],["supset","supset"],["supseteq","supset.eq"],["supsetneq","supset.neq"],["swarrow","arrow.bl"],["tau","tau"],["theta","theta"],["times","times"],["to","arrow.r"],["top","top"],["triangle","triangle.t"],["triangledown","triangle.b.small"],["triangleleft","triangle.l.small"],["triangleright","triangle.r.small"],["twoheadrightarrow","arrow.r.twohead"],["uparrow","arrow.t"],["updownarrow","arrow.t.b"],["upharpoonright","harpoon.tr"],["uplus","union.plus"],["upsilon","upsilon"],["varepsilon","epsilon"],["varnothing","diameter"],["varphi","phi"],["varpi","pi.alt"],["varrho","rho.alt"],["varsigma","sigma.alt"],["vartheta","theta.alt"],["vdash","tack.r"],["vdots","dots.v"],["vee","or"],["wedge","and"],["wr","wreath"],["xi","xi"],["yen","yen"],["zeta","zeta"],["mathscr","scr"],["LaTeX","#LaTeX"],["TeX","#TeX"]]);function L(z){if(/^[a-zA-Z0-9]$/.test(z))return z;else if(z==="\\\\")return"\\";else if(z=="/")return"\\/";else if(["\\$","\\#","\\&","\\_"].includes(z))return z;else if(z.startsWith("\\")){const Z=z.slice(1);if(Y.has(Z))return Y.get(Z);else return Z}return z}var o=["dim","id","im","mod","Pr","sech","csch"];class K extends Error{node;constructor(z,Z){super(z);this.name="TypstWriterError",this.node=Z}}class M{nonStrict;preferTypstIntrinsic;buffer="";queue=[];needSpaceAfterSingleItemScript=!1;insideFunctionDepth=0;constructor(z,Z){this.nonStrict=z,this.preferTypstIntrinsic=Z}writeBuffer(z){if(this.needSpaceAfterSingleItemScript&&/^[0-9a-zA-Z\(]/.test(z))this.buffer+=" ";else{let Z=!1;if(Z||=/[\(\|]$/.test(this.buffer)&&/^\w/.test(z),Z||=/^[}()_^,;!\|]$/.test(z),Z||=z==="'",Z||=/[0-9]$/.test(this.buffer)&&/^[0-9]/.test(z),Z||=/[\(\[{]\s*(-|\+)$/.test(this.buffer)||this.buffer==="-"||this.buffer==="+",Z||=z.startsWith("\n"),Z||=this.buffer==="",Z||=/[\s"_^{\(]$/.test(this.buffer),!Z)this.buffer+=" "}if(this.needSpaceAfterSingleItemScript)this.needSpaceAfterSingleItemScript=!1;this.buffer+=z}append(z){if(z.type==="empty"||z.type==="whitespace")return;else if(z.type==="ordgroup")z.args.forEach((Z)=>this.append(Z));else if(z.type==="element"){let Z=z.content;if(z.content===","&&this.insideFunctionDepth>0)Z="comma";this.queue.push({type:"symbol",content:Z})}else if(z.type==="symbol")this.queue.push({type:"symbol",content:z.content});else if(z.type==="text")this.queue.push(z);else if(z.type==="supsub"){let{base:Z,sup:J,sub:V}=z.data;if(Z&&Z.type==="unaryFunc"&&Z.content==="\\overbrace"&&J){this.append({type:"binaryFunc",content:"\\overbrace",args:[Z.args[0],J]});return}else if(Z&&Z.type==="unaryFunc"&&Z.content==="\\underbrace"&&V){this.append({type:"binaryFunc",content:"\\underbrace",args:[Z.args[0],V]});return}if(Z.type==="empty")this.queue.push({type:"text",content:""});else this.appendWithBracketsIfNeeded(Z);let X=!1;const $=J&&J.type==="symbol"&&J.content==="\\prime";if($)this.queue.push({type:"atom",content:"\'"}),X=!1;if(V)this.queue.push({type:"atom",content:"_"}),X=this.appendWithBracketsIfNeeded(V);if(J&&!$)this.queue.push({type:"atom",content:"^"}),X=this.appendWithBracketsIfNeeded(J);if(X)this.queue.push({type:"softSpace",content:""})}else if(z.type==="leftright"){const[Z,J,V]=z.args;if(["[]","()","\\{\\}","\\lfloor\\rfloor","\\lceil\\rceil"].includes(Z.content+V.content)){this.append(Z),this.append(J),this.append(V);return}const X={type:"symbol",content:"lr"};this.queue.push(X),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(Z),this.append(J),this.append(V),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--}else if(z.type==="binaryFunc"){const Z={type:"symbol",content:z.content},[J,V]=z.args;this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(J),this.queue.push({type:"atom",content:","}),this.append(V),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--}else if(z.type==="unaryFunc"){const Z={type:"symbol",content:z.content},J=z.args[0];if(z.content==="\\sqrt"&&z.data){Z.content="root",this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(z.data),this.queue.push({type:"atom",content:","}),this.append(J),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--;return}else if(z.content==="\\mathbf"){this.append({type:"symbol",content:"upright"}),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(J),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--,this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--;return}else if(z.content==="\\mathbb"){const V=z.args[0];if(V.type==="element"&&/^[A-Z]$/.test(V.content)){this.queue.push({type:"symbol",content:V.content+V.content});return}}else if(z.content==="\\operatorname"){let V=z.args;if(V.length===1&&V[0].type=="ordgroup")V=V[0].args;const X=V.reduce(($,j)=>{return $+=L(j.content),$},"");if(this.preferTypstIntrinsic&&o.includes(X))this.queue.push({type:"symbol",content:X});else this.queue.push({type:"symbol",content:"op"}),this.queue.push({type:"atom",content:"("}),this.queue.push({type:"text",content:X}),this.queue.push({type:"atom",content:")"});return}this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(J),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--}else if(z.type==="newline"){this.queue.push({type:"newline",content:"\n"});return}else if(z.type==="beginend")if(z.content.startsWith("align")){const Z=z.data;Z.forEach((J,V)=>{if(J.forEach((X,$)=>{if($>0)this.queue.push({type:"atom",content:"&"});this.append(X)}),V<Z.length-1)this.queue.push({type:"symbol",content:"\\\\"})})}else{const Z=z.data;this.queue.push({type:"symbol",content:"mat"}),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.queue.push({type:"symbol",content:"delim: #none, "}),Z.forEach((J,V)=>{J.forEach((X,$)=>{if(X.type==="ordgroup"&&X.args.length===0){this.queue.push({type:"atom",content:","});return}if(this.append(X),$<J.length-1)this.queue.push({type:"atom",content:","});else if(V<Z.length-1)this.queue.push({type:"atom",content:";"})})}),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--}else if(z.type==="matrix");else if(z.type==="unknownMacro")if(this.nonStrict)this.queue.push({type:"symbol",content:z.content});else throw new K(`Unknown macro: ${z.content}`,z);else if(z.type==="control")if(z.content==="\\\\")this.queue.push({type:"symbol",content:z.content});else if(z.content==="\\,")this.queue.push({type:"symbol",content:"thin"});else throw new K(`Unknown control sequence: ${z.content}`,z);else if(z.type==="comment")this.queue.push({type:"comment",content:z.content});else throw new K(`Unimplemented node type to append: ${z.type}`,z)}flushQueue(){this.queue.forEach((z)=>{let Z="";switch(z.type){case"atom":Z=z.content;break;case"symbol":Z=L(z.content);break;case"text":Z=`"${z.content}"`;break;case"softSpace":this.needSpaceAfterSingleItemScript=!0,Z="";break;case"comment":Z=`//${z.content}`;break;case"newline":Z="\n";break;default:throw new K(`Unexpected node type to stringify: ${z.type}`,z)}if(Z!=="")this.writeBuffer(Z)}),this.queue=[]}appendWithBracketsIfNeeded(z){const Z=["symbol","element","unaryFunc","binaryFunc","leftright"].includes(z.type);if(Z)this.append(z);else this.queue.push({type:"atom",content:"("}),this.append(z),this.queue.push({type:"atom",content:")"});return Z}finalize(){this.flushQueue();const z=function(J){let V=J.replace(/⌊\s*(.*?)\s*⌋/g,"floor($1)");return V=V.replace(/floor\(\)/g,'floor("")'),V},Z=function(J){let V=J.replace(/⌈\s*(.*?)\s*⌉/g,"ceil($1)");return V=V.replace(/ceil\(\)/g,'ceil("")'),V};return this.buffer=z(this.buffer),this.buffer=Z(this.buffer),this.buffer}}function P(z,Z){const J={nonStrict:!1,preferTypstIntrinsic:!0,customTexMacros:{}};if(Z){if(Z.nonStrict)J.nonStrict=Z.nonStrict;if(Z.preferTypstIntrinsic)J.preferTypstIntrinsic=Z.preferTypstIntrinsic;if(Z.customTexMacros)J.customTexMacros=Z.customTexMacros}const V=T(z,J.customTexMacros),X=new M(J.nonStrict,J.preferTypstIntrinsic);return X.append(V),X.finalize()}if(typeof window!=="undefined")window.tex2typst=P; | ||
var O=new Map([["nonumber",""],["vec","arrow"],["neq","eq.not"],["dot","dot"],["ddot","dot.double"],["doteq","dot(eq)"],["dots","dots.h"],["ldots","dots.h"],["vdots","dots.v"],["ddots","dots.down"],["widehat","hat"],["widetilde","tilde"],["quad","quad"],["qquad","wide"],["overbrace","overbrace"],["underbrace","underbrace"],["overline","overline"],["underline","underline"],["bar","macron"],["dbinom","binom"],["tbinom","binom"],["dfrac","frac"],["tfrac","frac"],["boldsymbol","bold"],["mathbb","bb"],["mathbf","bold"],["mathcal","cal"],["mathit","italic"],["mathfrak","frak"],["mathrm","upright"],["mathsf","sans"],["mathtt","mono"],["rm","upright"],["pmb","bold"],["pm","plus.minus"],["mp","minus.plus"],["oplus","xor"],["boxplus","plus.square"],["otimes","times.circle"],["boxtimes","times.square"],["sim","tilde"],["approx","approx"],["cong","tilde.equiv"],["simeq","tilde.eq"],["asymp","\u224D"],["equiv","equiv"],["propto","prop"],["lfloor","\u230A"],["rfloor","\u230B"],["lceil","\u2308"],["rceil","\u2309"],["gets","arrow.l"],["hookleftarrow","arrow.l.hook"],["leftharpoonup","harpoon.lt"],["leftharpoondown","harpoon.lb"],["rightleftharpoons","harpoons.rtlb"],["longleftarrow","arrow.l.long"],["longrightarrow","arrow.r.long"],["longleftrightarrow","arrow.l.r.long"],["Longleftarrow","arrow.l.double.long"],["Longrightarrow","arrow.r.double.long"],["Longleftrightarrow","arrow.l.r.double.long"],["longmapsto","arrow.r.bar"],["hookrightarrow","arrow.r.hook"],["rightharpoonup","harpoon.rt"],["rightharpoondown","harpoon.rb"],["iff","arrow.l.r.double.long"],["implies","arrow.r.double.long"],["uparrow","arrow.t"],["downarrow","arrow.b"],["updownarrow","arrow.t.b"],["Uparrow","arrow.t.double"],["Downarrow","arrow.b.double"],["Updownarrow","arrow.t.b.double"],["nearrow","arrow.tr"],["searrow","arrow.br"],["swarrow","arrow.bl"],["nwarrow","arrow.tl"],["leadsto","arrow.squiggly"],["leftleftarrows","arrows.ll"],["rightrightarrows","arrows.rr"],["Cap","sect.double"],["Cup","union.double"],["Delta","Delta"],["Gamma","Gamma"],["Join","join"],["Lambda","Lambda"],["Leftarrow","arrow.l.double"],["Leftrightarrow","arrow.l.r.double"],["Longrightarrow","arrow.r.double.long"],["Omega","Omega"],["P","pilcrow"],["Phi","Phi"],["Pi","Pi"],["Psi","Psi"],["Rightarrow","arrow.r.double"],["S","section"],["Sigma","Sigma"],["Theta","Theta"],["aleph","alef"],["alpha","alpha"],["angle","angle"],["approx","approx"],["approxeq","approx.eq"],["ast","ast"],["beta","beta"],["bigcap","sect.big"],["bigcirc","circle.big"],["bigcup","union.big"],["bigodot","dot.circle.big"],["bigoplus","xor.big"],["bigotimes","times.circle.big"],["bigsqcup","union.sq.big"],["bigtriangledown","triangle.b"],["bigtriangleup","triangle.t"],["biguplus","union.plus.big"],["bigvee","or.big"],["bigwedge","and.big"],["bullet","bullet"],["cap","sect"],["cdot","dot.op"],["cdots","dots.c"],["checkmark","checkmark"],["chi","chi"],["circ","circle.small"],["colon","colon"],["cong","tilde.equiv"],["coprod","product.co"],["copyright","copyright"],["cup","union"],["curlyvee","or.curly"],["curlywedge","and.curly"],["dagger","dagger"],["dashv","tack.l"],["ddagger","dagger.double"],["delta","delta"],["ddots","dots.down"],["diamond","diamond"],["div","div"],["divideontimes","times.div"],["dotplus","plus.dot"],["downarrow","arrow.b"],["ell","ell"],["emptyset","nothing"],["epsilon","epsilon.alt"],["equiv","equiv"],["eta","eta"],["exists","exists"],["forall","forall"],["gamma","gamma"],["ge","gt.eq"],["geq","gt.eq"],["geqslant","gt.eq.slant"],["gg","gt.double"],["hbar","planck.reduce"],["imath","dotless.i"],["iiiint","intgral.quad"],["iiint","integral.triple"],["iint","integral.double"],["in","in"],["infty","infinity"],["int","integral"],["intercal","top"],["iota","iota"],["jmath","dotless.j"],["kappa","kappa"],["lambda","lambda"],["land","and"],["langle","angle.l"],["lbrace","brace.l"],["lbrack","bracket.l"],["ldots","dots.l"],["le","lt.eq"],["leadsto","arrow.squiggly"],["leftarrow","arrow.l"],["leftthreetimes","times.three.l"],["leftrightarrow","arrow.l.r"],["leq","lt.eq"],["leqslant","lt.eq.slant"],["lhd","triangle.l"],["ll","lt.double"],["longmapsto","arrow.bar.long"],["longrightarrow","arrow.long"],["lor","or"],["ltimes","times.l"],["mapsto","arrow.bar"],["measuredangle","angle.arc"],["mid","divides"],["models","models"],["mp","minus.plus"],["mu","mu"],["nRightarrow","arrow.double.not"],["nabla","nabla"],["ncong","tilde.nequiv"],["ne","eq.not"],["neg","not"],["neq","eq.not"],["nexists","exists.not"],["ni","in.rev"],["nleftarrow","arrow.l.not"],["nleq","lt.eq.not"],["nparallel","parallel.not"],["ngeq","gt.eq.not"],["nmid","divides.not"],["notin","in.not"],["nrightarrow","arrow.not"],["nsim","tilde.not"],["nsubseteq","subset.eq.not"],["nu","nu"],["ntriangleleft","lt.tri.not"],["ntriangleright","gt.tri.not"],["nwarrow","arrow.tl"],["odot","dot.circle"],["oint","integral.cont"],["oiint","integral.surf"],["oiiint","integral.vol"],["omega","omega"],["ominus","minus.circle"],["oplus","xor"],["otimes","times.circle"],["parallel","parallel"],["partial","diff"],["perp","perp"],["phi","phi.alt"],["pi","pi"],["pm","plus.minus"],["pounds","pound"],["prec","prec"],["preceq","prec.eq"],["prime","prime"],["prod","product"],["propto","prop"],["psi","psi"],["rangle","angle.r"],["rbrace","brace.r"],["rbrack","bracket.r"],["rhd","triangle"],["rho","rho"],["rightarrow","arrow.r"],["rightthreetimes","times.three.r"],["rtimes","times.r"],["setminus","without"],["sigma","sigma"],["sim","tilde"],["simeq","tilde.eq"],["slash","slash"],["smallsetminus","without"],["spadesuit","suit.spade"],["sqcap","sect.sq"],["sqcup","union.sq"],["sqsubseteq","subset.eq.sq"],["sqsupseteq","supset.eq.sq"],["star","star"],["subset","subset"],["subseteq","subset.eq"],["subsetneq","subset.neq"],["succ","succ"],["succeq","succ.eq"],["sum","sum"],["supset","supset"],["supseteq","supset.eq"],["supsetneq","supset.neq"],["swarrow","arrow.bl"],["tau","tau"],["theta","theta"],["times","times"],["to","arrow.r"],["top","top"],["triangle","triangle.t"],["triangledown","triangle.b.small"],["triangleleft","triangle.l.small"],["triangleright","triangle.r.small"],["twoheadrightarrow","arrow.r.twohead"],["uparrow","arrow.t"],["updownarrow","arrow.t.b"],["upharpoonright","harpoon.tr"],["uplus","union.plus"],["upsilon","upsilon"],["varepsilon","epsilon"],["varnothing","diameter"],["varphi","phi"],["varpi","pi.alt"],["varrho","rho.alt"],["varsigma","sigma.alt"],["vartheta","theta.alt"],["vdash","tack.r"],["vdots","dots.v"],["vee","or"],["wedge","and"],["wr","wreath"],["xi","xi"],["yen","yen"],["zeta","zeta"],["mathscr","scr"],["LaTeX","#LaTeX"],["TeX","#TeX"]]);function W(z,Z=""){if(!z)throw new H(Z)}function x(z){if(c.includes(z))return 1;else if(_.includes(z))return 2;else return 0}function l(z,Z){W(j(z[Z],D));let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched curly brackets");if(j(z[V],D))J+=1;else if(j(z[V],v))J-=1;V+=1}return V-1}function d(z,Z){W(j(z[Z],I));let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched square brackets");if(j(z[V],I))J+=1;else if(j(z[V],i))J-=1;V+=1}return V-1}function L(z){return"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".includes(z)}function g(z){return"0123456789".includes(z)}function A(z,Z){let J=Z;while(J<z.length&&[4,5].includes(z[J].type))J++;return z.slice(Z,J)}function C(z,Z){const J=z[Z];if(J.type===0&&["(",")","[","]","|","\\{","\\}"].includes(J.value))return J;else if(J.type===1&&["lfloor","rfloor","lceil","rceil","langle","rangle"].includes(J.value.slice(1)))return J;else return null}function U(z,Z){let J=Z;while(J<z.length&&j(z[J],{type:0,value:"'"}))J+=1;return J-Z}function T(z,Z){let J=Z;while(J<z.length&&L(z[J]))J+=1;return z.substring(Z,J)}function k(z,Z){let J=1,V=Z;while(J>0){if(V>=z.length)return-1;if(j(z[V],f))J+=1;else if(j(z[V],p))J-=1;V+=1}return V-1}function n(z,Z){let J=1,V=Z;while(J>0){if(V>=z.length)return-1;if(j(z[V],B))J+=1;else if(j(z[V],a))J-=1;V+=1}return V-1}function r(z,Z){W(z[Z]==="{");let J=1,V=Z+1;while(J>0){if(V>=z.length)throw new H("Unmatched curly brackets");if(V+1<z.length&&["\\{","\\}"].includes(z.substring(V,V+2))){V+=2;continue}if(z[V]==="{")J+=1;else if(z[V]==="}")J-=1;V+=1}return V-1}function P(z){const Z=[];let J=0;while(J<z.length){const V=z[J];let X;switch(V){case"%":{let $=J+1;while($<z.length&&z[$]!=="\n")$+=1;X={type:3,value:z.slice(J+1,$)},J=$;break}case"{":case"}":case"_":case"^":case"&":X={type:6,value:V},J++;break;case"\n":X={type:5,value:V},J++;break;case"\r":{if(J+1<z.length&&z[J+1]==="\n")X={type:5,value:"\n"},J+=2;else X={type:5,value:"\n"},J++;break}case" ":{let $=J;while($<z.length&&z[$]===" ")$+=1;X={type:4,value:z.slice(J,$)},J=$;break}case"\\":{if(J+1>=z.length)throw new H("Expecting command name after \\");const $=z.slice(J,J+2);if(["\\\\","\\,"].includes($))X={type:6,value:$};else if(["\\{","\\}","\\%","\\$","\\&","\\#","\\_"].includes($))X={type:0,value:$};else{const q=T(z,J+1);X={type:1,value:"\\"+q}}J+=X.value.length;break}default:{if(g(V)){let $=J;while($<z.length&&g(z[$]))$+=1;X={type:0,value:z.slice(J,$)}}else if(L(V))X={type:0,value:V};else if("+-*/=\'<>!.,;?()[]|".includes(V))X={type:0,value:V};else X={type:7,value:V};J+=X.value.length}}if(Z.push(X),X.type===1&&["\\text","\\operatorname","\\begin","\\end"].includes(X.value)){if(J>=z.length||z[J]!=="{")throw new H(`No content for ${X.value} command`);Z.push({type:6,value:"{"});const $=r(z,J);J++;let q=z.slice(J,$);const Q=["{","}","\\","$","&","#","_","%"];for(let F of Q)q=q.replaceAll("\\"+F,F);Z.push({type:2,value:q}),Z.push({type:6,value:"}"}),J=$+1}}return Z}function j(z,Z){return z.type==Z.type&&z.value==Z.value}function t(z){const Z=(V)=>j(V,M)||j(V,h);let J=[];for(let V=0;V<z.length;V++){if(z[V].type===4&&V+1<z.length&&Z(z[V+1]))continue;if(z[V].type===4&&V-1>=0&&Z(z[V-1]))continue;J.push(z[V])}return J}function s(z,Z){let J=[];for(let V of z)if(V.type===1&&Z[V.value]){const X=P(Z[V.value]);J=J.concat(X)}else J.push(V);return J}function u(z,Z){const J=new y;let V=P(z);return V=t(V),V=s(V,Z),J.parse(V)}var c=["sqrt","text","bar","bold","boldsymbol","ddot","dot","hat","mathbb","mathbf","mathcal","mathfrak","mathit","mathrm","mathscr","mathsf","mathtt","operatorname","overbrace","overline","pmb","rm","tilde","underbrace","underline","vec","widehat","widetilde"],_=["frac","tfrac","binom","dbinom","dfrac","tbinom"],S={type:"empty",content:""},D={type:6,value:"{"},v={type:6,value:"}"},I={type:0,value:"["},i={type:0,value:"]"},f={type:1,value:"\\left"},p={type:1,value:"\\right"},B={type:1,value:"\\begin"},a={type:1,value:"\\end"};class H extends Error{constructor(z){super(z);this.name="LatexParserError"}}var M={type:6,value:"_"},h={type:6,value:"^"};class y{space_sensitive;newline_sensitive;constructor(z=!1,Z=!0){this.space_sensitive=z,this.newline_sensitive=Z}parse(z){const Z=[];let J=0;while(J<z.length){const V=[];let X=0;while(X<z.length){const[$,q]=this.parseNextExpr(z,X);if(X=q,!this.space_sensitive&&$.type==="whitespace")continue;if(!this.newline_sensitive&&$.type==="newline")continue;if($.type==="control"&&$.content==="&")throw new H("Unexpected & outside of an alignment");V.push($)}if(V.length===0)return S;else if(V.length===1)return V[0];else return{type:"ordgroup",content:"",args:V}}if(Z.length===0)return S;else if(Z.length===1)return Z[0];else return{type:"ordgroup",content:"",args:Z}}parseNextExpr(z,Z){let[J,V]=this.parseNextExprWithoutSupSub(z,Z),X=null,$=null,q=0;if(q+=U(z,V),V+=q,V<z.length&&j(z[V],M)){if([X,V]=this.parseNextExprWithoutSupSub(z,V+1),q+=U(z,V),V+=q,V<z.length&&j(z[V],h)){if([$,V]=this.parseNextExprWithoutSupSub(z,V+1),U(z,V)>0)throw new H("Double superscript")}}else if(V<z.length&&j(z[V],h)){if([$,V]=this.parseNextExprWithoutSupSub(z,V+1),U(z,V)>0)throw new H("Double superscript");if(V<z.length&&j(z[V],M)){if([X,V]=this.parseNextExprWithoutSupSub(z,V+1),U(z,V)>0)throw new H("Double superscript")}}if(X!==null||$!==null||q>0){const Q={base:J};if(X)Q.sub=X;if(q>0){Q.sup={type:"ordgroup",content:"",args:[]};for(let F=0;F<q;F++)Q.sup.args.push({type:"element",content:"'"});if($)Q.sup.args.push($);if(Q.sup.args.length===1)Q.sup=Q.sup.args[0]}else if($)Q.sup=$;return[{type:"supsub",content:"",data:Q},V]}else return[J,V]}parseNextExprWithoutSupSub(z,Z){const J=z[Z];switch(J.type){case 0:return[{type:"element",content:J.value},Z+1];case 2:return[{type:"text",content:J.value},Z+1];case 3:return[{type:"comment",content:J.value},Z+1];case 4:return[{type:"whitespace",content:J.value},Z+1];case 5:return[{type:"newline",content:J.value},Z+1];case 1:if(j(J,B))return this.parseBeginEndExpr(z,Z);else if(j(J,f))return this.parseLeftRightExpr(z,Z);else return this.parseCommandExpr(z,Z);case 6:switch(J.value){case"{":const $=l(z,Z),q=z.slice(Z+1,$);return[this.parse(q),$+1];case"}":throw new H("Unmatched '}'");case"\\\\":return[{type:"control",content:"\\\\"},Z+1];case"\\,":return[{type:"control",content:"\\,"},Z+1];case"_":return[S,Z];case"^":return[S,Z];case"&":return[{type:"control",content:"&"},Z+1];default:throw new H("Unknown control sequence")}default:throw new H("Unknown token type")}}parseCommandExpr(z,Z){W(z[Z].type===1);const J=z[Z].value;let V=Z+1;if(["left","right","begin","end"].includes(J.slice(1)))throw new H("Unexpected command: "+J);switch(x(J.slice(1))){case 0:if(!O.has(J.slice(1)))return[{type:"unknownMacro",content:J},V];return[{type:"symbol",content:J},V];case 1:{if(J==="\\sqrt"&&V<z.length&&j(z[V],I)){const Q=V,F=d(z,V),Y=z.slice(Q+1,F),R=this.parse(Y),[w,E]=this.parseNextExprWithoutSupSub(z,F+1);return[{type:"unaryFunc",content:J,args:[w],data:R},E]}else if(J==="\\text"){if(V+2>=z.length)throw new H("Expecting content for \\text command");return W(j(z[V],D)),W(z[V+1].type===2),W(j(z[V+2],v)),[{type:"text",content:z[V+1].value},V+3]}let[$,q]=this.parseNextExprWithoutSupSub(z,V);return[{type:"unaryFunc",content:J,args:[$]},q]}case 2:{const[$,q]=this.parseNextExprWithoutSupSub(z,V),[Q,F]=this.parseNextExprWithoutSupSub(z,q);return[{type:"binaryFunc",content:J,args:[$,Q]},F]}default:throw new Error("Invalid number of parameters")}}parseLeftRightExpr(z,Z){W(j(z[Z],f));let J=Z+1;if(J+=A(z,J).length,J>=z.length)throw new H("Expecting delimiter after \\left");const V=C(z,J);if(V===null)throw new H("Invalid delimiter after \\left");J++;const X=J,$=k(z,J);if($===-1)throw new H("No matching \\right");const q=$;if(J=$+1,J+=A(z,J).length,J>=z.length)throw new H("Expecting \\right after \\left");const Q=C(z,J);if(Q===null)throw new H("Invalid delimiter after \\right");J++;const F=z.slice(X,q),Y=this.parse(F);return[{type:"leftright",content:"",args:[{type:"element",content:V.value},Y,{type:"element",content:Q.value}]},J]}parseBeginEndExpr(z,Z){W(j(z[Z],B));let J=Z+1;W(j(z[J],D)),W(z[J+1].type===2),W(j(z[J+2],v));const V=z[J+1].value;J+=3,J+=A(z,J).length;const X=J,$=n(z,J);if($===-1)throw new H("No matching \\end");const q=$;if(J=$+1,W(j(z[J],D)),W(z[J+1].type===2),W(j(z[J+2],v)),z[J+1].value!==V)throw new H("Mismatched \\begin and \\end environments");J+=3;const Q=z.slice(X,q);while(Q.length>0&&[4,5].includes(Q[Q.length-1].type))Q.pop();const F=this.parseAligned(Q);return[{type:"beginend",content:V,data:F},J]}parseAligned(z){let Z=0;const J=[];let V=[];J.push(V);let X={type:"ordgroup",content:"",args:[]};V.push(X);while(Z<z.length){const[$,q]=this.parseNextExpr(z,Z);if(Z=q,$.type==="whitespace")continue;else if($.type==="newline"&&!this.newline_sensitive)continue;else if($.type==="control"&&$.content==="\\\\")V=[],X={type:"ordgroup",content:"",args:[]},V.push(X),J.push(V);else if($.type==="control"&&$.content==="&")X={type:"ordgroup",content:"",args:[]},V.push(X);else X.args.push($)}return J}}function G(z){switch(z.type){case"empty":case"whitespace":return{type:"empty",content:""};case"ordgroup":return{type:"group",content:"",args:z.args.map(G)};case"element":case"symbol":return{type:"symbol",content:N(z.content)};case"text":return{type:"text",content:z.content};case"comment":return{type:"comment",content:z.content};case"supsub":{let{base:Z,sup:J,sub:V}=z.data;if(Z&&Z.type==="unaryFunc"&&Z.content==="\\overbrace"&&J)return{type:"binaryFunc",content:"overbrace",args:[G(Z.args[0]),G(J)]};else if(Z&&Z.type==="unaryFunc"&&Z.content==="\\underbrace"&&V)return{type:"binaryFunc",content:"underbrace",args:[G(Z.args[0]),G(V)]};const X={base:G(Z)};if(X.base.type==="empty")X.base={type:"text",content:""};if(J)X.sup=G(J);if(V)X.sub=G(V);return{type:"supsub",content:"",data:X}}case"leftright":{const[Z,J,V]=z.args,X={type:"group",content:"",args:z.args.map(G)};if(["[]","()","\\{\\}","\\lfloor\\rfloor","\\lceil\\rceil"].includes(Z.content+V.content))return X;return{type:"unaryFunc",content:"lr",args:[X]}}case"binaryFunc":return{type:"binaryFunc",content:N(z.content),args:z.args.map(G)};case"unaryFunc":{const Z=G(z.args[0]);if(z.content==="\\sqrt"&&z.data)return{type:"binaryFunc",content:"root",args:[G(z.data),Z]};if(z.content==="\\mathbf")return{type:"unaryFunc",content:"upright",args:[{type:"unaryFunc",content:"bold",args:[Z]}]};if(z.content==="\\mathbb"&&Z.type==="symbol"&&/^[A-Z]$/.test(Z.content))return{type:"symbol",content:Z.content+Z.content};if(z.content==="\\operatorname"){const J=z.args;if(J.length!==1||J[0].type!=="text")throw new K("Expecting body of \\operatorname to be text but got",z);const V=J[0].content;if(o.includes(V))return{type:"symbol",content:V};else return{type:"unaryFunc",content:"op",args:[{type:"text",content:V}]}}return{type:"unaryFunc",content:N(z.content),args:z.args.map(G)}}case"newline":return{type:"newline",content:"\n"};case"beginend":{const J=z.data.map((V)=>V.map(G));if(z.content.startsWith("align"))return{type:"align",content:"",data:J};else return{type:"matrix",content:"mat",data:J}}case"unknownMacro":return{type:"unknown",content:N(z.content)};case"control":if(z.content==="\\\\")return{type:"symbol",content:"\\"};else if(z.content==="\\,")return{type:"symbol",content:"thin"};else throw new K(`Unknown control sequence: ${z.content}`,z);default:throw new K(`Unimplemented node type: ${z.type}`,z)}}function N(z){if(/^[a-zA-Z0-9]$/.test(z))return z;else if(z==="\\\\")return"\\";else if(z=="/")return"\\/";else if(["\\$","\\#","\\&","\\_"].includes(z))return z;else if(z.startsWith("\\")){const Z=z.slice(1);if(O.has(Z))return O.get(Z);else return Z}return z}var o=["dim","id","im","mod","Pr","sech","csch"];class K extends Error{node;constructor(z,Z){super(z);this.name="TypstWriterError",this.node=Z}}class b{nonStrict;preferTypstIntrinsic;buffer="";queue=[];needSpaceAfterSingleItemScript=!1;insideFunctionDepth=0;constructor(z,Z){this.nonStrict=z,this.preferTypstIntrinsic=Z}writeBuffer(z){if(this.needSpaceAfterSingleItemScript&&/^[0-9a-zA-Z\(]/.test(z))this.buffer+=" ";else{let Z=!1;if(Z||=/[\(\|]$/.test(this.buffer)&&/^\w/.test(z),Z||=/^[}()_^,;!\|]$/.test(z),Z||=z==="'",Z||=/[0-9]$/.test(this.buffer)&&/^[0-9]/.test(z),Z||=/[\(\[{]\s*(-|\+)$/.test(this.buffer)||this.buffer==="-"||this.buffer==="+",Z||=z.startsWith("\n"),Z||=this.buffer==="",Z||=/[\s"_^{\(]$/.test(this.buffer),!Z)this.buffer+=" "}if(this.needSpaceAfterSingleItemScript)this.needSpaceAfterSingleItemScript=!1;this.buffer+=z}append(z){switch(z.type){case"empty":break;case"symbol":{let Z=z.content;if(z.content===","&&this.insideFunctionDepth>0)Z="comma";this.queue.push({type:"symbol",content:Z});break}case"text":case"comment":case"newline":this.queue.push(z);break;case"group":for(let Z of z.args)this.append(Z);break;case"supsub":{let{base:Z,sup:J,sub:V}=z.data;this.appendWithBracketsIfNeeded(Z);let X=!1;const $=J&&J.type==="symbol"&&J.content==="\'";if($)this.queue.push({type:"atom",content:"\'"}),X=!1;if(V)this.queue.push({type:"atom",content:"_"}),X=this.appendWithBracketsIfNeeded(V);if(J&&!$)this.queue.push({type:"atom",content:"^"}),X=this.appendWithBracketsIfNeeded(J);if(X)this.queue.push({type:"softSpace",content:""});break}case"binaryFunc":{const Z={type:"symbol",content:z.content},[J,V]=z.args;this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(J),this.queue.push({type:"atom",content:","}),this.append(V),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--;break}case"unaryFunc":{const Z={type:"symbol",content:z.content},J=z.args[0];this.queue.push(Z),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.append(J),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--;break}case"align":{const Z=z.data;Z.forEach((J,V)=>{if(J.forEach((X,$)=>{if($>0)this.queue.push({type:"atom",content:"&"});this.append(X)}),V<Z.length-1)this.queue.push({type:"symbol",content:"\\"})});break}case"matrix":{const Z=z.data;this.queue.push({type:"symbol",content:"mat"}),this.insideFunctionDepth++,this.queue.push({type:"atom",content:"("}),this.queue.push({type:"symbol",content:"delim: #none, "}),Z.forEach((J,V)=>{J.forEach((X,$)=>{if(this.append(X),$<J.length-1)this.queue.push({type:"atom",content:","});else if(V<Z.length-1)this.queue.push({type:"atom",content:";"})})}),this.queue.push({type:"atom",content:")"}),this.insideFunctionDepth--;break}case"unknown":{if(this.nonStrict)this.queue.push({type:"symbol",content:z.content});else throw new K(`Unknown macro: ${z.content}`,z);break}default:throw new K(`Unimplemented node type to append: ${z.type}`,z)}}appendWithBracketsIfNeeded(z){const Z=!["group","supsub","empty"].includes(z.type);if(Z)this.append(z);else this.queue.push({type:"atom",content:"("}),this.append(z),this.queue.push({type:"atom",content:")"});return Z}flushQueue(){this.queue.forEach((z)=>{let Z="";switch(z.type){case"atom":case"symbol":Z=z.content;break;case"text":Z=`"${z.content}"`;break;case"softSpace":this.needSpaceAfterSingleItemScript=!0,Z="";break;case"comment":Z=`//${z.content}`;break;case"newline":Z="\n";break;default:throw new K(`Unexpected node type to stringify: ${z.type}`,z)}if(Z!=="")this.writeBuffer(Z)}),this.queue=[]}finalize(){this.flushQueue();const z=function(J){let V=J.replace(/⌊\s*(.*?)\s*⌋/g,"floor($1)");return V=V.replace(/floor\(\)/g,'floor("")'),V},Z=function(J){let V=J.replace(/⌈\s*(.*?)\s*⌉/g,"ceil($1)");return V=V.replace(/ceil\(\)/g,'ceil("")'),V};return this.buffer=z(this.buffer),this.buffer=Z(this.buffer),this.buffer}}function m(z,Z){const J={nonStrict:!0,preferTypstIntrinsic:!0,customTexMacros:{}};if(Z){if(Z.nonStrict)J.nonStrict=Z.nonStrict;if(Z.preferTypstIntrinsic)J.preferTypstIntrinsic=Z.preferTypstIntrinsic;if(Z.customTexMacros)J.customTexMacros=Z.customTexMacros}const V=u(z,J.customTexMacros),X=G(V),$=new b(J.nonStrict,J.preferTypstIntrinsic);return $.append(X),$.finalize()}if(typeof window!=="undefined")window.tex2typst=m; |
@@ -0,1 +1,15 @@ | ||
export declare enum TokenType { | ||
ELEMENT = 0, | ||
COMMAND = 1, | ||
TEXT = 2, | ||
COMMENT = 3, | ||
WHITESPACE = 4, | ||
NEWLINE = 5, | ||
CONTROL = 6, | ||
UNKNOWN = 7 | ||
} | ||
export interface Token { | ||
type: TokenType; | ||
value: string; | ||
} | ||
export interface TexSupsubData { | ||
@@ -9,3 +23,3 @@ base: TexNode; | ||
export interface TexNode { | ||
type: string; | ||
type: 'element' | 'text' | 'comment' | 'whitespace' | 'newline' | 'control' | 'ordgroup' | 'supsub' | 'unaryFunc' | 'binaryFunc' | 'leftright' | 'beginend' | 'symbol' | 'empty' | 'unknownMacro'; | ||
content: string; | ||
@@ -15,6 +29,13 @@ args?: TexNode[]; | ||
} | ||
export interface TypstSupsubData { | ||
base: TypstNode; | ||
sup?: TypstNode; | ||
sub?: TypstNode; | ||
} | ||
export type TypstArrayData = TypstNode[][]; | ||
export interface TypstNode { | ||
type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline'; | ||
type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline' | 'empty' | 'group' | 'supsub' | 'unaryFunc' | 'binaryFunc' | 'align' | 'matrix' | 'unknown'; | ||
content: string; | ||
args?: TypstNode[]; | ||
data?: TypstSupsubData | TypstArrayData; | ||
} | ||
@@ -21,0 +42,0 @@ export interface Tex2TypstOptions { |
import { TexNode, TypstNode } from "./types"; | ||
export declare class TypstWriterError extends Error { | ||
node: TexNode; | ||
node: TexNode | TypstNode; | ||
constructor(message: string, node: TexNode | TypstNode); | ||
@@ -15,6 +15,7 @@ } | ||
private writeBuffer; | ||
append(node: TexNode): void; | ||
append(node: TypstNode): void; | ||
private appendWithBracketsIfNeeded; | ||
protected flushQueue(): void; | ||
private appendWithBracketsIfNeeded; | ||
finalize(): string; | ||
} | ||
export declare function convertTree(node: TexNode): TypstNode; |
{ | ||
"name": "tex2typst", | ||
"version": "0.2.7", | ||
"version": "0.2.8", | ||
"description": "JavaScript library for converting TeX code to Typst", | ||
@@ -18,3 +18,3 @@ "type": "module", | ||
"prebuild": "rimraf dist/", | ||
"build:node": "bun build --entrypoints src/index.ts --outdir ./dist --target node --external katex", | ||
"build:node": "bun build --entrypoints src/index.ts --outdir ./dist --target node", | ||
"build:browser": "bun build --entrypoints src/tex2typst.ts --outdir ./dist --target browser --entry-naming [dir]/[name].min.[ext] --minify", | ||
@@ -21,0 +21,0 @@ "build:types": "tsc --project ./tsconfig.json", |
import { parseTex } from "./parser"; | ||
import { Tex2TypstOptions } from "./types"; | ||
import { TypstWriter } from "./writer"; | ||
import { convertTree, TypstWriter } from "./writer"; | ||
import { symbolMap } from "./map"; | ||
@@ -9,3 +9,3 @@ | ||
const opt: Tex2TypstOptions = { | ||
nonStrict: false, | ||
nonStrict: true, | ||
preferTypstIntrinsic: true, | ||
@@ -25,5 +25,6 @@ customTexMacros: {} | ||
} | ||
const t = parseTex(tex, opt.customTexMacros!); | ||
const texTree = parseTex(tex, opt.customTexMacros!); | ||
const typstTree = convertTree(texTree); | ||
const writer = new TypstWriter(opt.nonStrict!, opt.preferTypstIntrinsic!); | ||
writer.append(t); | ||
writer.append(typstTree); | ||
return writer.finalize(); | ||
@@ -30,0 +31,0 @@ } |
@@ -1,2 +0,3 @@ | ||
import { TexNode, TexSupsubData } from "./types"; | ||
import { symbolMap } from "./map"; | ||
import { TexNode, TexSupsubData, Token, TokenType } from "./types"; | ||
@@ -45,3 +46,3 @@ | ||
const EMPTY_NODE = { 'type': 'empty', 'content': '' } | ||
const EMPTY_NODE: TexNode = { type: 'empty', content: '' }; | ||
@@ -64,4 +65,4 @@ function assert(condition: boolean, message: string = ''): void { | ||
const LEFT_CURLY_BRACKET: Token = {type: 'control', value: '{'}; | ||
const RIGHT_CURLY_BRACKET: Token = {type: 'control', value: '}'}; | ||
const LEFT_CURLY_BRACKET: Token = {type: TokenType.CONTROL, value: '{'}; | ||
const RIGHT_CURLY_BRACKET: Token = {type: TokenType.CONTROL, value: '}'}; | ||
@@ -88,4 +89,4 @@ function find_closing_curly_bracket(tokens: Token[], start: number): number { | ||
const LEFT_SQUARE_BRACKET: Token = {type: 'element', value: '['}; | ||
const RIGHT_SQUARE_BRACKET: Token = {type: 'element', value: ']'}; | ||
const LEFT_SQUARE_BRACKET: Token = {type: TokenType.ELEMENT, value: '['}; | ||
const RIGHT_SQUARE_BRACKET: Token = {type: TokenType.ELEMENT, value: ']'}; | ||
@@ -123,3 +124,3 @@ function find_closing_square_bracket(tokens: Token[], start: number): number { | ||
let pos = start; | ||
while (pos < tokens.length && ['whitespace', 'newline'].includes(tokens[pos].type)) { | ||
while (pos < tokens.length && [TokenType.WHITESPACE, TokenType.NEWLINE].includes(tokens[pos].type)) { | ||
pos++; | ||
@@ -133,5 +134,5 @@ } | ||
const firstToken = tokens[start]; | ||
if (firstToken.type === 'element' && ['(', ')', '[', ']', '|', '\\{', '\\}'].includes(firstToken.value)) { | ||
if (firstToken.type === TokenType.ELEMENT && ['(', ')', '[', ']', '|', '\\{', '\\}'].includes(firstToken.value)) { | ||
return firstToken; | ||
} else if (firstToken.type === 'command' && ['lfloor', 'rfloor', 'lceil', 'rceil', 'langle', 'rangle'].includes(firstToken.value.slice(1))) { | ||
} else if (firstToken.type === TokenType.COMMAND && ['lfloor', 'rfloor', 'lceil', 'rceil', 'langle', 'rangle'].includes(firstToken.value.slice(1))) { | ||
return firstToken; | ||
@@ -145,3 +146,3 @@ } else { | ||
let pos = start; | ||
while (pos < tokens.length && token_eq(tokens[pos], { type: 'element', value: "'" })) { | ||
while (pos < tokens.length && token_eq(tokens[pos], { type: TokenType.ELEMENT, value: "'" })) { | ||
pos += 1; | ||
@@ -164,4 +165,4 @@ } | ||
const LEFT_COMMAND: Token = { type: 'command', value: '\\left' }; | ||
const RIGHT_COMMAND: Token = { type: 'command', value: '\\right' }; | ||
const LEFT_COMMAND: Token = { type: TokenType.COMMAND, value: '\\left' }; | ||
const RIGHT_COMMAND: Token = { type: TokenType.COMMAND, value: '\\right' }; | ||
@@ -188,4 +189,4 @@ function find_closing_right_command(tokens: Token[], start: number): number { | ||
const BEGIN_COMMAND: Token = { type: 'command', value: '\\begin' }; | ||
const END_COMMAND: Token = { type: 'command', value: '\\end' }; | ||
const BEGIN_COMMAND: Token = { type: TokenType.COMMAND, value: '\\begin' }; | ||
const END_COMMAND: Token = { type: TokenType.COMMAND, value: '\\end' }; | ||
@@ -237,7 +238,2 @@ | ||
export interface Token { | ||
type: 'element' | 'command' | 'text' | 'comment' | 'whitespace' | 'newline' | 'control' | 'unknown'; | ||
value: string; | ||
} | ||
export function tokenize(latex: string): Token[] { | ||
@@ -256,3 +252,3 @@ const tokens: Token[] = []; | ||
} | ||
token = { type: 'comment', value: latex.slice(pos + 1, newPos) }; | ||
token = { type: TokenType.COMMENT, value: latex.slice(pos + 1, newPos) }; | ||
pos = newPos; | ||
@@ -266,7 +262,7 @@ break; | ||
case '&': | ||
token = { type: 'control', value: firstChar}; | ||
token = { type: TokenType.CONTROL, value: firstChar}; | ||
pos++; | ||
break; | ||
case '\n': | ||
token = { type: 'newline', value: firstChar}; | ||
token = { type: TokenType.NEWLINE, value: firstChar}; | ||
pos++; | ||
@@ -276,6 +272,6 @@ break; | ||
if (pos + 1 < latex.length && latex[pos + 1] === '\n') { | ||
token = { type: 'newline', value: '\n' }; | ||
token = { type: TokenType.NEWLINE, value: '\n' }; | ||
pos += 2; | ||
} else { | ||
token = { type: 'newline', value: '\n' }; | ||
token = { type: TokenType.NEWLINE, value: '\n' }; | ||
pos ++; | ||
@@ -290,3 +286,3 @@ } | ||
} | ||
token = {type: 'whitespace', value: latex.slice(pos, newPos)}; | ||
token = {type: TokenType.WHITESPACE, value: latex.slice(pos, newPos)}; | ||
pos = newPos; | ||
@@ -301,8 +297,8 @@ break; | ||
if (['\\\\', '\\,'].includes(firstTwoChars)) { | ||
token = { type: 'control', value: firstTwoChars }; | ||
token = { type: TokenType.CONTROL, value: firstTwoChars }; | ||
} else if (['\\{','\\}', '\\%', '\\$', '\\&', '\\#', '\\_'].includes(firstTwoChars)) { | ||
token = { type: 'element', value: firstTwoChars }; | ||
token = { type: TokenType.ELEMENT, value: firstTwoChars }; | ||
} else { | ||
const command = eat_command_name(latex, pos + 1); | ||
token = { type: 'command', value: '\\' + command}; | ||
token = { type: TokenType.COMMAND, value: '\\' + command}; | ||
} | ||
@@ -318,9 +314,9 @@ pos += token.value.length; | ||
} | ||
token = { type: 'element', value: latex.slice(pos, newPos) } | ||
token = { type: TokenType.ELEMENT, value: latex.slice(pos, newPos) } | ||
} else if (isalpha(firstChar)) { | ||
token = { type: 'element', value: firstChar }; | ||
token = { type: TokenType.ELEMENT, value: firstChar }; | ||
} else if ('+-*/=\'<>!.,;?()[]|'.includes(firstChar)) { | ||
token = { type: 'element', value: firstChar } | ||
token = { type: TokenType.ELEMENT, value: firstChar } | ||
} else { | ||
token = { type: 'unknown', value: firstChar }; | ||
token = { type: TokenType.UNKNOWN, value: firstChar }; | ||
} | ||
@@ -333,7 +329,7 @@ pos += token.value.length; | ||
if (token.type === 'command' && ['\\text', '\\begin', '\\end'].includes(token.value)) { | ||
if (token.type === TokenType.COMMAND && ['\\text', '\\operatorname', '\\begin', '\\end'].includes(token.value)) { | ||
if (pos >= latex.length || latex[pos] !== '{') { | ||
throw new LatexParserError(`No content for ${token.value} command`); | ||
} | ||
tokens.push({ type: 'control', value: '{' }); | ||
tokens.push({ type: TokenType.CONTROL, value: '{' }); | ||
const posClosingBracket = find_closing_curly_bracket_char(latex, pos); | ||
@@ -347,4 +343,4 @@ pos++; | ||
} | ||
tokens.push({ type: 'text', value: textInside }); | ||
tokens.push({ type: 'control', value: '}' }); | ||
tokens.push({ type: TokenType.TEXT, value: textInside }); | ||
tokens.push({ type: TokenType.CONTROL, value: '}' }); | ||
pos = posClosingBracket + 1; | ||
@@ -371,4 +367,4 @@ } | ||
const SUB_SYMBOL:Token = { type: 'control', value: '_' }; | ||
const SUP_SYMBOL:Token = { type: 'control', value: '^' }; | ||
const SUB_SYMBOL:Token = { type: TokenType.CONTROL, value: '_' }; | ||
const SUP_SYMBOL:Token = { type: TokenType.CONTROL, value: '^' }; | ||
@@ -464,3 +460,3 @@ export class LatexParser { | ||
for (let i = 0; i < num_prime; i++) { | ||
res.sup.args!.push({ type: 'symbol', content: '\\prime' }); | ||
res.sup.args!.push({ type: 'element', content: "'" }); | ||
} | ||
@@ -486,9 +482,13 @@ if (sup) { | ||
switch (tokenType) { | ||
case 'element': | ||
case 'text': | ||
case 'comment': | ||
case 'whitespace': | ||
case 'newline': | ||
return [{ type: tokenType, content: firstToken.value }, start + 1]; | ||
case 'command': | ||
case TokenType.ELEMENT: | ||
return [{ type: 'element', content: firstToken.value }, start + 1]; | ||
case TokenType.TEXT: | ||
return [{ type: 'text', content: firstToken.value }, start + 1]; | ||
case TokenType.COMMENT: | ||
return [{ type: 'comment', content: firstToken.value }, start + 1]; | ||
case TokenType.WHITESPACE: | ||
return [{ type: 'whitespace', content: firstToken.value }, start + 1]; | ||
case TokenType.NEWLINE: | ||
return [{ type: 'newline', content: firstToken.value }, start + 1]; | ||
case TokenType.COMMAND: | ||
if (token_eq(firstToken, BEGIN_COMMAND)) { | ||
@@ -501,3 +501,3 @@ return this.parseBeginEndExpr(tokens, start); | ||
} | ||
case 'control': | ||
case TokenType.CONTROL: | ||
const controlChar = firstToken.value; | ||
@@ -532,3 +532,3 @@ switch (controlChar) { | ||
parseCommandExpr(tokens: Token[], start: number): ParseResult { | ||
assert(tokens[start].type === 'command'); | ||
assert(tokens[start].type === TokenType.COMMAND); | ||
@@ -543,31 +543,38 @@ const command = tokens[start].value; // command name starts with a \ | ||
const paramNum = get_command_param_num(command.slice(1)); | ||
if (paramNum === 0) { | ||
return [{ type: 'symbol', content: command }, pos]; | ||
} else if (paramNum === 1) { | ||
if (command === '\\sqrt' && pos < tokens.length && token_eq(tokens[pos], LEFT_SQUARE_BRACKET)) { | ||
const posLeftSquareBracket = pos; | ||
const posRightSquareBracket = find_closing_square_bracket(tokens, pos); | ||
const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket); | ||
const exponent = this.parse(exprInside); | ||
const [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1); | ||
return [{ type: 'unaryFunc', content: command, args: [arg1], data: exponent }, newPos]; | ||
} else if (command === '\\text') { | ||
if (pos + 2 >= tokens.length) { | ||
throw new LatexParserError('Expecting content for \\text command'); | ||
switch (paramNum) { | ||
case 0: | ||
if (!symbolMap.has(command.slice(1))) { | ||
return [{ type: 'unknownMacro', content: command }, pos]; | ||
} | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === 'text'); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
const text = tokens[pos + 1].value; | ||
return [{ type: 'text', content: text }, pos + 3]; | ||
return [{ type: 'symbol', content: command }, pos]; | ||
case 1: { | ||
if (command === '\\sqrt' && pos < tokens.length && token_eq(tokens[pos], LEFT_SQUARE_BRACKET)) { | ||
const posLeftSquareBracket = pos; | ||
const posRightSquareBracket = find_closing_square_bracket(tokens, pos); | ||
const exprInside = tokens.slice(posLeftSquareBracket + 1, posRightSquareBracket); | ||
const exponent = this.parse(exprInside); | ||
const [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, posRightSquareBracket + 1); | ||
return [{ type: 'unaryFunc', content: command, args: [arg1], data: exponent }, newPos]; | ||
} else if (command === '\\text') { | ||
if (pos + 2 >= tokens.length) { | ||
throw new LatexParserError('Expecting content for \\text command'); | ||
} | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === TokenType.TEXT); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
const text = tokens[pos + 1].value; | ||
return [{ type: 'text', content: text }, pos + 3]; | ||
} | ||
let [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
return [{ type: 'unaryFunc', content: command, args: [arg1] }, newPos]; | ||
} | ||
let [arg1, newPos] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
return [{ type: 'unaryFunc', content: command, args: [arg1] }, newPos]; | ||
} else if (paramNum === 2) { | ||
const [arg1, pos1] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
const [arg2, pos2] = this.parseNextExprWithoutSupSub(tokens, pos1); | ||
return [{ type: 'binaryFunc', content: command, args: [arg1, arg2] }, pos2]; | ||
} else { | ||
throw new Error( 'Invalid number of parameters'); | ||
case 2: { | ||
const [arg1, pos1] = this.parseNextExprWithoutSupSub(tokens, pos); | ||
const [arg2, pos2] = this.parseNextExprWithoutSupSub(tokens, pos1); | ||
return [{ type: 'binaryFunc', content: command, args: [arg1, arg2] }, pos2]; | ||
} | ||
default: | ||
throw new Error( 'Invalid number of parameters'); | ||
} | ||
@@ -612,3 +619,3 @@ } | ||
const body = this.parse(exprInside); | ||
const args = [ | ||
const args: TexNode[] = [ | ||
{ type: 'element', content: leftDelimiter.value }, | ||
@@ -618,3 +625,3 @@ body, | ||
] | ||
const res = { type: 'leftright', content: '', args: args }; | ||
const res: TexNode = { type: 'leftright', content: '', args: args }; | ||
return [res, pos]; | ||
@@ -628,3 +635,3 @@ } | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === 'text'); | ||
assert(tokens[pos + 1].type === TokenType.TEXT); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
@@ -646,3 +653,3 @@ const envName = tokens[pos + 1].value; | ||
assert(token_eq(tokens[pos], LEFT_CURLY_BRACKET)); | ||
assert(tokens[pos + 1].type === 'text'); | ||
assert(tokens[pos + 1].type === TokenType.TEXT); | ||
assert(token_eq(tokens[pos + 2], RIGHT_CURLY_BRACKET)); | ||
@@ -656,7 +663,7 @@ if (tokens[pos + 1].value !== envName) { | ||
// ignore whitespaces and '\n' before \end{envName} | ||
while(exprInside.length > 0 && ['whitespace', 'newline'].includes(exprInside[exprInside.length - 1].type)) { | ||
while(exprInside.length > 0 && [TokenType.WHITESPACE, TokenType.NEWLINE].includes(exprInside[exprInside.length - 1].type)) { | ||
exprInside.pop(); | ||
} | ||
const body = this.parseAligned(exprInside); | ||
const res = { type: 'beginend', content: envName, data: body }; | ||
const res: TexNode = { type: 'beginend', content: envName, data: body }; | ||
return [res, pos]; | ||
@@ -696,3 +703,3 @@ } | ||
// Remove all whitespace before and after _ or ^ | ||
// Remove all whitespace before or after _ or ^ | ||
function passIgnoreWhitespaceBeforeScriptMark(tokens: Token[]): Token[] { | ||
@@ -702,6 +709,6 @@ const is_script_mark = (token: Token) => token_eq(token, SUB_SYMBOL) || token_eq(token, SUP_SYMBOL); | ||
for (let i = 0; i < tokens.length; i++) { | ||
if (tokens[i].type === 'whitespace' && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) { | ||
if (tokens[i].type === TokenType.WHITESPACE && i + 1 < tokens.length && is_script_mark(tokens[i + 1])) { | ||
continue; | ||
} | ||
if (tokens[i].type === 'whitespace' && i - 1 >= 0 && is_script_mark(tokens[i - 1])) { | ||
if (tokens[i].type === TokenType.WHITESPACE && i - 1 >= 0 && is_script_mark(tokens[i - 1])) { | ||
continue; | ||
@@ -718,3 +725,3 @@ } | ||
for (const token of tokens) { | ||
if (token.type === 'command' && customTexMacros[token.value]) { | ||
if (token.type === TokenType.COMMAND && customTexMacros[token.value]) { | ||
const expanded_tokens = tokenize(customTexMacros[token.value]); | ||
@@ -721,0 +728,0 @@ out_tokens = out_tokens.concat(expanded_tokens); |
@@ -0,1 +1,18 @@ | ||
export enum TokenType { | ||
ELEMENT, | ||
COMMAND, | ||
TEXT, | ||
COMMENT, | ||
WHITESPACE, | ||
NEWLINE, | ||
CONTROL, | ||
UNKNOWN, | ||
} | ||
export interface Token { | ||
type: TokenType; | ||
value: string; | ||
} | ||
export interface TexSupsubData { | ||
@@ -12,3 +29,4 @@ base: TexNode; | ||
export interface TexNode { | ||
type: string; | ||
type: 'element' | 'text' | 'comment' | 'whitespace' | 'newline' | 'control' | 'ordgroup' | 'supsub' | ||
| 'unaryFunc' | 'binaryFunc' | 'leftright' | 'beginend' | 'symbol' | 'empty' | 'unknownMacro'; | ||
content: string; | ||
@@ -23,6 +41,16 @@ args?: TexNode[]; | ||
export interface TypstSupsubData { | ||
base: TypstNode; | ||
sup?: TypstNode; | ||
sub?: TypstNode; | ||
} | ||
export type TypstArrayData = TypstNode[][]; | ||
export interface TypstNode { | ||
type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline', | ||
type: 'atom' | 'symbol' | 'text' | 'softSpace' | 'comment' | 'newline' | ||
| 'empty' | 'group' | 'supsub' | 'unaryFunc' | 'binaryFunc' | 'align' | 'matrix' | 'unknown'; | ||
content: string; | ||
args?: TypstNode[]; | ||
data?: TypstSupsubData | TypstArrayData; | ||
} | ||
@@ -29,0 +57,0 @@ |
import { symbolMap } from "./map"; | ||
import { TexNode, TexSqrtData, TexSupsubData, TypstNode } from "./types"; | ||
import { TexNode, TexSqrtData, TexSupsubData, TypstNode, TypstSupsubData } from "./types"; | ||
@@ -18,3 +18,3 @@ | ||
export class TypstWriterError extends Error { | ||
node: TexNode; | ||
node: TexNode | TypstNode; | ||
@@ -77,107 +77,67 @@ constructor(message: string, node: TexNode | TypstNode) { | ||
public append(node: TexNode) { | ||
if (node.type === 'empty' || node.type === 'whitespace') { | ||
return; | ||
} else if (node.type === 'ordgroup') { | ||
// const index = this.startBlock(); | ||
node.args!.forEach((arg) => this.append(arg)); | ||
// this.endBlock(index); | ||
} else if (node.type === 'element') { | ||
let content = node.content!; | ||
if (node.content === ',' && this.insideFunctionDepth > 0) { | ||
content = 'comma'; | ||
public append(node: TypstNode) { | ||
switch (node.type) { | ||
case 'empty': | ||
break; | ||
case 'symbol': { | ||
let content = node.content!; | ||
if (node.content === ',' && this.insideFunctionDepth > 0) { | ||
content = 'comma'; | ||
} | ||
this.queue.push({ type: 'symbol', content: content }); | ||
break; | ||
} | ||
this.queue.push({ type: 'symbol', content: content }); | ||
} else if (node.type === 'symbol') { | ||
this.queue.push({ type: 'symbol', content: node.content }); | ||
} else if (node.type === 'text') { | ||
this.queue.push(node as TypstNode) | ||
} else if (node.type === 'supsub') { | ||
let { base, sup, sub } = node.data as TexSupsubData; | ||
// Special logic for overbrace | ||
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) { | ||
this.append({ type: 'binaryFunc', content: '\\overbrace', args: [base.args![0], sup] }); | ||
return; | ||
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) { | ||
this.append({ type: 'binaryFunc', content: '\\underbrace', args: [base.args![0], sub] }); | ||
return; | ||
} | ||
if (base.type === 'empty') { | ||
this.queue.push({ type: 'text', content: '' }); | ||
} else { | ||
case 'text': | ||
case 'comment': | ||
case 'newline': | ||
this.queue.push(node); | ||
break; | ||
case 'group': | ||
for (const item of node.args!) { | ||
this.append(item); | ||
} | ||
break; | ||
case 'supsub': { | ||
let { base, sup, sub } = node.data as TypstSupsubData; | ||
this.appendWithBracketsIfNeeded(base); | ||
} | ||
let trailing_space_needed = false; | ||
const has_prime = (sup && sup.type === 'symbol' && sup.content === '\\prime'); | ||
if (has_prime) { | ||
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far) | ||
// e.g. | ||
// y_1' -> y'_1 | ||
// y_{a_1}' -> y'_{a_1} | ||
this.queue.push({ type: 'atom', content: '\''}); | ||
trailing_space_needed = false; | ||
let trailing_space_needed = false; | ||
const has_prime = (sup && sup.type === 'symbol' && sup.content === '\''); | ||
if (has_prime) { | ||
// Put prime symbol before '_'. Because $y_1'$ is not displayed properly in Typst (so far) | ||
// e.g. | ||
// y_1' -> y'_1 | ||
// y_{a_1}' -> y'_{a_1} | ||
this.queue.push({ type: 'atom', content: '\''}); | ||
trailing_space_needed = false; | ||
} | ||
if (sub) { | ||
this.queue.push({ type: 'atom', content: '_'}); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sub); | ||
} | ||
if (sup && !has_prime) { | ||
this.queue.push({ type: 'atom', content: '^'}); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sup); | ||
} | ||
if (trailing_space_needed) { | ||
this.queue.push({ type: 'softSpace', content: ''}); | ||
} | ||
break; | ||
} | ||
if (sub) { | ||
this.queue.push({ type: 'atom', content: '_'}); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sub); | ||
} | ||
if (sup && !has_prime) { | ||
this.queue.push({ type: 'atom', content: '^'}); | ||
trailing_space_needed = this.appendWithBracketsIfNeeded(sup); | ||
} | ||
if (trailing_space_needed) { | ||
this.queue.push({ type: 'softSpace', content: ''}); | ||
} | ||
} else if (node.type === 'leftright') { | ||
const [left, body, right] = node.args!; | ||
// These pairs will be handled by Typst compiler by default. No need to add lr() | ||
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) { | ||
this.append(left); | ||
this.append(body); | ||
this.append(right); | ||
return; | ||
} | ||
const func_symbol: TypstNode = { type: 'symbol', content: 'lr' }; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth ++; | ||
this.queue.push({ type: 'atom', content: '('}); | ||
this.append(left); | ||
this.append(body); | ||
this.append(right); | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
this.insideFunctionDepth --; | ||
} else if (node.type === 'binaryFunc') { | ||
const func_symbol: TypstNode = { type: 'symbol', content: node.content }; | ||
const [arg0, arg1] = node.args!; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth ++; | ||
this.queue.push({ type: 'atom', content: '('}); | ||
this.append(arg0); | ||
this.queue.push({ type: 'atom', content: ','}); | ||
this.append(arg1); | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
this.insideFunctionDepth --; | ||
} else if (node.type === 'unaryFunc') { | ||
const func_symbol: TypstNode = { type: 'symbol', content: node.content }; | ||
const arg0 = node.args![0]; | ||
if (node.content === '\\sqrt' && node.data) { | ||
func_symbol.content = 'root'; | ||
case 'binaryFunc': { | ||
const func_symbol: TypstNode = { type: 'symbol', content: node.content }; | ||
const [arg0, arg1] = node.args!; | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth ++; | ||
this.queue.push({ type: 'atom', content: '('}); | ||
this.append(node.data as TexSqrtData); // the number of times to take the root | ||
this.append(arg0); | ||
this.queue.push({ type: 'atom', content: ','}); | ||
this.append(arg0); | ||
this.append(arg1); | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
this.insideFunctionDepth --; | ||
return; | ||
} else if (node.content === '\\mathbf') { | ||
this.append({ type: 'symbol', content: 'upright' }); | ||
this.insideFunctionDepth ++; | ||
this.queue.push({ type: 'atom', content: '('}); | ||
break; | ||
} | ||
case 'unaryFunc': { | ||
const func_symbol: TypstNode = { type: 'symbol', content: node.content }; | ||
const arg0 = node.args![0]; | ||
this.queue.push(func_symbol); | ||
@@ -189,50 +149,6 @@ this.insideFunctionDepth ++; | ||
this.insideFunctionDepth --; | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
this.insideFunctionDepth --; | ||
return; | ||
} else if (node.content === '\\mathbb') { | ||
const body = node.args![0]; | ||
if (body.type === 'element' && /^[A-Z]$/.test(body.content)) { | ||
// \mathbb{R} -> RR | ||
this.queue.push({ type: 'symbol', content: body.content + body.content}); | ||
return; | ||
} | ||
// Fall through | ||
} else if (node.content === '\\operatorname') { | ||
let body = node.args!; | ||
if (body.length === 1 && body[0].type == 'ordgroup') { | ||
body = body[0].args!; | ||
} | ||
const text = body.reduce((buff, n) => { | ||
// Hope convertToken() will not throw an error | ||
// If it does, the input is bad. | ||
buff += convertToken(n.content); | ||
return buff; | ||
}, "" as string); | ||
if (this.preferTypstIntrinsic && TYPST_INTRINSIC_SYMBOLS.includes(text)) { | ||
// e.g. we prefer just sech over op("sech") | ||
this.queue.push({ type: 'symbol', content: text}); | ||
} else { | ||
this.queue.push({ type: 'symbol', content: 'op' }); | ||
this.queue.push({ type: 'atom', content: '('}); | ||
this.queue.push({ type: 'text', content: text}); | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
} | ||
return; | ||
break; | ||
} | ||
this.queue.push(func_symbol); | ||
this.insideFunctionDepth ++; | ||
this.queue.push({ type: 'atom', content: '('}); | ||
this.append(arg0); | ||
this.queue.push({ type: 'atom', content: ')'}); | ||
this.insideFunctionDepth --; | ||
} else if (node.type === 'newline') { | ||
this.queue.push({ type: 'newline', content: '\n'}); | ||
return; | ||
} else if (node.type === 'beginend') { | ||
if (node.content!.startsWith('align')) { | ||
// align, align*, alignat, alignat*, aligned, etc. | ||
const matrix = node.data as TexNode[][]; | ||
case 'align': { | ||
const matrix = node.data as TypstNode[][]; | ||
matrix.forEach((row, i) => { | ||
@@ -246,7 +162,9 @@ row.forEach((cell, j) => { | ||
if (i < matrix.length - 1) { | ||
this.queue.push({ type: 'symbol', content: '\\\\' }); | ||
this.queue.push({ type: 'symbol', content: '\\' }); | ||
} | ||
}); | ||
} else { | ||
const matrix = node.data as TexNode[][]; | ||
break; | ||
} | ||
case 'matrix': { | ||
const matrix = node.data as TypstNode[][]; | ||
this.queue.push({ type: 'symbol', content: 'mat' }); | ||
@@ -259,6 +177,6 @@ this.insideFunctionDepth ++; | ||
// There is a leading & in row | ||
if (cell.type === 'ordgroup' && cell.args!.length === 0) { | ||
this.queue.push({ type: 'atom', content: ',' }); | ||
return; | ||
} | ||
// if (cell.type === 'ordgroup' && cell.args!.length === 0) { | ||
// this.queue.push({ type: 'atom', content: ',' }); | ||
// return; | ||
// } | ||
// if (j == 0 && cell.type === 'newline' && cell.content === '\n') { | ||
@@ -280,23 +198,33 @@ // return; | ||
this.insideFunctionDepth --; | ||
break; | ||
} | ||
} else if (node.type === 'matrix') { | ||
} else if (node.type === 'unknownMacro') { | ||
if (this.nonStrict) { | ||
this.queue.push({ type: 'symbol', content: node.content }); | ||
} else { | ||
throw new TypstWriterError(`Unknown macro: ${node.content}`, node); | ||
case 'unknown': { | ||
if (this.nonStrict) { | ||
this.queue.push({ type: 'symbol', content: node.content }); | ||
} else { | ||
throw new TypstWriterError(`Unknown macro: ${node.content}`, node); | ||
} | ||
break; | ||
} | ||
} else if (node.type === 'control') { | ||
if (node.content === '\\\\') { | ||
this.queue.push({ type: 'symbol', content: node.content }); | ||
} else if (node.content === '\\,') { | ||
this.queue.push({ type: 'symbol', content: 'thin' }); | ||
} else { | ||
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node); | ||
} | ||
} else if (node.type === 'comment') { | ||
this.queue.push({ type: 'comment', content: node.content }); | ||
default: | ||
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node); | ||
} | ||
} | ||
private appendWithBracketsIfNeeded(node: TypstNode): boolean { | ||
const is_single = !['group', 'supsub', 'empty'].includes(node.type); | ||
if (is_single) { | ||
this.append(node); | ||
} else { | ||
throw new TypstWriterError(`Unimplemented node type to append: ${node.type}`, node); | ||
this.queue.push({ | ||
type: 'atom', | ||
content: '(' | ||
}); | ||
this.append(node); | ||
this.queue.push({ | ||
type: 'atom', | ||
content: ')' | ||
}); | ||
} | ||
return is_single; | ||
} | ||
@@ -309,7 +237,5 @@ | ||
case 'atom': | ||
case 'symbol': | ||
str = node.content; | ||
break; | ||
case 'symbol': | ||
str = convertToken(node.content); | ||
break; | ||
case 'text': | ||
@@ -338,20 +264,2 @@ str = `"${node.content}"`; | ||
private appendWithBracketsIfNeeded(node: TexNode): boolean { | ||
const is_single = ['symbol', 'element', 'unaryFunc', 'binaryFunc', 'leftright'].includes(node.type); | ||
if (is_single) { | ||
this.append(node); | ||
} else { | ||
this.queue.push({ | ||
type: 'atom', | ||
content: '(' | ||
}); | ||
this.append(node); | ||
this.queue.push({ | ||
type: 'atom', | ||
content: ')' | ||
}); | ||
} | ||
return is_single; | ||
} | ||
public finalize(): string { | ||
@@ -379,3 +287,180 @@ this.flushQueue(); | ||
export function convertTree(node: TexNode): TypstNode { | ||
switch (node.type) { | ||
case 'empty': | ||
case 'whitespace': | ||
return { type: 'empty', content: '' }; | ||
case 'ordgroup': | ||
return { | ||
type: 'group', | ||
content: '', | ||
args: node.args!.map(convertTree), | ||
}; | ||
case 'element': | ||
case 'symbol': | ||
return { type: 'symbol', content: convertToken(node.content) }; | ||
case 'text': | ||
return { type: 'text', content: node.content }; | ||
case 'comment': | ||
return { type: 'comment', content: node.content }; | ||
case 'supsub': { | ||
let { base, sup, sub } = node.data as TexSupsubData; | ||
// Special logic for overbrace | ||
if (base && base.type === 'unaryFunc' && base.content === '\\overbrace' && sup) { | ||
return { | ||
type: 'binaryFunc', | ||
content: 'overbrace', | ||
args: [convertTree(base.args![0]), convertTree(sup)], | ||
}; | ||
} else if (base && base.type === 'unaryFunc' && base.content === '\\underbrace' && sub) { | ||
return { | ||
type: 'binaryFunc', | ||
content: 'underbrace', | ||
args: [convertTree(base.args![0]), convertTree(sub)], | ||
}; | ||
} | ||
const data: TypstSupsubData = { | ||
base: convertTree(base), | ||
}; | ||
if (data.base.type === 'empty') { | ||
data.base = { type: 'text', content: '' }; | ||
} | ||
if (sup) { | ||
data.sup = convertTree(sup); | ||
} | ||
if (sub) { | ||
data.sub = convertTree(sub); | ||
} | ||
return { | ||
type: 'supsub', | ||
content: '', | ||
data: data, | ||
}; | ||
} | ||
case 'leftright': { | ||
const [left, body, right] = node.args!; | ||
// These pairs will be handled by Typst compiler by default. No need to add lr() | ||
const group: TypstNode = { | ||
type: 'group', | ||
content: '', | ||
args: node.args!.map(convertTree), | ||
}; | ||
if (["[]", "()", "\\{\\}", "\\lfloor\\rfloor", "\\lceil\\rceil"].includes(left.content + right.content)) { | ||
return group; | ||
} | ||
return { | ||
type: 'unaryFunc', | ||
content: 'lr', | ||
args: [group], | ||
}; | ||
} | ||
case 'binaryFunc': { | ||
return { | ||
type: 'binaryFunc', | ||
content: convertToken(node.content), | ||
args: node.args!.map(convertTree), | ||
}; | ||
} | ||
case 'unaryFunc': { | ||
const arg0 = convertTree(node.args![0]); | ||
// \sqrt{3}{x} -> root(3, x) | ||
if (node.content === '\\sqrt' && node.data) { | ||
const data = convertTree(node.data as TexSqrtData); // the number of times to take the root | ||
return { | ||
type: 'binaryFunc', | ||
content: 'root', | ||
args: [data, arg0], | ||
}; | ||
} | ||
// \mathbf{a} -> upright(mathbf(a)) | ||
if (node.content === '\\mathbf') { | ||
const inner: TypstNode = { | ||
type: 'unaryFunc', | ||
content: 'bold', | ||
args: [arg0], | ||
}; | ||
return { | ||
type: 'unaryFunc', | ||
content: 'upright', | ||
args: [inner], | ||
}; | ||
} | ||
// \mathbb{R} -> RR | ||
if (node.content === '\\mathbb' && arg0.type === 'symbol' && /^[A-Z]$/.test(arg0.content)) { | ||
return { | ||
type: 'symbol', | ||
content: arg0.content + arg0.content, | ||
}; | ||
} | ||
// \operatorname{opname} -> op("opname") | ||
if (node.content === '\\operatorname') { | ||
const body = node.args!; | ||
if (body.length !== 1 || body[0].type !== 'text') { | ||
throw new TypstWriterError(`Expecting body of \\operatorname to be text but got`, node); | ||
} | ||
const text = body[0].content; | ||
if (TYPST_INTRINSIC_SYMBOLS.includes(text)) { | ||
return { | ||
type: 'symbol', | ||
content: text, | ||
}; | ||
} else { | ||
return { | ||
type: 'unaryFunc', | ||
content: 'op', | ||
args: [{ type: 'text', content: text }], | ||
}; | ||
} | ||
} | ||
// generic case | ||
return { | ||
type: 'unaryFunc', | ||
content: convertToken(node.content), | ||
args: node.args!.map(convertTree), | ||
}; | ||
} | ||
case 'newline': | ||
return { type: 'newline', content: '\n' }; | ||
case 'beginend': { | ||
const matrix = node.data as TexNode[][]; | ||
const data = matrix.map((row) => row.map(convertTree)); | ||
if (node.content!.startsWith('align')) { | ||
// align, align*, alignat, alignat*, aligned, etc. | ||
return { | ||
type: 'align', | ||
content: '', | ||
data: data, | ||
}; | ||
} else { | ||
return { | ||
type: 'matrix', | ||
content: 'mat', | ||
data: data, | ||
}; | ||
} | ||
} | ||
case 'unknownMacro': | ||
return { type: 'unknown', content: convertToken(node.content) }; | ||
case 'control': | ||
if (node.content === '\\\\') { | ||
return { type: 'symbol', content: '\\' }; | ||
} else if (node.content === '\\,') { | ||
return { type: 'symbol', content: 'thin' }; | ||
} else { | ||
throw new TypstWriterError(`Unknown control sequence: ${node.content}`, node); | ||
} | ||
default: | ||
throw new TypstWriterError(`Unimplemented node type: ${node.type}`, node); | ||
} | ||
} | ||
function convertToken(token: string): string { | ||
@@ -382,0 +467,0 @@ if (/^[a-zA-Z0-9]$/.test(token)) { |
1065762
30458