diff --git a/src/artigen/artigen.ts b/src/artigen/artigen.ts index 41ef654..5018677 100644 --- a/src/artigen/artigen.ts +++ b/src/artigen/artigen.ts @@ -41,7 +41,7 @@ export const runCmd = (fullCmd: string, modifiers: RollModifiers): SolvedRoll => assertPrePostBalance(sepCmds); // 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)}`); // Remove any floating spaces from fullCmd diff --git a/src/artigen/cmdTokenizer.ts b/src/artigen/cmdTokenizer.ts index 0cdb181..06296f2 100644 --- a/src/artigen/cmdTokenizer.ts +++ b/src/artigen/cmdTokenizer.ts @@ -15,7 +15,7 @@ import { loggingEnabled } from 'artigen/utils/logFlag.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 -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)}`); const returnData: ReturnData[] = []; @@ -28,8 +28,15 @@ export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: b const openIdx = cmd.indexOf(config.prefix); 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 - 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]; if (topLevel) { @@ -60,7 +67,7 @@ export const tokenizeCmd = (cmd: string[], modifiers: RollModifiers, topLevel: b loggingEnabled && log(LT.LOG, `Tokenizing math ${JSON.stringify(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]; loggingEnabled && log(LT.LOG, `Solved math is back ${JSON.stringify(data)} | ${JSON.stringify(returnData)}`); diff --git a/src/artigen/math/mathSolver.ts b/src/artigen/math/mathSolver.ts index c6efdf8..17a090a 100644 --- a/src/artigen/math/mathSolver.ts +++ b/src/artigen/math/mathSolver.ts @@ -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 = conf[i - 1]; + const curConfAsStr = 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) const allCurOps = [['^'], ['*', '/', '%'], ['+', '-']]; allCurOps.forEach((curOps) => { diff --git a/src/artigen/math/mathTokenizer.ts b/src/artigen/math/mathTokenizer.ts index d9e03a0..ee189b2 100644 --- a/src/artigen/math/mathTokenizer.ts +++ b/src/artigen/math/mathTokenizer.ts @@ -18,17 +18,17 @@ import { assertParenBalance } from 'artigen/utils/parenBalance.ts'; 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[] = []; - 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) const mathConf: MathConf[] = cmd .replace(cmdSplitRegex, '') .replace(internalWrapRegex, '') .replace(/ /g, '') - .split(/([-+()*/^]|(? x); 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])}`); - 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 mathConf.splice(i, 1); i--; - } else if (mathConf[i] == parseFloat(strMathConfI)) { + } else if (mathConf[i] == parseFloat(curMathConfStr)) { // If its a number, parse the number out - mathConf[i] = parseFloat(strMathConfI); - } else if (strMathConfI.toLowerCase() === 'e') { + mathConf[i] = parseFloat(curMathConfStr); + } else if (curMathConfStr.toLowerCase() === 'e') { // If the operand is the constant e, create a SolvedStep for it mathConf[i] = { total: Math.E, @@ -58,21 +58,21 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData containsCrit: false, containsFail: false, }; - } else if (strMathConfI.toLowerCase() === 'fart' || strMathConfI.toLowerCase() === '๐Ÿ’ฉ') { + } else if (curMathConfStr.toLowerCase() === 'fart' || curMathConfStr.toLowerCase() === '๐Ÿ’ฉ') { mathConf[i] = { total: 7, details: '๐Ÿ’ฉ', containsCrit: false, containsFail: false, }; - } else if (strMathConfI.toLowerCase() === 'sex') { + } else if (curMathConfStr.toLowerCase() === 'sex') { mathConf[i] = { total: 69, details: '( อกยฐ อœส– อกยฐ)', containsCrit: 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 mathConf[i] = { total: Infinity, @@ -80,7 +80,7 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData containsCrit: 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 mathConf[i] = { total: Math.PI, @@ -88,7 +88,7 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData containsCrit: 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) mathConf[i] = { total: Math.PI, @@ -110,16 +110,31 @@ export const tokenizeMath = (cmd: string, modifiers: RollModifiers): [ReturnData ], ); 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 - const matchedMathOp = legalMathOperators.filter((mathOp) => strMathConfI.endsWith(mathOp))[0]; - mathConf[i] = parseFloat(strMathConfI.replace(matchedMathOp, '')); + const matchedMathOp = legalMathOperators.filter((mathOp) => curMathConfStr.endsWith(mathOp))[0]; + mathConf[i] = parseFloat(curMathConfStr.replace(matchedMathOp, '')); mathConf.splice(i + 1, 0, ...['*', matchedMathOp]); 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 - const formattedRoll = generateFormattedRoll(strMathConfI, modifiers); + const formattedRoll = generateFormattedRoll(curMathConfStr, modifiers); mathConf[i] = formattedRoll.solvedStep; countDetails.push(formattedRoll.countDetails); } diff --git a/src/artigen/utils/translateError.ts b/src/artigen/utils/translateError.ts index d2d0fd4..fa63862 100644 --- a/src/artigen/utils/translateError.ts +++ b/src/artigen/utils/translateError.ts @@ -104,6 +104,9 @@ export const translateError = (solverError: Error): [string, string] => { case 'UndefinedStep': errorMsg = 'Error: Roll became undefined, one or more operands are not a roll or a number, check input'; break; + case 'IllegalVariable': + errorMsg = `Error: \`${errorDetails}\` is not a valid variable`; + break; default: 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`;