diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/terminal/fraction.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (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.zig | 142 |
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")); +} |
