@briza/illogical 2.2.2 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/illogical.cjs +120 -22
- package/lib/illogical.esm.js +120 -22
- package/package.json +1 -1
- package/readme.md +7 -5
package/lib/illogical.cjs
CHANGED
|
@@ -2293,7 +2293,8 @@ function extractInLikeChild(ca, state) {
|
|
|
2293
2293
|
return {
|
|
2294
2294
|
rawRef,
|
|
2295
2295
|
refKey,
|
|
2296
|
-
vals: right
|
|
2296
|
+
vals: right,
|
|
2297
|
+
operator: 'in'
|
|
2297
2298
|
};
|
|
2298
2299
|
}
|
|
2299
2300
|
|
|
@@ -2309,7 +2310,8 @@ function extractInLikeChild(ca, state) {
|
|
|
2309
2310
|
return {
|
|
2310
2311
|
rawRef,
|
|
2311
2312
|
refKey,
|
|
2312
|
-
vals: [right]
|
|
2313
|
+
vals: [right],
|
|
2314
|
+
operator: 'eq'
|
|
2313
2315
|
};
|
|
2314
2316
|
}
|
|
2315
2317
|
return null;
|
|
@@ -2341,6 +2343,10 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2341
2343
|
|
|
2342
2344
|
// Inverted index: each distinct setA value → merged setB values across all branches containing it
|
|
2343
2345
|
const setBValsByAValue = new Map();
|
|
2346
|
+
// Track which operators were used for each setA value (for ref1 operand reconstruction)
|
|
2347
|
+
const ref1OpsByAValue = new Map();
|
|
2348
|
+
// Track which operators were used for each setA value (for ref2 operand reconstruction)
|
|
2349
|
+
const ref2OpsByAValue = new Map();
|
|
2344
2350
|
for (let b = 1; b <= nBranches; b++) {
|
|
2345
2351
|
const branch = arr[b];
|
|
2346
2352
|
if (!Array.isArray(branch)) {
|
|
@@ -2396,6 +2402,20 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2396
2402
|
setBVals = new Set();
|
|
2397
2403
|
setBValsByAValue.set(aVal, setBVals);
|
|
2398
2404
|
}
|
|
2405
|
+
// Track ref1 operator
|
|
2406
|
+
let r1Ops = ref1OpsByAValue.get(aVal);
|
|
2407
|
+
if (r1Ops === undefined) {
|
|
2408
|
+
r1Ops = new Set();
|
|
2409
|
+
ref1OpsByAValue.set(aVal, r1Ops);
|
|
2410
|
+
}
|
|
2411
|
+
r1Ops.add(extA.operator);
|
|
2412
|
+
// Track ref2 operator
|
|
2413
|
+
let r2Ops = ref2OpsByAValue.get(aVal);
|
|
2414
|
+
if (r2Ops === undefined) {
|
|
2415
|
+
r2Ops = new Set();
|
|
2416
|
+
ref2OpsByAValue.set(aVal, r2Ops);
|
|
2417
|
+
}
|
|
2418
|
+
r2Ops.add(extB.operator);
|
|
2399
2419
|
for (const bVal of extB.vals) {
|
|
2400
2420
|
setBVals.add(bVal);
|
|
2401
2421
|
}
|
|
@@ -2407,14 +2427,25 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2407
2427
|
|
|
2408
2428
|
// Build entries: one (literal aVal, mergedSetBIdx) per distinct setA value
|
|
2409
2429
|
const entries = [];
|
|
2430
|
+
// Track operators per entry: [ref1Op, ref2Op]
|
|
2431
|
+
const entryOperators = [];
|
|
2410
2432
|
for (const [aVal, setBVals] of setBValsByAValue) {
|
|
2411
2433
|
const mergedSetB = [...setBVals].filter(v => v !== undefined);
|
|
2412
|
-
|
|
2434
|
+
const constIdx = internConst(mergedSetB, state);
|
|
2435
|
+
entries.push([aVal, constIdx]);
|
|
2436
|
+
// Determine ref1 operator: if all branches used 'eq', preserve 'eq'; otherwise use 'in'
|
|
2437
|
+
const r1Ops = ref1OpsByAValue.get(aVal);
|
|
2438
|
+
const ref1Op = r1Ops !== undefined && r1Ops.size === 1 && r1Ops.has('eq') ? 'eq' : 'in';
|
|
2439
|
+
// Determine ref2 operator: if all branches used 'eq', preserve 'eq'; otherwise use 'in'
|
|
2440
|
+
const r2Ops = ref2OpsByAValue.get(aVal);
|
|
2441
|
+
const ref2Op = r2Ops !== undefined && r2Ops.size === 1 && r2Ops.has('eq') ? 'eq' : 'in';
|
|
2442
|
+
entryOperators.push([ref1Op, ref2Op]);
|
|
2413
2443
|
}
|
|
2414
2444
|
return {
|
|
2415
2445
|
ref1Raw,
|
|
2416
2446
|
ref2Raw,
|
|
2417
|
-
entries
|
|
2447
|
+
entries,
|
|
2448
|
+
entryOperators
|
|
2418
2449
|
};
|
|
2419
2450
|
}
|
|
2420
2451
|
|
|
@@ -2472,18 +2503,22 @@ function emitExpression(raw, state) {
|
|
|
2472
2503
|
const {
|
|
2473
2504
|
ref1Raw,
|
|
2474
2505
|
ref2Raw,
|
|
2475
|
-
entries
|
|
2506
|
+
entries,
|
|
2507
|
+
entryOperators
|
|
2476
2508
|
} = orAnd2;
|
|
2477
2509
|
const {
|
|
2478
2510
|
bytecode
|
|
2479
2511
|
} = state;
|
|
2480
2512
|
const ref1Idx = internRef(ref1Raw, state);
|
|
2481
2513
|
const ref2Idx = internRef(ref2Raw, state);
|
|
2482
|
-
// Emit: OP_OR_AND_IN_CONST_2 ref1Idx ref2Idx M
|
|
2514
|
+
// Emit: OP_OR_AND_IN_CONST_2 ref1Idx ref2Idx M (aVal0 setBIdx0 ref1Op0 ref2Op0) ...
|
|
2483
2515
|
// M is the number of distinct setA values across all branches (after inverted-index merge).
|
|
2516
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in'
|
|
2484
2517
|
bytecode.push(OP_OR_AND_IN_CONST_2, ref1Idx, ref2Idx, entries.length);
|
|
2485
|
-
for (
|
|
2486
|
-
|
|
2518
|
+
for (let j = 0; j < entries.length; j++) {
|
|
2519
|
+
const [aVal, mergedSetBIdx] = entries[j];
|
|
2520
|
+
const [ref1Op, ref2Op] = entryOperators[j];
|
|
2521
|
+
bytecode.push(aVal, mergedSetBIdx, ref1Op === 'in' ? 1 : 0, ref2Op === 'in' ? 1 : 0);
|
|
2487
2522
|
}
|
|
2488
2523
|
return;
|
|
2489
2524
|
}
|
|
@@ -3215,20 +3250,21 @@ function interpret(compiled, ctx) {
|
|
|
3215
3250
|
}
|
|
3216
3251
|
case OP_OR_AND_IN_CONST_2:
|
|
3217
3252
|
{
|
|
3218
|
-
// bytecode layout: ref1Idx, ref2Idx, M, v0, setBIdx0, v1, setBIdx1,
|
|
3253
|
+
// bytecode layout: ref1Idx, ref2Idx, M, (v0, setBIdx0, ref1Op0, ref2Op0), (v1, setBIdx1, ref1Op1, ref2Op1), ...
|
|
3254
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in' (unused at runtime, kept for simplifier)
|
|
3219
3255
|
// constSets[setBIdx] is pre-built at first interpret() call — plain Set.has lookup.
|
|
3220
3256
|
const ref1Idx = numAt$1(bytecode[++i]);
|
|
3221
3257
|
const ref2Idx = numAt$1(bytecode[++i]);
|
|
3222
3258
|
const m = numAt$1(bytecode[++i]);
|
|
3223
3259
|
const entriesStart = i + 1;
|
|
3224
|
-
i += m *
|
|
3260
|
+
i += m * 4;
|
|
3225
3261
|
const v1 = resolveCompactRef(refs[ref1Idx], ctx);
|
|
3226
3262
|
const v2 = resolveCompactRef(refs[ref2Idx], ctx);
|
|
3227
3263
|
let found = false;
|
|
3228
3264
|
if (v1 !== undefined && v1 !== null && v2 !== undefined && v2 !== null) {
|
|
3229
3265
|
for (let j = 0; j < m; j++) {
|
|
3230
|
-
if (bytecode[entriesStart + j *
|
|
3231
|
-
found = constSets[numAt$1(bytecode[entriesStart + j *
|
|
3266
|
+
if (bytecode[entriesStart + j * 4] === v1) {
|
|
3267
|
+
found = constSets[numAt$1(bytecode[entriesStart + j * 4 + 1])].has(v2);
|
|
3232
3268
|
break;
|
|
3233
3269
|
}
|
|
3234
3270
|
}
|
|
@@ -4235,13 +4271,15 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4235
4271
|
}
|
|
4236
4272
|
case OP_OR_AND_IN_CONST_2:
|
|
4237
4273
|
{
|
|
4238
|
-
// bytecode layout: ref1Idx, ref2Idx, M, aVal0, setBIdx0,
|
|
4274
|
+
// bytecode layout: ref1Idx, ref2Idx, M, (aVal0, setBIdx0, ref1Op0, ref2Op0),
|
|
4275
|
+
// (aVal1, setBIdx1, ref1Op1, ref2Op1), ...
|
|
4239
4276
|
// aVal_j is a literal value; setBIdx_j is a constIdx for the merged setB.
|
|
4277
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in'
|
|
4240
4278
|
const ref1Idx = numAt(bytecode[i++]);
|
|
4241
4279
|
const ref2Idx = numAt(bytecode[i++]);
|
|
4242
4280
|
const n = numAt(bytecode[i++]);
|
|
4243
|
-
const
|
|
4244
|
-
i += n *
|
|
4281
|
+
const quadsStart = i;
|
|
4282
|
+
i += n * 4;
|
|
4245
4283
|
const rawKey1 = refRawKeys[ref1Idx];
|
|
4246
4284
|
const rawKey2 = refRawKeys[ref2Idx];
|
|
4247
4285
|
const v1 = resolveCompactRef(refs[ref1Idx], ctx);
|
|
@@ -4250,6 +4288,7 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4250
4288
|
const unknown2 = v2 === undefined && !strictSet?.has(rawKey2) && (!optionalSet || optionalSet.has(rawKey2));
|
|
4251
4289
|
if (unknown1 || unknown2) {
|
|
4252
4290
|
// Reconstruct the original complex expression tree
|
|
4291
|
+
// When one ref is known, only include entries where the known ref matches
|
|
4253
4292
|
const branches = [opNames[OP_OR]];
|
|
4254
4293
|
const andOp = opNames[OP_AND];
|
|
4255
4294
|
const eqOp = opNames[OP_EQ];
|
|
@@ -4257,12 +4296,71 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4257
4296
|
const r1 = refKeys[ref1Idx];
|
|
4258
4297
|
const r2 = refKeys[ref2Idx];
|
|
4259
4298
|
for (let j = 0; j < n; j++) {
|
|
4260
|
-
const aVal = literalAt(bytecode[
|
|
4261
|
-
const setB = compiled.consts[numAt(bytecode[
|
|
4262
|
-
const
|
|
4263
|
-
|
|
4299
|
+
const aVal = literalAt(bytecode[quadsStart + j * 4]);
|
|
4300
|
+
const setB = compiled.consts[numAt(bytecode[quadsStart + j * 4 + 1])];
|
|
4301
|
+
const ref1OpByte = numAt(bytecode[quadsStart + j * 4 + 2]);
|
|
4302
|
+
const ref2OpByte = numAt(bytecode[quadsStart + j * 4 + 3]);
|
|
4303
|
+
// Use the original operators for both operands
|
|
4304
|
+
const op1 = ref1OpByte === 1 ? inOp : eqOp;
|
|
4305
|
+
const op2 = ref2OpByte === 1 ? inOp : eqOp;
|
|
4306
|
+
// When reconstructing == with a scalar, use the scalar value
|
|
4307
|
+
// When reconstructing IN with a scalar, wrap it in an array
|
|
4308
|
+
const r1Val = op1 === eqOp ? aVal : [aVal];
|
|
4309
|
+
let r2Val = setB;
|
|
4310
|
+
if (op2 === eqOp && Array.isArray(setB) && setB.length === 1) {
|
|
4311
|
+
r2Val = setB[0];
|
|
4312
|
+
}
|
|
4313
|
+
// Skip entries where the known ref doesn't match
|
|
4314
|
+
if (!unknown1 && v1 !== undefined && v1 !== null) {
|
|
4315
|
+
// ref1 is known — only include matching entries
|
|
4316
|
+
// For ==, check exact match; for IN, check if value is in set
|
|
4317
|
+
let matches = false;
|
|
4318
|
+
if (op1 === eqOp) {
|
|
4319
|
+
matches = v1 === aVal;
|
|
4320
|
+
} else {
|
|
4321
|
+
matches = Array.isArray(v1) ? v1.includes(aVal) : v1 === aVal;
|
|
4322
|
+
}
|
|
4323
|
+
if (!matches) {
|
|
4324
|
+
continue;
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
if (!unknown2 && v2 !== undefined && v2 !== null) {
|
|
4328
|
+
// ref2 is known — only include matching entries
|
|
4329
|
+
let matches = false;
|
|
4330
|
+
if (op2 === eqOp) {
|
|
4331
|
+
matches = v2 === r2Val;
|
|
4332
|
+
} else {
|
|
4333
|
+
// setB is always an array here (it's a compiled const)
|
|
4334
|
+
matches = Array.isArray(v2) ? setB.some(item => item === v2) : v2 === r2Val;
|
|
4335
|
+
}
|
|
4336
|
+
if (!matches) {
|
|
4337
|
+
continue;
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
// Build the AND branch, omitting known refs that already matched
|
|
4341
|
+
const branchOperands = [];
|
|
4342
|
+
if (unknown1) {
|
|
4343
|
+
branchOperands.push([op1, r1, r1Val]);
|
|
4344
|
+
}
|
|
4345
|
+
if (unknown2) {
|
|
4346
|
+
branchOperands.push([op2, r2, r2Val]);
|
|
4347
|
+
}
|
|
4348
|
+
if (branchOperands.length === 1) {
|
|
4349
|
+
branches.push(branchOperands[0]);
|
|
4350
|
+
} else {
|
|
4351
|
+
branches.push([andOp, ...branchOperands]);
|
|
4352
|
+
}
|
|
4353
|
+
}
|
|
4354
|
+
// Handle edge cases: no matches → false, single match → unwrap OR
|
|
4355
|
+
if (branches.length === 1) {
|
|
4356
|
+
// No entries matched
|
|
4357
|
+
stack[++stackTop] = false;
|
|
4358
|
+
} else if (branches.length === 2) {
|
|
4359
|
+
// Only one entry matched — unwrap the OR
|
|
4360
|
+
stack[++stackTop] = branches[1];
|
|
4361
|
+
} else {
|
|
4362
|
+
stack[++stackTop] = makeResidual(branches);
|
|
4264
4363
|
}
|
|
4265
|
-
stack[++stackTop] = makeResidual(branches);
|
|
4266
4364
|
break;
|
|
4267
4365
|
}
|
|
4268
4366
|
|
|
@@ -4270,8 +4368,8 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4270
4368
|
let found = false;
|
|
4271
4369
|
if (v1 !== null && v1 !== undefined && v2 !== null && v2 !== undefined) {
|
|
4272
4370
|
for (let j = 0; j < n; j++) {
|
|
4273
|
-
if (bytecode[
|
|
4274
|
-
const setB = compiled.consts[numAt(bytecode[
|
|
4371
|
+
if (bytecode[quadsStart + j * 4] === v1) {
|
|
4372
|
+
const setB = compiled.consts[numAt(bytecode[quadsStart + j * 4 + 1])];
|
|
4275
4373
|
let s = overlapSetCache.get(setB);
|
|
4276
4374
|
if (s === undefined) {
|
|
4277
4375
|
s = new Set(setB);
|
package/lib/illogical.esm.js
CHANGED
|
@@ -2289,7 +2289,8 @@ function extractInLikeChild(ca, state) {
|
|
|
2289
2289
|
return {
|
|
2290
2290
|
rawRef,
|
|
2291
2291
|
refKey,
|
|
2292
|
-
vals: right
|
|
2292
|
+
vals: right,
|
|
2293
|
+
operator: 'in'
|
|
2293
2294
|
};
|
|
2294
2295
|
}
|
|
2295
2296
|
|
|
@@ -2305,7 +2306,8 @@ function extractInLikeChild(ca, state) {
|
|
|
2305
2306
|
return {
|
|
2306
2307
|
rawRef,
|
|
2307
2308
|
refKey,
|
|
2308
|
-
vals: [right]
|
|
2309
|
+
vals: [right],
|
|
2310
|
+
operator: 'eq'
|
|
2309
2311
|
};
|
|
2310
2312
|
}
|
|
2311
2313
|
return null;
|
|
@@ -2337,6 +2339,10 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2337
2339
|
|
|
2338
2340
|
// Inverted index: each distinct setA value → merged setB values across all branches containing it
|
|
2339
2341
|
const setBValsByAValue = new Map();
|
|
2342
|
+
// Track which operators were used for each setA value (for ref1 operand reconstruction)
|
|
2343
|
+
const ref1OpsByAValue = new Map();
|
|
2344
|
+
// Track which operators were used for each setA value (for ref2 operand reconstruction)
|
|
2345
|
+
const ref2OpsByAValue = new Map();
|
|
2340
2346
|
for (let b = 1; b <= nBranches; b++) {
|
|
2341
2347
|
const branch = arr[b];
|
|
2342
2348
|
if (!Array.isArray(branch)) {
|
|
@@ -2392,6 +2398,20 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2392
2398
|
setBVals = new Set();
|
|
2393
2399
|
setBValsByAValue.set(aVal, setBVals);
|
|
2394
2400
|
}
|
|
2401
|
+
// Track ref1 operator
|
|
2402
|
+
let r1Ops = ref1OpsByAValue.get(aVal);
|
|
2403
|
+
if (r1Ops === undefined) {
|
|
2404
|
+
r1Ops = new Set();
|
|
2405
|
+
ref1OpsByAValue.set(aVal, r1Ops);
|
|
2406
|
+
}
|
|
2407
|
+
r1Ops.add(extA.operator);
|
|
2408
|
+
// Track ref2 operator
|
|
2409
|
+
let r2Ops = ref2OpsByAValue.get(aVal);
|
|
2410
|
+
if (r2Ops === undefined) {
|
|
2411
|
+
r2Ops = new Set();
|
|
2412
|
+
ref2OpsByAValue.set(aVal, r2Ops);
|
|
2413
|
+
}
|
|
2414
|
+
r2Ops.add(extB.operator);
|
|
2395
2415
|
for (const bVal of extB.vals) {
|
|
2396
2416
|
setBVals.add(bVal);
|
|
2397
2417
|
}
|
|
@@ -2403,14 +2423,25 @@ function detectOrAndIn2Pattern(arr, state) {
|
|
|
2403
2423
|
|
|
2404
2424
|
// Build entries: one (literal aVal, mergedSetBIdx) per distinct setA value
|
|
2405
2425
|
const entries = [];
|
|
2426
|
+
// Track operators per entry: [ref1Op, ref2Op]
|
|
2427
|
+
const entryOperators = [];
|
|
2406
2428
|
for (const [aVal, setBVals] of setBValsByAValue) {
|
|
2407
2429
|
const mergedSetB = [...setBVals].filter(v => v !== undefined);
|
|
2408
|
-
|
|
2430
|
+
const constIdx = internConst(mergedSetB, state);
|
|
2431
|
+
entries.push([aVal, constIdx]);
|
|
2432
|
+
// Determine ref1 operator: if all branches used 'eq', preserve 'eq'; otherwise use 'in'
|
|
2433
|
+
const r1Ops = ref1OpsByAValue.get(aVal);
|
|
2434
|
+
const ref1Op = r1Ops !== undefined && r1Ops.size === 1 && r1Ops.has('eq') ? 'eq' : 'in';
|
|
2435
|
+
// Determine ref2 operator: if all branches used 'eq', preserve 'eq'; otherwise use 'in'
|
|
2436
|
+
const r2Ops = ref2OpsByAValue.get(aVal);
|
|
2437
|
+
const ref2Op = r2Ops !== undefined && r2Ops.size === 1 && r2Ops.has('eq') ? 'eq' : 'in';
|
|
2438
|
+
entryOperators.push([ref1Op, ref2Op]);
|
|
2409
2439
|
}
|
|
2410
2440
|
return {
|
|
2411
2441
|
ref1Raw,
|
|
2412
2442
|
ref2Raw,
|
|
2413
|
-
entries
|
|
2443
|
+
entries,
|
|
2444
|
+
entryOperators
|
|
2414
2445
|
};
|
|
2415
2446
|
}
|
|
2416
2447
|
|
|
@@ -2468,18 +2499,22 @@ function emitExpression(raw, state) {
|
|
|
2468
2499
|
const {
|
|
2469
2500
|
ref1Raw,
|
|
2470
2501
|
ref2Raw,
|
|
2471
|
-
entries
|
|
2502
|
+
entries,
|
|
2503
|
+
entryOperators
|
|
2472
2504
|
} = orAnd2;
|
|
2473
2505
|
const {
|
|
2474
2506
|
bytecode
|
|
2475
2507
|
} = state;
|
|
2476
2508
|
const ref1Idx = internRef(ref1Raw, state);
|
|
2477
2509
|
const ref2Idx = internRef(ref2Raw, state);
|
|
2478
|
-
// Emit: OP_OR_AND_IN_CONST_2 ref1Idx ref2Idx M
|
|
2510
|
+
// Emit: OP_OR_AND_IN_CONST_2 ref1Idx ref2Idx M (aVal0 setBIdx0 ref1Op0 ref2Op0) ...
|
|
2479
2511
|
// M is the number of distinct setA values across all branches (after inverted-index merge).
|
|
2512
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in'
|
|
2480
2513
|
bytecode.push(OP_OR_AND_IN_CONST_2, ref1Idx, ref2Idx, entries.length);
|
|
2481
|
-
for (
|
|
2482
|
-
|
|
2514
|
+
for (let j = 0; j < entries.length; j++) {
|
|
2515
|
+
const [aVal, mergedSetBIdx] = entries[j];
|
|
2516
|
+
const [ref1Op, ref2Op] = entryOperators[j];
|
|
2517
|
+
bytecode.push(aVal, mergedSetBIdx, ref1Op === 'in' ? 1 : 0, ref2Op === 'in' ? 1 : 0);
|
|
2483
2518
|
}
|
|
2484
2519
|
return;
|
|
2485
2520
|
}
|
|
@@ -3211,20 +3246,21 @@ function interpret(compiled, ctx) {
|
|
|
3211
3246
|
}
|
|
3212
3247
|
case OP_OR_AND_IN_CONST_2:
|
|
3213
3248
|
{
|
|
3214
|
-
// bytecode layout: ref1Idx, ref2Idx, M, v0, setBIdx0, v1, setBIdx1,
|
|
3249
|
+
// bytecode layout: ref1Idx, ref2Idx, M, (v0, setBIdx0, ref1Op0, ref2Op0), (v1, setBIdx1, ref1Op1, ref2Op1), ...
|
|
3250
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in' (unused at runtime, kept for simplifier)
|
|
3215
3251
|
// constSets[setBIdx] is pre-built at first interpret() call — plain Set.has lookup.
|
|
3216
3252
|
const ref1Idx = numAt$1(bytecode[++i]);
|
|
3217
3253
|
const ref2Idx = numAt$1(bytecode[++i]);
|
|
3218
3254
|
const m = numAt$1(bytecode[++i]);
|
|
3219
3255
|
const entriesStart = i + 1;
|
|
3220
|
-
i += m *
|
|
3256
|
+
i += m * 4;
|
|
3221
3257
|
const v1 = resolveCompactRef(refs[ref1Idx], ctx);
|
|
3222
3258
|
const v2 = resolveCompactRef(refs[ref2Idx], ctx);
|
|
3223
3259
|
let found = false;
|
|
3224
3260
|
if (v1 !== undefined && v1 !== null && v2 !== undefined && v2 !== null) {
|
|
3225
3261
|
for (let j = 0; j < m; j++) {
|
|
3226
|
-
if (bytecode[entriesStart + j *
|
|
3227
|
-
found = constSets[numAt$1(bytecode[entriesStart + j *
|
|
3262
|
+
if (bytecode[entriesStart + j * 4] === v1) {
|
|
3263
|
+
found = constSets[numAt$1(bytecode[entriesStart + j * 4 + 1])].has(v2);
|
|
3228
3264
|
break;
|
|
3229
3265
|
}
|
|
3230
3266
|
}
|
|
@@ -4231,13 +4267,15 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4231
4267
|
}
|
|
4232
4268
|
case OP_OR_AND_IN_CONST_2:
|
|
4233
4269
|
{
|
|
4234
|
-
// bytecode layout: ref1Idx, ref2Idx, M, aVal0, setBIdx0,
|
|
4270
|
+
// bytecode layout: ref1Idx, ref2Idx, M, (aVal0, setBIdx0, ref1Op0, ref2Op0),
|
|
4271
|
+
// (aVal1, setBIdx1, ref1Op1, ref2Op1), ...
|
|
4235
4272
|
// aVal_j is a literal value; setBIdx_j is a constIdx for the merged setB.
|
|
4273
|
+
// ref1Op/ref2Op: 0 for 'eq', 1 for 'in'
|
|
4236
4274
|
const ref1Idx = numAt(bytecode[i++]);
|
|
4237
4275
|
const ref2Idx = numAt(bytecode[i++]);
|
|
4238
4276
|
const n = numAt(bytecode[i++]);
|
|
4239
|
-
const
|
|
4240
|
-
i += n *
|
|
4277
|
+
const quadsStart = i;
|
|
4278
|
+
i += n * 4;
|
|
4241
4279
|
const rawKey1 = refRawKeys[ref1Idx];
|
|
4242
4280
|
const rawKey2 = refRawKeys[ref2Idx];
|
|
4243
4281
|
const v1 = resolveCompactRef(refs[ref1Idx], ctx);
|
|
@@ -4246,6 +4284,7 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4246
4284
|
const unknown2 = v2 === undefined && !strictSet?.has(rawKey2) && (!optionalSet || optionalSet.has(rawKey2));
|
|
4247
4285
|
if (unknown1 || unknown2) {
|
|
4248
4286
|
// Reconstruct the original complex expression tree
|
|
4287
|
+
// When one ref is known, only include entries where the known ref matches
|
|
4249
4288
|
const branches = [opNames[OP_OR]];
|
|
4250
4289
|
const andOp = opNames[OP_AND];
|
|
4251
4290
|
const eqOp = opNames[OP_EQ];
|
|
@@ -4253,12 +4292,71 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4253
4292
|
const r1 = refKeys[ref1Idx];
|
|
4254
4293
|
const r2 = refKeys[ref2Idx];
|
|
4255
4294
|
for (let j = 0; j < n; j++) {
|
|
4256
|
-
const aVal = literalAt(bytecode[
|
|
4257
|
-
const setB = compiled.consts[numAt(bytecode[
|
|
4258
|
-
const
|
|
4259
|
-
|
|
4295
|
+
const aVal = literalAt(bytecode[quadsStart + j * 4]);
|
|
4296
|
+
const setB = compiled.consts[numAt(bytecode[quadsStart + j * 4 + 1])];
|
|
4297
|
+
const ref1OpByte = numAt(bytecode[quadsStart + j * 4 + 2]);
|
|
4298
|
+
const ref2OpByte = numAt(bytecode[quadsStart + j * 4 + 3]);
|
|
4299
|
+
// Use the original operators for both operands
|
|
4300
|
+
const op1 = ref1OpByte === 1 ? inOp : eqOp;
|
|
4301
|
+
const op2 = ref2OpByte === 1 ? inOp : eqOp;
|
|
4302
|
+
// When reconstructing == with a scalar, use the scalar value
|
|
4303
|
+
// When reconstructing IN with a scalar, wrap it in an array
|
|
4304
|
+
const r1Val = op1 === eqOp ? aVal : [aVal];
|
|
4305
|
+
let r2Val = setB;
|
|
4306
|
+
if (op2 === eqOp && Array.isArray(setB) && setB.length === 1) {
|
|
4307
|
+
r2Val = setB[0];
|
|
4308
|
+
}
|
|
4309
|
+
// Skip entries where the known ref doesn't match
|
|
4310
|
+
if (!unknown1 && v1 !== undefined && v1 !== null) {
|
|
4311
|
+
// ref1 is known — only include matching entries
|
|
4312
|
+
// For ==, check exact match; for IN, check if value is in set
|
|
4313
|
+
let matches = false;
|
|
4314
|
+
if (op1 === eqOp) {
|
|
4315
|
+
matches = v1 === aVal;
|
|
4316
|
+
} else {
|
|
4317
|
+
matches = Array.isArray(v1) ? v1.includes(aVal) : v1 === aVal;
|
|
4318
|
+
}
|
|
4319
|
+
if (!matches) {
|
|
4320
|
+
continue;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
if (!unknown2 && v2 !== undefined && v2 !== null) {
|
|
4324
|
+
// ref2 is known — only include matching entries
|
|
4325
|
+
let matches = false;
|
|
4326
|
+
if (op2 === eqOp) {
|
|
4327
|
+
matches = v2 === r2Val;
|
|
4328
|
+
} else {
|
|
4329
|
+
// setB is always an array here (it's a compiled const)
|
|
4330
|
+
matches = Array.isArray(v2) ? setB.some(item => item === v2) : v2 === r2Val;
|
|
4331
|
+
}
|
|
4332
|
+
if (!matches) {
|
|
4333
|
+
continue;
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
// Build the AND branch, omitting known refs that already matched
|
|
4337
|
+
const branchOperands = [];
|
|
4338
|
+
if (unknown1) {
|
|
4339
|
+
branchOperands.push([op1, r1, r1Val]);
|
|
4340
|
+
}
|
|
4341
|
+
if (unknown2) {
|
|
4342
|
+
branchOperands.push([op2, r2, r2Val]);
|
|
4343
|
+
}
|
|
4344
|
+
if (branchOperands.length === 1) {
|
|
4345
|
+
branches.push(branchOperands[0]);
|
|
4346
|
+
} else {
|
|
4347
|
+
branches.push([andOp, ...branchOperands]);
|
|
4348
|
+
}
|
|
4349
|
+
}
|
|
4350
|
+
// Handle edge cases: no matches → false, single match → unwrap OR
|
|
4351
|
+
if (branches.length === 1) {
|
|
4352
|
+
// No entries matched
|
|
4353
|
+
stack[++stackTop] = false;
|
|
4354
|
+
} else if (branches.length === 2) {
|
|
4355
|
+
// Only one entry matched — unwrap the OR
|
|
4356
|
+
stack[++stackTop] = branches[1];
|
|
4357
|
+
} else {
|
|
4358
|
+
stack[++stackTop] = makeResidual(branches);
|
|
4260
4359
|
}
|
|
4261
|
-
stack[++stackTop] = makeResidual(branches);
|
|
4262
4360
|
break;
|
|
4263
4361
|
}
|
|
4264
4362
|
|
|
@@ -4266,8 +4364,8 @@ function interpretSimplify(compiled, ctx, strictKeys, optionalKeys) {
|
|
|
4266
4364
|
let found = false;
|
|
4267
4365
|
if (v1 !== null && v1 !== undefined && v2 !== null && v2 !== undefined) {
|
|
4268
4366
|
for (let j = 0; j < n; j++) {
|
|
4269
|
-
if (bytecode[
|
|
4270
|
-
const setB = compiled.consts[numAt(bytecode[
|
|
4367
|
+
if (bytecode[quadsStart + j * 4] === v1) {
|
|
4368
|
+
const setB = compiled.consts[numAt(bytecode[quadsStart + j * 4 + 1])];
|
|
4271
4369
|
let s = overlapSetCache.get(setB);
|
|
4272
4370
|
if (s === undefined) {
|
|
4273
4371
|
s = new Set(setB);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@briza/illogical",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.3",
|
|
4
4
|
"description": "A micro conditional javascript engine used to parse the raw logical and comparison expressions, evaluate the expression in the given data context, and provide access to a text form of the given expressions.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/illogical.cjs",
|
package/readme.md
CHANGED
|
@@ -13,11 +13,13 @@
|
|
|
13
13
|
<strong>illogical</strong> is a JSON DSL (domain-specific language) for expressing and evaluating business rules in the insurance industry. Underwriters use illogical to model business rules for their question sets, enabling distributors to render great user experiences.
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
[](https://github.com/briza-insurance/illogical/actions?branch=master)
|
|
17
|
+
[](https://scorecard.dev/viewer/?uri=github.com/briza-insurance/illogical)
|
|
18
|
+
[](https://badge.fury.io/js/@briza%2Fillogical)
|
|
19
|
+
[](https://packagephobia.com/result?p=@briza/illogical)
|
|
20
|
+

|
|
21
|
+

|
|
22
|
+
|
|
21
23
|
</div>
|
|
22
24
|
|
|
23
25
|
---
|