🐍🐍🐍
1
2function makeLeaf(tag, content) {
3 return () => {
4 const element = $mathElement(tag);
5 element.textContent = content;
6 return element;
7 };
8}
9
10function make(node) {
11 if (node.type === "group") {
12 return node.make(node.children);
13 }
14 else if (node.type === "leaf") {
15 return node.make();
16 }
17 else {
18 throw new Error(`Node of type ${node.type} should not exist by the final make stage.`);
19 }
20}
21
22function makeGroup(tag) {
23 return (children) => {
24 const element = $mathElement(tag);
25 for (const child of children) {
26 element.appendChild(make(child));
27 }
28 return element;
29 };
30}
31
32export const greek = {
33 "alpha": "α",
34 "beta": "β",
35 "gamma": "γ",
36 "delta": "δ",
37 "epsilon": "ε",
38 "zeta": "ζ",
39 "eta": "η",
40 "theta": "θ",
41 "iota": "ι",
42 "kappa": "κ",
43 "lambda": "λ",
44 "mu": "μ",
45 "nu": "ν",
46 "xi": "ξ",
47 "omicron": "ο",
48 "pi": "π",
49 "rho": "ρ",
50 "sigma": "σ",
51 "tau": "τ",
52 "upsilon": "υ",
53 "phi": "ϕ",
54 "curlyphi": "φ",
55 "chi": "χ",
56 "psi": "ψ",
57 "omega": "ω"
58}; // TODO: capital letters
59
60const commonOps = {
61 "interpunct": "·"
62};
63
64const whitespace = /\s/;
65const numeric = /[\d.,]/;
66const alphabet = /[a-zA-Z]/;
67
68function tokenize(expression, declarations) {
69 const tokens = [];
70 let current = "";
71 let i = 0;
72
73 function token(source) {
74 let result;
75 if (numeric.test(source[0])) {
76 result = declarations["numeric"](source)
77 } else {
78 result = declarations[source];
79 }
80 if (!result) throw new Error(`Undeclared token "${source}"`);
81 tokens.push(result);
82 }
83
84 while (i < expression.length) {
85 const char = expression[i];
86 const begun = current !== "";
87
88 if (/\s/.test(char)) {
89 if (begun) token(current);
90 current = "";
91 } else if (alphabet.test(char)) {
92 if (begun && !alphabet.test(current[current.length - 1])) {
93 token(current);
94 current = char;
95 } else {
96 current += char;
97 }
98 } else if (numeric.test(char)) { // numerics
99 if (begun && !numeric.test(current[current.length - 1])) {
100 token(current);
101 current = char;
102 } else {
103 current += char;
104 }
105 } else {
106 if (begun) token(current);
107 token(char);
108 current = "";
109 }
110 i++;
111 }
112
113 if (current !== "") token(current);
114
115 return tokens;
116}
117
118export async function main(target, expression) {
119
120 const lines = expression.trim().split("\n");
121
122 const idents = {};
123 const texts = {};
124 const ops = {};
125 const contentLines = [];
126
127 let contentStarted = false;
128 for (const line of lines) {
129 if (contentStarted) {
130 contentLines.push(line);
131 continue;
132 }
133 const trimmed = line.trim();
134 if (trimmed === "in") {
135 contentStarted = true;
136 continue;
137 }
138
139 if (trimmed.startsWith("ident ")) {
140 const declarations = trimmed.split(' ').filter(_=>_).slice(1);
141
142 for (const declaration of declarations) {
143 const splitAt = declaration.indexOf(':');
144
145 if (splitAt === -1) {
146 idents[declaration] = declaration;
147 } else {
148 const key = declaration.slice(0, splitAt);
149 const value = declaration.slice(splitAt + 1);
150
151 idents[key] = value === '~' ? greek[key] : value;
152 }
153 }
154 }
155
156 if (trimmed.startsWith("op ")) {
157 const declarations = trimmed.split(' ').filter(_=>_).slice(1);
158
159 for (const declaration of declarations) {
160 const splitAt = declaration.indexOf(':');
161
162 if (splitAt === -1) {
163 ops[declaration] = declaration;
164 } else {
165 const key = declaration.slice(0, splitAt);
166 const value = declaration.slice(splitAt + 1);
167
168 ops[key] = value.startsWith("$") ? commonOps[value.slice(1)] : value;
169 }
170 }
171 }
172
173 // Claude's work, not fully reviewed
174 if (trimmed.startsWith("text ")) {
175 const declarations = trimmed.slice(5);
176 let i = 0;
177
178 while (i < declarations.length) {
179 const splitAt = declarations.indexOf(':', i);
180 if (splitAt === -1) break;
181
182 const key = declarations.slice(i, splitAt);
183 i = splitAt + 1;
184
185 if (i >= declarations.length) break;
186
187 let value;
188 if (declarations[i] === '~') {
189 value = key;
190 i += 1;
191 } else if (declarations[i] === '"') {
192 i += 1;
193 const closingQuote = declarations.indexOf('"', i);
194 if (closingQuote === -1) {
195 console.error(`Unclosed quote in text declarations: ${declarations}`);
196 break;
197 }
198 value = declarations.slice(i, closingQuote);
199 i = closingQuote + 1;
200 } else {
201 console.error(`Invalid declaration for text ${key}: ${declarations}`);
202 break;
203 }
204
205 texts[key] = value;
206
207 while (i < declarations.length && declarations[i] === " ") {
208 i += 1;
209 }
210 }
211 }
212
213 }
214
215 const declaredTokens = {
216 "^": { type: "infix", make: makeGroup("msup") },
217 "_": { type: "infix", make: makeGroup("msub") },
218 "{": { type: "row_begin" },
219 "}": { type: "row_end" },
220 "numeric": n => ({ type: "leaf", make: makeLeaf("mn", n) })
221 };
222
223 for (const ident in idents) {
224 declaredTokens[ident] = { type: "leaf", make: makeLeaf("mi", idents[ident]) };
225 }
226 for (const op in ops) {
227 declaredTokens[op] = { type: "leaf", make: makeLeaf("mo", ops[op]) };
228 }
229 for (const text in texts) {
230 declaredTokens[text] = { type: "leaf", make: makeLeaf("mtext", texts[text]) };
231 }
232
233 for (const line of contentLines) {
234 const trimmed = line.trim();
235 if (!trimmed) continue;
236
237 const tokens = tokenize(trimmed, declaredTokens);
238
239 const root = { type: "group", make: makeGroup("math"), children: [] };
240 const groupStack = [root];
241 for (const token of tokens) {
242 if (token.type === "row_begin") {
243 groupStack.push({ type: "group", make: makeGroup("mrow"), children: [] });
244 }
245 else if (token.type === "row_end") {
246 const row = groupStack.pop();
247 groupStack[groupStack.length - 1].children.push(row);
248 }
249 else {
250 groupStack[groupStack.length - 1].children.push(token);
251 }
252 }
253
254 while (groupStack.length > 0) {
255 const node = groupStack.pop();
256 const modified = [];
257
258 let i = 0;
259 for (; i <= node.children.length - 3; i++) {
260 const [left, middle, right] = [node.children[i], node.children[i + 1], node.children[i + 2]];
261 if (middle.type === "infix") {
262 const newGroup = { type: "group", make: middle.make, children: [left, right] };
263 modified.push(newGroup);
264 groupStack.push(newGroup);
265 i += 2;
266 }
267 else {
268 modified.push(left);
269 if (left.type === "group") groupStack.push(left);
270 }
271 }
272 for (; i < node.children.length; i++) {
273 modified.push(node.children[i]);
274 if (node.children[i].type === "group") groupStack.push(node.children[i]);
275 }
276
277 node.children = modified;
278
279 }
280
281 target.appendChild(make(root));
282 }
283}
284