Add support for variables (pulling from previous commands in same message)

This commit is contained in:
Ean Milligan 2025-05-03 18:15:51 -04:00
parent d142b35522
commit b7e58f56a5
5 changed files with 62 additions and 22 deletions

View File

@ -41,7 +41,7 @@ export const runCmd = (fullCmd: string, modifiers: RollModifiers): SolvedRoll =>
assertPrePostBalance(sepCmds); assertPrePostBalance(sepCmds);
// Send the split roll into the command tokenizer to get raw response data // Send the split roll into the command tokenizer to get raw response data
const [tempReturnData, tempCountDetails] = tokenizeCmd(sepCmds, modifiers, true); const [tempReturnData, tempCountDetails] = tokenizeCmd(sepCmds, modifiers, true, []);
loggingEnabled && log(LT.LOG, `Return data is back ${JSON.stringify(tempReturnData)}`); loggingEnabled && log(LT.LOG, `Return data is back ${JSON.stringify(tempReturnData)}`);
// Remove any floating spaces from fullCmd // Remove any floating spaces from fullCmd

View File

@ -15,7 +15,7 @@ import { loggingEnabled } from 'artigen/utils/logFlag.ts';
import { getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts'; import { getMatchingInternalIdx, getMatchingPostfixIdx } from 'artigen/utils/parenBalance.ts';
// tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls // tokenizeCmd expects a string[] of items that are either config.prefix/config.postfix or some text that contains math and/or dice rolls
export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: boolean): [ReturnData[], CountDetails[]] => { export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: boolean, previousResults: number[]): [ReturnData[], CountDetails[]] => {
loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`); loggingEnabled && log(LT.LOG, `Tokenizing command ${JSON.stringify(cmd)}`);
const returnData: ReturnData[] = []; const returnData: ReturnData[] = [];
@ -28,8 +28,15 @@ export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: b
const openIdx = cmd.indexOf(config.prefix); const openIdx = cmd.indexOf(config.prefix);
const closeIdx = getMatchingPostfixIdx(cmd, openIdx); const closeIdx = getMatchingPostfixIdx(cmd, openIdx);
loggingEnabled && log(LT.LOG, `Setting previous results: topLevel:${topLevel} ${topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults}`);
// Handle any nested commands // Handle any nested commands
const [tempData, tempCounts] = tokenizeCmd(cmd.slice(openIdx + 1, closeIdx), modifiers, false); const [tempData, tempCounts] = tokenizeCmd(
cmd.slice(openIdx + 1, closeIdx),
modifiers,
false,
topLevel ? returnData.map((rd) => rd.rollTotal) : previousResults,
);
const data = tempData[0]; const data = tempData[0];
if (topLevel) { if (topLevel) {
@ -60,7 +67,7 @@ export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: b
loggingEnabled && log(LT.LOG, `Tokenizing math ${JSON.stringify(cmd)}`); loggingEnabled && log(LT.LOG, `Tokenizing math ${JSON.stringify(cmd)}`);
// Solve the math and rolls for this cmd // Solve the math and rolls for this cmd
const [tempData, tempCounts] = tokenizeMath(cmd.join(''), modifiers); const [tempData, tempCounts] = tokenizeMath(cmd.join(''), modifiers, previousResults);
const data = tempData[0]; const data = tempData[0];
loggingEnabled && log(LT.LOG, `Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)}`); loggingEnabled && log(LT.LOG, `Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)}`);

View File

@ -70,6 +70,21 @@ export const mathSolver = (conf: MathConf[], wrapDetails = false): SolvedStep =>
} }
} }
// Look for any implicit multiplication that may have been missed
// Start at index 1 as there will never be implicit multiplication before the first element
for (let i = 1; i < conf.length; i++) {
loopCountCheck();
const prevConfAsStr = <string> conf[i - 1];
const curConfAsStr = <string> conf[i];
if (!signs.includes(curConfAsStr) && !signs.includes(prevConfAsStr)) {
// Both previous and current conf are operators, slip in the "*"
conf.splice(i, 0, '*');
}
}
// At this point, conf should be [num, op, num, op, num, op, num, etc]
// Evaluate all EMDAS by looping thru each tier of operators (exponential is the highest tier, addition/subtraction the lowest) // Evaluate all EMDAS by looping thru each tier of operators (exponential is the highest tier, addition/subtraction the lowest)
const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']]; const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']];
allCurOps.forEach((curOps) => { allCurOps.forEach((curOps) => {

View File

@ -18,17 +18,17 @@ import { assertParenBalance } from 'artigen/utils/parenBalance.ts';
const operators = ['(', ')', '^', '*', '/', '%', '+', '-']; const operators = ['(', ')', '^', '*', '/', '%', '+', '-'];
export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData[], CountDetails[]] => { export const tokenizeMath = (cmd: string, modifiers: RollModifiers, previousResults: number[]): [ReturnData[], CountDetails[]] => {
const countDetails: CountDetails[] = []; const countDetails: CountDetails[] = [];
loggingEnabled && log(LT.LOG, `Parsing roll ${cmd}`); loggingEnabled && log(LT.LOG, `Parsing roll ${cmd} | ${JSON.stringify(modifiers)} | ${JSON.stringify(previousResults)}`);
// Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on) // Remove all spaces from the operation config and split it by any operator (keeping the operator in mathConf for fullSolver to do math on)
const mathConf: MathConf[] = cmd const mathConf: MathConf[] = cmd
.replace(cmdSplitRegex, '') .replace(cmdSplitRegex, '')
.replace(internalWrapRegex, '') .replace(internalWrapRegex, '')
.replace(/ /g, '') .replace(/ /g, '')
.split(/([-+()*/^]|(?<![d%])%)/g) .split(/([-+()*/^]|(?<![d%])%)|(x\d+(\.\d*)?)/g)
.filter((x) => x); .filter((x) => x);
loggingEnabled && log(LT.LOG, `Split roll into mathConf ${JSON.stringify(mathConf)}`); loggingEnabled && log(LT.LOG, `Split roll into mathConf ${JSON.stringify(mathConf)}`);
@ -41,16 +41,16 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData
loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`); loggingEnabled && log(LT.LOG, `Parsing roll ${JSON.stringify(cmd)} | Evaluating rolls into math-able items ${JSON.stringify(mathConf[i])}`);
const strMathConfI = mathConf[i].toString(); const curMathConfStr = mathConf[i].toString();
if (strMathConfI.length === 0) { if (curMathConfStr.length === 0) {
// If its an empty string, get it out of here // If its an empty string, get it out of here
mathConf.splice(i, 1); mathConf.splice(i, 1);
i--; i--;
} else if (mathConf[i] == parseFloat(strMathConfI)) { } else if (mathConf[i] == parseFloat(curMathConfStr)) {
// If its a number, parse the number out // If its a number, parse the number out
mathConf[i] = parseFloat(strMathConfI); mathConf[i] = parseFloat(curMathConfStr);
} else if (strMathConfI.toLowerCase() === 'e') { } else if (curMathConfStr.toLowerCase() === 'e') {
// If the operand is the constant e, create a SolvedStep for it // If the operand is the constant e, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {
total: Math.E, total: Math.E,
@ -58,21 +58,21 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (strMathConfI.toLowerCase() === 'fart' || strMathConfI.toLowerCase() === '💩') { } else if (curMathConfStr.toLowerCase() === 'fart' || curMathConfStr.toLowerCase() === '💩') {
mathConf[i] = { mathConf[i] = {
total: 7, total: 7,
details: '💩', details: '💩',
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (strMathConfI.toLowerCase() === 'sex') { } else if (curMathConfStr.toLowerCase() === 'sex') {
mathConf[i] = { mathConf[i] = {
total: 69, total: 69,
details: '( ͡° ͜ʖ ͡°)', details: '( ͡° ͜ʖ ͡°)',
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (strMathConfI.toLowerCase() === 'inf' || strMathConfI.toLowerCase() === 'infinity' || strMathConfI.toLowerCase() === '∞') { } else if (curMathConfStr.toLowerCase() === 'inf' || curMathConfStr.toLowerCase() === 'infinity' || curMathConfStr.toLowerCase() === '∞') {
// If the operand is the constant Infinity, create a SolvedStep for it // If the operand is the constant Infinity, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {
total: Infinity, total: Infinity,
@ -80,7 +80,7 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (strMathConfI.toLowerCase() === 'pi' || strMathConfI.toLowerCase() === '𝜋') { } else if (curMathConfStr.toLowerCase() === 'pi' || curMathConfStr.toLowerCase() === '𝜋') {
// If the operand is the constant pi, create a SolvedStep for it // If the operand is the constant pi, create a SolvedStep for it
mathConf[i] = { mathConf[i] = {
total: Math.PI, total: Math.PI,
@ -88,7 +88,7 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData
containsCrit: false, containsCrit: false,
containsFail: false, containsFail: false,
}; };
} else if (strMathConfI.toLowerCase() === 'pie') { } else if (curMathConfStr.toLowerCase() === 'pie') {
// If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them) // If the operand is pie, pi*e, create a SolvedStep for e and pi (and the multiplication symbol between them)
mathConf[i] = { mathConf[i] = {
total: Math.PI, total: Math.PI,
@ -110,16 +110,31 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData
], ],
); );
i += 2; i += 2;
} else if (!legalMathOperators.includes(strMathConfI) && legalMathOperators.some((mathOp) => strMathConfI.endsWith(mathOp))) { } else if (!legalMathOperators.includes(curMathConfStr) && legalMathOperators.some((mathOp) => curMathConfStr.endsWith(mathOp))) {
// Identify when someone does something weird like 4floor(2.5) and split 4 and floor // Identify when someone does something weird like 4floor(2.5) and split 4 and floor
const matchedMathOp = legalMathOperators.filter((mathOp) => strMathConfI.endsWith(mathOp))[0]; const matchedMathOp = legalMathOperators.filter((mathOp) => curMathConfStr.endsWith(mathOp))[0];
mathConf[i] = parseFloat(strMathConfI.replace(matchedMathOp, '')); mathConf[i] = parseFloat(curMathConfStr.replace(matchedMathOp, ''));
mathConf.splice(i + 1, 0, ...['*', matchedMathOp]); mathConf.splice(i + 1, 0, ...['*', matchedMathOp]);
i += 2; i += 2;
} else if (![...operators, ...legalMathOperators].includes(strMathConfI)) { } else if (/(x\d+(\.\d*)?)/.test(curMathConfStr)) {
// Identify when someone is using a variable from previous commands
if (curMathConfStr.includes('.')) {
// Verify someone did not enter x1.1 as a variable
throw new Error(`IllegalVariable_${curMathConfStr}`);
}
const varIdx = parseInt(curMathConfStr.replaceAll('x', ''));
// Get the index from the variable and attempt to use it to query the previousResults
if (previousResults.length > varIdx) {
mathConf[i] = parseFloat(previousResults[varIdx].toString());
} else {
throw new Error(`IllegalVariable_${curMathConfStr}`);
}
} else if (![...operators, ...legalMathOperators].includes(curMathConfStr)) {
// If nothing else has handled it by now, try it as a roll // If nothing else has handled it by now, try it as a roll
const formattedRoll = generateFormattedRoll(strMathConfI, modifiers); const formattedRoll = generateFormattedRoll(curMathConfStr, modifiers);
mathConf[i] = formattedRoll.solvedStep; mathConf[i] = formattedRoll.solvedStep;
countDetails.push(formattedRoll.countDetails); countDetails.push(formattedRoll.countDetails);
} }

View File

@ -104,6 +104,9 @@ export const translateError = (solverError: Error): [string, string] => {
case 'UndefinedStep': case 'UndefinedStep':
errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input'; errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input';
break; break;
case 'IllegalVariable':
errorMsg = `Error: \`${errorDetails}\` is not a valid variable`;
break;
default: default:
log(LT.ERROR, `Unhandled Parser Error: ${errorName}, ${errorDetails}`); log(LT.ERROR, `Unhandled Parser Error: ${errorName}, ${errorDetails}`);
errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`; errorMsg = `Unhandled Error: ${solverError.message}\nCheck input and try again, if issue persists, please use \`${config.prefix}report\` to alert the devs of the issue`;