covertable
Version:
Efficient TypeScript library for pairwise testing, generating minimal covering arrays with constraint support.
675 lines (674 loc) • 21.5 kB
JavaScript
import { C as T } from "./controller-LAPl-CwL.js";
class B extends Error {
constructor(t) {
const n = t.filter((r) => r.severity === "error"), o = n.map((r) => ` [${r.source}#${r.index} line ${r.line}] ${r.message}`).join(`
`);
super(`PictModel has ${n.length} error(s):
${o}`), this.name = "PictModelError", this.issues = t;
}
}
function H(l) {
const t = [];
let n = "", o = !1;
for (const r of l)
r === '"' ? (o = !o, n += r) : r === "," && !o ? (n.trim() && t.push(n.trim()), n = "") : n += r;
return n.trim() && t.push(n.trim()), t;
}
function $(l) {
return l.startsWith('"') && l.endsWith('"') ? l.slice(1, -1) : l;
}
function K(l, t, n) {
const o = l.trim();
if (o.startsWith("<") && o.endsWith(">")) {
const E = o.slice(1, -1);
if (!(E in t))
throw new Error(`Unknown parameter reference: "${E}"`);
return { values: [...t[E]], isNegative: !1, weight: 1 };
}
let r = o, a = !1, i = 1;
r.startsWith("~") && (r = r.slice(1), a = !0);
const p = r.match(/\s*\((\d+)\)\s*$/);
if (p && (i = parseInt(p[1], 10), r = r.slice(0, r.lastIndexOf("(")).trim()), r.includes("|")) {
const E = r.split("|").map((y) => y.trim()), v = $(E[0]);
for (let y = 1; y < E.length; y++) {
const d = $(E[y]);
n.set(d, v);
}
r = E[0];
}
if (r.startsWith('"') && r.endsWith('"'))
return { values: [r.slice(1, -1)], isNegative: a, weight: i };
const h = Number(r);
return {
values: [r !== "" && !isNaN(h) ? h : r],
isNegative: a,
weight: i
};
}
function k(l) {
return !(l.indexOf(":") <= 0 || l.startsWith("[") || /^IF\s/i.test(l));
}
function F(l) {
const t = {}, n = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), r = {}, a = [];
let i = 0;
const p = (h, E) => {
a.push({
severity: "error",
source: "factor",
index: i,
line: h,
message: E
});
};
for (const { text: h, line: E } of l) {
const v = h.trim();
if (v === "" || v.startsWith("#")) continue;
const y = v.indexOf(":");
if (y === -1) {
p(E, `Invalid line (missing ":"): ${v}`), i++;
continue;
}
const d = v.slice(0, y).trim();
if (d === "") {
p(E, `Empty parameter name in line: ${v}`), i++;
continue;
}
try {
const u = H(v.slice(y + 1)), w = [], N = /* @__PURE__ */ new Set(), O = {};
for (const M of u) {
const L = K(M, t, n), _ = w.length;
if (w.push(...L.values), L.isNegative)
for (const x of L.values) N.add(x);
if (L.weight !== 1)
for (let x = 0; x < L.values.length; x++)
O[_ + x] = L.weight;
}
if (w.length === 0) {
p(E, `No values for parameter "${d}"`), i++;
continue;
}
t[d] = w, N.size > 0 && o.set(d, N), Object.keys(O).length > 0 && (r[d] = O);
} catch (u) {
p(E, u.message);
}
i++;
}
return { factors: t, aliases: n, negatives: o, weights: r, issues: a };
}
const W = /^\{\s*(.+?)\s*\}\s*@\s*(\d+)\s*$/;
function z(l) {
const t = l.match(W);
if (!t) return null;
const n = t[1].split(",").map((r) => r.trim()).filter((r) => r !== ""), o = parseInt(t[2], 10);
return { fields: n, strength: o };
}
function I(l, t) {
if (l.startsWith("[") && l.endsWith("]"))
return { type: "REF", value: l, line: t };
if (l.startsWith('"') && l.endsWith('"'))
return { type: "STRING", value: l, line: t };
if (!isNaN(parseFloat(l)))
return { type: "NUMBER", value: l, line: t };
if (["TRUE", "FALSE"].includes(l.toUpperCase()))
return { type: "BOOLEAN", value: l.toUpperCase(), line: t };
if (l.toUpperCase() === "NULL")
return { type: "NULL", value: l.toUpperCase(), line: t };
if ([
"IF",
"ELSE",
"THEN"
/* THEN */
].includes(l.toUpperCase()))
return { type: l.toUpperCase(), value: l.toUpperCase(), line: t };
if (["=", "<>", ">", "<", ">=", "<=", "IN", "LIKE"].includes(l.toUpperCase()))
return { type: "COMPARER", value: l.toUpperCase(), line: t };
if (["AND", "OR", "NOT"].includes(l.toUpperCase()))
return { type: "OPERATOR", value: l.toUpperCase(), line: t };
if (["+", "-", "*", "/", "%", "^"].includes(l))
return { type: "ARITHMETIC", value: l, line: t };
if (l === "**")
return { type: "ARITHMETIC", value: "^", line: t };
switch (l) {
case "(":
return { type: "LPAREN", value: l, line: t };
case ")":
return { type: "RPAREN", value: l, line: t };
case "{":
return { type: "LBRACE", value: l, line: t };
case "}":
return { type: "RBRACE", value: l, line: t };
case ",":
return { type: "COMMA", value: l, line: t };
case ":":
return { type: "COLON", value: l, line: t };
case ";":
return { type: "SEMICOLON", value: l, line: t };
default:
return { type: "UNKNOWN", value: l, line: t };
}
}
const S = (l) => l === " " || l === `
` || l === " ";
class j {
constructor(t, n = !1, o = /* @__PURE__ */ new Map(), r = !1, a = 1) {
this.input = t, this.debug = n, this.aliases = o, this.caseInsensitive = r, this.startLine = a, this.tokens = [], this.filters = [], this.errors = [], this.filterLines = [], this.filterKeys = [], this.filter = (i, ...p) => {
for (const h of this.filters)
if (h != null && !h(i))
return !1;
for (const h of p)
if (!h(i))
return !1;
return !0;
};
try {
this.tokenize();
} catch (i) {
this.debug && console.error("Tokenize error:", i.message), this.errors.push(i.message), this.filterLines.push(this.startLine);
return;
}
this.analyze();
}
tokenize() {
const t = this.input, n = [];
let o = "", r = this.startLine, a = this.startLine, i = !1, p = !1, h = !1;
const E = (d, u, w) => {
n.push({ type: d, value: u, line: w });
}, v = () => {
o.length > 0 && (n.push(I(o, r)), o = "");
}, y = (d) => {
o.length === 0 && (r = a), o += d;
};
for (let d = 0; d < t.length; d++) {
const u = t[d];
if (u === '"')
i = !i, y(u), i || (E("STRING", o, r), o = "");
else if (i)
y(u);
else if (u === "[")
v(), h = !0, y(u);
else if (u === "]" && h)
y(u), n.push(I(o, r)), h = !1, o = "";
else if (u === "{")
p = !0, v(), E("LBRACE", u, a);
else if (u === "}")
p = !1, v(), E("RBRACE", u, a);
else if (u === "," && p)
v(), E("COMMA", u, a);
else if ("+-*/%^".includes(u) && !p && !h)
v(), u === "*" && t[d + 1] === "*" ? (n.push(I("**", a)), d++) : n.push(I(u, a));
else if ("[]=<>!();:".includes(u) && !p && !h)
if (v(), u === "<" || u === ">" || u === "!" || u === "=") {
const w = t[d + 1];
w === "=" ? (n.push(I(u + "=", a)), d++) : u === "<" && w === ">" ? (n.push(I("<>", a)), d++) : n.push(I(u, a));
} else
n.push(I(u, a));
else if (S(u) && !p && !h) {
v();
let w = u;
for (u === `
` && a++; d + 1 < t.length && S(t[d + 1]); )
d++, w += t[d], t[d] === `
` && a++;
E("WHITESPACE", w, a);
} else S(u) && (p || h) && u === `
` && a++, y(u);
}
if (i)
throw new Error(`Unterminated string literal: ${o}`);
if (h)
throw new Error(`Unterminated field reference: ${o}`);
if (p)
throw new Error('Unterminated set (missing closing "}")');
return o.length > 0 && n.push(I(o, r)), this.tokens = n, n;
}
analyze() {
let t = 0;
const n = this.tokens, o = this.caseInsensitive, r = (s) => o && typeof s == "string" ? s.toLowerCase() : s;
let a = /* @__PURE__ */ new Set();
const i = () => {
for (; t < n.length && n[t].type === "WHITESPACE"; )
t++;
return t < n.length ? n[t++] : null;
}, p = () => {
let s = h(), e = i();
if ((e == null ? void 0 : e.type) === "UNKNOWN")
throw new Error(`Unexpected token: ${e.value}`);
for (; e && e.type === "OPERATOR" && e.value === "OR"; ) {
const c = s, g = h();
s = (f) => c(f) || g(f), e = i();
}
return t--, s;
}, h = () => {
let s = v(), e = i();
if ((e == null ? void 0 : e.type) === "UNKNOWN")
throw new Error(`Unexpected token: ${e.value}`);
for (; e && e.type === "OPERATOR" && e.value === "AND"; ) {
const c = s, g = v();
s = (f) => c(f) && g(f), e = i();
}
return t--, s;
}, E = () => {
const s = t;
let e = 1, c = !1;
for (; t < n.length; ) {
const g = n[t++];
if (g.type !== "WHITESPACE") {
if (g.type === "LPAREN") e++;
else if (g.type === "RPAREN") {
if (e--, e === 0) break;
} else if (e === 1 && g.type === "COMPARER") {
c = !0;
break;
}
}
}
return t = s, c;
}, v = () => {
let s = i();
if (s != null) {
if (s.type === "OPERATOR" && s.value === "NOT") {
const e = v();
return (c) => !e(c);
}
if (s.type === "LPAREN")
if (E()) {
const e = p();
if (s = i(), !s || s.type !== "RPAREN")
throw new Error("Expected closing parenthesis");
return e;
} else
return t--, y();
if (s.type === "BOOLEAN") {
const e = s.value.toUpperCase() === "TRUE";
return () => e;
}
if (s.type === "UNKNOWN")
throw new Error(`Unexpected token: ${s.value}`);
}
return t--, y();
}, y = () => {
const s = M();
if (s == null)
throw new Error('Expected field or value after "IF", "THEN", "ELSE"');
const e = i();
if ([
"NUMBER",
"STRING",
"BOOLEAN",
"NULL"
/* NULL */
].includes(e == null ? void 0 : e.type))
throw new Error(`Expected comparison operator but found value: ${e == null ? void 0 : e.value}`);
if ((e == null ? void 0 : e.type) === "THEN")
throw new Error("A comparison operator and value are required after the field.");
if ((e == null ? void 0 : e.type) === "OPERATOR")
throw new Error(`Expected comparison operator but found operator: ${e.value}`);
if (!e || e.type !== "COMPARER")
throw (e == null ? void 0 : e.value) === "!=" ? new Error('"!=" is not supported. Use "<>" for inequality comparison') : new Error(`Unknown comparison operator: ${e == null ? void 0 : e.value}`);
const c = e.value;
if (c === "IN") {
const f = d();
return (R) => f.has(r(s(R)));
}
if (c === "LIKE") {
const f = M();
if (f == null)
throw new Error("Expected string pattern after LIKE");
const R = f({});
if (typeof R != "string")
throw new Error("Expected string pattern after LIKE");
const m = R.replace(/\*/g, ".*").replace(/\?/g, "."), A = new RegExp("^" + m + "$", o ? "i" : "");
return (C) => A.test(s(C));
}
const g = M();
if (g == null)
throw new Error("Expected field or value");
switch (c) {
case "=":
return (f) => r(s(f)) === r(g(f));
case "<>":
return (f) => r(s(f)) !== r(g(f));
case ">":
return (f) => s(f) > g(f);
case "<":
return (f) => s(f) < g(f);
case ">=":
return (f) => s(f) >= g(f);
case "<=":
return (f) => s(f) <= g(f);
default:
throw new Error(`Unknown comparison operator: ${c}`);
}
}, d = () => {
const s = [];
let e = i();
if (e && e.type === "LBRACE")
for (e = i(); e && e.type !== "RBRACE"; ) {
if (e.type === "STRING") {
const c = e.value.slice(1, -1), g = o ? c.toLowerCase() : c, f = this.aliases.get(g) ?? c;
s.push(r(f));
} else if (e.type !== "COMMA" && e.type !== "WHITESPACE")
throw new Error(`Unexpected token in array: ${e.value}`);
e = i();
}
else
throw new Error(`Expected '{' but found ${e ? e.value : "NULL"}`);
if (s.length === 0)
throw new Error("Empty set in IN clause");
return new Set(s);
}, u = {
"+": (s, e) => s + e,
"-": (s, e) => s - e,
"*": (s, e) => s * e,
"/": (s, e) => s / e,
"%": (s, e) => s % e,
"^": (s, e) => s ** e
}, w = () => {
const s = i();
if (s == null)
return null;
if (s.type === "LPAREN") {
const e = M();
if (e == null)
throw new Error('Expected expression after "("');
const c = i();
if (!c || c.type !== "RPAREN")
throw new Error('Expected closing ")" in arithmetic expression');
return e;
} else if (s.type === "REF") {
const e = s.value.slice(1, -1);
return a.add(e), (c) => c[e];
} else if (s.type === "STRING") {
const e = s.value.slice(1, -1), c = o ? e.toLowerCase() : e, g = this.aliases.get(c) ?? e;
return () => g;
} else if (s.type === "NUMBER") {
const e = parseFloat(s.value);
return () => e;
} else if (s.type === "BOOLEAN") {
const e = s.value === "TRUE";
return () => e;
} else return s.type === "NULL" ? () => null : null;
}, N = () => {
let s = w();
if (s == null) return null;
const e = t, c = i();
if (c && c.type === "ARITHMETIC" && c.value === "^") {
const g = N();
if (g == null)
throw new Error("Expected operand after '^'");
s = /* @__PURE__ */ ((m, A) => (C) => m(C) ** A(C))(s, g);
} else
t = e;
return s;
}, O = () => {
let s = N();
if (s == null) return null;
for (; ; ) {
const e = t, c = i();
if (c && c.type === "ARITHMETIC" && "*/%".includes(c.value)) {
const g = u[c.value], f = N();
if (f == null)
throw new Error(`Expected operand after '${c.value}'`);
s = /* @__PURE__ */ ((A, C, P) => (b) => P(A(b), C(b)))(s, f, g);
} else {
t = e;
break;
}
}
return s;
}, M = () => {
let s = O();
if (s == null) return null;
for (; ; ) {
const e = t, c = i();
if (c && c.type === "ARITHMETIC" && "+-".includes(c.value)) {
const g = u[c.value], f = O();
if (f == null)
throw new Error(`Expected operand after '${c.value}'`);
s = /* @__PURE__ */ ((A, C, P) => (b) => P(A(b), C(b)))(s, f, g);
} else {
t = e;
break;
}
}
return s;
}, L = () => {
for (; t < n.length && n[t].type !== "SEMICOLON"; )
t++;
};
let _ = this.startLine;
const x = (s, e) => {
s == null ? (this.debug && console.error(`Error[${this.errors.length}]:`, e), this.filters.push(null), this.errors.push(e)) : (this.debug && console.debug(`Filter[${this.filters.length}]: compiled`), this.filters.push(s), this.errors.push(null)), this.filterLines.push(_), this.filterKeys.push(a), a = /* @__PURE__ */ new Set();
}, U = () => {
try {
return p();
} catch (s) {
x(null, s.message);
}
return t--, L(), null;
};
for (; n[t] != null; ) {
const s = i();
if (s == null)
break;
if (_ = s.line, s.type === "IF") {
const e = U();
if (e == null)
continue;
const c = i();
if (!c || c.type !== "THEN") {
x(null, `Expected "THEN" but found ${c ? c.value : "end of input"}`), L();
continue;
}
const g = U();
if (g == null)
continue;
const f = i();
let R = () => !0;
if (f && f.type === "ELSE") {
const m = U();
if (m == null)
continue;
R = m;
} else
t--;
x((m) => e(m) ? g(m) : R(m), null);
} else if (s.type !== "SEMICOLON") if (s.type === "UNKNOWN")
x(null, `Unknown token: ${s.value}`), L();
else {
t--;
const e = U();
e != null && x(e, null);
}
}
}
}
function G(l) {
const t = l.split(`
`), n = [], o = [];
let r = t.length;
for (let a = 0; a < t.length; a++) {
const i = t[a], p = i.trim(), h = a + 1;
if (p === "" || p.startsWith("#")) {
n.push({ text: i, line: h });
continue;
}
if (k(p))
n.push({ text: i, line: h });
else if (W.test(p))
o.push({ text: p, line: h });
else {
r = a;
break;
}
}
return {
parameterLines: n,
subModelLines: o,
constraintText: t.slice(r).map((a) => a.trim().startsWith("#") ? "" : a).join(`
`),
constraintStartLine: r + 1
};
}
function V(l, t = {}) {
const { caseInsensitive: n = !0 } = t, o = G(l), r = [], { factors: a, aliases: i, negatives: p, weights: h, issues: E } = F(o.parameterLines);
r.push(...E);
const v = n ? new Map(Array.from(i, ([u, w]) => [u.toLowerCase(), w])) : i, y = [];
o.subModelLines.forEach(({ text: u, line: w }, N) => {
const O = z(u);
O ? y.push(O) : r.push({
severity: "error",
source: "subModel",
index: N,
line: w,
message: `Invalid sub-model definition: ${u}`
});
});
let d = null;
return o.constraintText.trim() && (d = new j(
o.constraintText,
!1,
v,
n,
o.constraintStartLine
), d.errors.forEach((u, w) => {
if (u == null) return;
const N = d.filterLines[w] ?? o.constraintStartLine;
r.push({
severity: "error",
source: "constraint",
index: w,
line: N,
message: u
});
})), { factors: a, aliases: i, negatives: p, weights: h, subModels: y, lexer: d, issues: r };
}
class Q {
constructor(t, n = {}) {
this._controller = null, this.filter = (i) => {
let p = !1;
for (const [h, E] of this._negatives)
if (h in i && E.has(i[h])) {
if (p) return !1;
p = !0;
}
if (this._lexer) {
for (const h of this._lexer.filters)
if (h != null && !h(i))
return !1;
}
return !0;
};
const { caseInsensitive: o = !0, strict: r = !1 } = n, a = V(t, { caseInsensitive: o });
if (this._parameters = a.factors, this._subModels = a.subModels, this._negatives = a.negatives, this._weights = a.weights, this._lexer = a.lexer, this.issues = a.issues, r && this.issues.some((i) => i.severity === "error"))
throw new B(this.issues);
}
get parameters() {
return this._parameters;
}
get subModels() {
return this._subModels;
}
get constraints() {
return this._modelConstraints();
}
get negatives() {
return this._negatives;
}
get weights() {
return this._weights;
}
get progress() {
var t;
return ((t = this._controller) == null ? void 0 : t.progress) ?? 0;
}
get stats() {
var t;
return ((t = this._controller) == null ? void 0 : t.stats) ?? null;
}
/**
* Convert this model's constraints into `Expression[]` for the controller.
* Each lexer filter becomes a `custom` condition with its dependency keys.
* The negative-value rule also becomes a `custom` condition.
*/
_modelConstraints() {
const t = [];
if (this._lexer) {
const n = this._lexer.filters, o = this._lexer.filterKeys;
for (let r = 0; r < n.length; r++) {
const a = n[r];
a != null && t.push({
operator: "fn",
requires: [...o[r]],
evaluate: a
});
}
}
if (this._negatives.size > 0) {
const n = [...this._negatives.keys()], o = this._negatives;
t.push({
operator: "fn",
requires: n,
evaluate: (r) => {
let a = !1;
for (const [i, p] of o)
if (p.has(r[i])) {
if (a) return !1;
a = !0;
}
return !0;
}
});
}
return t;
}
_buildOptions(t = {}) {
const {
constraints: n,
subModels: o,
weights: r,
...a
} = t, i = [
...this._modelConstraints(),
...n ?? []
], p = o ?? (this._subModels.length > 0 ? this._subModels : void 0), h = r ?? (Object.keys(this._weights).length > 0 ? this._weights : void 0);
return { ...a, constraints: i, subModels: p, weights: h };
}
_applyNegativePrefix(t) {
if (this._negatives.size === 0) return t;
const n = { ...t };
for (const [o, r] of this._negatives)
r.has(n[o]) && (n[o] = `~${n[o]}`);
return n;
}
make(t = {}) {
return this._controller = new T(this._parameters, this._buildOptions(t)), [...this._controller.makeAsync()].map(
(n) => this._applyNegativePrefix(n)
);
}
*makeAsync(t = {}) {
this._controller = new T(this._parameters, this._buildOptions(t));
for (const n of this._controller.makeAsync())
yield this._applyNegativePrefix(n);
}
}
function D(l, t) {
const n = {};
for (const [o, r] of Object.entries(t)) {
const a = l[o];
if (!a) continue;
const i = {};
for (const [p, h] of Object.entries(r)) {
const E = a.findIndex((v) => String(v) === p);
E >= 0 && (i[E] = h);
}
Object.keys(i).length > 0 && (n[o] = i);
}
return n;
}
export {
Q as PictModel,
B as PictModelError,
V as parse,
D as weightsByValue
};