UNPKG

covertable

Version:

Efficient TypeScript library for pairwise testing, generating minimal covering arrays with constraint support.

675 lines (674 loc) 21.5 kB
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 };