diff --git a/lib/tcmd/Resolver.tsx b/lib/tcmd/Resolver.tsx index bcbc3fa..8ed11bb 100644 --- a/lib/tcmd/Resolver.tsx +++ b/lib/tcmd/Resolver.tsx @@ -8,7 +8,15 @@ export function Resolver({ resolver }: { resolver: string }) { const [res] = useState(new TTCQueryResolver(parser)); const [content, setContent] = useState(""); useEffect(() => { - setContent(res.resolve(resolver)); + let resolved = res.resolve(resolver); + + setContent( + typeof resolved?.display === "function" ? ( + + ) : ( + resolved?.display + ) + ); }, [resolver, res]); return {content}; } @@ -31,8 +39,9 @@ export function OnDemandResolver({ for (const idx of stackIdxs) { let thing = res.current.getFromStack(idx); if (Array.isArray(thing)) thing = thing.at(0); - if (typeof thing === "function") thing = thing(); - content = content.replaceAll(idx, thing as string); + if (typeof thing.display === "function") + content = content.replaceAll(idx, thing.display() as string); + else content = content.replaceAll(idx, thing.display as string); } setContent(content); }, [res, template]); diff --git a/lib/ttcQuery/TTCResolvers.ts b/lib/ttcQuery/TTCResolvers.ts index f48ac4f..010ce88 100644 --- a/lib/ttcQuery/TTCResolvers.ts +++ b/lib/ttcQuery/TTCResolvers.ts @@ -1,14 +1,20 @@ /* eslint-disable react/display-name */ +import { ReactNode } from "react"; import { Dice } from "../dice"; import { sum } from "../utils/sum"; import { DiceChart } from "./DiceChart"; import { TTCQueryParser } from "./TTCQueryParser"; +interface StackItem { + value: any; + display: ReactNode | (() => ReactNode); +} + export class TTCQueryResolver { private parser: TTCQueryParser | null; private context: QueryableObject | null = null; - private stack: any[] = []; + private stack: StackItem[] = []; constructor(parser?: TTCQueryParser) { this.parser = parser || null; @@ -22,25 +28,35 @@ export class TTCQueryResolver { this.context = obj; } - public resolve(resolver: string, onDemand?: boolean) { + public resolve(resolver: string, onDemand?: boolean): StackItem | undefined { 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(); + if (typeof last?.display === "function" && !onDemand) { + last.display = last.display(); + } return last; } catch (e) { - return e?.toString(); + return { value: e?.toString(), display: e?.toString() }; } } - private parseResolver(resolver: string) { + private parseResolver(resolver: string): StackItem { if (this.isArithmetic(resolver)) return this.solveArithmetic(resolver); if (this.isQuery(resolver)) return this.runQuery(resolver); - return resolver; + if (Dice.isDice(resolver)) { + const value = new Dice(resolver); + const dice: StackItem = { + value, + display: () => value.toString(), + }; + return dice; + } + return { value: resolver, display: resolver }; } private isQuery(resolver: string) { return ( @@ -49,100 +65,140 @@ export class TTCQueryResolver { /^\$\d\./.test(resolver) ); } - private runQuery(query: string) { + private runQuery(query: string): StackItem { 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 - ); + + if (stackItem.value instanceof Dice) { + return { + value: query, + display: this.handleDice(stackItem.value, q), + }; } - return this.parser.search(q, stackItem as QueryableObject); + const [res] = this.parser.search(q, stackItem.value as QueryableObject); + debugger; + if (Dice.isDice(res)) { + const value = new Dice(res); + return { + value, + display: () => value.toString(), + }; + } + return { + value: res, + display() { + return ( + this.value.render ?? this.value ?? "Error resolving query: " + query + ); + }, + }; } // 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"; + const res = + query.startsWith("_") && this.context + ? this.parser.search(query.replace("_", "^"), this.context).at(0) + : this.parser.search(query.replace(/^[?_].?/, "")).at(0); + if (Dice.isDice(res)) { + const value = new Dice(res); + return { + value, + display: () => value.toString(), + }; } + return { + value: res, + display() { + return ( + this.value.render ?? this.value ?? "Error resolving query: " + query + ); + }, + }; } private isArithmetic(resolver: string) { return resolver.split(/\+|\/|-|\*|\^/).filter((e) => !!e).length > 1; } - private solveArithmetic(resolver: string) { + private solveArithmetic(resolver: string): StackItem { const [n1, op, n2] = resolver .match(/(\$?\d+)([+\-*\/^])(\$?\d+)/) ?.slice(1) || ["", "+", ""]; - let num1 = Number(n1), - num2 = Number(n2); + let num1: number = Number(n1), + num2: number = 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"; + if (n1.startsWith("$")) { + const result = this.getFromStack(n1).value; + num1 = result instanceof Dice ? result.roll().total : Number(result); } + if (n2.startsWith("$")) { + const result = this.getFromStack(n1).value; + num2 = result instanceof Dice ? result.roll().total : Number(result); + } + + const thing: StackItem = { + value: () => { + 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"; + } + }, + display() { + return typeof this.value === "function" ? this.value() : this.value; + }, + }; + + return thing; } - public getFromStack(stackIndex: string): T { + public getFromStack(stackIndex: string): StackItem { const i = Number(stackIndex.replace("$", "")); - const val = this.stack[i] as T; + const val = this.stack[i]; return val; } + private handleDice(dice: Dice, query: string) { + const [method, n] = query.split(":"); + let num = Number(n); + // if (n && n.startsWith("$")) num = this.getFromStack(n); + switch (method) { + case "roll": + return () => dice.roll.apply(dice).total; + case "rollAvg": + return () => dice.rollAvg.apply(dice); + case "rollTimes": + return () => dice.rollTimes.apply(dice, [num]); + case "rollTimesAvg": + return () => dice.rollTimesAvg.apply(dice, [num]); + case "rollLowest": + return () => dice.rollMin.apply(dice); + case "rollHighest": + return () => dice.rollMax.apply(dice); + case "rollDropHighest": + return () => + sum(...dice.roll.apply(dice).results.toSorted().toSpliced(-1, 1)); + case "rollDropLowest": + return () => + sum(...dice.roll.apply(dice).results.toSorted().toSpliced(0, 1)); + case "distribution": + return () => DiceChart({ dice: dice.getRollDistribution.apply(dice) }); + case "distribution.normalized": + return () => + DiceChart({ dice: dice.getNormalizedRollDistribution.apply(dice) }); + default: + return () => "No valid method provided for dice"; + } + } }