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 }