mirror of
https://github.com/oven-sh/bun
synced 2026-02-13 04:18:58 +00:00
feat: Add an interval utility for range operations
This commit is contained in:
@@ -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");
|
||||
|
||||
1
src/math.zig
Normal file
1
src/math.zig
Normal file
@@ -0,0 +1 @@
|
||||
pub const interval = @import("./math/interval.zig");
|
||||
196
src/math/interval.zig
Normal file
196
src/math/interval.zig
Normal file
@@ -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");
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user