aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/terminal/fraction.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/terminal/fraction.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff)
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/terminal/fraction.zig')
-rw-r--r--vendor/libghostty-vt/src/terminal/fraction.zig142
1 files changed, 142 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/terminal/fraction.zig b/vendor/libghostty-vt/src/terminal/fraction.zig
new file mode 100644
index 00000000..67134419
--- /dev/null
+++ b/vendor/libghostty-vt/src/terminal/fraction.zig
@@ -0,0 +1,142 @@
+//! Parsing of decimal fractions in the unit interval [0, 1].
+//!
+//! Escape sequences that carry fractional parameters (XTerm `rgbi:`
+//! color specifications, glyph protocol padding) only ever need plain
+//! decimal values such as "0.5". Using `std.fmt.parseFloat` for these
+//! pulls the full correctly-rounded float parser and its lookup tables
+//! into the binary (~26KB). This implements only the restricted subset
+//! we need.
+const std = @import("std");
+const assert = std.debug.assert;
+
+/// Parse a decimal fraction in the range [0, 1] inclusive.
+///
+/// Accepts an optional leading `+` or `-` sign followed by decimal
+/// digits with an optional decimal point ("0.5", ".5", "1.", "1"); at
+/// least one digit is required. Exponent and hexadecimal float syntax
+/// are not supported. Returns null for invalid syntax and for any value
+/// outside [0, 1] (so negative values other than -0 are rejected).
+///
+/// Results are correctly rounded for inputs of up to 15 fractional
+/// digits; further digits are validated but ignored, which can move the
+/// result by less than 1e-15.
+pub fn parse(value: []const u8) ?f64 {
+ var v = value;
+ var negative = false;
+ if (v.len > 0) switch (v[0]) {
+ '+' => v = v[1..],
+ '-' => {
+ negative = true;
+ v = v[1..];
+ },
+ else => {},
+ };
+
+ var i: usize = 0;
+ const int_part: f64 = int_part: {
+ var int_part: f64 = 0;
+ while (i < v.len and v[i] != '.') : (i += 1) {
+ const d = v[i];
+ if (d < '0' or d > '9') return null;
+ int_part = int_part * 10 + @as(f64, @floatFromInt(d - '0'));
+ }
+ break :int_part int_part;
+ };
+
+ var digits: usize = i;
+ var frac: u64 = 0;
+ var scale: u64 = 1;
+ if (i < v.len) {
+ assert(v[i] == '.');
+ i += 1;
+ while (i < v.len) : (i += 1) {
+ const d = v[i];
+ if (d < '0' or d > '9') return null;
+ // Stop accumulating after 15 digits so that frac and scale
+ // both stay exactly representable in f64 (and cannot
+ // overflow), making the division below round only once.
+ if (scale < 1_000_000_000_000_000) {
+ frac = frac * 10 + (d - '0');
+ scale *= 10;
+ }
+ digits += 1;
+ }
+ }
+ if (digits == 0) return null;
+
+ // frac and scale are both exact in f64 (below 2^53 and a power of
+ // ten no greater than 1e15), so this division rounds only once.
+ const magnitude = int_part +
+ @as(f64, @floatFromInt(frac)) / @as(f64, @floatFromInt(scale));
+ const result = if (negative) -magnitude else magnitude;
+
+ // Range check. Written so that -0 passes and any out-of-range or
+ // non-finite value fails.
+ if (!(result >= 0 and result <= 1)) return null;
+ return result;
+}
+
+test parse {
+ const testing = std.testing;
+
+ // Plain values
+ try testing.expectEqual(0, parse("0").?);
+ try testing.expectEqual(1, parse("1").?);
+ try testing.expectEqual(0.5, parse("0.5").?);
+ try testing.expectEqual(0.25, parse("0.25").?);
+ try testing.expectEqual(1, parse("1.0").?);
+ try testing.expectEqual(1, parse("1.000000").?);
+ try testing.expectEqual(0, parse("0.0").?);
+
+ // Partial forms
+ try testing.expectEqual(0.5, parse(".5").?);
+ try testing.expectEqual(1, parse("1.").?);
+ try testing.expectEqual(0, parse("00.00").?);
+
+ // Signs
+ try testing.expectEqual(0.5, parse("+0.5").?);
+ try testing.expectEqual(0, parse("-0").?);
+ try testing.expectEqual(0, parse("-0.000").?);
+ try testing.expectEqual(null, parse("-0.5"));
+ try testing.expectEqual(null, parse("-1"));
+
+ // Matches std.fmt.parseFloat rounding for typical inputs.
+ try testing.expectEqual(
+ std.fmt.parseFloat(f64, "0.3") catch unreachable,
+ parse("0.3").?,
+ );
+ try testing.expectEqual(
+ std.fmt.parseFloat(f64, "0.123456789012345") catch unreachable,
+ parse("0.123456789012345").?,
+ );
+
+ // Digits past the 15th are ignored: long fractions stay finite and
+ // equal their 15-digit truncation.
+ try testing.expectEqual(
+ parse("0.333333333333333").?,
+ parse("0." ++ "3" ** 400).?,
+ );
+
+ // Out of range
+ try testing.expectEqual(null, parse("1.0000001"));
+ try testing.expectEqual(null, parse("2"));
+ try testing.expectEqual(null, parse("255"));
+ try testing.expectEqual(null, parse("1" ** 400));
+
+ // Invalid syntax
+ try testing.expectEqual(null, parse(""));
+ try testing.expectEqual(null, parse("."));
+ try testing.expectEqual(null, parse("+"));
+ try testing.expectEqual(null, parse("-"));
+ try testing.expectEqual(null, parse("+."));
+ try testing.expectEqual(null, parse("abc"));
+ try testing.expectEqual(null, parse("0.5x"));
+ try testing.expectEqual(null, parse("0x0.8"));
+ try testing.expectEqual(null, parse("0..5"));
+ try testing.expectEqual(null, parse("0.5.5"));
+ try testing.expectEqual(null, parse(" 0.5"));
+ try testing.expectEqual(null, parse("0.5 "));
+ try testing.expectEqual(null, parse("1e-1"));
+ try testing.expectEqual(null, parse("nan"));
+ try testing.expectEqual(null, parse("inf"));
+}