From 20c793b31b2b32b2f72846a8b3beda8db848f227 Mon Sep 17 00:00:00 2001 From: filifa Date: Thu, 11 Dec 2025 23:49:34 -0500 Subject: [PATCH] do not allow expressions in exponent --- modules/compute.js | 5 ----- modules/parser.js | 32 +++++++++++++++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/modules/compute.js b/modules/compute.js index 408417b..ea80b25 100644 --- a/modules/compute.js +++ b/modules/compute.js @@ -94,11 +94,6 @@ function compute(queue, modulus) { const c = (a * binv) % modulus; stack.push(c); } else if (token === "^") { - // FIXME: not always valid - consider 2^3^4 mod 7 - // also 2^(3+4) - // solution might be to change parser - // might also be better to just mod once at the end - // instead of with every op const [a, b] = binaryOpPop(stack); const c = modpow(a, b, modulus); stack.push(c); diff --git a/modules/parser.js b/modules/parser.js index ebb5754..d4ff333 100644 --- a/modules/parser.js +++ b/modules/parser.js @@ -2,7 +2,7 @@ function isLeftAssociative(op) { return op === "+" || op === "-" || op === "*" || op === "/"; } -function popOps(opstack, queue, op) { +function popOps(opstack, queue, op, powInStack) { const prec = {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3} while (opstack.length > 0) { const op2 = opstack.at(-1); @@ -13,25 +13,40 @@ function popOps(opstack, queue, op) { if ((prec[op2] > prec[op]) || (prec[op2] === prec[op] && isLeftAssociative(op))) { opstack.pop(); queue.push(op2); + if (op2 === "^") { + powInStack = false; + } } else { break; } } - opstack.push(op); + // practically, we want 2^(3+4) mod 7 to evaluate to 2^7 mod 7 = 2 + // however, since our operators are all modular, this would instead + // evaluate as 2^0 mod 7 = 1 + // rather than complicate things by evaluating exponent expressions + // normally instead of modularly (and consequently needing to deal with + // fractions) we simply require exponents to be integers + if (powInStack) { + throw new Error("exponent must be an integer, not an expression"); + } } -function popBetweenParens(opstack, queue) { +function popBetweenParens(opstack, queue, powInStack) { while (opstack.at(-1) !== "(") { if (opstack.length === 0) { throw new Error("mismatched parentheses"); } const op = opstack.pop(); + if (op === "^") { + powInStack = false; + } queue.push(op); } opstack.pop(); + return powInStack; } function empty(opstack, queue) { @@ -48,15 +63,22 @@ function empty(opstack, queue) { function shunt(tokens) { const queue = []; const opstack = []; + let powInStack = false; for (const token of tokens) { if (typeof token === "bigint") { queue.push(token); } else if (/[-+*/^]/.test(token)) { - popOps(opstack, queue, token); + popOps(opstack, queue, token, powInStack); + powInStack = false; + + opstack.push(token); + if (token === "^") { + powInStack = true; + } } else if (token === "(") { opstack.push(token); } else if (token === ")") { - popBetweenParens(opstack, queue); + powInStack = popBetweenParens(opstack, queue, powInStack); } }