UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,457 lines • 63.5 kB
import { literalTildeSpelling } from "../../shell/lex.js";
import { PROGRAM_ROWS } from "./program-read-model.rows.js";
export const DATUM_FORMS = Object.freeze(["count", "pattern", "name-pattern", "label", "message", "delimiter"]);
export const PATH_VALUE_FORMS = Object.freeze(["indirect", "form", "header", "url"]);
export const OPTION_MODES = Object.freeze(["banner", "interactive", "names-program", "follows", "repeats", "quiet", "forced", "queries"]);
export const ARG_ROLE_KINDS = Object.freeze(["path", "mount-source", "object-path", "program-text", "program-file", "shell-text", "command", "assignment", "binds-name", "entry-probe", "datum"]);
export const PATH_EFFECTS = Object.freeze(["read", "names", "write", "create", "edit-in-place"]);
export const WRITING_EFFECTS = new Set(["write", "create", "edit-in-place"]);
const ROWS_BY_KEY = new Map(PROGRAM_ROWS.map((r) => [r.key, r]));
export function programRow(name) {
    const exact = ROWS_BY_KEY.get(name);
    if (exact !== undefined)
        return exact;
    const stripped = name.replace(/\d+(?:\.\d+)*$/, "");
    const row = stripped === name ? undefined : ROWS_BY_KEY.get(stripped);
    return row?.versioned === true ? row : undefined;
}
export function programRows() {
    return PROGRAM_ROWS;
}
export const LAUNCHER_NAMES = new Set(PROGRAM_ROWS.filter((r) => r.launcher !== undefined).map((r) => r.key));
export const BLOCK_WORD_NAMES = new Set(PROGRAM_ROWS.filter((r) => r.syntax === "block").map((r) => r.key));
export function runsShellText(name) {
    return programRow(name)?.reentry?.kind === "shell";
}
export function feedsArgvFromStdin(name) {
    return programRow(name)?.reentry?.kind === "argv";
}
export function streamDefaultProgram(name) {
    const r = programRow(name)?.reentry;
    return r?.kind === "argv" ? r.defaultProgram : undefined;
}
export function opensNoOperand(name) {
    const rest = programRow(name)?.operands?.rest;
    return rest?.kind === "datum" || rest?.kind === "entry-probe";
}
export function evaluatesOperands(name) {
    return programRow(name)?.operands?.evaluates === true;
}
export function assignmentArgumentsAreNotPaths(name) {
    return programRow(name)?.operands?.assignmentArguments === "not-paths";
}
export function isDeclarationBuiltin(name) {
    return programRow(name)?.builtin === "declaration";
}
export function declaredAttributes(row, args) {
    const out = new Set(row?.assigns !== undefined ? [row.assigns] : []);
    for (const s of selectedOptions(row, args))
        if (s.assigns !== undefined)
            out.add(s.assigns);
    if (row === undefined)
        return out;
    if (readArgRoles(row, args).some((e) => e.kind === "option" && e.reading === "speculative"))
        out.add("unsettled");
    const plus = row.grammars.find((g) => g.plusShort !== undefined)?.plusShort;
    if (plus === undefined)
        return out;
    const end = args.indexOf("--");
    for (const a of end < 0 ? args : args.slice(0, end)) {
        if (/^\+./.test(a) && [...a.slice(1)].some((ch) => plus[ch] === undefined || plus[ch]?.assigns === "unsettled"))
            out.add("unsettled");
    }
    return out;
}
export function isInertBuiltin(name) {
    const b = programRow(name)?.builtin;
    return b === "inert" || b === "declaration";
}
export function movesBaseByStack(name) {
    return programRow(name)?.movesBase === "stack";
}
export function longOptionNameOf(tok) {
    if (!tok.startsWith("--") || tok.length === 2)
        return undefined;
    const body = tok.slice(2);
    const eq = body.indexOf("=");
    const name = eq >= 0 ? body.slice(0, eq) : body;
    return name.length > 0 ? name : undefined;
}
const abbreviates = (g) => g.acceptsAbbreviations !== false;
export function isLongOptionSpellingOf(name, full, accepts) {
    return full === name || (accepts && full.startsWith(name));
}
const MERGED = new WeakMap();
function mergedTable(row, table) {
    let byTable = MERGED.get(row);
    if (byTable === undefined) {
        byTable = new Map();
        MERGED.set(row, byTable);
    }
    const cached = byTable.get(table);
    if (cached !== undefined)
        return cached;
    const out = new Map();
    const names = new Set(row.grammars.flatMap((g) => Object.keys(g[table] ?? {})));
    for (const name of names) {
        const declaring = row.grammars.filter((g) => g[table]?.[name] !== undefined);
        const specs = declaring.map((g) => g[table][name]);
        const arities = new Set(specs.map((s) => s.arity));
        const widest = specs.reduce((a, b) => (roleRank(b.value) > roleRank(a.value) ? b : a));
        out.set(name, { arity: arities.size === 1 ? specs[0].arity : "disputed", spec: widest, takesValueEverywhere: specs.every((s) => s.arity === 1), ownsRestEverywhere: specs.every((s) => s.arity !== 0), takesValueSomewhere: specs.some((s) => s.arity === 1), abbreviable: declaring.some(abbreviates) });
    }
    byTable.set(table, out);
    return out;
}
function preciseRole(role) {
    if (role === undefined)
        return false;
    return role.kind === "path" || role.kind === "datum" || role.kind === "mount-source" || role.kind === "object-path" || role.kind === "program-text" || role.kind === "program-file";
}
export const READ_LANGUAGES = new Set(["sed"]);
const PROGRAM_TEXT_OPENS = {
    sed: "parsed",
    arithmetic: "arithmetic",
    jq: ["import", "include"],
    awk: ["getline", "system", "ARGV", "ARGC", "@", "(", "|"],
    bc: "never",
};
export function programTextOpensOf(language) {
    return PROGRAM_TEXT_OPENS[language] ?? "any";
}
export function programTextMayOpen(language, text) {
    const opens = programTextOpensOf(language);
    if (opens === "any")
        return true;
    if (typeof opens === "string")
        return false;
    return opens.some((t) => (/^\w+$/.test(t) ? new RegExp(`(?<![\\w$])${t}(?!\\w)`).test(text) : text.includes(t)));
}
function roleRank(role) {
    if (role?.kind !== "path")
        return 0;
    switch (role.valueForm) {
        case "indirect": return 5;
        case undefined: return 4;
        case "form": return 3;
        case "header": return 2;
        case "url": return 1;
    }
}
function viewTable(row, table, view) {
    const out = new Map();
    for (const [name, m] of mergedTable(row, table)) {
        const role = m.spec.value;
        const spec = m.spec;
        const abbreviable = m.abbreviable;
        if (view === "roles") {
            if (m.arity === 0)
                out.set(name, { arity: 0, spec, abbreviable });
            else if (m.arity === 1)
                out.set(name, { arity: 1, spec, abbreviable, ...(role !== undefined ? { role } : {}) });
            else if (m.arity === "suffix")
                out.set(name, { arity: "suffix", spec, abbreviable, ...(role !== undefined ? { role } : {}) });
            else if (m.arity === "attached" || (m.arity === "disputed" && table === "long"))
                out.set(name, { arity: "attached", spec, abbreviable, ...(role !== undefined ? { role } : {}) });
            continue;
        }
        if (m.arity === 0)
            out.set(name, { arity: 0, spec, abbreviable });
        else if (m.arity === "disputed") {
            if (table === "long" && preciseRole(role))
                out.set(name, { arity: "attached", role: role, spec, abbreviable });
        }
        else if (m.arity === 1 && preciseRole(role))
            out.set(name, { arity: 1, role: role, spec, abbreviable });
        else if (m.arity === "attached" && table === "long" && preciseRole(role))
            out.set(name, { arity: "attached", role: role, spec, abbreviable });
    }
    return out;
}
function readOptionIn(row, tok, view, next) {
    if (!tok.startsWith("-") || tok === "-" || tok === "--")
        return { form: "unexplained" };
    if (tok.startsWith("--")) {
        const long = longOptionNameOf(tok);
        if (long === undefined)
            return { form: "unexplained" };
        const longs = viewTable(row, "long", view);
        const eq = tok.indexOf("=");
        const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
        const withValue = (role, spec) => (attached !== undefined ? { form: "attached", value: attached, role, ...(spec !== undefined ? { spec } : {}) } : { form: "separated", role, ...(spec !== undefined ? { spec } : {}) });
        const exact = longs.get(long);
        if (exact !== undefined) {
            if (exact.arity === 1)
                return withValue(exact.role, exact.spec);
            if (exact.arity === "attached")
                return attached !== undefined ? { form: "attached", value: attached, role: exact.role, spec: exact.spec } : { form: "no-value" };
            return attached === undefined ? { form: "no-value" } : { form: "unexplained" };
        }
        const reach = [...longs.entries()].filter(([full, s]) => isLongOptionSpellingOf(long, full, s.abbreviable));
        const valueNames = reach.filter(([, s]) => s.arity === 1 || (attached !== undefined && s.arity === "attached"));
        const plainNames = reach.filter(([, s]) => s.arity === 0 || (attached === undefined && s.arity === "attached"));
        if (valueNames.length === 0)
            return plainNames.length > 0 && attached === undefined ? { form: "no-value" } : { form: "unexplained" };
        const roles = valueNames.map(([, s]) => s.role);
        const only = valueNames.length === 1 ? valueNames[0][1].spec : undefined;
        if (attached !== undefined)
            return { form: "attached", value: attached, role: roles.reduce((a, b) => (roleRank(b) > roleRank(a) ? b : a)), ...(only !== undefined ? { spec: only } : {}) };
        if (plainNames.length > 0 || new Set(roles.map(roleKey)).size !== 1)
            return { form: "unexplained" };
        return withValue(roles[0], only);
    }
    const shorts = viewTable(row, "short", view);
    const dashWords = viewTable(row, "dashWords", view);
    const word = dashWords.size > 0 ? /^-([A-Za-z_][\w-]+)$/.exec(tok)?.[1] : undefined;
    if (word !== undefined) {
        if (shorts.get(word[0])?.arity === 1)
            return { form: "unexplained" };
        const spec = dashWords.get(word);
        if (spec?.arity === 1)
            return { form: "separated", role: spec.role, spec: spec.spec };
        if (spec?.arity === 0)
            return { form: "no-value" };
        return { form: "unexplained" };
    }
    for (let i = 1; i < tok.length; i++) {
        const spec = shorts.get(tok[i]);
        if (spec?.arity === 1) {
            const rest = tok.slice(i + 1);
            return rest !== "" ? { form: "attached", value: rest, role: spec.role, spec: spec.spec } : { form: "separated", role: spec.role, spec: spec.spec };
        }
        if (spec?.arity === "attached" || spec?.arity === "suffix") {
            const rest = tok.slice(i + 1);
            if (rest !== "")
                return { form: "attached", value: rest, role: spec.role, spec: spec.spec };
            return spec.arity === "suffix" && (view === "roles" || suffixSpelled(next)) ? { form: "separated", role: spec.role, spec: spec.spec } : { form: "no-value" };
        }
        if (spec?.arity !== 0)
            return { form: "unexplained" };
    }
    return { form: "no-value" };
}
export function readOptionSlot(row, tok) {
    const r = readOptionIn(row, tok, "candidates");
    if (r.form === "attached" || r.form === "separated")
        return r.role === undefined ? { form: "unexplained" } : { ...r, role: r.role };
    return r;
}
export function readOptionRole(row, tok, next) {
    return readOptionIn(row, tok, "roles", next);
}
function suffixSpelled(tok) {
    return tok !== undefined && (tok === "" || tok.startsWith("."));
}
const roleKey = (r) => (r === undefined ? "?" : r.kind === "path" ? `path:${r.valueForm ?? ""}` : JSON.stringify(r));
export function firstOperandSlot(row, view = "candidates") {
    const role = row?.operands?.positional?.[0];
    if (row === undefined || role === undefined || (view === "candidates" && !preciseRole(role)))
        return undefined;
    const shorts = [...mergedTable(row, "short")];
    const letters = shorts.filter(([, m]) => m.spec.fillsSlot === true).map(([l]) => l).join("");
    const valueLetters = shorts.filter(([, m]) => m.spec.fillsSlot !== true && m.ownsRestEverywhere).map(([l]) => l).join("");
    const supplying = [...mergedTable(row, "long")].filter(([, m]) => m.spec.fillsSlot === true);
    return { role, suppliedByLetters: letters, suppliedByLongNames: supplying.map(([n]) => n), abbreviableLongNames: supplying.filter(([, m]) => m.abbreviable).map(([n]) => n), valueLetters };
}
export function suppliesFirstOperandSlot(slot, tok) {
    if (tok.startsWith("--")) {
        const long = longOptionNameOf(tok);
        return long !== undefined && slot.suppliedByLongNames.some((full) => isLongOptionSpellingOf(long, full, slot.abbreviableLongNames.includes(full)));
    }
    if (!tok.startsWith("-"))
        return false;
    for (let i = 1; i < tok.length; i++) {
        const ch = tok[i];
        if (!/[A-Za-z0-9]/.test(ch))
            return false;
        if (slot.suppliedByLetters.includes(ch))
            return true;
        if (slot.valueLetters.includes(ch))
            return false;
    }
    return false;
}
export function optionsStandAnywhere(row) {
    return row !== undefined && row.grammars.length > 0 && row.grammars.every((g) => g.afterFirstOperand === "options");
}
function localFileUrlPath(value) {
    const m = /^file:(?:\/\/[^/?#]*)?(\/[^?#]*)/i.exec(value.trim());
    if (m === null)
        return undefined;
    const raw = m[1];
    try {
        return decodeURIComponent(raw);
    }
    catch {
        return raw;
    }
}
function formSpecFileNames(value) {
    const out = [];
    const after = (s) => {
        const at = s.search(/[@<]/);
        if (at !== -1 && at < s.length - 1)
            out.push(s.slice(at + 1));
    };
    after(value);
    for (const part of value.split(/[;,]/))
        after(part);
    return [...new Set(out)];
}
export const PROGRAM_GLOB_MAX = 256;
function globRange(text) {
    const close = text.indexOf("]");
    if (close < 0)
        return undefined;
    const body = text.slice(0, close);
    const letters = /^([A-Za-z])-(.)(?::\s*\+?(\d+))?$/.exec(body);
    if (letters !== null) {
        const lo = letters[1].charCodeAt(0), hi = letters[2].charCodeAt(0), step = letters[3] !== undefined ? Number(letters[3]) : 1;
        if (step === 0 || (lo === hi && step !== 1) || (lo !== hi && (lo > hi || step > hi - lo || hi - lo > 25)))
            return undefined;
        const values = [];
        for (let c = lo; c <= hi && values.length <= PROGRAM_GLOB_MAX; c += step)
            values.push(String.fromCharCode(c));
        return { values, length: close + 1 };
    }
    const digits = /^(\d+)-[ \t]*(\d+)(?::\s*\+?(\d+))?$/.exec(body);
    if (digits === null)
        return undefined;
    const pad = digits[1].startsWith("0") ? digits[1].length : 0;
    const lo = Number(digits[1]), hi = Number(digits[2]), step = digits[3] !== undefined ? Number(digits[3]) : 1;
    if (!Number.isSafeInteger(lo) || !Number.isSafeInteger(hi) || step === 0 || (lo === hi && step !== 1) || (lo !== hi && (lo > hi || step > hi - lo)))
        return undefined;
    const values = [];
    for (let n = lo; n <= hi && values.length <= PROGRAM_GLOB_MAX; n += step)
        values.push(String(n).padStart(pad, "0"));
    return { values, length: close + 1 };
}
const IPV6_LITERAL = /^\[[0-9A-Fa-f.]*:[0-9A-Fa-f.]*:[0-9A-Fa-f:.]*(?:%[^\]]*)?\]/;
export function programGlobExpansions(value) {
    let out = [""];
    const append = (alternatives) => {
        out = out.flatMap((head) => alternatives.map((tail) => head + tail));
        return out.length <= PROGRAM_GLOB_MAX;
    };
    let literal = "";
    let i = 0;
    while (i < value.length) {
        const ch = value[i];
        if (ch === "\\" && "{[}]".includes(value[i + 1] ?? "\u0000")) {
            literal += value[i + 1];
            i += 2;
            continue;
        }
        if (ch === "}" || ch === "]")
            return undefined;
        if (ch === "[" && IPV6_LITERAL.test(value.slice(i))) {
            const end = value.indexOf("]", i) + 1;
            literal += value.slice(i, end);
            i = end;
            continue;
        }
        if (ch !== "{" && ch !== "[") {
            literal += ch;
            i++;
            continue;
        }
        if (!append([literal]))
            return undefined;
        literal = "";
        if (ch === "[") {
            const range = globRange(value.slice(i + 1));
            if (range === undefined)
                return undefined;
            if (!append(range.values))
                return undefined;
            i += 1 + range.length;
            continue;
        }
        const set = [];
        let element = "";
        let j = i + 1;
        for (;; j++) {
            const c = value[j];
            if (c === undefined || c === "{" || c === "[" || c === "]")
                return undefined;
            if (c === "}") {
                if (j === i + 1)
                    return undefined;
                set.push(element);
                break;
            }
            if (c === ",") {
                set.push(element);
                element = "";
                continue;
            }
            if (c === "\\" && value[j + 1] !== undefined) {
                element += value[++j];
                continue;
            }
            element += c;
        }
        if (!append(set))
            return undefined;
        i = j + 1;
    }
    return append([literal]) ? out : undefined;
}
export function slotValueCandidates(value, role, globs = "expanded") {
    if (role.kind === "mount-source")
        return mountSourceOf(value, role.form);
    if (role.kind === "program-file")
        return [value];
    if (role.kind !== "path")
        return [];
    if (role.kind === "path" && role.programGlob === true && globs === "expanded") {
        const values = programGlobExpansions(value);
        if (values === undefined)
            return undefined;
        const out = [];
        for (const v of values)
            for (const c of slotValueCandidates(v, role, "literal") ?? [])
                if (!out.includes(c))
                    out.push(c);
        return out;
    }
    const form = role.kind === "path" ? role.valueForm : undefined;
    if (form === "url") {
        const local = localFileUrlPath(value);
        return local !== undefined ? [local] : [];
    }
    if (form === "form")
        return formSpecFileNames(value).map(literalTildeSpelling);
    const named = value.startsWith("@") && value.length > 1 ? literalTildeSpelling(value.slice(1)) : undefined;
    if (form === "header")
        return named !== undefined ? [value, named] : [];
    const out = [value];
    if (form === "indirect" && named !== undefined)
        out.push(named);
    return out;
}
function mountSourceOf(value, form) {
    const source = form === "volume" ? value.split(":")[0] : value.split(",").map((kv) => /^(?:source|src)=(.*)$/i.exec(kv)?.[1]).find((v) => v !== undefined) ?? "";
    return source !== "" && (source.includes("/") || source.startsWith(".") || source.startsWith("~")) ? [source] : [];
}
export function objectPathsOf(value) {
    if (/^[A-Za-z][\w+.-]*:\/\//.test(value))
        return [];
    const out = [];
    for (let i = value.indexOf(":"); i >= 0; i = value.indexOf(":", i + 1)) {
        const path = value.slice(i + 1);
        if (path !== "" && !out.includes(path))
            out.push(path);
    }
    return out;
}
function walkArgs(row, words, readOption, slot, readsRole, ownedValue) {
    const out = [];
    const valueAt = new Set();
    const folded = words.map((w) => w.folded);
    const unsettling = (at, anyOption) => (folded[at] === "--" || (anyOption && folded[at].startsWith("-") && folded[at] !== "-") ? { unsettled: true } : {});
    const plain = slot === undefined ? [] : walkArgs(row, words, readOption, undefined, readsRole);
    const eoo = plain.find((e) => e.kind === "end-of-options")?.at ?? -1;
    const operandsPast = new Set(plain.flatMap((e) => (e.kind === "dash-word-operand" ? [e.at] : [])));
    const notOptions = new Set(plain.flatMap((e) => (e.kind === "value" && (e.consumed || operandsPast.has(e.of)) ? [e.at] : [])));
    const slotSupplied = slot !== undefined && folded.some((t, k) => (eoo === -1 || k < eoo) && !notOptions.has(k) && suppliesFirstOperandSlot(slot, t));
    const permutes = !optionsStandAnywhere(row);
    const dashRead = row?.operands?.dash === "file" ? "file" : "stdin";
    const plus = row?.grammars.some((g) => g.plusOptions === true) === true;
    let sawOperand = false;
    let ordinal = 0;
    let endOfOptions = false;
    for (let k = 0; k < words.length; k++) {
        if (words[k].skip === true)
            continue;
        const t = plus && /^\+[^+]/.test(folded[k]) ? `-${folded[k].slice(1)}` : folded[k];
        if (!endOfOptions && t === "--") {
            endOfOptions = true;
            out.push({ kind: "end-of-options", at: k });
            continue;
        }
        if (!endOfOptions && t.startsWith("-") && t !== "-") {
            const r = row === undefined ? { form: "unexplained" } : readOption(t, folded[k + 1]);
            const both = sawOperand && permutes;
            if (both) {
                out.push({ kind: "dash-word-operand", at: k });
                if (r.form === "separated" && k + 1 < words.length) {
                    out.push({ kind: "value", at: k + 1, of: k, role: r.role, consumed: false, ...(r.spec !== undefined ? { spec: r.spec } : {}), ...unsettling(k + 1, false) });
                    valueAt.add(k + 1);
                }
            }
            const owned = r.form === "no-value" || r.form === "unexplained" ? (valueAt.has(k) ? undefined : ownedValue?.(t, folded[k + 1])) : undefined;
            const maybeValue = () => {
                if (owned !== undefined && k + 1 < words.length) {
                    out.push({ kind: "value", at: k + 1, of: k, role: owned.role, consumed: false, ...(owned.spec !== undefined ? { spec: owned.spec } : {}), ...unsettling(k + 1, true) });
                    valueAt.add(k + 1);
                }
            };
            if (r.form === "no-value") {
                out.push({ kind: "option", at: k, reading: "no-value" });
                maybeValue();
                continue;
            }
            if (r.form === "attached") {
                out.push({ kind: "option", at: k, reading: { attached: r.value, role: r.role, ...(r.spec !== undefined ? { spec: r.spec } : {}) } });
                continue;
            }
            if (r.form === "separated" && !both) {
                if (k + 1 < words.length)
                    out.push({ kind: "value", at: k + 1, of: k, role: r.role, consumed: true, ...(r.spec !== undefined ? { spec: r.spec } : {}) });
                k++;
                continue;
            }
            out.push({ kind: "option", at: k, reading: "speculative" });
            maybeValue();
            continue;
        }
        const inSlot = slot !== undefined && !slotSupplied && !sawOperand;
        sawOperand = true;
        const declared = inSlot ? undefined : slot !== undefined ? row?.operands?.positional?.[slotSupplied ? ordinal + 1 : ordinal] ?? row?.operands?.rest : row?.operands?.rest;
        const role = declared !== undefined && readsRole(declared) ? declared : undefined;
        out.push({ kind: "operand", at: k, ordinal: ordinal++, ...(inSlot ? { slot: slot.role } : {}), ...(role !== undefined ? { role } : {}), ...(t === "-" ? { dash: dashRead } : {}) });
    }
    return out;
}
export function readArgs(row, words, opts) {
    const slot = opts.firstOperandSlot === false ? undefined : firstOperandSlot(row);
    const owned = opts.ownedValues === true && row?.traversal?.discloses === "contents"
        ? (tok, next) => {
            const v = bareOptionValueOf(row, tok, next);
            return v !== undefined && preciseRole(v.role) ? v : undefined;
        }
        : undefined;
    return walkArgs(row, words, (tok) => readOptionSlot(row, tok), opts.firstOperandSlot === "program-text" && slot?.role.kind !== "program-text" ? undefined : slot, preciseRole, owned);
}
export function readArgRoles(row, args) {
    return walkArgs(row, args.map((folded) => ({ folded })), (tok, next) => readOptionRole(row, tok, next), firstOperandSlot(row, "roles"), () => true);
}
function keyWordIndex(row, args) {
    if (row.traversal?.keyWordLetters === undefined)
        return undefined;
    const at = args.findIndex((t) => t.length > 0);
    return at >= 0 && /^[A-Za-z]+$/.test(args[at]) ? at : undefined;
}
export function deliversOperands(row, args) {
    if (row === undefined)
        return false;
    const key = keyWordIndex(row, args);
    if (key !== undefined) {
        const shorts = mergedTable(row, "short");
        if ([...args[key]].some((ch) => shorts.get(ch)?.spec.delivers === "operands"))
            return true;
    }
    return selectedOptions(row, key === undefined ? args : args.filter((_, k) => k !== key)).some((s) => s.delivers === "operands");
}
export function selectsContentsTraversal(row, args) {
    const model = row?.traversal;
    if (row === undefined || model === undefined || model.discloses !== "contents")
        return false;
    if (model.always === true)
        return true;
    if (model.keyWordLetters !== undefined) {
        const at = keyWordIndex(row, args);
        if (at !== undefined && [...args[at]].some((ch) => model.keyWordLetters.includes(ch)))
            return true;
    }
    const shorts = mergedTable(row, "short");
    const longs = mergedTable(row, "long");
    let endOfOptions = false;
    for (let k = 0; k < args.length; k++) {
        const t = args[k];
        if (endOfOptions)
            continue;
        if (t === "--") {
            const prev = k > 0 ? args[k - 1] : undefined;
            const prevMayOwnValue = prev !== undefined && prev.startsWith("--") && prev.length > 2 && !prev.includes("=");
            if (!prevMayOwnValue)
                endOfOptions = true;
            continue;
        }
        if (t.startsWith("--")) {
            const long = longOptionNameOf(t);
            if (long === undefined)
                continue;
            if ([...longs].some(([full, m]) => m.spec.selects === "traversal" && isLongOptionSpellingOf(long, full, m.abbreviable)))
                return true;
            for (const [full, m] of longs) {
                const recursive = m.spec.selectsWhenValue;
                if (recursive === undefined || !isLongOptionSpellingOf(long, full, m.abbreviable))
                    continue;
                const eq = t.indexOf("=");
                const v = eq >= 0 ? t.slice(eq + 1) : args[k + 1];
                if (v !== undefined && v.length > 0 && recursive.startsWith(v.toLowerCase()))
                    return true;
            }
            continue;
        }
        if (!t.startsWith("-") || t === "-")
            continue;
        for (let i = 1; i < t.length; i++) {
            const ch = t[i];
            const m = shorts.get(ch);
            if (m?.spec.selects === "traversal")
                return true;
            if (m?.spec.selectsWhenValue !== undefined) {
                const v = i === t.length - 1 ? args[k + 1] : t.slice(i + 1);
                if (v !== undefined && v.length > 0 && m.spec.selectsWhenValue.startsWith(v.toLowerCase()))
                    return true;
                if (i === t.length - 1)
                    k++;
                break;
            }
            if (m?.takesValueEverywhere === true) {
                if (i === t.length - 1)
                    k++;
                break;
            }
            if (!/[A-Za-z0-9]/.test(ch))
                break;
        }
    }
    return false;
}
export function walksCwdWithoutOperand(row) {
    return row !== undefined && row.grammars.some((g) => g.walksCwdWithoutOperand === true);
}
export function valuesAreLiteral(row, args) {
    if (row === undefined || !row.grammars.some((g) => [...Object.values(g.short), ...Object.values(g.long)].some((s) => s.literalValues === true)))
        return false;
    const shorts = mergedTable(row, "short");
    const longs = mergedTable(row, "long");
    const negates = (t) => {
        const name = t.startsWith("--no-") ? longOptionNameOf(t)?.slice(3) : undefined;
        return name !== undefined && name.length > 0 && [...longs].some(([full, m]) => m.spec.literalValues === true && isLongOptionSpellingOf(name, full, m.abbreviable));
    };
    if (args.some(negates))
        return false;
    let doubt = false;
    for (const e of readArgRoles(row, args)) {
        if (e.kind === "option" && e.reading === "speculative") {
            doubt = true;
            continue;
        }
        const at = e.kind === "option" ? e.at : e.kind === "value" ? e.of : undefined;
        if (at === undefined || doubt)
            continue;
        const t = args[at];
        if (t.startsWith("--")) {
            const long = longOptionNameOf(t);
            if (long === undefined || t.includes("="))
                continue;
            const exact = longs.get(long);
            const hits = exact !== undefined ? [exact] : [...longs].filter(([full, m]) => isLongOptionSpellingOf(long, full, m.abbreviable)).map(([, m]) => m);
            if (hits.length === 1 && hits[0].spec.literalValues === true)
                return true;
            continue;
        }
        for (const ch of t.slice(1)) {
            const m = shorts.get(ch);
            if (m === undefined || m.arity !== 0)
                break;
            if (m.spec.literalValues === true)
                return true;
        }
    }
    return false;
}
export function dashOperandIsStdin(row) {
    return row?.operands?.dash !== "file";
}
export function declaresOption(row, pred) {
    return row.grammars.some((g) => [...Object.values(g.short), ...Object.values(g.long), ...Object.values(g.dashWords ?? {})].some(pred));
}
const isDatumValue = (m) => m.spec.value?.kind === "datum" && m.spec.fillsSlot !== true;
const isFileValue = (spec) => spec.value === undefined || spec.value.kind === "path" || spec.value.kind === "program-file";
export function takesSeparatedDatum(row, tok) {
    if (row === undefined)
        return false;
    if (tok.startsWith("--")) {
        if (tok.includes("="))
            return false;
        const name = longOptionNameOf(tok);
        return name !== undefined && [...mergedTable(row, "long")].some(([full, m]) => isLongOptionSpellingOf(name, full, m.abbreviable) && m.takesValueSomewhere && isDatumValue(m));
    }
    if (!/^-[A-Za-z0-9]/.test(tok))
        return false;
    const shorts = mergedTable(row, "short");
    for (let i = 1; i < tok.length; i++) {
        const ch = tok[i];
        const m = shorts.get(ch);
        if (m !== undefined && m.arity !== 0)
            return m.takesValueSomewhere && isDatumValue(m) && i === tok.length - 1;
        if (!/[A-Za-z0-9]/.test(ch))
            return false;
    }
    return false;
}
const COUNT_SPELLING = /^[+-]?[0-9_]+$/;
const UNDECLARED_VALUE = { kind: "datum", form: "label" };
export function bareOptionValueOf(row, tok, next) {
    if (next === undefined || !tok.startsWith("-") || tok === "-" || tok === "--")
        return undefined;
    if (tok.startsWith("--") && !tok.includes("=")) {
        const name = longOptionNameOf(tok);
        if (name !== undefined && ![...mergedTable(row, "long")].some(([full, m]) => isLongOptionSpellingOf(name, full, m.abbreviable)))
            return { role: UNDECLARED_VALUE };
    }
    const owns = (s) => s.value !== undefined && (s.arity === 1 || (s.arity === "attached" && s.value.kind === "datum" && s.value.form === "count" && COUNT_SPELLING.test(next)));
    for (const g of row.grammars) {
        let found;
        if (tok.startsWith("--")) {
            const name = longOptionNameOf(tok);
            if (name === undefined || tok.includes("="))
                continue;
            const exact = g.long[name];
            const reach = exact !== undefined ? [exact] : Object.entries(g.long).filter(([full]) => isLongOptionSpellingOf(name, full, abbreviates(g))).map(([, s]) => s);
            if (reach.length > 0 && reach.every(owns))
                found = reach[0];
        }
        else {
            for (let i = 1; i < tok.length; i++) {
                const spec = g.short[tok[i]];
                if (spec === undefined || spec.arity === 0)
                    continue;
                if (i === tok.length - 1 && owns(spec))
                    found = spec;
                break;
            }
        }
        if (found?.value !== undefined)
            return { role: found.value, spec: found };
    }
    return undefined;
}
export function attachedStdinFile(row, tok) {
    if (row === undefined)
        return false;
    if (tok.startsWith("--")) {
        const eq = tok.indexOf("=");
        const name = longOptionNameOf(tok);
        if (eq < 0 || tok.slice(eq + 1) !== "-" || name === undefined)
            return false;
        const longs = mergedTable(row, "long");
        const exact = longs.get(name);
        const reach = exact !== undefined ? [exact] : [...longs].filter(([full, m]) => isLongOptionSpellingOf(name, full, m.abbreviable)).map(([, m]) => m);
        return reach.length === 0 || reach.some((m) => m.arity !== 0 && isFileValue(m.spec));
    }
    if (!/^-[A-Za-z0-9]/.test(tok))
        return false;
    const shorts = mergedTable(row, "short");
    for (let i = 1; i < tok.length; i++) {
        const ch = tok[i];
        const m = shorts.get(ch);
        if (m !== undefined && m.arity !== 0)
            return tok.slice(i + 1) === "-" && isFileValue(m.spec);
        if (!/[A-Za-z0-9]/.test(ch))
            return false;
    }
    return false;
}
const IMPLEMENTATIONS = new WeakMap();
export function implementationsOf(row) {
    let views = IMPLEMENTATIONS.get(row);
    if (views === undefined) {
        views = row.grammars.length <= 1 ? [row] : row.grammars.map((g) => ({ ...row, grammars: [g] }));
        IMPLEMENTATIONS.set(row, views);
    }
    return views;
}
export function selectedOptions(row, args, under) {
    return row === undefined ? [] : [...scanOptions(row, args, under === "one-implementation").specs];
}
export function selectedSpellings(row, args) {
    const scan = scanOptions(row, args, true);
    return { names: scan.names, values: scan.values };
}
export function implementationRejects(row, view, words) {
    if (view === row)
        return false;
    const declares = (n) => (n.startsWith("--") ? mergedTable(view, "long").has(n.slice(2)) : mergedTable(view, n.length === 2 ? "short" : "dashWords").has(n.slice(1)));
    return selectedSpellings(row, words).names.some((n) => !declares(n));
}
function scanOptions(row, args, one) {
    const stopsAtOperand = one && row.grammars[0]?.afterFirstOperand === "operands";
    const shorts = mergedTable(row, "short");
    const longs = mergedTable(row, "long");
    const dashWords = mergedTable(row, "dashWords");
    const obsolete = row.grammars.flatMap((g) => (g.obsoleteFirst === undefined ? [] : [g.obsoleteFirst]));
    const specs = [];
    const names = [];
    const values = [];
    const operands = [];
    const select = (spec, name) => { specs.push(spec); names.push(name); };
    let endOfOptions = false;
    for (let k = 0; k < args.length; k++) {
        const t = args[k];
        if (endOfOptions) {
            operands.push(k);
            continue;
        }
        const traditional = k === 0 || !one ? obsolete.filter((o) => o.signs.includes(t[0] ?? "") && /^.\d+[A-Za-z]*$/.test(t)) : [];
        if (traditional.length > 0) {
            for (const o of traditional)
                for (const ch of t.replace(/^.\d+/, "")) {
                    const spec = o.letters[ch];
                    if (spec !== undefined)
                        select(spec, `-${ch}`);
                }
            continue;
        }
        if (t === "--") {
            if (one)
                endOfOptions = true;
            continue;
        }
        if (!t.startsWith("-") || t === "-") {
            operands.push(k);
            if (stopsAtOperand)
                endOfOptions = true;
            continue;
        }
        const take = (spec, name, attached) => {
            if (attached !== undefined) {
                values.push({ at: k, value: attached, spec, name });
                return;
            }
            if (one && k + 1 < args.length) {
                values.push({ at: k + 1, spec, name });
                k++;
            }
        };
        if (t.startsWith("--")) {
            const name = longOptionNameOf(t);
            if (name === undefined)
                continue;
            const eq = t.indexOf("=");
            const exact = longs.get(name);
            const hits = exact !== undefined ? [[name, exact]] : [...longs].filter(([full, m]) => isLongOptionSpellingOf(name, full, m.abbreviable));
            for (const [full, m] of hits)
                select(m.spec, `--${full}`);
            const [full, only] = hits[0] ?? [];
            if (hits.length === 1 && (only.arity === 1 || (eq >= 0 && only.arity === "attached")))
                take(only.spec, `--${full}`, eq >= 0 ? t.slice(eq + 1) : undefined);
            else if (one && eq < 0 && hits.length > 1 && hits.every(([, m]) => m.arity === 1))
                k++;
            continue;
        }
        const word = /^-([A-Za-z_][\w-]+)$/.exec(t)?.[1];
        const dash = word === undefined ? undefined : dashWords.get(word);
        if (dash !== undefined) {
            select(dash.spec, t);
            if (one) {
                if (dash.arity === 1)
                    take(dash.spec, t, undefined);
                continue;
            }
        }
        for (let i = 1; i < t.length; i++) {
            const m = shorts.get(t[i]);
            if (m === undefined) {
                if (/[A-Za-z0-9]/.test(t[i]))
                    continue;
                break;
            }
            select(m.spec, `-${t[i]}`);
            if (m.arity === 0)
                continue;
            const rest = t.slice(i + 1);
            if (rest !== "")
                values.push({ at: k, value: rest, spec: m.spec, name: `-${t[i]}` });
            else if (m.arity === 1 || m.arity === "suffix")
                take(m.spec, `-${t[i]}`, undefined);
            break;
        }
    }
    return { specs, names, values, operands };
}
export function writesPath(role) {
    return role?.kind === "path" && WRITING_EFFECTS.has(role.effect);
}
export function writeReadings(row, args) {
    return implementationsOf(row).map((view) => {
        const ops = view.operands;
        const scan = scanOptions(view, args, true);
        const inPlace = scan.specs.some((s) => s.selects === "edit-in-place");
        const unstated = scan.specs.some((s) => s.effect !== "none" && s.selects !== "edit-in-place" && s.writesAt === undefined && s.fillsLast !== true && !writesPath(s.value));
        const ordinary = inPlace && ops?.editedInPlace !== undefined ? ops.editedInPlace : ops?.rest;
        const operandRole = writesPath(ordinary) ? ordinary : writesPath(ops?.last) ? ops.last : undefined;
        const suffixes = scan.values.flatMap((v) => (v.spec.selects === "edit-in-place" ? [v.value ?? args[v.at]] : [])).filter((x) => x !== "");
        const written = [];
        let unexplained = unstated;
        for (const e of readArgRoles(view, args)) {
            if (e.kind === "option") {
                if (e.reading === "speculative")
                    unexplained = true;
                else if (e.reading !== "no-value" && writesPath(e.reading.role))
                    written.push({ at: e.at, value: e.reading.attached, role: e.reading.role });
            }
            else if (e.kind === "value") {
                if (writesPath(e.role))
                    written.push({ at: e.at, role: e.role });
            }
            else if (e.kind === "operand") {
                if (e.dash === "stdin" || e.slot !== undefined)
                    continue;
                const role = e.role ?? ordinary;
                if (writesPath(role))
                    written.push({ at: e.at, role });
            }
            else if (e.kind === "dash-word-operand") {
                if (writesPath(ordinary))
                    written.push({ at: e.at, role: ordinary });
            }
        }
        let copy;
        const last = ops?.last;
        if (writesPath(last)) {
            const filled = scan.values.filter((v) => v.spec.fillsLast === true).at(-1);
            const destination = filled !== undefined ? { at: filled.at, ...(filled.value !== undefined ? { value: filled.value } : {}) } : scan.operands.length > 0 ? { at: scan.operands.at(-1) } : undefined;
            if (destination !== undefined) {
                copy = { destination, sources: scan.operands.filter((at) => at !== destination.at) };
                for (let i = written.length - 1; i >= 0; i--)
                    if (written[i].at === destination.at && written[i].value === destination.value)
                        written.splice(i, 1);
                written.push({ at: destination.at, ...(destination.value !== undefined ? { value: destination.value } : {}), role: last });
            }
        }
        return { written, ...(operandRole !== undefined ? { operandRole } : {}), unexplained, suffixes, ...(copy !== undefined ? { copy } : {}) };
    });
}
export function programDelivery(row, args) {
    const delivery = row?.delivery;
    if (row === undefined || delivery === undefined)
        return undefined;
    const longs = mergedTable(row, "long");
    const dashWords = mergedTable(row, "dashWords");
    const shorts = mergedTable(row, "short");
    const namesByValue = (spec) => (spec.value?.kind === "program-file" && spec.fillsSlot === true) || (spec.mode === "names-program" && spec.arity === 1);
    const positional = row.operands?.positional ?? [];
    const programAt = positional.findIndex((r) => r?.kind === "program-text" || r?.kind === "program-file");
    const programIsText = programAt >= 0 && positional[programAt].kind === "program-text";
    let endOfOptions = false;
    let banner = false;
    let interactive = false;
    let named = false;
    let stdin = false;
    let explained = true;
    let operands = 0;
    scan: for (let i = 0; i < args.length; i++) {
        const arg = args[i];
        if (!endOfOptions && arg === "--") {
            endOfOptions = true;
            continue;
        }
        if (arg === "-") {
            if (!programIsText)
                stdin = true;
            continue;
        }
        if (!endOfOptions && arg.startsWith("-")) {
            const eq = arg.indexOf("=");
            const whole = eq < 0 ? arg : arg.slice(0, eq);
            const valueOf = () => (eq < 0 ? args[++i] : arg.slice(eq + 1));
            const spec = (whole.startsWith("--") ? longs.get(whole.slice(2)) : whole.length > 2 ? dashWords.get(whole.slice(1)) : undefined)?.spec;
            if (spec !== undefined) {
                if (spec.value?.kind === "program-text")
                    return "text";
                if (namesByValue(spec)) {
                    const value = valueOf();
                    if (value === "-") {
                        stdin = true;
                        continue;
                    }
                    if (value !== undefined && row.modules?.[value]?.kind === "program-text")
                        return "text";
                    named = true;
                    if (explained && spec.endsOptions === true)
                        break scan;
                    continue;
                }
                if (spec.mode === "names-program") {
                    named = true;
                    continue;
                }
                if (spec.mode === "banner") {
                    banner = true;
                    continue;
                }
                if (spec.arity === 1) {
                    valueOf();
                    continue;
                }
                continue;
            }
            if (arg.startsWith("--")) {
                if (eq < 0)
                    explained = false;
                continue;
            }
            for (let k = 1; k < arg.length; k++) {
                const ch = arg[k];
                if (ch >= "0" && ch <= "9")
                    continue;
                const s = shorts.get(ch)?.spec;
                if (s?.value?.kind === "program-text")
                    return "text";
                if (s !== undefined && namesByValue(s)) {
                    const value = k + 1 < arg.length ? arg.slice(k + 1) : args[++i];
                    if (value === "-") {
                        stdin = true;
                        break;
                    }
                    if (explained && value !== undefined && row.modules?.[value]?.kind === "program-text")
                        return "text";
                    if (!explained)
                        break;
                    named = true;
                    if (explained && s.endsOptions === true)
                        break scan;
                    break;
                }
                if (s?.arity === "attached") {
                    if (s.mode === "banner")
                        banner = true;
                    break;
                }
                if (s?.mode === "banner") {
                    banner = true;
                    continue;
                }
                if (s?.mode === "interactive") {
                    interactive = true;
                    continue;
                }
                if (s?.arity === 1) {
                    if (k + 1 >= arg.length)
                        i++;
                    break;
                }
                if (s?.arity !== 0)
                    explained = false;
            }
            continue;
        }
        operands++;
        if (!named && explained && programAt >= 0 && operands === programAt + 1) {
            if (programIsText)
                return "text";
            named = true;
            break;
        }
    }
    if (!named && !explained && programIsText && operands > programAt)
        return "text";
    if (stdin || interactive)
        return "stdin";
    if (named || banner)
        return undefined;
    return delivery.stdinWhenUnnamed ? "stdin" : undefined;
}
export function programLanguageOf(row) {
    if (row?.delivery === undefined)
        return undefined;
    const roles = [...(row.operands?.positional ?? []), ...row.grammars.flatMap((g) => [...Object.values(g.short), ...Object.values(g.long), ...Object.values(g.dashWords ?? {})].map((s) => s.value))];
    for (const r of roles)
        if (r?.kind === "program-text" || r?.kind === "program-file")
            return r.language;
    return undefined;
}
export function wordsReadBySomeImplementations(row, args) {
    if (row === undefined || row.grammars.length < 2)
        return new Set();
    const opened = (role) => role === undefined || (role.kind === "path" && (role.effect === "read" || role.effect === "edit-in-place")) || role.kind === "program-file";
    const readings = [];
    for (const view of implementationsOf(row)) {
        const reads = new Set();
        for (const e of readArgRoles(view, args)) {
            if (e.kind === "option" && e.reading === "speculative")
                return new Set();
            if (e.kind === "operand") {
                if (e.slot === undefined && e.dash === undefined && opened(e.role))
                    reads.add(e.at);
            }
            else if (e.kind === "value") {
                if (e.role !== undefined && opened(e.role) && e.consumed)
                    reads.add(e.at);
            }
        }
        readings.push(reads);
    }
    const some = new Set(readings.flatMap((r) => [...r]));
    return new Set([...some].filter((k) => !readings.every((r) => r.has(k))));
}
export function moduleTailOf(row, args) {
    if (row?.modules === undefined)
        return undefined;
    for (const e of readArgRoles(row, args)) {
        const spec = e.kind === "value" ? e.spec : e.kind === "option" && typeof e.reading === "object" ? e.reading.spec : undefined;
        if (spec?.mode !== "names-program")
            continue;
        const name = e.kind === "value" ? args[e.at] : e.kind === "option" && typeof e.reading === "object" ? e.reading.attached : undefined;
        const role = name === undefined || !Object.hasOwn(row.modules, name) ? undefined : row.modules[name];
        if (role !== undefined)
            return { from: e.at + 1, role };
    }
    return undefined;
}
function steppableLauncherOption(row, tok) {
    const m = tok.startsWith("--") ? (tok.includes("=") ? undefined : mergedTable(row, "long").get(tok.slice(2))) : tok.length === 2 && tok.startsWith("-") ? mergedTable(row, "short").get(tok[1]) : undefined;
    if (m === undefined || m.spec.value?.kind === "shell-text")
        return undefined;
    return m.arity !== "disputed" || takesNoWordBare(row, tok) ? m : undefined;
}
function takesNoWordBare(row, spelling) {
    const key = spelling.startsWith("--") ? spelling.slice(2) : spelling.slice(1);
    return row.grammars.every((g) => {
        const s = (spelling.startsWith("--") ? g.long : g.short)[key];
        return s === undefined || s.arity === 0 || s.arity === "attached";
    });
}
export function launcherOptionArity(row, tok) {
    if (row.grammars.length === 0)
        return undefined;
    if (tok === "--")
        return row.grammars.every((g) => g.stepsDashDash === true) ? 0 : undefined;
    const m = steppableLauncherOption(row, tok);
    return m === undefined ? undefined : m.arity === 1 ? 1 : 0;
}
function readLauncherOption(row, tok) {
    if (tok === "-")
        return row.launcher?.bareDash === true ? { step: 1 } : undefined;
    if (tok === "--")
        return launcherOptionArity(row, tok) === 0 ? { step: 1 } : undefined;
    if (tok.startsWith("--")) {
        const eq = tok.indexOf("=");
        const m = steppableLauncherOption(row, eq > 2 ? tok.slice(0, eq) : tok);
        if (m === undefined)
            return undefined;
        if (eq > 2)
            return m.arity === 0 || m.arity === "disputed" ? undefined : { step: 1, valued: m };
        return m.arity === 1 ? { step: 2, valued: m } : { step: 1 };
    }
    if (!/^-[^-]/.test(tok))
        return undefined;
    for (let i = 1; i < tok.length; i++) {
        const m = steppableLauncherOption(row, `-${tok[i]}`);
        if (m === undefined)
            return undefined;
        if (m.arity === 0)
            continue;
        if (i + 1 < tok.length)
            return m.arity === "disputed" ? undefined : { step: 1, valued: m };
        return m.arity === 1 ? { step: 2, valued: m } : { step: 1 };
    }
    return { step: 1 };
}
export function launcherOptionPathRole(row, tok) {
    const role = readLauncherOption(row, tok)?.valued?.spec.value;
    return role?.kind === "path" ? role : undefined;
}
export function launcherOptionMovesBase(row, tok) {
    const moves = (m) => m?.spec.value?.kind === "path" && m.spec.value.base !== undefined;
    if (tok === "-" || tok === "--")
        return false;
    if (tok.startsWith("--")) {
        const eq = tok.indexOf("=");
        return moves(steppableLauncherOption(row, eq > 2 ? tok.slice(0, eq) : tok));
    }
    if (!/^-[^-]/.test(tok))
        return false;
    for (let i = 1; i < tok.length; i++) {
        const m = steppableLauncherOption(row, `-${tok[i]}`);
        if (m === undefined)
            return false;
        if (moves(m))
            return true;
        if (m.arity !== 0)
            return false;
    }
    return false;
}
export function launcherOptionQueries(row, tok) {
    if (tok === "-" || tok === "--")
        return false;
    if (tok.startsWith("--")) {
        const eq = tok.indexOf("=");
        return steppableLauncherOption(row, eq > 2 ? tok.slice(0, eq) : tok)?.spec.mode === "queries";
    }
    if (!/^-[^-]/.test(tok))
        return false;
    for (let i = 1; i < tok.length; i++) {
        const m = steppableLauncherOption(row, `-${tok[i]}`);
        if (m === undefined)
            return false;
        if (m.spec.mode === "queries")
            return true;
        if (m.arity !== 0)
            return false;
    }
    return false;
}
export function launcherOptionStep(row, tok) {
    return readLauncherOption(row, tok)?.step;
}
export function launcherRunsShell(row, args) {
    const l = row.launcher;
    if (l?.shellText === undefined)
        return false;
    const programWith = l.programWith ?? [];
    return !implementationsOf(row).some((view) => selectedSpellings(view, args).names.some((n) => programWith.includes(n)));
}
export function targetUserOf(row, words, operand) {
    const as = row.launcher?.asUser;
    if (as === undefined)
        return undefined;
    const own = selectedSpellings(row, words);
    const user = own.values.filter((v) => as.userWith?.includes(v.name) === true).at(-1);
    const named = user !== undefined ? user.value ?? words[user.at] : operand === undefined ? "root" : operand.name;
    const login = operand?.dash === true || own.names.some((n) => as.loginWith.includes(n));
    const keepsCaller = selectedOptions(row, words).some((o) => o.mode === "names-program");
    const name = named === "#0" ? "root" : named !== undefined && /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(named) ? named : undefined;
    return { ...(name !== undefined ? { name } : {}), home: login ? "target" : as.home === "policy" ? "either" : keepsCaller ? "caller" : "target", login };
}
export function commandLineOptionSpellings(row) {
    if (row === undefined)
        return [];
    return [
        ...[...mergedTable(row, "short")].filter(([, m]) => m.spec.value?.kind === "shell-text").map(([l]) => `-${l}`),
        ...[...mergedTable(row, "long")].filter(([, m]) => m.spec.value?.kind === "shell-text").map(([n]) => `--${n}`),
    ];
}
export function commandLineOf(row, tok) {
    const lines = new Set(commandLineOptionSpellings(row));
    if (tok.startsWith("--")) {
        const eq = tok.indexOf("=");
        if (!lines.has(eq > 2 ? tok.slice(0, eq) : tok))
            return undefined;
        return eq > 2 ? { attached: tok.slice(eq + 1) } : "next";
    }
    if (!/^-[^-]/.test(tok))
        return undefined;
    for (let i = 1; i < tok.length; i++) {
        const letter = `-${tok[i]}`;
        if (lines.has(letter))
            return i + 1 < tok.length ? { attached: tok.slice(i + 1) } : "next";
        if (steppableLauncherOption(row, letter)?.arity !== 0)
            return undefined;
    }
    return undefined;
}
export function readProgramTextLanguage(row) {
    const first = row?.operands?.positional?.[0];
    return first?.kind === "program-text" && READ_LANGUAGES.has(first.language) ? first.language : undefined;
}
export function programTextReadings(row, args) {
    return row.grammars.map((g) => programTextUnder(g, args));
}
function programTextUnder(g, args) {
    const scripts = [];
    let supplied = false;
    let endOfOptions = false;
    let firstOperand;
    const longs = Object.entries(g.long);
    const reach = (long, pick) => longs.some(([full, s]) => pick(s) && isLongOptionSpellingOf(long, full, abbreviates(g)));
    for (let k = 0; k < args.length; k++) {
        const tok = args[k];
        if (!endOfOptions && tok === "--") {
            endOfOptions = true;
            continue;
        }
        if (endOfOptions || !tok.startsWith("-") || tok === "-") {
            if (firstOperand === undefined) {
                firstOperand = tok;
                if (g.afterFirstOperand === "operands")
                    endOfOptions = true;
            }
            continue;
        }
        if (tok.startsWith("--")) {
            const long = longOptionNameOf(tok);
            if (long === undefined)
                return "unparsed";
            const eq = tok.indexOf("=");
            const attached = eq >= 0 ? tok.slice(eq + 1) : undefined;
            if (reach(long, (s) => s.fillsSlot === true && s.value?.kind !== "program-text"))
                return "from-file";
            if (reach(long, (s) => s.fillsSlot === true && s.value?.kind === "program-text")) {
                const value = attached ?? args[++k];
                if (value === undefined)
                    return "unparsed";
                scripts.push(value);
                supplied = true;
                continue;
            }
            if (reach(long, (s) => s.arity === 1)) {
                if (attached === undefined)
                    k++;
                continue;
            }
            if (reach(long, (s) => s.arity === 0 || s.arity === "attached"))
                continue;
            return "unparsed";
        }
        for (let c = 1; c < tok.length; c++) {
            const s = g.short[tok[c]];
            if (s === undefined)
                return "unparsed";
            if (s.arity === "suffix") {
                if (c + 1 === tok.length && suffixSpelled(args[k + 1]))
                    k++;
                break;
            }
            if (s.arity === "attached")
                break;
            if (s.arity === 1) {
                const rest = tok.slice(c + 1);
                const value = rest !== "" ? rest : args[++k];
                if (s.fillsSlot === true && s.value?.kind !== "program-text")
                    return "from-file";
                if (s.fillsSlot === true) {
                    if (value === undefined)
                        return "unparsed";
                    scripts.push(value);
                    supplied = true;
                }
                break;
            }
        }
    }
    const script = supplied ? scripts.join("\n") : firstOperand;
    return script === undefined ? "none" : { text: script };
}
function exactSpec(g, tok) {
    if (tok.startsWith("--"))
        return tok.includes("=") ? undefined : g.long[tok.slice(2)];
    return tok.length === 2 && tok.startsWith("-") ? g.short[tok[1]] : undefined;
}
const isMessage = (s) => s?.value?.kind === "datum" && s.value.form === "message";
const SUBCOMMAND_VIEWS = new WeakMap();
export function subcommandOf(row, words) {
    const global = row?.grammars[0];
    if (row?.subcommands === undefined || global === undefined)
        return undefined;
    let relocated = false;
    let j = 0;
    for (; j < words.length; j++) {
        const w = words[j];
        if (!w.startsWith("-"))
            break;
        const spec = exactSpec(global, w);
        const longWithValue = w.startsWith("--") && w.includes("=") ? global.long[w.slice(2, w.indexOf("="))] : undefined;
        const taken = spec ?? longWithValue;
        if (taken?.value?.kind === "path" && taken.value.base !== undefined)
            relocated = true;
        if (spec?.arity === 1)
            j++;
        else if (spec?.arity !== 0 && longWithValue?.arity !== 1)
            return undefined;
    }
    const name = words[j];
    const sub = name === undefined || !Object.hasOwn(row.subcommands, name) ? undefined : row.subcommands[name];
    if (name === undefined || sub === undefined)
        return undefined;
    let views = SUBCOMMAND_VIEWS.get(row);
    if (views === undefined) {
        views = new Map();
        SUBCOMMAND_VIEWS.set(row, views);
    }
    let view = views.get(name);
    if (view === undefined) {
        view = { key: `${row.key} ${name}`, grammars: [sub.grammar], ...(sub.operands !== undefined ? { operands: sub.operands } : {}), ...(sub.traversal !== undefined ? { traversal: sub.traversal } : {}), witness: row.witness };
        views.set(name, view);
    }
    return { name, at: j, row: view, relocated };
}
export function messageWords(program, words, programIndex) {
    const out = new Set();
    const found = subcommandOf(programRow(program), words.slice(programIndex + 1).map((w) => w.folded));
    if (found === undefined)
        return out;
    const j = programIndex + 1 + found.at;
    const sub = found.row.grammars[0];
    const settled = (k) => words[k] !== undefined && words[k].expands === false;
    const oneArgument = (k) => {
        const pieces = words[k]?.word?.expansions ?? [];
        return pieces.length > 0 && pieces.every((p) => p.expansion?.kind === "command" && p.expansion.yields === "one");
    };
    const longValues = Object.entries(sub.long).filter(([, s]) => s.arity === 1);
    let pending;
    for (let k = j + 1; k < words.length; k++) {
        const w = words[k].folded;
        if (pending !== undefined) {
            if (pending === "message" && (settled(k) || oneArgument(k)))
                out.add(k);
            pending = undefined;
            continue;
        }
        if (w === "--")
            break;
        if (!settled(k)) {
            pending = "other";
            continue;
        }
        if (!w.startsWith("-") || w === "-")
            continue;
        if (w.startsWith("--")) {
            const eq = w.indexOf("=");
            const name = w.slice(2, eq < 0 ? undefined : eq);
            const takers = longValues.filter(([n]) => isLongOptionSpellingOf(name, n, abbreviates(sub)));
            const message = (name.length > 0 && takers.length > 0 && takers.every(([, s]) => isMessage(s))) || isMessage(sub.long[name]);
            if (eq >= 0) {
                if (message)
                    out.add(k);
                continue;
            }
            if (takers.length > 0)
                pending = message ? "message" : "other";
            continue;
        }
        const letters = w.slice(1);
        for (let i = 0; i < letters.length; i++) {
            const s = sub.short[letters[i]];
            if (s?.arity === 0)
                continue;
            if (s?.arity === "attached")
                break;
            if (s?.arity === 1) {
                if (i < letters.length - 1) {
                    if (isMessage(s))
                        out.add(k);
                }
                else
                    pending = isMessage(s) ? "message" : "other";
                break;
            }
            pending = "other";
            break;
        }
    }
    return out;
}
export const SMALL_BYTE_BOUND_MAX = 1_000_000;
export function byteBoundIsSmall(row, args) {
    if (row === undefined)
        return false;
    let byteBound = false;
    for (const e of readArgRoles(row, args)) {
        if (e.kind === "operand" || e.kind === "end-of-options")
            continue;
        if (e.kind === "value" && e.spec?.bounds === "bytes") {
            if (!/^\d+$/.test(args[e.at]) || Number(args[e.at]) > SMALL_BYTE_BOUND_MAX)
                return false;
            byteBound = true;
            continue;
        }
        if (e.kind === "option" && typeof e.reading === "object" && e.reading.spec?.bounds === "bytes") {
            if (!/^\d+$/.test(e.reading.attached) || Number(e.reading.attached) > SMALL_BYTE_BOUND_MAX)
                return false;
            byteBound = true;
            continue;
        }
        return false;
    }
    return byteBound;
}