From 25189661c7b707c8c64f8b25c0e0ea131804ca93 Mon Sep 17 00:00:00 2001 From: filifa Date: Thu, 11 Dec 2025 23:49:34 -0500 Subject: [PATCH] handle unary minus --- modules/compute.js | 8 ++++++++ modules/lexer.js | 3 +-- modules/parser.js | 22 +++++++++++++++++----- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/modules/compute.js b/modules/compute.js index ea80b25..e96a660 100644 --- a/modules/compute.js +++ b/modules/compute.js @@ -93,6 +93,14 @@ function compute(queue, modulus) { const binv = modinv(b, modulus); const c = (a * binv) % modulus; stack.push(c); + } else if (token === "u") { + let a = stack.pop(); + if (a === undefined) { + throw new Error("invalid expression"); + } + + a *= -1n; + stack.push(a); } else if (token === "^") { const [a, b] = binaryOpPop(stack); const c = modpow(a, b, modulus); diff --git a/modules/lexer.js b/modules/lexer.js index 0f2c370..e44267b 100644 --- a/modules/lexer.js +++ b/modules/lexer.js @@ -1,8 +1,7 @@ function tokenize(expr) { // NOTE: not handling whitespace // NOTE: currently ends early if string doesn't match token - // FIXME: need to handle unary minus (e.g. on parentheses) - const regexp = /-?[0-9]+|[-+*/^]|\(|\)/gy; + const regexp = /[0-9]+|[-+*/^]|\(|\)/gy; const matches = expr.matchAll(regexp); const tokens = []; diff --git a/modules/parser.js b/modules/parser.js index d4ff333..45990a9 100644 --- a/modules/parser.js +++ b/modules/parser.js @@ -3,7 +3,7 @@ function isLeftAssociative(op) { } function popOps(opstack, queue, op, powInStack) { - const prec = {"+": 1, "-": 1, "*": 2, "/": 2, "^": 3} + const prec = {"+": 1, "-": 1, "*": 2, "/": 2, "u": 3, "^": 3} while (opstack.length > 0) { const op2 = opstack.at(-1); if (op2 === "(") { @@ -27,9 +27,11 @@ function popOps(opstack, queue, op, powInStack) { // 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) { + if (powInStack && op !== "u") { throw new Error("exponent must be an integer, not an expression"); } + + return powInStack; } function popBetweenParens(opstack, queue, powInStack) { @@ -60,16 +62,24 @@ function empty(opstack, queue) { } } +function isUnaryMinus(token, lastToken) { + return token === "-" && (lastToken === null || /[(-+*/^]/.test(lastToken)); +} + function shunt(tokens) { const queue = []; const opstack = []; + let lastToken = null; let powInStack = false; - for (const token of tokens) { + for (let token of tokens) { if (typeof token === "bigint") { queue.push(token); } else if (/[-+*/^]/.test(token)) { - popOps(opstack, queue, token, powInStack); - powInStack = false; + if (isUnaryMinus(token, lastToken)) { + token = "u"; + } + + powInStack = popOps(opstack, queue, token, powInStack); opstack.push(token); if (token === "^") { @@ -80,6 +90,8 @@ function shunt(tokens) { } else if (token === ")") { powInStack = popBetweenParens(opstack, queue, powInStack); } + + lastToken = token; } empty(opstack, queue);