lib/zen/src/math/symbol.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 pub const Kind = enum {
  4     identifier,
  5     function,
  6     operator,
  7     fence,
  8     largeop,
  9     integral,
 10     movableop,
 11     space,
 12     accent,
 13 };
 14 
 15 pub const Entry = struct {
 16     kind: Kind,
 17     text: []const u8,
 18     upright: bool = false,
 19 };
 20 
 21 pub const commands = std.StaticStringMap(Entry).initComptime(.{
 22     .{ "alpha", Entry{ .kind = .identifier, .text = "α" } },
 23     .{ "beta", Entry{ .kind = .identifier, .text = "β" } },
 24     .{ "gamma", Entry{ .kind = .identifier, .text = "γ" } },
 25     .{ "delta", Entry{ .kind = .identifier, .text = "δ" } },
 26     .{ "epsilon", Entry{ .kind = .identifier, .text = "ϵ" } },
 27     .{ "varepsilon", Entry{ .kind = .identifier, .text = "ε" } },
 28     .{ "zeta", Entry{ .kind = .identifier, .text = "ζ" } },
 29     .{ "eta", Entry{ .kind = .identifier, .text = "η" } },
 30     .{ "theta", Entry{ .kind = .identifier, .text = "θ" } },
 31     .{ "vartheta", Entry{ .kind = .identifier, .text = "ϑ" } },
 32     .{ "iota", Entry{ .kind = .identifier, .text = "ι" } },
 33     .{ "kappa", Entry{ .kind = .identifier, .text = "κ" } },
 34     .{ "lambda", Entry{ .kind = .identifier, .text = "λ" } },
 35     .{ "mu", Entry{ .kind = .identifier, .text = "μ" } },
 36     .{ "nu", Entry{ .kind = .identifier, .text = "ν" } },
 37     .{ "xi", Entry{ .kind = .identifier, .text = "ξ" } },
 38     .{ "pi", Entry{ .kind = .identifier, .text = "π" } },
 39     .{ "varpi", Entry{ .kind = .identifier, .text = "ϖ" } },
 40     .{ "rho", Entry{ .kind = .identifier, .text = "ρ" } },
 41     .{ "varrho", Entry{ .kind = .identifier, .text = "ϱ" } },
 42     .{ "sigma", Entry{ .kind = .identifier, .text = "σ" } },
 43     .{ "varsigma", Entry{ .kind = .identifier, .text = "ς" } },
 44     .{ "tau", Entry{ .kind = .identifier, .text = "τ" } },
 45     .{ "upsilon", Entry{ .kind = .identifier, .text = "υ" } },
 46     .{ "phi", Entry{ .kind = .identifier, .text = "ϕ" } },
 47     .{ "varphi", Entry{ .kind = .identifier, .text = "φ" } },
 48     .{ "chi", Entry{ .kind = .identifier, .text = "χ" } },
 49     .{ "psi", Entry{ .kind = .identifier, .text = "ψ" } },
 50     .{ "omega", Entry{ .kind = .identifier, .text = "ω" } },
 51     .{ "Gamma", Entry{ .kind = .identifier, .text = "Γ", .upright = true } },
 52     .{ "Delta", Entry{ .kind = .identifier, .text = "Δ", .upright = true } },
 53     .{ "Theta", Entry{ .kind = .identifier, .text = "Θ", .upright = true } },
 54     .{ "Lambda", Entry{ .kind = .identifier, .text = "Λ", .upright = true } },
 55     .{ "Xi", Entry{ .kind = .identifier, .text = "Ξ", .upright = true } },
 56     .{ "Pi", Entry{ .kind = .identifier, .text = "Π", .upright = true } },
 57     .{ "Sigma", Entry{ .kind = .identifier, .text = "Σ", .upright = true } },
 58     .{ "Upsilon", Entry{ .kind = .identifier, .text = "Υ", .upright = true } },
 59     .{ "Phi", Entry{ .kind = .identifier, .text = "Φ", .upright = true } },
 60     .{ "Psi", Entry{ .kind = .identifier, .text = "Ψ", .upright = true } },
 61     .{ "Omega", Entry{ .kind = .identifier, .text = "Ω", .upright = true } },
 62     .{ "infty", Entry{ .kind = .identifier, .text = "∞", .upright = true } },
 63     .{ "partial", Entry{ .kind = .identifier, .text = "∂" } },
 64     .{ "nabla", Entry{ .kind = .identifier, .text = "∇", .upright = true } },
 65     .{ "hbar", Entry{ .kind = .identifier, .text = "ℏ" } },
 66     .{ "ell", Entry{ .kind = .identifier, .text = "ℓ" } },
 67     .{ "Re", Entry{ .kind = .identifier, .text = "ℜ", .upright = true } },
 68     .{ "Im", Entry{ .kind = .identifier, .text = "ℑ", .upright = true } },
 69     .{ "aleph", Entry{ .kind = .identifier, .text = "ℵ", .upright = true } },
 70     .{ "emptyset", Entry{ .kind = .identifier, .text = "∅", .upright = true } },
 71     .{ "varnothing", Entry{ .kind = .identifier, .text = "∅", .upright = true } },
 72     .{ "imath", Entry{ .kind = .identifier, .text = "ı" } },
 73     .{ "jmath", Entry{ .kind = .identifier, .text = "ȷ" } },
 74     .{ "cdot", Entry{ .kind = .operator, .text = "⋅" } },
 75     .{ "times", Entry{ .kind = .operator, .text = "×" } },
 76     .{ "div", Entry{ .kind = .operator, .text = "÷" } },
 77     .{ "pm", Entry{ .kind = .operator, .text = "±" } },
 78     .{ "mp", Entry{ .kind = .operator, .text = "∓" } },
 79     .{ "le", Entry{ .kind = .operator, .text = "≤" } },
 80     .{ "leq", Entry{ .kind = .operator, .text = "≤" } },
 81     .{ "ge", Entry{ .kind = .operator, .text = "≥" } },
 82     .{ "geq", Entry{ .kind = .operator, .text = "≥" } },
 83     .{ "ne", Entry{ .kind = .operator, .text = "≠" } },
 84     .{ "neq", Entry{ .kind = .operator, .text = "≠" } },
 85     .{ "ll", Entry{ .kind = .operator, .text = "≪" } },
 86     .{ "gg", Entry{ .kind = .operator, .text = "≫" } },
 87     .{ "approx", Entry{ .kind = .operator, .text = "≈" } },
 88     .{ "sim", Entry{ .kind = .operator, .text = "∼" } },
 89     .{ "simeq", Entry{ .kind = .operator, .text = "≃" } },
 90     .{ "cong", Entry{ .kind = .operator, .text = "≅" } },
 91     .{ "equiv", Entry{ .kind = .operator, .text = "≡" } },
 92     .{ "propto", Entry{ .kind = .operator, .text = "∝" } },
 93     .{ "in", Entry{ .kind = .operator, .text = "∈" } },
 94     .{ "notin", Entry{ .kind = .operator, .text = "∉" } },
 95     .{ "ni", Entry{ .kind = .operator, .text = "∋" } },
 96     .{ "subset", Entry{ .kind = .operator, .text = "⊂" } },
 97     .{ "supset", Entry{ .kind = .operator, .text = "⊃" } },
 98     .{ "subseteq", Entry{ .kind = .operator, .text = "⊆" } },
 99     .{ "supseteq", Entry{ .kind = .operator, .text = "⊇" } },
100     .{ "cup", Entry{ .kind = .operator, .text = "∪" } },
101     .{ "cap", Entry{ .kind = .operator, .text = "∩" } },
102     .{ "setminus", Entry{ .kind = .operator, .text = "∖" } },
103     .{ "wedge", Entry{ .kind = .operator, .text = "∧" } },
104     .{ "land", Entry{ .kind = .operator, .text = "∧" } },
105     .{ "vee", Entry{ .kind = .operator, .text = "∨" } },
106     .{ "lor", Entry{ .kind = .operator, .text = "∨" } },
107     .{ "oplus", Entry{ .kind = .operator, .text = "⊕" } },
108     .{ "ominus", Entry{ .kind = .operator, .text = "⊖" } },
109     .{ "otimes", Entry{ .kind = .operator, .text = "⊗" } },
110     .{ "odot", Entry{ .kind = .operator, .text = "⊙" } },
111     .{ "circ", Entry{ .kind = .operator, .text = "∘" } },
112     .{ "bullet", Entry{ .kind = .operator, .text = "•" } },
113     .{ "star", Entry{ .kind = .operator, .text = "⋆" } },
114     .{ "ast", Entry{ .kind = .operator, .text = "∗" } },
115     .{ "to", Entry{ .kind = .operator, .text = "→" } },
116     .{ "rightarrow", Entry{ .kind = .operator, .text = "→" } },
117     .{ "leftarrow", Entry{ .kind = .operator, .text = "←" } },
118     .{ "gets", Entry{ .kind = .operator, .text = "←" } },
119     .{ "mapsto", Entry{ .kind = .operator, .text = "↦" } },
120     .{ "hookrightarrow", Entry{ .kind = .operator, .text = "↪" } },
121     .{ "Rightarrow", Entry{ .kind = .operator, .text = "⇒" } },
122     .{ "Leftarrow", Entry{ .kind = .operator, .text = "⇐" } },
123     .{ "leftrightarrow", Entry{ .kind = .operator, .text = "↔" } },
124     .{ "Leftrightarrow", Entry{ .kind = .operator, .text = "⇔" } },
125     .{ "iff", Entry{ .kind = .operator, .text = "⇔" } },
126     .{ "implies", Entry{ .kind = .operator, .text = "⇒" } },
127     .{ "uparrow", Entry{ .kind = .operator, .text = "↑" } },
128     .{ "downarrow", Entry{ .kind = .operator, .text = "↓" } },
129     .{ "rightharpoonup", Entry{ .kind = .operator, .text = "⇀" } },
130     .{ "mid", Entry{ .kind = .operator, .text = "∣" } },
131     .{ "parallel", Entry{ .kind = .operator, .text = "∥" } },
132     .{ "perp", Entry{ .kind = .operator, .text = "⊥" } },
133     .{ "vdash", Entry{ .kind = .operator, .text = "⊢" } },
134     .{ "models", Entry{ .kind = .operator, .text = "⊨" } },
135     .{ "angle", Entry{ .kind = .operator, .text = "∠" } },
136     .{ "forall", Entry{ .kind = .operator, .text = "∀" } },
137     .{ "exists", Entry{ .kind = .operator, .text = "∃" } },
138     .{ "neg", Entry{ .kind = .operator, .text = "¬" } },
139     .{ "lnot", Entry{ .kind = .operator, .text = "¬" } },
140     .{ "prime", Entry{ .kind = .operator, .text = "′" } },
141     .{ "dots", Entry{ .kind = .operator, .text = "…" } },
142     .{ "ldots", Entry{ .kind = .operator, .text = "…" } },
143     .{ "cdots", Entry{ .kind = .operator, .text = "⋯" } },
144     .{ "vdots", Entry{ .kind = .operator, .text = "⋮" } },
145     .{ "ddots", Entry{ .kind = .operator, .text = "⋱" } },
146     .{ "backslash", Entry{ .kind = .operator, .text = "\\" } },
147     .{ "bmod", Entry{ .kind = .function, .text = "mod" } },
148     .{ "langle", Entry{ .kind = .fence, .text = "⟨" } },
149     .{ "rangle", Entry{ .kind = .fence, .text = "⟩" } },
150     .{ "lfloor", Entry{ .kind = .fence, .text = "⌊" } },
151     .{ "rfloor", Entry{ .kind = .fence, .text = "⌋" } },
152     .{ "lceil", Entry{ .kind = .fence, .text = "⌈" } },
153     .{ "rceil", Entry{ .kind = .fence, .text = "⌉" } },
154     .{ "lvert", Entry{ .kind = .fence, .text = "|" } },
155     .{ "rvert", Entry{ .kind = .fence, .text = "|" } },
156     .{ "vert", Entry{ .kind = .fence, .text = "|" } },
157     .{ "lVert", Entry{ .kind = .fence, .text = "‖" } },
158     .{ "rVert", Entry{ .kind = .fence, .text = "‖" } },
159     .{ "Vert", Entry{ .kind = .fence, .text = "‖" } },
160     .{ "|", Entry{ .kind = .fence, .text = "‖" } },
161     .{ "{", Entry{ .kind = .fence, .text = "{" } },
162     .{ "}", Entry{ .kind = .fence, .text = "}" } },
163     .{ "$", Entry{ .kind = .identifier, .text = "$", .upright = true } },
164     .{ "%", Entry{ .kind = .identifier, .text = "%", .upright = true } },
165     .{ "#", Entry{ .kind = .identifier, .text = "#", .upright = true } },
166     .{ "&", Entry{ .kind = .identifier, .text = "&", .upright = true } },
167     .{ "_", Entry{ .kind = .identifier, .text = "_", .upright = true } },
168     .{ "sum", Entry{ .kind = .largeop, .text = "∑" } },
169     .{ "prod", Entry{ .kind = .largeop, .text = "∏" } },
170     .{ "coprod", Entry{ .kind = .largeop, .text = "∐" } },
171     .{ "bigcup", Entry{ .kind = .largeop, .text = "⋃" } },
172     .{ "bigcap", Entry{ .kind = .largeop, .text = "⋂" } },
173     .{ "bigoplus", Entry{ .kind = .largeop, .text = "⨁" } },
174     .{ "bigotimes", Entry{ .kind = .largeop, .text = "⨂" } },
175     .{ "bigodot", Entry{ .kind = .largeop, .text = "⨀" } },
176     .{ "bigwedge", Entry{ .kind = .largeop, .text = "⋀" } },
177     .{ "bigvee", Entry{ .kind = .largeop, .text = "⋁" } },
178     .{ "int", Entry{ .kind = .integral, .text = "∫" } },
179     .{ "iint", Entry{ .kind = .integral, .text = "∬" } },
180     .{ "iiint", Entry{ .kind = .integral, .text = "∭" } },
181     .{ "oint", Entry{ .kind = .integral, .text = "∮" } },
182     .{ "lim", Entry{ .kind = .movableop, .text = "lim" } },
183     .{ "limsup", Entry{ .kind = .movableop, .text = "lim sup" } },
184     .{ "liminf", Entry{ .kind = .movableop, .text = "lim inf" } },
185     .{ "max", Entry{ .kind = .movableop, .text = "max" } },
186     .{ "min", Entry{ .kind = .movableop, .text = "min" } },
187     .{ "sup", Entry{ .kind = .movableop, .text = "sup" } },
188     .{ "inf", Entry{ .kind = .movableop, .text = "inf" } },
189     .{ "argmax", Entry{ .kind = .movableop, .text = "arg max" } },
190     .{ "argmin", Entry{ .kind = .movableop, .text = "arg min" } },
191     .{ "log", Entry{ .kind = .function, .text = "log" } },
192     .{ "ln", Entry{ .kind = .function, .text = "ln" } },
193     .{ "lg", Entry{ .kind = .function, .text = "lg" } },
194     .{ "exp", Entry{ .kind = .function, .text = "exp" } },
195     .{ "sin", Entry{ .kind = .function, .text = "sin" } },
196     .{ "cos", Entry{ .kind = .function, .text = "cos" } },
197     .{ "tan", Entry{ .kind = .function, .text = "tan" } },
198     .{ "cot", Entry{ .kind = .function, .text = "cot" } },
199     .{ "sec", Entry{ .kind = .function, .text = "sec" } },
200     .{ "csc", Entry{ .kind = .function, .text = "csc" } },
201     .{ "arcsin", Entry{ .kind = .function, .text = "arcsin" } },
202     .{ "arccos", Entry{ .kind = .function, .text = "arccos" } },
203     .{ "arctan", Entry{ .kind = .function, .text = "arctan" } },
204     .{ "sinh", Entry{ .kind = .function, .text = "sinh" } },
205     .{ "cosh", Entry{ .kind = .function, .text = "cosh" } },
206     .{ "tanh", Entry{ .kind = .function, .text = "tanh" } },
207     .{ "coth", Entry{ .kind = .function, .text = "coth" } },
208     .{ "det", Entry{ .kind = .function, .text = "det" } },
209     .{ "dim", Entry{ .kind = .function, .text = "dim" } },
210     .{ "ker", Entry{ .kind = .function, .text = "ker" } },
211     .{ "deg", Entry{ .kind = .function, .text = "deg" } },
212     .{ "gcd", Entry{ .kind = .function, .text = "gcd" } },
213     .{ "Pr", Entry{ .kind = .function, .text = "Pr" } },
214     .{ "arg", Entry{ .kind = .function, .text = "arg" } },
215     .{ ",", Entry{ .kind = .space, .text = "0.167em" } },
216     .{ ":", Entry{ .kind = .space, .text = "0.222em" } },
217     .{ ";", Entry{ .kind = .space, .text = "0.278em" } },
218     .{ " ", Entry{ .kind = .space, .text = "0.25em" } },
219     .{ "quad", Entry{ .kind = .space, .text = "1em" } },
220     .{ "qquad", Entry{ .kind = .space, .text = "2em" } },
221     .{ "hat", Entry{ .kind = .accent, .text = "ˆ" } },
222     .{ "widehat", Entry{ .kind = .accent, .text = "ˆ" } },
223     .{ "bar", Entry{ .kind = .accent, .text = "¯" } },
224     .{ "overline", Entry{ .kind = .accent, .text = "¯" } },
225     .{ "tilde", Entry{ .kind = .accent, .text = "˜" } },
226     .{ "widetilde", Entry{ .kind = .accent, .text = "˜" } },
227     .{ "vec", Entry{ .kind = .accent, .text = "⃗" } },
228     .{ "dot", Entry{ .kind = .accent, .text = "˙" } },
229     .{ "ddot", Entry{ .kind = .accent, .text = "¨" } },
230     .{ "check", Entry{ .kind = .accent, .text = "ˇ" } },
231     .{ "breve", Entry{ .kind = .accent, .text = "˘" } },
232     .{ "mathring", Entry{ .kind = .accent, .text = "˚" } },
233 });
234 
235 pub const Environment = struct {
236     open: ?[]const u8 = null,
237     close: ?[]const u8 = null,
238     class: ?[]const u8 = null,
239     display_cells: bool = false,
240 };
241 
242 pub const environments = std.StaticStringMap(Environment).initComptime(.{
243     .{ "matrix", Environment{} },
244     .{ "pmatrix", Environment{ .open = "(", .close = ")" } },
245     .{ "bmatrix", Environment{ .open = "[", .close = "]" } },
246     .{ "Bmatrix", Environment{ .open = "{", .close = "}" } },
247     .{ "vmatrix", Environment{ .open = "|", .close = "|" } },
248     .{ "Vmatrix", Environment{ .open = "‖", .close = "‖" } },
249     .{ "cases", Environment{ .open = "{", .class = "zen-cases" } },
250     .{ "aligned", Environment{ .class = "zen-aligned", .display_cells = true } },
251 });
252 
253 pub const Style = enum { bb, cal, frak, bf };
254 
255 pub fn styled(style: Style, char: u8) ?u21 {
256     return switch (style) {
257         .bb => styledBb(char),
258         .cal => styledCal(char),
259         .frak => styledFrak(char),
260         .bf => styledBf(char),
261     };
262 }
263 
264 fn styledBb(char: u8) ?u21 {
265     switch (char) {
266         'C' => return 0x2102,
267         'H' => return 0x210D,
268         'N' => return 0x2115,
269         'P' => return 0x2119,
270         'Q' => return 0x211A,
271         'R' => return 0x211D,
272         'Z' => return 0x2124,
273         else => {},
274     }
275     if (char >= 'A' and char <= 'Z') return 0x1D538 + @as(u21, char - 'A');
276     if (char >= 'a' and char <= 'z') return 0x1D552 + @as(u21, char - 'a');
277     if (char >= '0' and char <= '9') return 0x1D7D8 + @as(u21, char - '0');
278     return null;
279 }
280 
281 fn styledCal(char: u8) ?u21 {
282     switch (char) {
283         'B' => return 0x212C,
284         'E' => return 0x2130,
285         'F' => return 0x2131,
286         'H' => return 0x210B,
287         'I' => return 0x2110,
288         'L' => return 0x2112,
289         'M' => return 0x2133,
290         'R' => return 0x211B,
291         'e' => return 0x212F,
292         'g' => return 0x210A,
293         'o' => return 0x2134,
294         else => {},
295     }
296     if (char >= 'A' and char <= 'Z') return 0x1D49C + @as(u21, char - 'A');
297     if (char >= 'a' and char <= 'z') return 0x1D4B6 + @as(u21, char - 'a');
298     return null;
299 }
300 
301 fn styledFrak(char: u8) ?u21 {
302     switch (char) {
303         'C' => return 0x212D,
304         'H' => return 0x210C,
305         'I' => return 0x2111,
306         'R' => return 0x211C,
307         'Z' => return 0x2128,
308         else => {},
309     }
310     if (char >= 'A' and char <= 'Z') return 0x1D504 + @as(u21, char - 'A');
311     if (char >= 'a' and char <= 'z') return 0x1D51E + @as(u21, char - 'a');
312     return null;
313 }
314 
315 fn styledBf(char: u8) ?u21 {
316     if (char >= 'A' and char <= 'Z') return 0x1D400 + @as(u21, char - 'A');
317     if (char >= 'a' and char <= 'z') return 0x1D41A + @as(u21, char - 'a');
318     if (char >= '0' and char <= '9') return 0x1D7CE + @as(u21, char - '0');
319     return null;
320 }
321 
322 test "symbol table resolves commands and classes" {
323     try std.testing.expectEqualStrings("∑", commands.get("sum").?.text);
324     try std.testing.expect(commands.get("sum").?.kind == .largeop);
325     try std.testing.expect(commands.get("int").?.kind == .integral);
326     try std.testing.expect(commands.get("lim").?.kind == .movableop);
327     try std.testing.expect(commands.get("nonesuch") == null);
328 }
329 
330 test "environment table resolves fences and classes" {
331     try std.testing.expectEqualStrings("(", environments.get("pmatrix").?.open.?);
332     try std.testing.expectEqualStrings("‖", environments.get("Vmatrix").?.close.?);
333     try std.testing.expect(environments.get("matrix").?.open == null);
334     const cases = environments.get("cases").?;
335     try std.testing.expectEqualStrings("{", cases.open.?);
336     try std.testing.expect(cases.close == null);
337     try std.testing.expectEqualStrings("zen-cases", cases.class.?);
338     const aligned = environments.get("aligned").?;
339     try std.testing.expect(aligned.display_cells);
340     try std.testing.expect(environments.get("align") == null);
341 }
342 
343 test "styled alphabets honor unicode exception slots" {
344     try std.testing.expectEqual(@as(?u21, 0x211D), styled(.bb, 'R'));
345     try std.testing.expectEqual(@as(?u21, 0x1D538), styled(.bb, 'A'));
346     try std.testing.expectEqual(@as(?u21, 0x2112), styled(.cal, 'L'));
347     try std.testing.expectEqual(@as(?u21, 0x212F), styled(.cal, 'e'));
348     try std.testing.expectEqual(@as(?u21, 0x211C), styled(.frak, 'R'));
349     try std.testing.expectEqual(@as(?u21, 0x1D41A), styled(.bf, 'a'));
350     try std.testing.expectEqual(@as(?u21, null), styled(.cal, '3'));
351 }