Files
headroom/frontend/node_modules/@ark/util/out/numbers.js
Santhosh Janardhanan de2d83092e feat: Reinitialize frontend with SvelteKit and TypeScript
- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
2026-02-17 16:19:59 -05:00

139 lines
6.3 KiB
JavaScript

import { throwParseError } from "./errors.js";
import { anchoredRegex, RegexPatterns } from "./strings.js";
/*
* The goal of the number literal and bigint literal regular expressions is to:
*
* 1. Ensure definitions form a bijection with the values they represent.
* 2. Attempt to mirror TypeScript's own format for stringification of numeric
* values such that the regex should match a given definition if any only if
* a precise literal type will be inferred (in TS4.8+).
*/
const anchoredNegativeZeroPattern = /^-0\.?0*$/.source;
const positiveIntegerPattern = /[1-9]\d*/.source;
const looseDecimalPattern = /\.\d+/.source;
const strictDecimalPattern = /\.\d*[1-9]/.source;
const createNumberMatcher = (opts) => anchoredRegex(RegexPatterns.negativeLookahead(anchoredNegativeZeroPattern) +
RegexPatterns.nonCapturingGroup("-?" +
RegexPatterns.nonCapturingGroup(RegexPatterns.nonCapturingGroup("0|" + positiveIntegerPattern) +
RegexPatterns.nonCapturingGroup(opts.decimalPattern) +
"?") +
(opts.allowDecimalOnly ? "|" + opts.decimalPattern : "") +
"?"));
/**
* Matches a well-formatted numeric expression according to the following rules:
* 1. Must include an integer portion (i.e. '.321' must be written as '0.321')
* 2. The first digit of the value must not be 0, unless the entire integer portion is 0
* 3. If the value includes a decimal, its last digit may not be 0
* 4. The value may not be "-0"
*/
export const wellFormedNumberMatcher = createNumberMatcher({
decimalPattern: strictDecimalPattern,
allowDecimalOnly: false
});
export const isWellFormedNumber = wellFormedNumberMatcher.test.bind(wellFormedNumberMatcher);
/**
* Similar to wellFormedNumber but more permissive in the following ways:
*
* - Allows numbers without an integer portion like ".5" (well-formed equivalent is "0.5")
* - Allows decimals with trailing zeroes like "0.10" (well-formed equivalent is "0.1")
*/
export const numericStringMatcher = createNumberMatcher({
decimalPattern: looseDecimalPattern,
allowDecimalOnly: true
});
export const isNumericString = numericStringMatcher.test.bind(numericStringMatcher);
export const numberLikeMatcher = /^-?\d*\.?\d*$/;
const isNumberLike = (s) => s.length !== 0 && numberLikeMatcher.test(s);
/**
* Matches a well-formatted integer according to the following rules:
* 1. must begin with an integer, the first digit of which cannot be 0 unless the entire value is 0
* 2. The value may not be "-0"
*/
export const wellFormedIntegerMatcher = anchoredRegex(RegexPatterns.negativeLookahead("^-0$") +
"-?" +
RegexPatterns.nonCapturingGroup(RegexPatterns.nonCapturingGroup("0|" + positiveIntegerPattern)));
export const isWellFormedInteger = wellFormedIntegerMatcher.test.bind(wellFormedIntegerMatcher);
export const integerLikeMatcher = /^-?\d+$/;
const isIntegerLike = integerLikeMatcher.test.bind(integerLikeMatcher);
const numericLiteralDescriptions = {
number: "a number",
bigint: "a bigint",
integer: "an integer"
};
export const writeMalformedNumericLiteralMessage = (def, kind) => `'${def}' was parsed as ${numericLiteralDescriptions[kind]} but could not be narrowed to a literal value. Avoid unnecessary leading or trailing zeros and other abnormal notation`;
const isWellFormed = (def, kind) => kind === "number" ? isWellFormedNumber(def) : isWellFormedInteger(def);
const parseKind = (def, kind) => kind === "number" ? Number(def) : Number.parseInt(def);
const isKindLike = (def, kind) => kind === "number" ? isNumberLike(def) : isIntegerLike(def);
export const tryParseNumber = (token, options) => parseNumeric(token, "number", options);
export const tryParseWellFormedNumber = (token, options) => parseNumeric(token, "number", { ...options, strict: true });
export const tryParseInteger = (token, options) => parseNumeric(token, "integer", options);
const parseNumeric = (token, kind, options) => {
const value = parseKind(token, kind);
if (!Number.isNaN(value)) {
if (isKindLike(token, kind)) {
if (options?.strict) {
return isWellFormed(token, kind) ? value : (throwParseError(writeMalformedNumericLiteralMessage(token, kind)));
}
return value;
}
}
return (options?.errorOnFail ?
throwParseError(options?.errorOnFail === true ?
`Failed to parse ${numericLiteralDescriptions[kind]} from '${token}'`
: options?.errorOnFail)
: undefined);
};
export const tryParseWellFormedBigint = (def) => {
if (def[def.length - 1] !== "n")
return;
const maybeIntegerLiteral = def.slice(0, -1);
let value;
try {
value = BigInt(maybeIntegerLiteral);
}
catch {
return;
}
if (wellFormedIntegerMatcher.test(maybeIntegerLiteral))
return value;
if (integerLikeMatcher.test(maybeIntegerLiteral)) {
// If the definition looks like a bigint but is
// not well-formed, throw.
return throwParseError(writeMalformedNumericLiteralMessage(def, "bigint"));
}
};
/**
* Returns the next or previous representable floating-point number after the given input.
*
* @param {"+" | "-"} [direction="+"] - The direction to find the nearest float. "+" for the next float, "-" for the previous float.
* @throws {Error} If the input is not a finite number.
*
* @example
* console.log(nearestFloat(0)); // Smallest positive number
* console.log(nearestFloat(2)); // 2.0000000000000004
* console.log(nearestFloat(2.1)); // 2.1000000000000005
* console.log(nearestFloat(2, "-")); // 1.9999999999999998
* console.log(nearestFloat(2.1, "-")); // 2.0999999999999996
* // as size of input increases, the increments become larger to stay within what
* // JS can represent in a numeric value
* console.log(nearestFloat(5555555555555555)); // 5555555555555556
* console.log(nearestFloat(5555555555555555, "-")); // 5555555555555554
*/
export const nearestFloat = (n, direction = "+") => {
const buffer = new ArrayBuffer(8);
const f64 = new Float64Array(buffer);
const u32 = new Uint32Array(buffer);
f64[0] = n;
if (n === 0) {
u32[0] = 1;
u32[1] = direction === "-" ? 1 << 31 : 0;
}
else if ((n > 0 && direction === "+") || (n < 0 && direction === "-")) {
if (u32[0]++ === 0xffffffff)
u32[1]++;
}
else if (u32[0]-- === 0)
u32[1]--;
return f64[0];
};