temml 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- package/LICENSE +21 -0
- package/README.md +44 -0
- package/contrib/auto-render/README.md +89 -0
- package/contrib/auto-render/auto-render.js +128 -0
- package/contrib/auto-render/dist/auto-render.js +217 -0
- package/contrib/auto-render/dist/auto-render.min.js +1 -0
- package/contrib/auto-render/splitAtDelimiters.js +84 -0
- package/contrib/auto-render/test/auto-render-spec.js +234 -0
- package/contrib/auto-render/test/auto-render.js +217 -0
- package/contrib/auto-render/test/test_page.html +59 -0
- package/contrib/mhchem/README.md +26 -0
- package/contrib/mhchem/mhchem.js +1705 -0
- package/contrib/mhchem/mhchem.min.js +1 -0
- package/contrib/physics/README.md +20 -0
- package/contrib/physics/physics.js +131 -0
- package/contrib/texvc/README.md +23 -0
- package/contrib/texvc/texvc.js +61 -0
- package/dist/Temml-Asana.css +201 -0
- package/dist/Temml-Latin-Modern.css +216 -0
- package/dist/Temml-Libertinus.css +214 -0
- package/dist/Temml-Local.css +194 -0
- package/dist/Temml-STIX2.css +203 -0
- package/dist/Temml.woff2 +0 -0
- package/dist/temml.cjs +13122 -0
- package/dist/temml.js +11225 -0
- package/dist/temml.min.js +1 -0
- package/dist/temml.mjs +13120 -0
- package/dist/temmlPostProcess.js +70 -0
- package/package.json +34 -0
- package/src/Lexer.js +121 -0
- package/src/MacroExpander.js +437 -0
- package/src/Namespace.js +107 -0
- package/src/ParseError.js +64 -0
- package/src/Parser.js +977 -0
- package/src/Settings.js +49 -0
- package/src/SourceLocation.js +29 -0
- package/src/Style.js +144 -0
- package/src/Token.js +40 -0
- package/src/buildMathML.js +235 -0
- package/src/constants.js +25 -0
- package/src/defineEnvironment.js +25 -0
- package/src/defineFunction.js +69 -0
- package/src/defineMacro.js +11 -0
- package/src/domTree.js +185 -0
- package/src/environments/array.js +791 -0
- package/src/environments/cd.js +252 -0
- package/src/environments.js +8 -0
- package/src/functions/accent.js +127 -0
- package/src/functions/accentunder.js +38 -0
- package/src/functions/arrow.js +204 -0
- package/src/functions/cancelto.js +36 -0
- package/src/functions/char.js +33 -0
- package/src/functions/color.js +253 -0
- package/src/functions/cr.js +46 -0
- package/src/functions/def.js +259 -0
- package/src/functions/delimsizing.js +304 -0
- package/src/functions/enclose.js +193 -0
- package/src/functions/envTag.js +38 -0
- package/src/functions/environment.js +59 -0
- package/src/functions/font.js +123 -0
- package/src/functions/genfrac.js +333 -0
- package/src/functions/hbox.js +29 -0
- package/src/functions/horizBrace.js +32 -0
- package/src/functions/href.js +90 -0
- package/src/functions/html.js +95 -0
- package/src/functions/includegraphics.js +131 -0
- package/src/functions/kern.js +75 -0
- package/src/functions/label.js +29 -0
- package/src/functions/lap.js +75 -0
- package/src/functions/math.js +40 -0
- package/src/functions/mathchoice.js +41 -0
- package/src/functions/mclass.js +201 -0
- package/src/functions/multiscript.js +91 -0
- package/src/functions/not.js +46 -0
- package/src/functions/op.js +338 -0
- package/src/functions/operatorname.js +139 -0
- package/src/functions/ordgroup.js +9 -0
- package/src/functions/phantom.js +73 -0
- package/src/functions/pmb.js +31 -0
- package/src/functions/raise.js +68 -0
- package/src/functions/ref.js +28 -0
- package/src/functions/relax.js +16 -0
- package/src/functions/rule.js +52 -0
- package/src/functions/sizing.js +64 -0
- package/src/functions/smash.js +66 -0
- package/src/functions/sqrt.js +31 -0
- package/src/functions/styling.js +58 -0
- package/src/functions/supsub.js +135 -0
- package/src/functions/symbolsOp.js +53 -0
- package/src/functions/symbolsOrd.js +102 -0
- package/src/functions/symbolsSpacing.js +53 -0
- package/src/functions/tag.js +8 -0
- package/src/functions/text.js +75 -0
- package/src/functions/tip.js +63 -0
- package/src/functions/toggle.js +13 -0
- package/src/functions/verb.js +33 -0
- package/src/functions.js +57 -0
- package/src/linebreaking.js +159 -0
- package/src/macros.js +708 -0
- package/src/mathMLTree.js +175 -0
- package/src/parseNode.js +42 -0
- package/src/parseTree.js +40 -0
- package/src/postProcess.js +57 -0
- package/src/replace.js +225 -0
- package/src/stretchy.js +66 -0
- package/src/svg.js +110 -0
- package/src/symbols.js +972 -0
- package/src/tree.js +50 -0
- package/src/unicodeAccents.js +16 -0
- package/src/unicodeScripts.js +119 -0
- package/src/unicodeSupOrSub.js +108 -0
- package/src/unicodeSymbolBuilder.js +31 -0
- package/src/unicodeSymbols.js +320 -0
- package/src/units.js +109 -0
- package/src/utils.js +109 -0
- package/src/variant.js +103 -0
- package/temml.js +181 -0
@@ -0,0 +1,253 @@
|
|
1
|
+
import defineFunction, { ordargument } from "../defineFunction"
|
2
|
+
import { wrapWithMstyle } from "../mathMLTree"
|
3
|
+
import { assertNodeType } from "../parseNode"
|
4
|
+
import ParseError from "../ParseError"
|
5
|
+
import * as mml from "../buildMathML"
|
6
|
+
|
7
|
+
// Helpers
|
8
|
+
const htmlRegEx = /^(#[a-f0-9]{3}|#?[a-f0-9]{6})$/i
|
9
|
+
const htmlOrNameRegEx = /^(#[a-f0-9]{3}|#?[a-f0-9]{6}|[a-z]+)$/i
|
10
|
+
const RGBregEx = /^ *\d{1,3} *(?:, *\d{1,3} *){2}$/
|
11
|
+
const rgbRegEx = /^ *[10](?:\.\d*)? *(?:, *[10](?:\.\d*)? *){2}$/
|
12
|
+
const xcolorHtmlRegEx = /^[a-f0-9]{6}$/i
|
13
|
+
const toHex = num => {
|
14
|
+
let str = num.toString(16)
|
15
|
+
if (str.length === 1) { str = "0" + str }
|
16
|
+
return str
|
17
|
+
}
|
18
|
+
|
19
|
+
// Colors from Tables 4.1 and 4.2 of the xcolor package.
|
20
|
+
// Table 4.1 (lower case) RGB values are taken from chroma and xcolor.dtx.
|
21
|
+
// Table 4.2 (Capitalizzed) values were sampled, because Chroma contains a unreliable
|
22
|
+
// conversion from cmyk to RGB. See https://tex.stackexchange.com/a/537274.
|
23
|
+
const xcolors = JSON.parse(`{
|
24
|
+
"Apricot": "#ffb484",
|
25
|
+
"Aquamarine": "#08b4bc",
|
26
|
+
"Bittersweet": "#c84c14",
|
27
|
+
"blue": "#0000FF",
|
28
|
+
"Blue": "#303494",
|
29
|
+
"BlueGreen": "#08b4bc",
|
30
|
+
"BlueViolet": "#503c94",
|
31
|
+
"BrickRed": "#b8341c",
|
32
|
+
"brown": "#BF8040",
|
33
|
+
"Brown": "#802404",
|
34
|
+
"BurntOrange": "#f8941c",
|
35
|
+
"CadetBlue": "#78749c",
|
36
|
+
"CarnationPink": "#f884b4",
|
37
|
+
"Cerulean": "#08a4e4",
|
38
|
+
"CornflowerBlue": "#40ace4",
|
39
|
+
"cyan": "#00FFFF",
|
40
|
+
"Cyan": "#08acec",
|
41
|
+
"Dandelion": "#ffbc44",
|
42
|
+
"darkgray": "#404040",
|
43
|
+
"DarkOrchid": "#a8548c",
|
44
|
+
"Emerald": "#08ac9c",
|
45
|
+
"ForestGreen": "#089c54",
|
46
|
+
"Fuchsia": "#90348c",
|
47
|
+
"Goldenrod": "#ffdc44",
|
48
|
+
"gray": "#808080",
|
49
|
+
"Gray": "#98949c",
|
50
|
+
"green": "#00FF00",
|
51
|
+
"Green": "#08a44c",
|
52
|
+
"GreenYellow": "#e0e474",
|
53
|
+
"JungleGreen": "#08ac9c",
|
54
|
+
"Lavender": "#f89cc4",
|
55
|
+
"lightgray": "#c0c0c0",
|
56
|
+
"lime": "#BFFF00",
|
57
|
+
"LimeGreen": "#90c43c",
|
58
|
+
"magenta": "#FF00FF",
|
59
|
+
"Magenta": "#f0048c",
|
60
|
+
"Mahogany": "#b0341c",
|
61
|
+
"Maroon": "#b03434",
|
62
|
+
"Melon": "#f89c7c",
|
63
|
+
"MidnightBlue": "#086494",
|
64
|
+
"Mulberry": "#b03c94",
|
65
|
+
"NavyBlue": "#086cbc",
|
66
|
+
"olive": "#7F7F00",
|
67
|
+
"OliveGreen": "#407c34",
|
68
|
+
"orange": "#FF8000",
|
69
|
+
"Orange": "#f8843c",
|
70
|
+
"OrangeRed": "#f0145c",
|
71
|
+
"Orchid": "#b074ac",
|
72
|
+
"Peach": "#f8945c",
|
73
|
+
"Periwinkle": "#8074bc",
|
74
|
+
"PineGreen": "#088c74",
|
75
|
+
"pink": "#ff7f7f",
|
76
|
+
"Plum": "#98248c",
|
77
|
+
"ProcessBlue": "#08b4ec",
|
78
|
+
"purple": "#BF0040",
|
79
|
+
"Purple": "#a0449c",
|
80
|
+
"RawSienna": "#983c04",
|
81
|
+
"red": "#ff0000",
|
82
|
+
"Red": "#f01c24",
|
83
|
+
"RedOrange": "#f86434",
|
84
|
+
"RedViolet": "#a0246c",
|
85
|
+
"Rhodamine": "#f0549c",
|
86
|
+
"Royallue": "#0874bc",
|
87
|
+
"RoyalPurple": "#683c9c",
|
88
|
+
"RubineRed": "#f0047c",
|
89
|
+
"Salmon": "#f8948c",
|
90
|
+
"SeaGreen": "#30bc9c",
|
91
|
+
"Sepia": "#701404",
|
92
|
+
"SkyBlue": "#48c4dc",
|
93
|
+
"SpringGreen": "#c8dc64",
|
94
|
+
"Tan": "#e09c74",
|
95
|
+
"teal": "#007F7F",
|
96
|
+
"TealBlue": "#08acb4",
|
97
|
+
"Thistle": "#d884b4",
|
98
|
+
"Turquoise": "#08b4cc",
|
99
|
+
"violet": "#800080",
|
100
|
+
"Violet": "#60449c",
|
101
|
+
"VioletRed": "#f054a4",
|
102
|
+
"WildStrawberry": "#f0246c",
|
103
|
+
"yellow": "#FFFF00",
|
104
|
+
"Yellow": "#fff404",
|
105
|
+
"YellowGreen": "#98cc6c",
|
106
|
+
"YellowOrange": "#ffa41c"
|
107
|
+
}`)
|
108
|
+
|
109
|
+
export const colorFromSpec = (model, spec) => {
|
110
|
+
let color = ""
|
111
|
+
if (model === "HTML") {
|
112
|
+
if (!htmlRegEx.test(spec)) {
|
113
|
+
throw new ParseError("Invalid HTML input.")
|
114
|
+
}
|
115
|
+
color = spec
|
116
|
+
} else if (model === "RGB") {
|
117
|
+
if (!RGBregEx.test(spec)) {
|
118
|
+
throw new ParseError("Invalid RGB input.")
|
119
|
+
}
|
120
|
+
spec.split(",").map(e => { color += toHex(Number(e.trim())) })
|
121
|
+
} else {
|
122
|
+
if (!rgbRegEx.test(spec)) {
|
123
|
+
throw new ParseError("Invalid rbg input.")
|
124
|
+
}
|
125
|
+
spec.split(",").map(e => {
|
126
|
+
const num = Number(e.trim())
|
127
|
+
if (num > 1) { throw new ParseError("Color rgb input must be < 1.") }
|
128
|
+
color += toHex((num * 255))
|
129
|
+
})
|
130
|
+
}
|
131
|
+
if (color.charAt(0) !== "#") { color = "#" + color }
|
132
|
+
return color
|
133
|
+
}
|
134
|
+
|
135
|
+
export const validateColor = (color, macros, token) => {
|
136
|
+
const macroName = `\\\\color@${color}` // from \defineColor.
|
137
|
+
const match = htmlOrNameRegEx.exec(color);
|
138
|
+
if (!match) { throw new ParseError("Invalid color: '" + color + "'", token) }
|
139
|
+
// We allow a 6-digit HTML color spec without a leading "#".
|
140
|
+
// This follows the xcolor package's HTML color model.
|
141
|
+
// Predefined color names are all missed by this RegEx pattern.
|
142
|
+
if (xcolorHtmlRegEx.test(color)) {
|
143
|
+
return "#" + color
|
144
|
+
} else if (color.charAt(0) === "#") {
|
145
|
+
return color
|
146
|
+
} else if (macros.has(macroName)) {
|
147
|
+
color = macros.get(macroName).tokens[0].text
|
148
|
+
} else if (xcolors[color]) {
|
149
|
+
color = xcolors[color]
|
150
|
+
}
|
151
|
+
return color
|
152
|
+
}
|
153
|
+
|
154
|
+
const mathmlBuilder = (group, style) => {
|
155
|
+
const inner = mml.buildExpression(group.body, style.withColor(group.color))
|
156
|
+
// Wrap with an <mstyle> element.
|
157
|
+
const node = wrapWithMstyle(inner)
|
158
|
+
node.setAttribute("mathcolor", group.color)
|
159
|
+
return node
|
160
|
+
}
|
161
|
+
|
162
|
+
defineFunction({
|
163
|
+
type: "color",
|
164
|
+
names: ["\\textcolor"],
|
165
|
+
props: {
|
166
|
+
numArgs: 2,
|
167
|
+
numOptionalArgs: 1,
|
168
|
+
allowedInText: true,
|
169
|
+
argTypes: ["raw", "raw", "original"]
|
170
|
+
},
|
171
|
+
handler({ parser, token }, args, optArgs) {
|
172
|
+
const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string
|
173
|
+
let color = ""
|
174
|
+
if (model) {
|
175
|
+
const spec = assertNodeType(args[0], "raw").string
|
176
|
+
color = colorFromSpec(model, spec)
|
177
|
+
} else {
|
178
|
+
color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token)
|
179
|
+
}
|
180
|
+
const body = args[1];
|
181
|
+
return {
|
182
|
+
type: "color",
|
183
|
+
mode: parser.mode,
|
184
|
+
color,
|
185
|
+
body: ordargument(body)
|
186
|
+
}
|
187
|
+
},
|
188
|
+
mathmlBuilder
|
189
|
+
})
|
190
|
+
|
191
|
+
defineFunction({
|
192
|
+
type: "color",
|
193
|
+
names: ["\\color"],
|
194
|
+
props: {
|
195
|
+
numArgs: 1,
|
196
|
+
numOptionalArgs: 1,
|
197
|
+
allowedInText: true,
|
198
|
+
argTypes: ["raw", "raw"]
|
199
|
+
},
|
200
|
+
handler({ parser, token }, args, optArgs) {
|
201
|
+
const model = optArgs[0] && assertNodeType(optArgs[0], "raw").string
|
202
|
+
let color = ""
|
203
|
+
if (model) {
|
204
|
+
const spec = assertNodeType(args[0], "raw").string
|
205
|
+
color = colorFromSpec(model, spec)
|
206
|
+
} else {
|
207
|
+
color = validateColor(assertNodeType(args[0], "raw").string, parser.gullet.macros, token)
|
208
|
+
}
|
209
|
+
|
210
|
+
// Set macro \current@color in current namespace to store the current
|
211
|
+
// color, mimicking the behavior of color.sty.
|
212
|
+
// This is currently used just to correctly color a \right
|
213
|
+
// that follows a \color command.
|
214
|
+
parser.gullet.macros.set("\\current@color", color)
|
215
|
+
|
216
|
+
// Parse out the implicit body that should be colored.
|
217
|
+
// Since \color nodes should not be nested, break on \color.
|
218
|
+
const body = parser.parseExpression(true, "\\color")
|
219
|
+
|
220
|
+
return {
|
221
|
+
type: "color",
|
222
|
+
mode: parser.mode,
|
223
|
+
color,
|
224
|
+
body
|
225
|
+
}
|
226
|
+
},
|
227
|
+
mathmlBuilder
|
228
|
+
})
|
229
|
+
|
230
|
+
defineFunction({
|
231
|
+
type: "color",
|
232
|
+
names: ["\\definecolor"],
|
233
|
+
props: {
|
234
|
+
numArgs: 3,
|
235
|
+
allowedInText: true,
|
236
|
+
argTypes: ["raw", "raw", "raw"]
|
237
|
+
},
|
238
|
+
handler({ parser, funcName, token }, args) {
|
239
|
+
const name = assertNodeType(args[0], "raw").string
|
240
|
+
if (!/^[A-Za-z]+$/.test(name)) {
|
241
|
+
throw new ParseError("Color name must be latin letters.", token)
|
242
|
+
}
|
243
|
+
const model = assertNodeType(args[1], "raw").string
|
244
|
+
if (!["HTML", "RGB", "rgb"].includes(model)) {
|
245
|
+
throw new ParseError("Color model must be HTML, RGB, or rgb.", token)
|
246
|
+
}
|
247
|
+
const spec = assertNodeType(args[2], "raw").string
|
248
|
+
const color = colorFromSpec(model, spec)
|
249
|
+
parser.gullet.macros.set(`\\\\color@${name}`, { tokens: [{ text: color }], numArgs: 0 })
|
250
|
+
return { type: "internal", mode: parser.mode }
|
251
|
+
}
|
252
|
+
// No mathmlBuilder. The point of \definecolor is to set a macro.
|
253
|
+
})
|
@@ -0,0 +1,46 @@
|
|
1
|
+
// Row breaks within tabular environments, and line breaks at top level
|
2
|
+
|
3
|
+
import defineFunction from "../defineFunction"
|
4
|
+
import mathMLTree from "../mathMLTree"
|
5
|
+
import { calculateSize } from "../units"
|
6
|
+
import { assertNodeType } from "../parseNode"
|
7
|
+
|
8
|
+
// \DeclareRobustCommand\\{...\@xnewline}
|
9
|
+
defineFunction({
|
10
|
+
type: "cr",
|
11
|
+
names: ["\\\\"],
|
12
|
+
props: {
|
13
|
+
numArgs: 0,
|
14
|
+
numOptionalArgs: 1,
|
15
|
+
argTypes: ["size"],
|
16
|
+
allowedInText: true
|
17
|
+
},
|
18
|
+
|
19
|
+
handler({ parser }, args, optArgs) {
|
20
|
+
const size = optArgs[0];
|
21
|
+
const newLine = !parser.settings.displayMode;
|
22
|
+
return {
|
23
|
+
type: "cr",
|
24
|
+
mode: parser.mode,
|
25
|
+
newLine,
|
26
|
+
size: size && assertNodeType(size, "size").value
|
27
|
+
}
|
28
|
+
},
|
29
|
+
|
30
|
+
// The following builder is called only at the top level,
|
31
|
+
// not within tabular/array environments.
|
32
|
+
|
33
|
+
mathmlBuilder(group, style) {
|
34
|
+
// MathML 3.0 calls for newline to occur in an <mo> or an <mspace>.
|
35
|
+
// Ref: https://www.w3.org/TR/MathML3/chapter3.html#presm.linebreaking
|
36
|
+
const node = new mathMLTree.MathNode("mo")
|
37
|
+
if (group.newLine) {
|
38
|
+
node.setAttribute("linebreak", "newline")
|
39
|
+
if (group.size) {
|
40
|
+
const size = calculateSize(group.size, style)
|
41
|
+
node.setAttribute("height", size.number + size.unit)
|
42
|
+
}
|
43
|
+
}
|
44
|
+
return node
|
45
|
+
}
|
46
|
+
})
|
@@ -0,0 +1,259 @@
|
|
1
|
+
import defineFunction from "../defineFunction";
|
2
|
+
import ParseError from "../ParseError";
|
3
|
+
import { assertNodeType } from "../parseNode";
|
4
|
+
|
5
|
+
const globalMap = {
|
6
|
+
"\\global": "\\global",
|
7
|
+
"\\long": "\\\\globallong",
|
8
|
+
"\\\\globallong": "\\\\globallong",
|
9
|
+
"\\def": "\\gdef",
|
10
|
+
"\\gdef": "\\gdef",
|
11
|
+
"\\edef": "\\xdef",
|
12
|
+
"\\xdef": "\\xdef",
|
13
|
+
"\\let": "\\\\globallet",
|
14
|
+
"\\futurelet": "\\\\globalfuture"
|
15
|
+
}
|
16
|
+
|
17
|
+
const checkControlSequence = (tok) => {
|
18
|
+
const name = tok.text;
|
19
|
+
if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) {
|
20
|
+
throw new ParseError("Expected a control sequence", tok);
|
21
|
+
}
|
22
|
+
return name;
|
23
|
+
};
|
24
|
+
|
25
|
+
const getRHS = (parser) => {
|
26
|
+
let tok = parser.gullet.popToken();
|
27
|
+
if (tok.text === "=") {
|
28
|
+
// consume optional equals
|
29
|
+
tok = parser.gullet.popToken();
|
30
|
+
if (tok.text === " ") {
|
31
|
+
// consume one optional space
|
32
|
+
tok = parser.gullet.popToken();
|
33
|
+
}
|
34
|
+
}
|
35
|
+
return tok;
|
36
|
+
};
|
37
|
+
|
38
|
+
const letCommand = (parser, name, tok, global) => {
|
39
|
+
let macro = parser.gullet.macros.get(tok.text);
|
40
|
+
if (macro == null) {
|
41
|
+
// don't expand it later even if a macro with the same name is defined
|
42
|
+
// e.g., \let\foo=\frac \def\frac{\relax} \frac12
|
43
|
+
tok.noexpand = true;
|
44
|
+
macro = {
|
45
|
+
tokens: [tok],
|
46
|
+
numArgs: 0,
|
47
|
+
// reproduce the same behavior in expansion
|
48
|
+
unexpandable: !parser.gullet.isExpandable(tok.text)
|
49
|
+
};
|
50
|
+
}
|
51
|
+
parser.gullet.macros.set(name, macro, global);
|
52
|
+
};
|
53
|
+
|
54
|
+
// <assignment> -> <non-macro assignment>|<macro assignment>
|
55
|
+
// <non-macro assignment> -> <simple assignment>|\global<non-macro assignment>
|
56
|
+
// <macro assignment> -> <definition>|<prefix><macro assignment>
|
57
|
+
// <prefix> -> \global|\long|\outer
|
58
|
+
defineFunction({
|
59
|
+
type: "internal",
|
60
|
+
names: [
|
61
|
+
"\\global",
|
62
|
+
"\\long",
|
63
|
+
"\\\\globallong" // can’t be entered directly
|
64
|
+
],
|
65
|
+
props: {
|
66
|
+
numArgs: 0,
|
67
|
+
allowedInText: true
|
68
|
+
},
|
69
|
+
handler({ parser, funcName }) {
|
70
|
+
parser.consumeSpaces();
|
71
|
+
const token = parser.fetch();
|
72
|
+
if (globalMap[token.text]) {
|
73
|
+
// Temml doesn't have \par, so ignore \long
|
74
|
+
if (funcName === "\\global" || funcName === "\\\\globallong") {
|
75
|
+
token.text = globalMap[token.text];
|
76
|
+
}
|
77
|
+
return assertNodeType(parser.parseFunction(), "internal");
|
78
|
+
}
|
79
|
+
throw new ParseError(`Invalid token after macro prefix`, token);
|
80
|
+
}
|
81
|
+
});
|
82
|
+
|
83
|
+
// Basic support for macro definitions: \def, \gdef, \edef, \xdef
|
84
|
+
// <definition> -> <def><control sequence><definition text>
|
85
|
+
// <def> -> \def|\gdef|\edef|\xdef
|
86
|
+
// <definition text> -> <parameter text><left brace><balanced text><right brace>
|
87
|
+
defineFunction({
|
88
|
+
type: "internal",
|
89
|
+
names: ["\\def", "\\gdef", "\\edef", "\\xdef"],
|
90
|
+
props: {
|
91
|
+
numArgs: 0,
|
92
|
+
allowedInText: true,
|
93
|
+
primitive: true
|
94
|
+
},
|
95
|
+
handler({ parser, funcName }) {
|
96
|
+
let tok = parser.gullet.popToken();
|
97
|
+
const name = tok.text;
|
98
|
+
if (/^(?:[\\{}$&#^_]|EOF)$/.test(name)) {
|
99
|
+
throw new ParseError("Expected a control sequence", tok);
|
100
|
+
}
|
101
|
+
|
102
|
+
let numArgs = 0;
|
103
|
+
let insert;
|
104
|
+
const delimiters = [[]];
|
105
|
+
// <parameter text> contains no braces
|
106
|
+
while (parser.gullet.future().text !== "{") {
|
107
|
+
tok = parser.gullet.popToken();
|
108
|
+
if (tok.text === "#") {
|
109
|
+
// If the very last character of the <parameter text> is #, so that
|
110
|
+
// this # is immediately followed by {, TeX will behave as if the {
|
111
|
+
// had been inserted at the right end of both the parameter text
|
112
|
+
// and the replacement text.
|
113
|
+
if (parser.gullet.future().text === "{") {
|
114
|
+
insert = parser.gullet.future();
|
115
|
+
delimiters[numArgs].push("{");
|
116
|
+
break;
|
117
|
+
}
|
118
|
+
|
119
|
+
// A parameter, the first appearance of # must be followed by 1,
|
120
|
+
// the next by 2, and so on; up to nine #’s are allowed
|
121
|
+
tok = parser.gullet.popToken();
|
122
|
+
if (!/^[1-9]$/.test(tok.text)) {
|
123
|
+
throw new ParseError(`Invalid argument number "${tok.text}"`);
|
124
|
+
}
|
125
|
+
if (parseInt(tok.text) !== numArgs + 1) {
|
126
|
+
throw new ParseError(`Argument number "${tok.text}" out of order`);
|
127
|
+
}
|
128
|
+
numArgs++;
|
129
|
+
delimiters.push([]);
|
130
|
+
} else if (tok.text === "EOF") {
|
131
|
+
throw new ParseError("Expected a macro definition");
|
132
|
+
} else {
|
133
|
+
delimiters[numArgs].push(tok.text);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
// replacement text, enclosed in '{' and '}' and properly nested
|
137
|
+
let { tokens } = parser.gullet.consumeArg();
|
138
|
+
if (insert) {
|
139
|
+
tokens.unshift(insert);
|
140
|
+
}
|
141
|
+
|
142
|
+
if (funcName === "\\edef" || funcName === "\\xdef") {
|
143
|
+
tokens = parser.gullet.expandTokens(tokens);
|
144
|
+
tokens.reverse(); // to fit in with stack order
|
145
|
+
}
|
146
|
+
// Final arg is the expansion of the macro
|
147
|
+
parser.gullet.macros.set(
|
148
|
+
name,
|
149
|
+
{ tokens, numArgs, delimiters },
|
150
|
+
funcName === globalMap[funcName]
|
151
|
+
);
|
152
|
+
return { type: "internal", mode: parser.mode };
|
153
|
+
}
|
154
|
+
});
|
155
|
+
|
156
|
+
// <simple assignment> -> <let assignment>
|
157
|
+
// <let assignment> -> \futurelet<control sequence><token><token>
|
158
|
+
// | \let<control sequence><equals><one optional space><token>
|
159
|
+
// <equals> -> <optional spaces>|<optional spaces>=
|
160
|
+
defineFunction({
|
161
|
+
type: "internal",
|
162
|
+
names: [
|
163
|
+
"\\let",
|
164
|
+
"\\\\globallet" // can’t be entered directly
|
165
|
+
],
|
166
|
+
props: {
|
167
|
+
numArgs: 0,
|
168
|
+
allowedInText: true,
|
169
|
+
primitive: true
|
170
|
+
},
|
171
|
+
handler({ parser, funcName }) {
|
172
|
+
const name = checkControlSequence(parser.gullet.popToken());
|
173
|
+
parser.gullet.consumeSpaces();
|
174
|
+
const tok = getRHS(parser);
|
175
|
+
letCommand(parser, name, tok, funcName === "\\\\globallet");
|
176
|
+
return { type: "internal", mode: parser.mode };
|
177
|
+
}
|
178
|
+
});
|
179
|
+
|
180
|
+
// ref: https://www.tug.org/TUGboat/tb09-3/tb22bechtolsheim.pdf
|
181
|
+
defineFunction({
|
182
|
+
type: "internal",
|
183
|
+
names: [
|
184
|
+
"\\futurelet",
|
185
|
+
"\\\\globalfuture" // can’t be entered directly
|
186
|
+
],
|
187
|
+
props: {
|
188
|
+
numArgs: 0,
|
189
|
+
allowedInText: true,
|
190
|
+
primitive: true
|
191
|
+
},
|
192
|
+
handler({ parser, funcName }) {
|
193
|
+
const name = checkControlSequence(parser.gullet.popToken());
|
194
|
+
const middle = parser.gullet.popToken();
|
195
|
+
const tok = parser.gullet.popToken();
|
196
|
+
letCommand(parser, name, tok, funcName === "\\\\globalfuture");
|
197
|
+
parser.gullet.pushToken(tok);
|
198
|
+
parser.gullet.pushToken(middle);
|
199
|
+
return { type: "internal", mode: parser.mode };
|
200
|
+
}
|
201
|
+
});
|
202
|
+
|
203
|
+
defineFunction({
|
204
|
+
type: "internal",
|
205
|
+
names: ["\\newcommand", "\\renewcommand", "\\providecommand"],
|
206
|
+
props: {
|
207
|
+
numArgs: 0,
|
208
|
+
allowedInText: true,
|
209
|
+
primitive: true
|
210
|
+
},
|
211
|
+
handler({ parser, funcName }) {
|
212
|
+
let name = ""
|
213
|
+
const tok = parser.gullet.popToken()
|
214
|
+
if (tok.text === "{") {
|
215
|
+
name = checkControlSequence(parser.gullet.popToken())
|
216
|
+
parser.gullet.popToken()
|
217
|
+
} else {
|
218
|
+
name = checkControlSequence(tok)
|
219
|
+
}
|
220
|
+
|
221
|
+
const exists = parser.gullet.isDefined(name);
|
222
|
+
if (exists && funcName === "\\newcommand") {
|
223
|
+
throw new ParseError(
|
224
|
+
`\\newcommand{${name}} attempting to redefine ${name}; use \\renewcommand`
|
225
|
+
);
|
226
|
+
}
|
227
|
+
if (!exists && funcName === "\\renewcommand") {
|
228
|
+
throw new ParseError(
|
229
|
+
`\\renewcommand{${name}} when command ${name} does not yet exist; use \\newcommand`
|
230
|
+
);
|
231
|
+
}
|
232
|
+
|
233
|
+
let numArgs = 0;
|
234
|
+
if (parser.gullet.future().text === "[") {
|
235
|
+
let tok = parser.gullet.popToken();
|
236
|
+
tok = parser.gullet.popToken();
|
237
|
+
if (!/^[0-9]$/.test(tok.text)) {
|
238
|
+
throw new ParseError(`Invalid number of arguments: "${tok.text}"`);
|
239
|
+
}
|
240
|
+
numArgs = parseInt(tok.text);
|
241
|
+
tok = parser.gullet.popToken();
|
242
|
+
if (tok.text !== "]") {
|
243
|
+
throw new ParseError(`Invalid argument "${tok.text}"`);
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
// replacement text, enclosed in '{' and '}' and properly nested
|
248
|
+
const { tokens } = parser.gullet.consumeArg();
|
249
|
+
|
250
|
+
parser.gullet.macros.set(
|
251
|
+
name,
|
252
|
+
{ tokens, numArgs },
|
253
|
+
!parser.settings.strict
|
254
|
+
)
|
255
|
+
|
256
|
+
return { type: "internal", mode: parser.mode };
|
257
|
+
|
258
|
+
}
|
259
|
+
});
|