diff --git a/src/bun.zig b/src/bun.zig index acaa0ec66a..ac4638c181 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -3798,6 +3798,8 @@ pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFra return jsc.JSValue.jsBoolean(Arguments.Bun__Node__UseSystemCA); } +pub const math = @import("./math.zig"); + const CopyFile = @import("./copy_file.zig"); const builtin = @import("builtin"); const std = @import("std"); diff --git a/src/math.zig b/src/math.zig new file mode 100644 index 0000000000..49f8c223ed --- /dev/null +++ b/src/math.zig @@ -0,0 +1 @@ +pub const interval = @import("./math/interval.zig"); diff --git a/src/math/interval.zig b/src/math/interval.zig new file mode 100644 index 0000000000..18458c8162 --- /dev/null +++ b/src/math/interval.zig @@ -0,0 +1,196 @@ +//! Mathematical intervals and operations on them. + +/// Represents a mathematical interval with a start and an end of type `T`. The interval may be +/// `open (x, y)`, `closed [x, y]`, `left-closed [x, y)` or `right-closed (x, y]`. +fn Interval( + comptime T: type, + comptime closed_trait: enum { open, closed, left_closed, right_closed }, +) type { + switch (@typeInfo(T)) { + .int, .float, .comptime_int, .comptime_float => {}, + else => { + @compileError("Interval type T must be an integer or floating-point type."); + }, + } + + return struct { + const Self = @This(); + pub const closedness = closed_trait; + + /// Check if the left side of the interval is closed. + pub const left_closed = switch (closedness) { + .open => false, + .closed => true, + .left_closed => true, + .right_closed => false, + }; + + /// Check if the right side of the interval is closed. + pub const right_closed = switch (closedness) { + .open => false, + .closed => true, + .left_closed => false, + .right_closed => true, + }; + + start: T, + end: T, + + /// Errors thrown in .init + pub const InitError = error{InvalidInterval}; + + /// Initializes a new interval with the given start and end values. + /// + /// Note that this function may return either Self or !Self, depending on whether it is + /// used in a comptime or non-comptime context. In a comptime context (ie. both arguments + /// are comptime), it will return Self and emit a compile error if the interval is invalid. + /// In a non-comptime context, it will return !Self and return an error if the interval is + /// invalid. + pub fn init(start: anytype, end: anytype) Self.comptimeInitType(start, end) { + if (start > end) { + if (comptime Self.isInitComptime(start, end)) { + @compileError(std.fmt.comptimePrint( + "Invalid interval with start ({}) greater than end ({})", + .{ start, end }, + )); + } else { + return error.InvalidInterval; + } + } + + return .{ + .start = start, + .end = end, + }; + } + + /// Check if two intervals are equal. + pub fn eql(self: *const Self, other: anytype) bool { + return self.start == other.start and self.end == other.end and + closedness == other.closedness; + } + + /// Test whether the given value is contained within the interval. + pub fn contains(self: *const Self, value: T) bool { + switch (closedness) { + .open => return (value > self.start) and (value < self.end), + .closed => return (value >= self.start) and (value <= self.end), + .left_closed => return (value >= self.start) and (value < self.end), + .right_closed => return (value > self.start) and (value <= self.end), + } + } + + /// An interval is empty if it could not mathematically contain any values. + pub fn isEmpty(self: *const Self) bool { + return self.start == self.end and closedness != .closed; + } + + /// Test whether this interval intersects with another interval. + pub fn intersects(self: *const Self, other: anytype) bool { + if (self.isEmpty() or other.isEmpty()) { + return false; + } + + if (self.end < other.start or self.start > other.end) { + return false; + } + + if (self.end == other.start) { + return self.right_closed and other.left_closed; + } + + if (self.start == other.end) { + return self.left_closed and other.right_closed; + } + + return true; + } + + /// Clamp a value to be within the interval. + pub fn clamp(self: *const Self, value: T) T { + if (closedness != .closed) { + // The reason we do this is to avoid ambiguity. Should open intervals clamp to the + // very edge or to the epsilon? Hard to answer, so we just disallow it for now. + @compileError("Clamping values within an interval is only supported for closed " ++ + "intervals."); + } + + if (value < self.start) { + return self.start; + } + + if (value > self.end) { + return self.end; + } + + return value; + } + + /// Format the interval using the given format string and options. + /// + /// The format specifier is given to the start and end values in that order. + pub fn format( + self: *const Self, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) !void { + _ = options; + + const lp, const rp = switch (closedness) { + .open => .{ "(", ")" }, + .closed => .{ "[", "]" }, + .left_closed => .{ "[", ")" }, + .right_closed => .{ "(", "]" }, + }; + + const f = std.fmt.comptimePrint("{s}{{{s}}},{{{s}}}{s}", .{ lp, fmt, fmt, rp }); + + try writer.print(f, .{ self.start, self.end }); + } + + fn isInitComptime(start: anytype, end: anytype) bool { + const start_is_comptime = switch (@typeInfo(@TypeOf(start))) { + .comptime_int, .comptime_float => true, + else => false, + }; + const end_is_comptime = switch (@typeInfo(@TypeOf(end))) { + .comptime_int, .comptime_float => true, + else => false, + }; + + return start_is_comptime and end_is_comptime; + } + + fn comptimeInitType(start: anytype, end: anytype) type { + return if (Self.isInitComptime(start, end)) Self else InitError!Self; + } + }; +} + +/// Closed interval [a, b] +pub fn Closed(comptime T: type) type { + return Interval(T, .closed); +} + +/// Open interval (a, b) +pub fn Open(comptime T: type) type { + return Interval(T, .open); +} + +/// Left-closed interval [a, b) +pub fn LeftClosed(comptime T: type) type { + return Interval(T, .left_closed); +} + +/// Right-closed interval (a, b] +pub fn RightClosed(comptime T: type) type { + return Interval(T, .right_closed); +} + +/// Create an interval which spans the possible range of values for the given type `T`. +pub fn Int(comptime T: type) Closed(T) { + return Closed(T).init(@as(T, std.math.minInt(T)), @as(T, std.math.maxInt(T))); +} + +const std = @import("std"); diff --git a/src/string/immutable/visible.zig b/src/string/immutable/visible.zig index a2fea60879..96cb828c5a 100644 --- a/src/string/immutable/visible.zig +++ b/src/string/immutable/visible.zig @@ -1,47 +1,31 @@ pub fn isZeroWidthCodepointType(comptime T: type, cp: T) bool { - if (cp <= 0x1f) { - return true; - } - - if (cp >= 0x7f and cp <= 0x9f) { - // C1 control characters - return true; - } + const c1_control_chars = bun.math.interval.Closed(T).init(0x7f, 0x9f); + const combining_diacritical_marks = bun.math.interval.Closed(T).init(0x300, 0x36f); + const modifying_invisible_chars = bun.math.interval.Closed(T).init(0x200b, 0x200f); + const combining_diacritical_marks_for_symbols = bun.math.interval.Closed(T).init(0x20d0, 0x20ff); + const variation_selectors_1 = bun.math.interval.Closed(T).init(0xfe00, 0xfe0f); + const combining_half_marks = bun.math.interval.Closed(T).init(0xfe20, 0xfe2f); + const variation_selectors_2 = bun.math.interval.Closed(T).init(0xe0100, 0xe01ef); if (comptime @sizeOf(T) == 1) { return false; } - if (cp >= 0x300 and cp <= 0x36f) { - // Combining Diacritical Marks - return true; - } - - if (cp >= 0x200b and cp <= 0x200f) { - // Modifying Invisible Characters - return true; - } - - if (cp >= 0x20d0 and cp <= 0x20ff) - // Combining Diacritical Marks for Symbols - return true; - - if (cp >= 0xfe00 and cp <= 0xfe0f) - // Variation Selectors - return true; - if (cp >= 0xfe20 and cp <= 0xfe2f) - // Combining Half Marks - return true; - if (cp == 0xfeff) // Zero Width No-Break Space (BOM, ZWNBSP) return true; - if (cp >= 0xe0100 and cp <= 0xe01ef) - // Variation Selectors + if (cp <= 0x1f) { return true; + } - return false; + return c1_control_chars.contains(cp) or + combining_diacritical_marks.contains(cp) or + modifying_invisible_chars.contains(cp) or + combining_diacritical_marks_for_symbols.contains(cp) or + variation_selectors_1.contains(cp) or + combining_half_marks.contains(cp) or + variation_selectors_2.contains(cp); } /// Official unicode reference: https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt