import { expect, test, beforeEach } from 'vitest' import { CertusEvaluator } from '../parser/Evaluator.js' import { CertusContext } from '../model/Context.js'; import { NumericalDomain, UnitInterval, trap } from 'vaguely'; import { IndicatorCategory, NodeType } from '../model/Argument.js'; import { MaxMacro, MinMacro } from '../macro/MinAndMax.js'; import { FuseStrictMacro } from '../macro/FuseStrict.js'; import { FuseEliminativeMacro } from '../macro/FuseEliminative.js'; import { MaxEliminativeMacro, MinEliminativeMacro } from '../macro/MinAndMaxEliminative.js'; import { FuseMacro } from '../macro/Fuse.js'; import { MaxStrictMacro, MinStrictMacro } from '../macro/MinAndMaxStrict.js'; let evaluator : CertusEvaluator; let ctx : CertusContext; let UI : NumericalDomain; let catDanger : IndicatorCategory; let catOK : IndicatorCategory; beforeEach(() => { UI = new NumericalDomain("Belief", -1, 1, 0.01); ctx = new CertusContext(); evaluator = new CertusEvaluator(); catDanger = {id: "DANGER"}; catOK = {id: "OK"}; ctx.addMacro(new MinMacro()); ctx.addMacro(new MaxMacro()); ctx.addMacro(new FuseStrictMacro()); ctx.addMacro(new FuseMacro()); ctx.addMacro(new FuseEliminativeMacro()); ctx.addMacro(new MinEliminativeMacro()); ctx.addMacro(new MaxEliminativeMacro()); ctx.addMacro(new MinStrictMacro()); ctx.addMacro(new MaxStrictMacro()); ctx.addIndicatorCategory(catDanger); ctx.addIndicatorCategory(catOK); }); test("should evaluate basic assignment of a canonical set", () => { ctx.setRoot("c0"); // high let result = evaluator.evaluate("c0 is high;", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("high"); // reject ctx.setRoot("c1"); let resultZero = evaluator.evaluate("c1 is reject;", ctx); expect(resultZero).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("reject"); // certain ctx.setRoot("c2"); let resultCertain = evaluator.evaluate("c2 is certain;", ctx); expect(resultCertain).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("certain"); // vopp ctx.setRoot("c3"); let resultVlow = evaluator.evaluate("c3 is vopp;", ctx); expect(resultVlow).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should record an error when a named set is not found in the context', () => { ctx.setRoot("c4"); evaluator.evaluate("c4 is unknownSet;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); evaluator.evaluate("c4 is notASet;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); evaluator.evaluate("c4 is fakeSet;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); evaluator.evaluate("c4 is missingSet;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should propagate assignment from a child identifier', () => { ctx.setRoot("C1"); ctx.addChild("C2", NodeType.CLAIM, ctx.getNamedSet('low')); expect(ctx.getChildValuation("C2").name).toBe("low"); evaluator.evaluate("C1 is C2;", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should record an error when assignment target is not the current context root', () => { ctx.setRoot("root"); evaluator.evaluate("notRoot is high;", ctx); expect(ctx.getEvaluationErrors()).toHaveLength(1); expect(ctx.getEvaluationErrors()[0].message) .toMatch(/Assignment target notRoot is not the root of the current scope/); }); test('should handle assignment case insensitively', () => { ctx.setRoot("CaSeTeSt"); // Assignment with different casing let resultHigh = evaluator.evaluate("casetest is HIGH;", ctx); expect(resultHigh).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); ctx.setRoot("casetest"); let resultLow = evaluator.evaluate("CASETEST is uncert;", ctx); expect(resultLow).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); ctx.setRoot("CASETEST"); let resultMed = evaluator.evaluate("CaSeTeSt is low;", ctx); expect(resultMed).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("low"); // Propagate assignment from one identifier to another with different casing ctx.setRoot("casetest"); ctx.addChild("childSet", NodeType.CLAIM, ctx.getNamedSet("high")); let resultPropagate = evaluator.evaluate("casetest is childSet;", ctx); expect(resultPropagate).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); ctx.setRoot("CASETEST"); ctx.addChild("AnotherChild", NodeType.CLAIM, ctx.getNamedSet("uncert")); let resultPropagate2 = evaluator.evaluate("CASETEST is AnotherChild;", ctx); expect(resultPropagate2).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); }); test('should evaluate assignment with intersect operator', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("low")); ctx.addNamedSet("c2", ctx.getNamedSet("uncert")); let result = evaluator.evaluate("c0 is c1 intersect c2;", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low ∧ uncert"); }); test('should evaluate assignment with union operator', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("low")); ctx.addNamedSet("c2", ctx.getNamedSet("uncert")); let result = evaluator.evaluate("c0 is c1 union c2;", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low ∨ uncert"); }); test('should record error for intersect/union with unknown sets', () => { ctx.setRoot("c0"); evaluator.evaluate("c0 is unknown1 intersect unknown2;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); evaluator.evaluate("c0 is unknown1 union unknown2;", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should record an error when assignment produces a non-convex set', () => { ctx.setRoot("c0"); evaluator.evaluate("c0 is trap(-0.9, -0.8, -0.7, -0.6) union trap(0.6, 0.7, 0.8, 0.9);", ctx); expect(ctx.getEvaluationErrors()).toHaveLength(1); expect(ctx.getEvaluationErrors()[0].message).toMatch(/non-convex/i); }); test('should evaluate assignment with nested intersect and union operators', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("high")); ctx.addNamedSet("c2", ctx.getNamedSet("uncert")); ctx.addNamedSet("c3", ctx.getNamedSet("low")); let result = evaluator.evaluate("c0 is c1 intersect c2 union c3;", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("high ∧ uncert ∨ low"); let result2 = evaluator.evaluate("c0 is (c1 union c2) intersect c3;", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("high ∧ uncert ∨ low"); }); test('should evaluate assignment with min operator over canonical sets', () => { ctx.setRoot("c0"); let result = evaluator.evaluate("c0 is min(vopp, uncert, high);", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("vopp"); let result2 = evaluator.evaluate("c0 is min(high, uncert);", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); let result3 = evaluator.evaluate("c0 is min(high, high);", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); let result4 = evaluator.evaluate("c0 is min(high);", ctx); expect(result4).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); let result5 = evaluator.evaluate("c0 is min(reject, high);", ctx); expect(result5).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("reject"); let result6 = evaluator.evaluate("c0 is min(certain, vhigh);", ctx); expect(result6).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("vhigh"); let result7 = evaluator.evaluate("c0 is min(reject, certain);", ctx); expect(result7).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("reject"); }); test('should evaluate assignment with max operator over canonical sets', () => { ctx.setRoot("c0"); let result = evaluator.evaluate("c0 is max(vopp, uncert, high);", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); let result2 = evaluator.evaluate("c0 is max(uncert, vopp);", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); let result3 = evaluator.evaluate("c0 is max(vopp, vopp);", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("vopp"); let result4 = evaluator.evaluate("c0 is max(reject, high);", ctx); expect(result4).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); let result5 = evaluator.evaluate("c0 is max(reject, certain);", ctx); expect(result5).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("certain"); let result6 = evaluator.evaluate("c0 is max(vhigh, certain);", ctx); expect(result6).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("certain"); let result7 = evaluator.evaluate("c0 is max(reject, reject);", ctx); expect(result7).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("reject"); }); test('should record error for min/max operator with unknown sets', () => { ctx.setRoot("c0"); evaluator.evaluate("c0 is min(unknown1, unknown2);", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); evaluator.evaluate("c0 is max(unknown1, unknown2);", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should evaluate min operator over identifiers', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("high")); ctx.addNamedSet("c2", ctx.getNamedSet("uncert")); ctx.addNamedSet("c3", ctx.getNamedSet("vopp")); let result = evaluator.evaluate("c0 is min(c1, c2, c3)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("vopp"); let result2 = evaluator.evaluate("c0 is min(c1, c2)", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); let result3 = evaluator.evaluate("c0 is min(c1, c1)", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); }); test('should evaluate max operator over identifiers', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("high")); ctx.addNamedSet("c2", ctx.getNamedSet("uncert")); ctx.addNamedSet("c3", ctx.getNamedSet("vopp")); let result = evaluator.evaluate("c0 is max(c1, c2, c3)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("high"); let result2 = evaluator.evaluate("c0 is max(c2, c3)", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("uncert"); let result3 = evaluator.evaluate("c0 is max(c3, c3)", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe("vopp"); }); test('should evaluate min operator with intersect/union expressions as arguments', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("vhigh")); ctx.addNamedSet("c2", ctx.getNamedSet("high")); ctx.addNamedSet("c3", ctx.getNamedSet("low")); let result = evaluator.evaluate("c0 is min(c1 intersect c2, c3)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low"); ctx.setRoot("c1"); let result2 = evaluator.evaluate("c1 is min(c1 union c2, c3)", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low"); ctx.setRoot("c2"); let result3 = evaluator.evaluate("c2 is min(c1, c2 union c3)", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("high ∨ low"); evaluator.evaluate("c0 is min(c1 intersect c3, c2)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); }); test('should evaluate max operator with intersect/union expressions as arguments', () => { ctx.setRoot("c0"); ctx.addNamedSet("c1", ctx.getNamedSet("reject")); ctx.addNamedSet("c2", ctx.getNamedSet("vopp")); ctx.addNamedSet("c3", ctx.getNamedSet("opp")); let result = evaluator.evaluate("c0 is max(c1 intersect c2, c3)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("opp"); ctx.setRoot("c1"); let result2 = evaluator.evaluate("c1 is max(c1 union c2, c3)", ctx); expect(result2).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("opp"); ctx.setRoot("c2"); let result3 = evaluator.evaluate("c2 is max(c1, c2 union c3)", ctx); expect(result3).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("vopp ∨ opp"); evaluator.evaluate("c0 is max(c1 intersect c3, c2)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); }); test('should evaluate assignment with trap fuzzy expression', () => { ctx.setRoot("trapSet"); let result = evaluator.evaluate("trapSet is trap(0.1, 0.3, 0.7, 0.9)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toMatch(/trap/); }); test('should evaluate assignment with up fuzzy expression', () => { ctx.setRoot("upSet"); let result = evaluator.evaluate("upSet is up(0.2, 0.8)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toMatch(/up/); }); test('should evaluate assignment with down fuzzy expression', () => { ctx.setRoot("downSet"); let result = evaluator.evaluate("downSet is down(0.15, 0.85)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toMatch(/down/); }); test('should evaluate assignment with constant fuzzy expression', () => { ctx.setRoot("constSet"); let result = evaluator.evaluate("constSet is const(0.5) union high", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toMatch(/const/); }); test('should evaluate assignment with crisp fuzzy expression', () => { ctx.setRoot("crispSet"); let result = evaluator.evaluate("crispSet is crisp(0.1, 0.2)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name).toMatch(/trap/); }); test('should record error for trap with invalid arguments', () => { ctx.setRoot("trapSet"); ctx = evaluator.evaluate("trapSet is trap(0.9, 0.7, 0.3, 0.1)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should record error for up with invalid arguments', () => { ctx.setRoot("upSet"); evaluator.evaluate("upSet is up(0.8, 0.2)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should record error for down with invalid arguments', () => { ctx.setRoot("downSet"); ctx = evaluator.evaluate("downSet is down(0.85, 0.15)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should record error for constant with out-of-range value', () => { ctx.setRoot("constSet"); ctx = evaluator.evaluate("constSet is const(-0.1)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); ctx.clearSyntaxErrors(); ctx = evaluator.evaluate("constSet is const(1.1)", ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); }); test('should throw error for crisp with invalid arguments', () => { ctx.setRoot("crispSet"); ctx = evaluator.evaluate("crispSet is crisp(0.8, 0.2)", ctx) expect(ctx.getEvaluationErrors()).toHaveLength(1); }); test('should evaluate min operator with trap fuzzy expression as argument', () => { ctx.setRoot("minTrapSet"); let result = evaluator.evaluate("minTrapSet is min(trap(0.1, 0.3, 0.7, 0.9), high)", ctx); expect(result).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toMatch(/trap|high/); }); test('should evaluate simple cases with otherwise', () => { ctx.setRoot('c0'); // high >= low is true, so first case matches and assigns high const res = evaluator.evaluate( 'c0 is cases high >= low -> high, otherwise uncert', ctx, ); expect(res).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe('high'); }); test('should evaluate cases with AND condition', () => { ctx.setRoot('c0'); // Both conjuncts true: high >= low AND uncert < low -> vopp const res = evaluator.evaluate( 'c0 is cases high >= low and uncert < low -> vopp, otherwise high', ctx, ); expect(res).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe('vopp'); }); test('should evaluate cases with OR condition', () => { ctx.setRoot('c0'); // Both disjuncts false: uncert >= low OR high <= uncert -> low, otherwise uncert const res = evaluator.evaluate( 'c0 is cases (uncert >= low) OR (high <= uncert) -> low, otherwise uncert', ctx, ); expect(res).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe('uncert'); }); test('should evaluate cases with NOT condition', () => { ctx.setRoot('c0'); // NOT (high <= low) is true, assign high const res = evaluator.evaluate( 'c0 is cases NOT (high <= low) -> high, otherwise uncert', ctx, ); expect(res).toBeTruthy(); expect(ctx.getRoot().valuation.name.toLowerCase()).toBe('high'); }); test('should evaluate NOT in boolean and fuzzy expressions correctly', () => { ctx.setRoot('c0'); let result = evaluator.evaluate('c0 is cases NOT (NOT vhigh >= vhigh) -> reject, otherwise certain', ctx); expect(result).toBeTruthy(); expect(result.getRoot().valuation.name).toBe('reject'); result = evaluator.evaluate('c0 is cases NOT (NOT (NOT vhigh >= vhigh)) -> reject, otherwise certain', ctx); expect(result).toBeTruthy(); expect(result.getRoot().valuation.name).toBe('certain'); result = evaluator.evaluate('c0 is cases NOT vhigh >= vhigh -> reject, otherwise certain', ctx); expect(result).toBeTruthy(); expect(result.getRoot().valuation.name).toBe('certain'); }); test('should evaluate complement of a canonical set using comp(...)', () => { ctx.setRoot('c0'); const vhigh = ctx.getNamedSet('vhigh'); const result = evaluator.evaluate('c0 is comp(vhigh)', ctx); expect(result).toBeTruthy(); const comp = ctx.getRoot().valuation; // Check complement semantics at a few points const samples = [0.0, 0.5, 1.0]; for (const x of samples) { expect(comp.eval(x)).toBeCloseTo(1 - vhigh.eval(x), 5); } }); test('should evaluate invert of a canonical directly', () => { ctx.setRoot('c0'); const opp = ctx.getNamedSet('opp'); const result = evaluator.evaluate('c0 is INVERT high', ctx); expect(result).toBeTruthy(); const inv = ctx.getRoot().valuation; // Check inverted semantics at a few points const samples = [0.0, 0.5, 1.0]; for (const x of samples) { expect(inv.eval(x)).toBeCloseTo(opp.eval(x), 5); } }); test('should evaluate invert of non-canonical fully', () => { ctx.setRoot('c0'); const result = evaluator.evaluate('c0 is invert(up(0.1, 0.2))', ctx); expect(result).toBeTruthy(); const inv = ctx.getRoot().valuation; // Check inverted semantics at a few points expect(inv.name).toBe("~up(0.1,0.2)"); expect(inv.eval(-0.3)).toBeCloseTo(1, 5); expect(inv.eval(-0.2)).toBeCloseTo(1, 5); expect(inv.eval(-0.15)).toBeCloseTo(0.5, 5); expect(inv.eval(-0.10)).toBeCloseTo(0, 5); expect(inv.eval(0.00)).toBeCloseTo(0, 5); expect(inv.eval(0.00)).toBeCloseTo(0, 5); expect(inv.eval(0.10)).toBeCloseTo(0, 5); expect(inv.eval(0.15)).toBeCloseTo(0, 5); expect(inv.eval(0.20)).toBeCloseTo(0, 5); expect(inv.eval(0.50)).toBeCloseTo(0, 5); expect(inv.eval(0.80)).toBeCloseTo(0, 5); expect(inv.eval(0.85)).toBeCloseTo(0, 5); expect(inv.eval(0.90)).toBeCloseTo(0, 5); expect(inv.eval(0.99)).toBeCloseTo(0, 5); expect(inv.eval(1.00)).toBeCloseTo(0, 5); }); test('should accept new fuzzy set definition', () => { ctx.setRoot('c0'); evaluator.evaluate('with foo as up(0.1, 0.2); end c0 is foo', ctx); const comp = ctx.getRoot().valuation; expect(comp.name).toBe('foo'); expect(comp.eval(0)).toBeCloseTo(0); expect(comp.eval(0.05)).toBeCloseTo(0); expect(comp.eval(0.1)).toBeCloseTo(0); expect(comp.eval(0.15)).toBeCloseTo(0.5); expect(comp.eval(0.20)).toBeCloseTo(1.0); expect(comp.eval(0.21)).toBeCloseTo(1.0); expect(comp.eval(0.99)).toBeCloseTo(1.0); expect(comp.eval(1.0)).toBeCloseTo(1.0); }); test('should accept new op definition, even if it is not used', () => { ctx.setRoot('c0'); evaluator.evaluate('with myOp(aa: any, bb: any) as min(aa, bb); myOp2(cc:any, dd:any) as max(cc,dd); end c0 is high', ctx); expect(ctx.getNamedOp("myOp")).toBeTruthy(); expect(ctx.getNamedOp("myOp2")).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe('high'); }); test('should raise error when op definition has duplicate parameters', () => { ctx = evaluator.evaluate('global myOp(aa: any, aa: any) as min(aa, aa) end', ctx); expect(ctx.getEvaluationErrors()).toHaveLength(1); expect(ctx.getEvaluationErrors()[0].message.toLowerCase()).toContain("duplicate"); }); test('should record error when op definition has recursion', () => { evaluator.evaluate('global myOp(aa: any, bb: any) as myOp(aa, bb) end', ctx); expect(ctx.getEvaluationErrors()).toHaveLength(1); expect(ctx.getEvaluationErrors()[0].message).toMatch(/Recursion/i); ctx.clearEvaluationErrors(); }); test('should record error on unknown node type', () => { evaluator.evaluate('with myOp(aa: foobar) as invert(aa) end', ctx); expect(ctx.getEvaluationErrors()).toHaveLength(1); expect(ctx.getEvaluationErrors()[0].message).toMatch(/Unknown/i); ctx.clearEvaluationErrors(); }); test('should accept new op definition, even if it is not used', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); expect(ctx.getChildValuation('c1').name).toBe('high'); expect(ctx.getChildValuation('c2').name).toBe('uncert'); ctx = evaluator.evaluate('with myOp(aa: any, bb: claim) as min(aa, bb); end c0 is myOp(c1, c2)', ctx); expect(ctx.getRoot().valuation.name).toBe('uncert'); ctx = evaluator.evaluate('with myOp2(aa: claim) as cases aa is uncert -> vopp, otherwise reject; end c0 is myOp2(c2)', ctx); expect(ctx.getRoot().valuation.name).toBe('vopp'); }); test('should reject operator invocations with incompatible types', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.DEFEATER, ctx.getNamedSet('uncert')); evaluator.evaluate('with myOp(aa: claim, bb: premise) as min(aa, bb); end c0 is myOp(c1, c2)', ctx); expect(ctx.getEvaluationErrors().length).toBeGreaterThan(0); ctx.clearEvaluationErrors(); }); test('should evaluate min macro over children', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx = evaluator.evaluate("c0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min macro over mixed children', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min macro over defeater only children', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('skep')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx = evaluator.evaluate("c0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("opp"); }); test('should evaluate min macro over negative defeater children', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('skep')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('opp')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min macro for defeater parent over premise-typed children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate min macro for defeater parent over defeater-typed children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate min macro for defeater parent over mixed-typed children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('opp')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should expand min macro', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); const ret = ctx.getMacro("min").expand(ctx, "foo"); expect(ret).toBe("foo is min(c1, c2)"); }); test('should evaluate min macro for premise-typed parent with no children', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MIN", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate min_elim macro for parent claim over mixed defeater and claim children', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("c0 is #MIN_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate min_elim macro for parent defeater over mixed defeater and claim children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("d0 is #MIN_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("opp"); }); test('should evaluate min_elim macro with no children for premise-typed parent', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MIN_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min_elim macro with no children for defeater-typed parent', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MIN_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should expand min_elim macro', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('uncert')); const ret = ctx.getMacro("min_elim").expand(ctx, ctx.getRoot().id); expect(ret).toBe("c0 is min(c1, invert d2)"); }); test('should evaluate max_elim macro for parent claim over mixed defeater and claim children', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("c0 is #MAX_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate max_elim macro for parent defeater over mixed defeater and claim children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("d0 is #MAX_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("vhigh"); }); test('should expand max_def macro', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('uncert')); const ret = ctx.getMacro("max_elim").expand(ctx, ctx.getRoot().id); expect(ret).toBe("c0 is max(c1, invert d2)"); }); test('should evaluate max_elim macro with no children for premise-typed parent', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MAX_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate max_elim macro with no children for defeater-typed parent', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MAX_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should expand max macro', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('uncert')); const ret = ctx.getMacro("max").expand(ctx, "foo"); expect(ret).toBe("foo is max(c1, c2, c3)"); }); test('should evaluate max macro over children', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("c0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toBe("vhigh"); }); test('should evaluate max macro for premise-typed parent over positive defeater children', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("c0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should evaluate max macro for premise-typed parent over negative defeater children', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('skep')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('opp')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate max macro for premise-typed parent with no children', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate max macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate min_strict macro for premise-typed parent with only child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vopp')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate min_strict macro for premise-typed parent with only certain child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate min_strict macro for premise-typed parent with only reject child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for premise-typed parent with only rejected defeaters', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min_strict macro for premise-typed parent with un-rejected defeaters', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for premise-typed parent with child claims and rejected defeater', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('skep')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should evaluate min_strict macro for premise-typed parent with child claims and un-rejected defeater', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for premise-typed parent with no children', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate min_strict macro for defeater-typed parent with defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate min_strict macro for defeater-typed parent with rejected defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for defeater-typed parent with certain defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('certain')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate min_strict macro for defeater-typed parent with one certain premise child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for defeater-typed parent with certain child premise and another less certain child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should evaluate min_strict macro for defeater-typed parent with mixed premises', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('skep')); ctx.addChild('d4', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate min_strict macro for defeater-typed with certain premises and rejected defeaters', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d4', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate min_strict macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MIN_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate max_strict macro for premise-typed parent with only child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vopp')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate max_strict macro for premise-typed parent with only certain child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate max_strict macro for premise-typed parent with only reject child premises', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for premise-typed parent with only rejected defeaters', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate max_strict macro for premise-typed parent with un-rejected defeaters', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for premise-typed parent with child claims and rejected defeater', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('skep')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("vhigh"); }); test('should evaluate max_strict macro for premise-typed parent with child claims and un-rejected defeater', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate MAX_strict macro for premise-typed parent with no children', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate MAX_strict macro for defeater-typed parent with defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate max_strict macro for defeater-typed parent with rejected defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for defeater-typed parent with certain defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('certain')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate max_strict macro for defeater-typed parent with one certain premise child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for defeater-typed parent with certain child premise and another less certain child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for defeater-typed parent with mixed premises', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('skep')); ctx.addChild('d4', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate max_strict macro for defeater-typed with certain premises and rejected defeaters', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('d3', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('d4', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate max_strict macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #MAX_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate fuse_strict macro for parent claim with child claims', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate fuse_strict macro for parent claim with single rejected defeater child', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #FUSE_STRICT", ctx); expect(ctx.getEvaluationErrors().length).toBe(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse_strict macro for parent defeater with single rejected defeater child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #FUSE_STRICT", ctx); expect(ctx.getEvaluationErrors().length).toBe(0); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse_strict macro for parent claim with defeaters among children', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse_strict macro for parent claim with rejected defeaters among children', () => { ctx.setRoot('c0'); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse_strict macro for all children certain', () => { ctx.setRoot('c0'); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c3', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("c0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate fuse_strict macro for parent defeater with premise-only children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should evaluate fuse_strict macro for parent defeater with all rejected child defeaters and all certain claims', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse_strict macro for parent defeater with non-reject defeater child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vhigh')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx = evaluator.evaluate("d0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate fuse macro for premise-typed parent with no children', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate fuse macro for premise-typed parent with only premise-typed children', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('high')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate fuse macro for premise-typed parent with only premise-typed children of reject confidence', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse macro for premise-typed parent with only defeater-typed children', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('uncert')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse macro for premise-typed parent with only defeater-typed children vopp', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('vopp')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse macro for premise-typed parent with only defeater-typed children high', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("opp"); }); test('should evaluate fuse macro for premise-typed parent with only defeater-typed children with reject confidence', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse macro for premise-typed parent with multiple defeater-typed children', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('uncert')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("skep"); }); test('should evaluate fuse macro for premise-typed parent with mixed children, premises are stronger', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('uncert')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate fuse macro for premise-typed parent with mixed children, defeaters are stronger', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("opp"); }); test('should evaluate fuse macro to vopp when one vopp and one reject', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vopp')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate fuse macro to reject when both children are reject', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse macro to vhigh when one certain and one vhigh', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("vhigh"); }); test('should evaluate fuse macro to certain when both children are certain', () => { ctx.setRoot('c0', null, NodeType.PREMISE); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("c0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate fuse macro for defeater-typed parent defeater-only children with reject confidence', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse macro for defeater-typed parent with no children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('should evaluate fuse macro for defeater-typed parent defeater-only children with non-reject confidence', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('low')); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate fuse macro for defeater-typed parent defeater-only children with insufficient child claim', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate fuse macro for defeater-typed parent defeater-only children with certain child claim', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('certain')); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("reject"); }); test('should evaluate fuse macro for defeater-typed parent with mixed children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #FUSE", ctx); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate FUSE_STRICT macro for parent defeater with reject defeater child', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('d1', NodeType.DEFEATER, ctx.getNamedSet('reject')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx = evaluator.evaluate("d0 is #FUSE_STRICT", ctx); expect(ctx.getRoot().valuation.name).toBe("vopp"); }); test('should evaluate FUSE_ELIM macro for claim parent without defeater children', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('vopp')); ctx = evaluator.evaluate("c0 is #FUSE_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate FUSE_ELIM macro for claim parent with defeater children', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vhigh')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("c0 is #FUSE_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate FUSE_ELIM macro for defeater parent with mixed claim and defeater children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('vopp')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('high')); ctx = evaluator.evaluate("d0 is #FUSE_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('should evaluate FUSE_ELIM macro defeater-typed parent without children', () => { ctx.setRoot('c0'); ctx = evaluator.evaluate("c0 is #FUSE_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('should evaluate FUSE_ELIM macro defeater-typed parent without children', () => { ctx.setRoot('d0', null, NodeType.DEFEATER); ctx = evaluator.evaluate("d0 is #FUSE_ELIM", ctx); expect(ctx.getRoot().valuation.name).toBe("certain"); }); test('MAX macro should not return certain unless is certain', () => { ctx.addNamedSet("foo", trap(UI, 0.91, 0.92, 0.93, 0.94)); ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('foo')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("c0 is #MAX", ctx); expect(ctx.getRoot().valuation.name).toContain("vhigh"); }); test('should remove named set from context', () => { ctx.addNamedSet("foo", trap(UI, 0.1, 0.2, 0.3, 0.4)); expect(ctx.hasNamedSet('foo')).toBeTruthy(); ctx.removeNamedSet('foo'); expect(ctx.hasNamedSet('foo')).toBeFalsy(); expect(() => ctx.removeNamedSet('bar')).toThrowError(); }); test('should remove named operation from context', () => { ctx = evaluator.evaluate("global foo(aa: any, bb:any) as min(aa, bb) end", ctx); expect(ctx.hasNamedOp('foo')).toBeTruthy(); ctx.removeNamedOp('foo'); expect(ctx.hasNamedOp('foo')).toBeFalsy(); expect(() => ctx.removeNamedOp('bar')).toThrowError(); }); test('should evaluate assignment preceded by WITH with terminating ;', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("with foo(aa: any, bb:any) as min(aa, bb) end c0 is foo(c1, c2)", ctx); expect(ctx.hasNamedOp('foo')).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should evaluate assignment preceded by WITH terminated by ;', () => { ctx.setRoot('c0', null, NodeType.CLAIM); ctx.addChild('c1', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('c2', NodeType.CLAIM, ctx.getNamedSet('low')); ctx = evaluator.evaluate("WITH foo(aa: any, bb:any) as min(aa, bb); END c0 is foo(c1, c2)", ctx); expect(ctx.hasNamedOp('foo')).toBeTruthy(); expect(ctx.getRoot().valuation.name).toBe("low"); }); test('should remove all children from context', () => { ctx.addChild("c1", NodeType.CLAIM); ctx.addChild("c2", NodeType.CLAIM); ctx.addChild("c3", NodeType.CLAIM); expect(ctx.hasChild('c1')).toBeTruthy(); expect(ctx.hasChild('c2')).toBeTruthy(); expect(ctx.hasChild('c3')).toBeTruthy(); ctx.removeAllChildren(); expect(ctx.hasChild('c1')).toBeFalsy(); expect(ctx.hasChild('c2')).toBeFalsy(); expect(ctx.hasChild('c3')).toBeFalsy(); }); test('should remove just one children from context', () => { ctx.addChild("c1", NodeType.CLAIM); ctx.addChild("c2", NodeType.CLAIM); ctx.addChild("c3", NodeType.CLAIM); expect(ctx.hasChild('c1')).toBeTruthy(); expect(ctx.hasChild('c2')).toBeTruthy(); expect(ctx.hasChild('c3')).toBeTruthy(); ctx.removeChild("c2"); expect(ctx.hasChild('c1')).toBeTruthy(); expect(ctx.hasChild('c2')).toBeFalsy(); expect(ctx.hasChild('c3')).toBeTruthy(); expect(() => ctx.removeChild('bar')).toThrowError(); }); test('Should sanitize node IDs to lower case without padded zeros', () => { ctx.setRoot('C0001', null, NodeType.CLAIM); ctx.addChild('C0003', NodeType.CLAIM, ctx.getNamedSet('high')); ctx.addChild('C1234', NodeType.CLAIM, ctx.getNamedSet('uncert')); ctx.addChild('d2', NodeType.DEFEATER, ctx.getNamedSet('reject')); expect(ctx.hasChild('C0003')).toBeTruthy(); expect(ctx.hasChild('c3')).toBeTruthy(); expect(ctx.hasChild('C3')).toBeTruthy(); expect(ctx.hasChild('c03')).toBeTruthy(); expect(ctx.hasChild('C1234')).toBeTruthy(); expect(ctx.hasChild('c1234')).toBeTruthy(); expect(ctx.hasChild('d2')).toBeTruthy(); expect(ctx.hasChild('D2')).toBeTruthy(); expect(ctx.hasChild('D0002')).toBeTruthy(); expect(ctx.hasChild('D02')).toBeTruthy(); expect(ctx.hasChild('c30')).toBeFalsy(); expect(ctx.hasChild('D20')).toBeFalsy(); expect(ctx.hasChild('X0003')).toBeFalsy(); expect(() => ctx.hasChild('')).toThrowError(); expect(() => ctx.hasChild(undefined)).toThrowError(); expect(() => ctx.hasChild(null)).toThrowError(); ctx = evaluator.evaluate("c0001 is min(c03, c1234, D0002)", ctx); expect(ctx.getRoot().valuation.name).toBe('reject'); }); test('Should collect syntax errors', () => { ctx.setRoot('C1', null, NodeType.CLAIM); ctx.addChild('C2', NodeType.CLAIM, ctx.getNamedSet('high')); ctx = evaluator.evaluate("c1 SYNTAX_ERROR c2", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(1); }); test('Should parse indicators without spaces in the name', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo", valuation: catDanger, value: 0.5}); ctx = evaluator.evaluate("c1 is cases $foo is DANGER -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('Should parse indicators with spaces in the name', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo bar_baz", valuation: catOK, value: 0.6}); ctx = evaluator.evaluate('c1 is cases $"foo bar_baz" is ok -> uncert, otherwise high', ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('Should support numerical evaluation of indicator values', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo", valuation: catDanger, value: 0.5}); ctx = evaluator.evaluate("c1 is cases $foo = 0.5 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo == 0.5 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo > 0.4 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo >= 0.4 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo < 0.6 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo <= 0.6 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo != 0.6 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo != 0.5 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('Should compare two indicators using =, !=, >, >=, <, <=', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo", valuation: catDanger, value: 0.4}); ctx.addIndicator({id: "bar", valuation: catOK, value: 0.6}); ctx = evaluator.evaluate("c1 is cases $foo = $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("high"); ctx = evaluator.evaluate("c1 is cases $foo != $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo > $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("high"); ctx = evaluator.evaluate("c1 is cases $foo >= $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("high"); ctx = evaluator.evaluate("c1 is cases $foo < $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); ctx = evaluator.evaluate("c1 is cases $foo <= $bar -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('Should raise error when indicator not found', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo", valuation: catDanger, value: 0.5}); ctx = evaluator.evaluate("c1 is cases $NOT_FOUND = 0.5 -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(1); ctx.clearEvaluationErrors(); ctx.clearSyntaxErrors(); ctx = evaluator.evaluate('c1 is cases $"also doesnt exist" = 0.5 -> uncert, otherwise high', ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(1); }); test('Should raise error when indicator category not found', () => { ctx.setRoot('C1'); ctx.addIndicator({id: "foo", valuation: catDanger, value: 0.5}); ctx = evaluator.evaluate("c1 is cases $foo is BAD_CAT -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(1); }); test('Should evaluate HAS artifact when artifact exists in context', () => { ctx.setRoot('C1'); ctx.addArtifact({id: "report"}); ctx = evaluator.evaluate("c1 is cases has @report -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); }); test('Should evaluate HAS artifact when artifact does not exist in context', () => { ctx.setRoot('C1'); ctx = evaluator.evaluate("c1 is cases has @report -> uncert, otherwise high", ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("high"); }); test('Should evaluate HAS artifact with spaces in artifact name', () => { ctx.setRoot('C1'); ctx.addArtifact({id: "foo bar_baz"}); ctx = evaluator.evaluate('c1 is cases has @"foo bar_baz" -> uncert, otherwise high', ctx); expect(ctx.getSyntaxErrors()).toHaveLength(0); expect(ctx.getEvaluationErrors()).toHaveLength(0); expect(ctx.getRoot().valuation.name).toBe("uncert"); });