lib/termtex/src/operator.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const ast = @import("ast.zig");
3
4 pub const StretchArrowShaft = enum {
5 single,
6 double,
7 squiggle,
8 };
9
10 pub const StretchArrow = struct {
11 left_head: ?[]const u8 = null,
12 right_head: ?[]const u8 = null,
13 shaft: StretchArrowShaft = .single,
14 left_bar: bool = false,
15 };
16
17 const StretchArrowEntry = struct {
18 value: []const u8,
19 arrow: StretchArrow,
20 };
21
22 pub fn limitsBase(expr: *const ast.Expr) bool {
23 switch (expr.*) {
24 .operator => |value| return switch (value.limit_policy) {
25 .auto => limitsBase(value.body),
26 .limits => true,
27 .nolimits => false,
28 },
29 .annotation => |value| return value.kind == .overbrace or value.kind == .underbrace,
30 else => {},
31 }
32 const value = ast.textValue(expr) orelse return false;
33 return limitsText(value);
34 }
35
36 pub fn limitsText(value: []const u8) bool {
37 inline for (.{
38 "∑",
39 "∏",
40 "∐",
41 "lim",
42 "max",
43 "min",
44 "sup",
45 "inf",
46 }) |candidate| {
47 if (std.mem.eql(u8, value, candidate)) return true;
48 }
49 return stretchArrowText(value) != null;
50 }
51
52 pub fn displayOperatorText(value: []const u8) bool {
53 inline for (.{
54 "∑",
55 "⨁",
56 "⨂",
57 "⨀",
58 "∏",
59 "∐",
60 "∫",
61 "∬",
62 "∭",
63 "∮",
64 "⋀",
65 "⋁",
66 "⋃",
67 "⋂",
68 }) |candidate| {
69 if (std.mem.eql(u8, value, candidate)) return true;
70 }
71 return false;
72 }
73
74 pub fn stretchArrowBase(expr: *const ast.Expr) ?StretchArrow {
75 switch (expr.*) {
76 .operator => |value| return switch (value.limit_policy) {
77 .auto, .limits => stretchArrowBase(value.body),
78 .nolimits => null,
79 },
80 else => {},
81 }
82 const value = ast.textValue(expr) orelse return null;
83 return stretchArrowText(value);
84 }
85
86 pub fn stretchArrowText(value: []const u8) ?StretchArrow {
87 inline for (stretch_arrows) |entry| {
88 if (std.mem.eql(u8, value, entry.value)) return entry.arrow;
89 }
90 return null;
91 }
92
93 const stretch_arrows = [_]StretchArrowEntry{
94 .{ .value = "→", .arrow = .{ .right_head = "→" } },
95 .{ .value = "←", .arrow = .{ .left_head = "←" } },
96 .{ .value = "↔", .arrow = .{ .left_head = "←", .right_head = "→" } },
97 .{ .value = "⇒", .arrow = .{ .right_head = "⇒", .shaft = .double } },
98 .{ .value = "⇐", .arrow = .{ .left_head = "⇐", .shaft = .double } },
99 .{ .value = "⇔", .arrow = .{ .left_head = "⇐", .right_head = "⇒", .shaft = .double } },
100 .{ .value = "⟶", .arrow = .{ .right_head = "→" } },
101 .{ .value = "⟵", .arrow = .{ .left_head = "←" } },
102 .{ .value = "⟷", .arrow = .{ .left_head = "←", .right_head = "→" } },
103 .{ .value = "⟹", .arrow = .{ .right_head = "⇒", .shaft = .double } },
104 .{ .value = "⟸", .arrow = .{ .left_head = "⇐", .shaft = .double } },
105 .{ .value = "⟺", .arrow = .{ .left_head = "⇐", .right_head = "⇒", .shaft = .double } },
106 .{ .value = "↦", .arrow = .{ .right_head = "→", .left_bar = true } },
107 .{ .value = "⟼", .arrow = .{ .right_head = "→", .left_bar = true } },
108 .{ .value = "↪", .arrow = .{ .right_head = "↪" } },
109 .{ .value = "↩", .arrow = .{ .left_head = "↩" } },
110 .{ .value = "↠", .arrow = .{ .right_head = "↠" } },
111 .{ .value = "↞", .arrow = .{ .left_head = "↞" } },
112 .{ .value = "⇀", .arrow = .{ .right_head = "⇀" } },
113 .{ .value = "⇁", .arrow = .{ .right_head = "⇁" } },
114 .{ .value = "↼", .arrow = .{ .left_head = "↼" } },
115 .{ .value = "↽", .arrow = .{ .left_head = "↽" } },
116 .{ .value = "⇌", .arrow = .{ .left_head = "↼", .right_head = "⇁", .shaft = .double } },
117 .{ .value = "↝", .arrow = .{ .right_head = "↝", .shaft = .squiggle } },
118 .{ .value = "⇝", .arrow = .{ .right_head = "⇝", .shaft = .squiggle } },
119 .{ .value = "⊸", .arrow = .{ .right_head = "⊸" } },
120 };
121
122 test "detect limit-bearing operator bases" {
123 var sum = ast.Expr{ .text = "∑" };
124 var lim = ast.Expr{ .text = "lim" };
125 var x = ast.Expr{ .text = "x" };
126 const limited = ast.Expr{ .operator = .{ .body = &x, .limit_policy = .limits } };
127 const nolimits = ast.Expr{ .operator = .{ .body = &sum, .limit_policy = .nolimits } };
128
129 try std.testing.expect(limitsBase(&sum));
130 try std.testing.expect(limitsBase(&lim));
131 try std.testing.expect(!limitsBase(&x));
132 try std.testing.expect(limitsBase(&limited));
133 try std.testing.expect(!limitsBase(&nolimits));
134 try std.testing.expect(limitsText("max"));
135 try std.testing.expect(displayOperatorText("∑"));
136 try std.testing.expect(displayOperatorText("∫"));
137 try std.testing.expect(!displayOperatorText("lim"));
138 }
139
140 test "detect stretchable arrow bases" {
141 var right = ast.Expr{ .text = "→" };
142 var mapsto = ast.Expr{ .text = "↦" };
143 var sum = ast.Expr{ .text = "∑" };
144 const nolimits = ast.Expr{ .operator = .{ .body = &right, .limit_policy = .nolimits } };
145
146 try std.testing.expect(stretchArrowBase(&right) != null);
147 try std.testing.expect(stretchArrowBase(&mapsto).?.left_bar);
148 try std.testing.expect(stretchArrowBase(&sum) == null);
149 try std.testing.expect(stretchArrowBase(&nolimits) == null);
150 }