Skip to content
287 changes: 243 additions & 44 deletions loopx/control_plane/work_items/task_lease_acquire_decision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,52 +140,249 @@ function optionalInteger(value: unknown, label: string): number | null {
return value;
}

function classMatch(
pattern: string,
start: number,
value: string,
): { end: number; matches: boolean } | null {
const end = characterClassEnd(pattern, start);
if (end < 0) return null;
let body = pattern.slice(start + 1, end);
const negated = body.startsWith("!");
if (negated) body = body.slice(1);
let matches = false;
for (let index = 0; index < body.length; index += 1) {
if (index + 2 < body.length && body[index + 1] === "-") {
if (body[index] <= value && value <= body[index + 2]) matches = true;
/**
* Canonical fold for one code point, iterated until it is stable. Host case
* aliases are symmetric (Σ/ς, K/KELVIN SIGN) and some change length (ß -> ss),
* so one upper/lower pass is not an equivalence relation on its own.
*/
function foldCharacter(character: string): string {
let folded = character;
// Two passes settle every code point in current Unicode data; the bound only
// guards against a future mapping that never reaches a fixed point.
for (let pass = 0; pass < 4; pass += 1) {
const next = folded.toUpperCase().toLowerCase();
if (next === folded) return folded;
folded = next;
}
return folded;
}

/**
* Folding one code point at a time keeps the fold composable across a path, so a
* comparison-only view can fold substrings without changing the retained
* spelling or the immutable request identity.
*/
function caseFold(value: string): string {
let folded = "";
for (const character of value) folded += foldCharacter(character);
return folded;
}

/** The code point of single-character text, or null for longer text. */
function singleCodePoint(value: string): string | null {
const code = value.codePointAt(0);
if (code === undefined) return null;
const character = String.fromCodePoint(code);
return character.length === value.length ? character : null;
}

type GlobToken =
| {kind: "literal"; folded: string}
| {kind: "star"}
| {kind: "question"}
| {kind: "class"; body: string; negated: boolean};

function globTokens(pattern: string): GlobToken[] {
const tokens: GlobToken[] = [];
let literal = "";
const flushLiteral = () => {
if (literal.length === 0) return;
tokens.push({kind: "literal", folded: caseFold(literal)});
literal = "";
};
for (let index = 0; index < pattern.length; index += 1) {
const character = pattern[index];
if (character === "*") {
flushLiteral();
tokens.push({kind: "star"});
continue;
}
if (character === "?") {
flushLiteral();
tokens.push({kind: "question"});
continue;
}
if (character === "[") {
const end = characterClassEnd(pattern, index);
if (end >= 0) {
flushLiteral();
let body = pattern.slice(index + 1, end);
const negated = body.startsWith("!");
if (negated) body = body.slice(1);
tokens.push({kind: "class", body, negated});
index = end;
continue;
}
}
literal += character;
}
flushLiteral();
return tokens;
}

type CharacterClass = {
literals: readonly string[];
ranges: readonly (readonly [number, number])[];
};

/** Class members are code points, and a range keeps its original interval bounds. */
function characterClass(body: string): CharacterClass {
const members = [...body];
const literals: string[] = [];
const ranges: Array<readonly [number, number]> = [];
for (let index = 0; index < members.length; index += 1) {
const high = index + 2 < members.length && members[index + 1] === "-"
? members[index + 2].codePointAt(0)
: undefined;
const low = high === undefined ? undefined : members[index].codePointAt(0);
if (low !== undefined && high !== undefined) {
ranges.push([low, high]);
index += 2;
} else if (body[index] === value) {
matches = true;
} else {
literals.push(members[index]);
}
}
return { end, matches: negated ? !matches : matches };
return {literals, ranges};
}

function classContains(members: CharacterClass, character: string): boolean {
const code = character.codePointAt(0);
if (code === undefined) return false;
return members.literals.includes(character) ||
members.ranges.some(([low, high]) => low <= code && code <= high);
}

/**
* Code points whose canonical fold is not themselves, grouped by fold value.
* Class membership needs this inverse: walking an interval is unbounded, while a
* fold class holds at most a handful of code points, so an interval of any width
* is decided by a few comparisons. The inverse is only discoverable by folding
* the code space, so it is built once on first use.
*/
let foldPreimages: ReadonlyMap<string, readonly string[]> | null = null;

function foldPreimagesByFold(): ReadonlyMap<string, readonly string[]> {
const cached = foldPreimages;
if (cached !== null) return cached;
const preimages = new Map<string, string[]>();
for (let start = 0; start <= 0x10ffff; start += 0x400) {
const end = Math.min(start + 0x3ff, 0x10ffff);
let block = "";
for (let code = start; code <= end; code += 1) {
if (code < 0xd800 || code > 0xdfff) block += String.fromCodePoint(code);
}
// A block without a case mapping cannot hold a non-identity fold.
if (block.toUpperCase() === block && block.toLowerCase() === block) continue;
for (let code = start; code <= end; code += 1) {
if (code >= 0xd800 && code <= 0xdfff) continue;
const character = String.fromCodePoint(code);
const folded = foldCharacter(character);
if (folded === character) continue;
const bucket = preimages.get(folded);
if (bucket === undefined) preimages.set(folded, [character]);
else bucket.push(character);
}
}
foldPreimages = preimages;
return preimages;
}

/** Longest fold one code point expands to; derived rather than assumed. */
let foldConsumptionLimit = 0;

function maximumFoldConsumption(): number {
if (foldConsumptionLimit === 0) {
let limit = 1;
for (const folded of foldPreimagesByFold().keys()) {
limit = Math.max(limit, [...folded].length);
}
foldConsumptionLimit = limit;
}
return foldConsumptionLimit;
}

/**
* A class member covers the compared text when the two share a fold value, so
* `[Σ]` covers ς, `[ß]` covers SS, and `[ß-…]` covers both through its own
* bounds. Fold classes are compared instead of walking interval members, so a
* wide range costs the same as a narrow one.
*/
function classMemberMatches(body: string, candidate: string): boolean {
const members = characterClass(body);
const folded = caseFold(candidate);
const preimages = foldPreimagesByFold().get(folded) ?? [];
if (preimages.some((character) => classContains(members, character))) return true;
// A single code point whose fold is itself is its own preimage.
const own = singleCodePoint(folded);
return own !== null && classContains(members, own);
}

/**
* A class consumes one code point by default, and several when the compared text
* folds to the same value as a member (ß/SS, ffi/FFI).
*/
function classMatchLengths(body: string, symbols: readonly string[], start: number): number[] {
const lengths: number[] = [];
for (let length = 1; length <= maximumFoldConsumption() && start + length <= symbols.length; length += 1) {
if (classMemberMatches(body, symbols.slice(start, start + length).join(""))) {
lengths.push(length);
}
}
return lengths;
}

/** A folded span can stand for one code point when it is a one-code-point fold or a mapped character. */
function singleCharacterMatchLengths(symbols: readonly string[], start: number): number[] {
const lengths: number[] = [];
const preimages = foldPreimagesByFold();
for (let length = 1; length <= maximumFoldConsumption() && start + length <= symbols.length; length += 1) {
const candidate = symbols.slice(start, start + length).join("");
if ((length === 1 && caseFold(candidate) === candidate) || preimages.has(candidate)) {
lengths.push(length);
}
}
return lengths;
}

/**
* Match against the compared path's folded code points. One original code point
* may fold to several symbols (ß/SS), so token boundaries intentionally operate
* on the folded stream rather than assuming one symbol per host character.
*/
function fnmatchcase(value: string, pattern: string): boolean {
const symbols = [...caseFold(value)];
const tokens = globTokens(pattern);
const memo = new Map<string, boolean>();
const match = (valueIndex: number, patternIndex: number): boolean => {
const key = `${valueIndex}:${patternIndex}`;
const match = (valueIndex: number, tokenIndex: number): boolean => {
const key = `${valueIndex}:${tokenIndex}`;
const cached = memo.get(key);
if (cached !== undefined) return cached;
let result: boolean;
if (patternIndex === pattern.length) {
result = valueIndex === value.length;
} else if (pattern[patternIndex] === "*") {
result = match(valueIndex, patternIndex + 1) ||
(valueIndex < value.length && match(valueIndex + 1, patternIndex));
} else if (valueIndex === value.length) {
result = false;
} else if (pattern[patternIndex] === "?") {
result = match(valueIndex + 1, patternIndex + 1);
} else if (pattern[patternIndex] === "[") {
const characterClass = classMatch(pattern, patternIndex, value[valueIndex]);
result = characterClass === null
? value[valueIndex] === "[" && match(valueIndex + 1, patternIndex + 1)
: characterClass.matches && match(valueIndex + 1, characterClass.end + 1);
if (tokenIndex === tokens.length) {
result = valueIndex === symbols.length;
} else {
result = value[valueIndex] === pattern[patternIndex] &&
match(valueIndex + 1, patternIndex + 1);
const token = tokens[tokenIndex];
if (token.kind === "star") {
result = match(valueIndex, tokenIndex + 1) ||
(valueIndex < symbols.length && match(valueIndex + 1, tokenIndex));
} else if (valueIndex === symbols.length) {
result = false;
} else if (token.kind === "question") {
result = singleCharacterMatchLengths(symbols, valueIndex)
.some((length) => match(valueIndex + length, tokenIndex + 1));
} else if (token.kind === "class") {
const lengths = classMatchLengths(token.body, symbols, valueIndex);
result = token.negated
? singleCharacterMatchLengths(symbols, valueIndex).some((length) =>
!lengths.includes(length) && match(valueIndex + length, tokenIndex + 1))
: lengths.some((length) => match(valueIndex + length, tokenIndex + 1));
} else {
const literal = [...token.folded];
const end = valueIndex + literal.length;
result = end <= symbols.length &&
literal.every((symbol, index) => symbols[valueIndex + index] === symbol) &&
match(end, tokenIndex + 1);
}
}
memo.set(key, result);
return result;
Expand All @@ -201,7 +398,9 @@ function scopeLiteralPrefix(scope: string): string {
}

function scopePairOverlaps(left: string, right: string): boolean {
if (left === right) return true;
const equal = (first: string, second: string) => first === second ||
caseFold(first) === caseFold(second);
if (equal(left, right)) return true;
if (["*", "**", "./"].includes(left) || ["*", "**", "./"].includes(right)) {
return true;
}
Expand All @@ -210,23 +409,23 @@ function scopePairOverlaps(left: string, right: string): boolean {
if (leftGlob && !rightGlob) {
const prefix = scopeLiteralPrefix(left);
return fnmatchcase(right, left) ||
(prefix.endsWith("/") && right.replace(/\/$/u, "") === prefix.replace(/\/$/u, ""));
(prefix.endsWith("/") && equal(right.replace(/\/$/u, ""), prefix.replace(/\/$/u, "")));
}
if (rightGlob && !leftGlob) {
const prefix = scopeLiteralPrefix(right);
return fnmatchcase(left, right) ||
(prefix.endsWith("/") && left.replace(/\/$/u, "") === prefix.replace(/\/$/u, ""));
(prefix.endsWith("/") && equal(left.replace(/\/$/u, ""), prefix.replace(/\/$/u, "")));
}
if (leftGlob && rightGlob) {
const leftPrefix = scopeLiteralPrefix(left);
const rightPrefix = scopeLiteralPrefix(right);
const leftPrefix = caseFold(scopeLiteralPrefix(left));
const rightPrefix = caseFold(scopeLiteralPrefix(right));
return !leftPrefix || !rightPrefix || leftPrefix.startsWith(rightPrefix) ||
rightPrefix.startsWith(leftPrefix);
}
const leftRoot = left.replace(/\/$/u, "");
const rightRoot = right.replace(/\/$/u, "");
return (left.endsWith("/") && right.startsWith(`${leftRoot}/`)) ||
(right.endsWith("/") && left.startsWith(`${rightRoot}/`));
const leftRoot = caseFold(left.replace(/\/$/u, ""));
const rightRoot = caseFold(right.replace(/\/$/u, ""));
return (left.endsWith("/") && caseFold(right).startsWith(`${leftRoot}/`)) ||
(right.endsWith("/") && caseFold(left).startsWith(`${rightRoot}/`));
}

function writeScopesOverlap(left: readonly string[], right: readonly string[]): boolean {
Expand Down
Loading
Loading