import { Node } from "../model/Argument"; import { CertusContext, FZ } from "../model/Context"; import { EvaluationError } from "../parser/Evaluator"; export class NoChildrenMacroError extends Error { constructor(msg: string) { super(msg); } } export abstract class Macro { constructor(public name: string) { /* intentionally empty */ } /** * Evaluates a macro directly within the provided context. Does not expand the macro, * uses internal calculations over canonical sets from the context to evaluate. * * @param ctx Context object to evaluate the macro within, contains fuzzy sets */ public abstract evaluate(ctx: CertusContext): FZ; /** * Expands the macro into a cases expression over the provided children nodes. * * @param ctx Context object to expand the macro within, contains fuzzy sets * @param target assignment target (e.g., C0 is ... ), if not passed then assignment is not expanded. * @return a string representing the expanded macro that can be evaluated by Certus. */ public abstract expand(ctx: CertusContext, target?: string): string; protected getNodesFromContext(ctx: CertusContext): Node[] { return ctx.children.map(c => { return { id: c, type: ctx.getChildType(c), valuation: ctx.getChildValuation(c) }; }); } /** * Checks for the identified node in the provided context. If it is not found * then an error is thrown, otherwise the method completes without errors. * * @param ctx Context to search for node within. * @param id Identifier for the node in the context. */ protected checkNodeInContext(ctx: CertusContext, id: string): void { if (!ctx.hasChild(id)) { throw new EvaluationError(`Cannot evaluate macro for node '${id}', it is not in the current context.`) } } protected checkMinimumNodes(nodes: Node[]): void { if (nodes.length < 1) { throw new EvaluationError(`Cannot evaluate macro without children.`) } } protected scoreNode(ctx: CertusContext, n: Node, sortedSets: FZ[]): number { const N = sortedSets.length - 1; let ret = -4; for (let i = 0; i < sortedSets.length; i++) { if (n.valuation.gt(sortedSets[i])) { ret++; } else { // Handle special case where we hit the highest value among the sorted // sets but have not exceed the value. if (ret == 4) { if (n.valuation.geq(sortedSets[N])) { return ret; } else { return ret - 1; } } else { return ret; } } } return ret; } protected scoreToSet(score: number, sortedSets: FZ[], invert = false): FZ { const half = Math.floor(sortedSets.length/2.0); const clamped = Math.max(-1*half, Math.min(half, this.roundScore(score))); const idx = clamped + half; return sortedSets[idx]; } protected setsToNumbers(sets: FZ[]): number[] { if (sets.length % 2 == 0) { throw Error(`Requires odd number of canonical sets`) } else { const half = Math.floor(sets.length/2.0); const ret = [] for (let i = 0; i < sets.length; i++) { ret.push(i - half); } return ret; } } /** * Computes the iterative product of sets, up to N times */ protected combinations(sets: number[], N: number): number[][] { // For sets: z, l, m, h and N=4 we have: // [z, z, z, z] // [z, z, z, l] // [z, z, z, m] // [z, z, z, h] // [z, z, h, z] // [z, z, h, l] // ... // [h, h, h, h] const X: number[][] = []; if (N == 1) { for (const s of sets) { X.push([s]); } } else if (N > 1) { // [z] -> [[z, z], [z, l], [z, m], [z, h]] // [l] -> [l, z], [l, l], [l, m], [l, h] // [m] ... // [h] -> [h, z], [] const prev: number[][] = this.combinations(sets, N-1); for (const p of prev) { for (const s of sets) { X.push(p.concat(s)); } } } else { throw new Error(`Recursion error while generation combinations for macro ${this.name}, must have N >= 1`); } return X; } protected buildCase(nodes: string[], sets: FZ[], result: FZ, comb: string = "and", comparison = ">"): string { const ops = nodes.map((n,i) => `${n} ${comparison} ${sets[i].name}`) const expr = `${ops.join(` ${comb} `)} -> ${result.name}`; return expr; } protected roundScore(x: number): number { if (x < 0) { return Math.ceil(x); } else { return Math.floor(x); } } }