temml 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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
|
+
});
|