/* eslint-disable react/display-name */ import { Dice } from "../dice"; import { sum } from "../utils/sum"; import { DiceChart } from "./DiceChart"; import { TTCQueryParser } from "./TTCQueryParser"; export class TTCQueryResolver { private parser: TTCQueryParser | null; private context: QueryableObject | null = null; private stack: any[] = []; constructor(parser?: TTCQueryParser) { this.parser = parser || null; } public setParser(parser: TTCQueryParser) { this.parser = parser; } public setContext(obj: QueryableObject) { this.context = obj; } public resolve(resolver: string, onDemand?: boolean) { try { const resList = resolver.split(","); for (const res of resList) { this.stack.push(this.parseResolver(res)); } const last = this.stack.at(-1); if (typeof last === "function" && !onDemand) return last(); return last; } catch (e) { return e?.toString(); } } private parseResolver(resolver: string) { if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); if (this.isQuery(resolver)) return this.runQuery(resolver); return resolver; } private isQuery(resolver: string) { return ( resolver.startsWith("?") || resolver.startsWith("_") || /^\$\d\./.test(resolver) ); } private runQuery(query: string) { if (!this.parser) throw "Parser not defined in query resolver"; if (query.startsWith("$")) { const [_, idx, q] = query.match(/^(\$\d+)\.(.*)/) || []; if (!_) throw "Detected stack query but did not match the regex"; const stackItem = this.getFromStack(idx); if ( typeof stackItem === "string" && Dice.isDice( stackItem.replace(/\$\d+/g, (e) => this.getFromStack(e).toString() ) ) ) { return this.handleDice( stackItem.replace(/\$\d+/g, (e) => this.getFromStack(e).toString() ), q ); } return this.parser.search(q, stackItem as QueryableObject); } // if (query.startsWith("?") || query.startsWith("_")) return query.startsWith("_") && this.context ? this.parser.search(query.replace("_", "^"), this.context).at(0) : this.parser.search(query.replace(/^[?_].?/, "")).at(0); } private handleDice(dice: string, query: string) { const d = new Dice(dice); const [method, n] = query.split(":"); let num = Number(n); // if (n && n.startsWith("$")) num = this.getFromStack(n); switch (method) { case "roll": return () => d.roll().total; case "rollAvg": return () => d.rollAvg(); case "rollTimes": return () => d.rollTimes(num); case "rollTimesAvg": return () => d.rollTimesAvg(num); case "rollLowest": return () => d.rollMin(); case "rollHighest": return () => d.rollMax(); case "rollDropHighest": return () => sum(...d.roll().results.toSorted().toSpliced(-1, 1)); case "rollDropLowest": return () => sum(...d.roll().results.toSorted().toSpliced(0, 1)); case "distribution": return () => DiceChart({ dice: d.getRollDistribution() }); case "distribution.normalized": return () => DiceChart({ dice: d.getNormalizedRollDistribution() }); default: return "No valid method provided for dice"; } } private isArithmetic(resolver: string) { return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1; } private solveArithmetic(resolver: string) { const [n1, op, n2] = resolver .match(/(\$?\d+)([+\-*\/^])(\$?\d+)/) ?.slice(1) || ["", "+", ""]; let num1 = Number(n1), num2 = Number(n2); if (n1.startsWith("$")) num1 = this.getFromStack(n1); if (n2.startsWith("$")) num2 = this.getFromStack(n2); switch (op) { case "+": return num1 + num2; case "-": return num1 - num2; case "*": return num1 * num2; case "/": return num1 / num2; case "^": return num1 ^ num2; default: throw "Arithmetic detected but no proper operator assigned"; } } public getFromStack(stackIndex: string): T { const i = Number(stackIndex.replace("$", "")); const val = this.stack[i] as T; return val; } }