diff options
Diffstat (limited to 'src/main/java')
10 files changed, 590 insertions, 1 deletions
diff --git a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java index bd54dfba..d372602e 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/NotEnoughUpdates.java @@ -57,6 +57,7 @@ import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.Custom import io.github.moulberry.notenoughupdates.miscfeatures.customblockzones.DwarvenMinesTextures; import io.github.moulberry.notenoughupdates.miscgui.CalendarOverlay; import io.github.moulberry.notenoughupdates.miscgui.InventoryStorageSelector; +import io.github.moulberry.notenoughupdates.miscgui.SignCalculator; import io.github.moulberry.notenoughupdates.mixins.AccessorMinecraft; import io.github.moulberry.notenoughupdates.options.NEUConfig; import io.github.moulberry.notenoughupdates.overlays.FuelBar; @@ -277,7 +278,7 @@ public class NotEnoughUpdates { MinecraftForge.EVENT_BUS.register(new ItemTooltipListener(this)); MinecraftForge.EVENT_BUS.register(new RenderListener(this)); MinecraftForge.EVENT_BUS.register(new OldAnimationChecker()); - MinecraftForge.EVENT_BUS.register(new CookieWarning()); + MinecraftForge.EVENT_BUS.register(new SignCalculator()); MinecraftForge.EVENT_BUS.register(navigation); if (Minecraft.getMinecraft().getResourceManager() instanceof IReloadableResourceManager) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java index 86411f4f..b152bc09 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/Commands.java @@ -37,6 +37,7 @@ import io.github.moulberry.notenoughupdates.commands.help.LinksCommand; import io.github.moulberry.notenoughupdates.commands.help.SettingsCommand; import io.github.moulberry.notenoughupdates.commands.help.StorageViewerWhyCommand; import io.github.moulberry.notenoughupdates.commands.misc.AhCommand; +import io.github.moulberry.notenoughupdates.commands.misc.CalculatorCommand; import io.github.moulberry.notenoughupdates.commands.misc.CalendarCommand; import io.github.moulberry.notenoughupdates.commands.misc.CosmeticsCommand; import io.github.moulberry.notenoughupdates.commands.misc.CustomizeCommand; @@ -91,6 +92,7 @@ public class Commands { ClientCommandHandler.instance.registerCommand(new ScreenCommand("neuoverlay", NEUOverlayPlacements::new)); //ClientCommandHandler.instance.registerCommand(new ScreenCommand("neututorial", NeuTutorial::new)); ClientCommandHandler.instance.registerCommand(new AhCommand()); + ClientCommandHandler.instance.registerCommand(new CalculatorCommand()); ClientCommandHandler.instance.registerCommand(new CalendarCommand()); // Fairy Soul Commands diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/help/HelpCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/help/HelpCommand.java index ba49221e..a52fcd1d 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/commands/help/HelpCommand.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/help/HelpCommand.java @@ -52,6 +52,7 @@ public class HelpCommand extends ClientCommandBase { "\u00a76/neuah \u00a7r\u00a77- Opens neu's custom ah GUI.", "\u00a76/neumap \u00a7r\u00a77- Opens the dungeon map GUI.", "\u00a76/neucalendar \u00a7r\u00a77- Opens neu's custom calendar GUI.", + "\u00a76/neucalc \u00a7r\u00a77- Run calculations.", "", "\u00a76\u00a7lOld commands:", "\u00a76/peek \u00a7b?{user} \u00a72\u2D35 \u00a7r\u00a77- Shows quickly stats for a user.", diff --git a/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/CalculatorCommand.java b/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/CalculatorCommand.java new file mode 100644 index 00000000..e73e3b32 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/commands/misc/CalculatorCommand.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.commands.misc; + +import io.github.moulberry.notenoughupdates.commands.ClientCommandBase; +import io.github.moulberry.notenoughupdates.util.Calculator; +import net.minecraft.command.CommandException; +import net.minecraft.command.ICommandSender; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.EnumChatFormatting; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public class CalculatorCommand extends ClientCommandBase { + public CalculatorCommand() { + super("neucalc"); + } + + @Override + public List<String> getCommandAliases() { + return Arrays.asList("neucalculator"); + } + + @Override + public void processCommand(ICommandSender sender, String[] args) throws CommandException { + if ((args.length == 1 && Objects.equals(args[0], "help")) || args.length == 0) { + sender.addChatMessage(new ChatComponentText( + "\n§e[NEU] §5Its a calculator.\n" + + "§eFor Example §b/neucalc 3m*7k§e.\n" + + "§eYou can also use suffixes (k, m, b, t, s)§e.\n" + + "§eThe \"s\" suffix acts as 64.\n" + + "§eTurn on Sign Calculator in /neu misc to also support this in sign popups.\n")); + return; + } + String source = String.join(" ", args); + try { + BigDecimal calculate = Calculator.calculate(source); + sender.addChatMessage(new ChatComponentText( + EnumChatFormatting.YELLOW + "[NEU] " + EnumChatFormatting.WHITE + source + " = " + EnumChatFormatting.GREEN + + calculate.toPlainString() + )); + } catch (Calculator.CalculatorException e) { + sender.addChatMessage(new ChatComponentText( + EnumChatFormatting.YELLOW + "[NEU] " + EnumChatFormatting.RED + "Error during calculation: " + + e.getMessage() + "\n" + + EnumChatFormatting.WHITE + source.substring(0, e.getOffset()) + EnumChatFormatting.DARK_RED + + source.substring(e.getOffset(), e.getLength() + e.getOffset()) + EnumChatFormatting.GRAY + + source.substring(e.getLength() + e.getOffset()) + )); + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/events/SignSubmitEvent.java b/src/main/java/io/github/moulberry/notenoughupdates/events/SignSubmitEvent.java new file mode 100644 index 00000000..9bc7b8b0 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/events/SignSubmitEvent.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.events; + +import net.minecraft.client.gui.inventory.GuiEditSign; + +public class SignSubmitEvent extends NEUEvent { + public final GuiEditSign sign; + public final String[] lines; + + public SignSubmitEvent(GuiEditSign sign, String[] hehe) { + this.sign = sign; + this.lines = hehe; + } + + public GuiEditSign getSign() { + return sign; + } + + public String[] getLines() { + return lines; + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/listener/ChatListener.java b/src/main/java/io/github/moulberry/notenoughupdates/listener/ChatListener.java index 0aade4b0..457042ee 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/listener/ChatListener.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/listener/ChatListener.java @@ -35,6 +35,7 @@ import net.minecraft.util.ChatComponentText; import net.minecraft.util.ChatComponentTranslation; import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.IChatComponent; +import net.minecraftforge.client.ClientCommandHandler; import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.fml.common.eventhandler.EventPriority; import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; @@ -213,6 +214,13 @@ public class ChatListener { SlayerOverlay.unloadOverlayTimer = System.currentTimeMillis(); } else if (unformatted.startsWith("You consumed a Booster Cookie!")) { CookieWarning.resetNotification(); + } else if (unformatted.startsWith("QUICK MATHS! Solve:")) { + if (Math.random() < 0.2) { + ClientCommandHandler.instance.executeCommand( + Minecraft.getMinecraft().thePlayer, + "/neucalc " + unformatted.substring("QUICK MATHS! Solve: ".length()) + ); + } } if (e.message.getFormattedText().contains( EnumChatFormatting.YELLOW + "Visit the Auction House to collect your item!")) { diff --git a/src/main/java/io/github/moulberry/notenoughupdates/miscgui/SignCalculator.java b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/SignCalculator.java new file mode 100644 index 00000000..115458c9 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/miscgui/SignCalculator.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.miscgui; + +import io.github.moulberry.notenoughupdates.NotEnoughUpdates; +import io.github.moulberry.notenoughupdates.events.SignSubmitEvent; +import io.github.moulberry.notenoughupdates.mixins.AccessorGuiEditSign; +import io.github.moulberry.notenoughupdates.util.Calculator; +import io.github.moulberry.notenoughupdates.util.Utils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.inventory.GuiEditSign; +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.util.EnumChatFormatting; +import net.minecraftforge.client.event.GuiScreenEvent; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; + +import java.math.BigDecimal; +import java.util.Objects; + +public class SignCalculator { + + String lastSource = null; + BigDecimal lastResult = null; + Calculator.CalculatorException lastException = null; + + private boolean isEnabled() { + return NotEnoughUpdates.INSTANCE.config.misc.calculationMode != 0; + } + + @SubscribeEvent + public void onSignDrawn(GuiScreenEvent.DrawScreenEvent.Post event) { + if (!(event.gui instanceof GuiEditSign)) + return; + if (!isEnabled()) return; + GuiEditSign guiEditSign = (GuiEditSign) event.gui; + TileEntitySign tileSign = ((AccessorGuiEditSign) guiEditSign).getTileSign(); + if (!tileSign.signText[1].getUnformattedText().equals("^^^^^^^^^^^^^^^")) return; + refresh(tileSign.signText[0].getUnformattedText()); + Utils.drawStringCentered( + getRenderedString(), + Minecraft.getMinecraft().fontRendererObj, + guiEditSign.width / 2F, + guiEditSign.height / 4F - 120, + false, + 0x808080FF + ); + } + + @SubscribeEvent + public void onSignSubmitted(SignSubmitEvent event) { + if (!isEnabled()) return; + if (Objects.equals(event.lines[1], "^^^^^^^^^^^^^^^")) { + refresh(event.lines[0]); + if (lastResult != null) { + event.lines[0] = lastResult.toPlainString(); + } + } + } + + public String getRenderedString() { + if (lastResult != null) { + String lr = lastResult.toPlainString(); + if (Minecraft.getMinecraft().fontRendererObj.getStringWidth(lr) > 90) { + return EnumChatFormatting.WHITE + lastSource + " = " + EnumChatFormatting.RED + "Result too long"; + } + return EnumChatFormatting.WHITE + lastSource + " = " + EnumChatFormatting.GREEN + lr; + } else if (lastException != null) { + return EnumChatFormatting.RED + lastException.getMessage(); + } + return EnumChatFormatting.RED + "No calculation has been done."; + } + + private void refresh(String source) { + if (Objects.equals(source, lastSource)) return; + lastSource = source; + int calculationMode = NotEnoughUpdates.INSTANCE.config.misc.calculationMode; + if (source.isEmpty() || calculationMode == 0 || (calculationMode == 1 && !source.startsWith("!"))) { + lastResult = null; + lastException = null; + return; + } + try { + lastResult = Calculator.calculate(calculationMode == 1 ? source.substring(1) : source); + lastException = null; + } catch (Calculator.CalculatorException ex) { + lastException = ex; + lastResult = null; + } + } +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiEditSign.java b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiEditSign.java new file mode 100644 index 00000000..bfdf05ab --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/mixins/MixinGuiEditSign.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.mixins; + +import io.github.moulberry.notenoughupdates.events.SignSubmitEvent; +import net.minecraft.client.gui.inventory.GuiEditSign; +import net.minecraft.tileentity.TileEntitySign; +import net.minecraft.util.ChatComponentText; +import net.minecraft.util.IChatComponent; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(GuiEditSign.class) +public class MixinGuiEditSign { + + @Redirect(method = "onGuiClosed", at = @At(value = "FIELD", opcode = Opcodes.GETFIELD, target = "Lnet/minecraft/tileentity/TileEntitySign;signText:[Lnet/minecraft/util/IChatComponent;")) + public IChatComponent[] onOnGuiClosed(TileEntitySign instance) { + String[] x = new String[4]; + for (int i = 0; i < 4; i++) { + x[i] = instance.signText[i].getUnformattedText(); + } + SignSubmitEvent signSubmitEvent = new SignSubmitEvent((GuiEditSign) (Object) this, x); + signSubmitEvent.post(); + IChatComponent[] arr = new IChatComponent[4]; + for (int i = 0; i < 4; i++) { + arr[i] = new ChatComponentText(signSubmitEvent.lines[i]); + } + return arr; + } + +} diff --git a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java index c0f4e8c9..08ae5745 100644 --- a/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java +++ b/src/main/java/io/github/moulberry/notenoughupdates/options/seperateSections/Misc.java @@ -215,4 +215,12 @@ public class Misc { @ConfigEditorBoolean public boolean warpTwice = true; + @Expose + @ConfigOption( + name = "Sign Calculator", + desc = "§7Replace calculations like §9\"1+2\"§7 with the calculation result in sign popups (AH/BZ)" + ) + @ConfigEditorDropdown(values = {"Off", "Enabled with ! Prefix", "Always enabled"}) + public int calculationMode = 2; + } diff --git a/src/main/java/io/github/moulberry/notenoughupdates/util/Calculator.java b/src/main/java/io/github/moulberry/notenoughupdates/util/Calculator.java new file mode 100644 index 00000000..45d7c051 --- /dev/null +++ b/src/main/java/io/github/moulberry/notenoughupdates/util/Calculator.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2022 NotEnoughUpdates contributors + * + * This file is part of NotEnoughUpdates. + * + * NotEnoughUpdates is free software: you can redistribute it + * and/or modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * NotEnoughUpdates is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with NotEnoughUpdates. If not, see <https://www.gnu.org/licenses/>. + */ + +package io.github.moulberry.notenoughupdates.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.NoSuchElementException; + +public class Calculator { + public static BigDecimal calculate(String source) throws CalculatorException { + return evaluate(shuntingYard(lex(source))); + } + + ///<editor-fold desc="Lexing Time"> + public enum TokenType { + NUMBER, BINOP, LPAREN, RPAREN, POSTOP + } + + public static class Token { + public TokenType type; + String operatorValue; + long numericValue; + int exponent; + int tokenStart; + int tokenLength; + } + + static String binops = "+-*/x"; + static String postops = "mkbts"; + static String digits = "0123456789"; + + static void readDigitsInto(Token token, String source, boolean decimals) { + int startIndex = token.tokenStart + token.tokenLength; + for (int j = 0; j + startIndex < source.length(); j++) { + char d = source.charAt(j + startIndex); + int d0 = digits.indexOf(d); + if (d0 != -1) { + if (decimals) + token.exponent--; + token.numericValue *= 10; + token.numericValue += d0; + token.tokenLength += 1; + } else { + return; + } + } + } + + public static class CalculatorException extends Exception { + int offset, length; + + public CalculatorException(String message, int offset, int length) { + super(message); + this.offset = offset; + this.length = length; + } + + public int getLength() { + return length; + } + + public int getOffset() { + return offset; + } + } + + public static List<Token> lex(String source) throws CalculatorException { + List<Token> tokens = new ArrayList<>(); + for (int i = 0; i < source.length(); ) { + char c = source.charAt(i); + if (Character.isWhitespace(c)) { + i++; + continue; + } + Token token = new Token(); + token.tokenStart = i; + if (binops.indexOf(c) != -1) { + token.tokenLength = 1; + token.type = TokenType.BINOP; + token.operatorValue = c + ""; + } else if (postops.indexOf(c) != -1) { + token.tokenLength = 1; + token.type = TokenType.POSTOP; + token.operatorValue = c + ""; + } else if (c == ')') { + token.tokenLength = 1; + token.type = TokenType.RPAREN; + token.operatorValue = ")"; + } else if (c == '(') { + token.tokenLength = 1; + token.type = TokenType.LPAREN; + token.operatorValue = "("; + } else if ('.' == c) { + token.tokenLength = 1; + token.type = TokenType.NUMBER; + readDigitsInto(token, source, true); + if (token.tokenLength == 1) { + throw new CalculatorException("Invalid number literal", i, 1); + } + } else if (digits.indexOf(c) != -1) { + token.type = TokenType.NUMBER; + readDigitsInto(token, source, false); + if (i + token.tokenLength < source.length()) { + char p = source.charAt(i + token.tokenLength); + if ('.' == p) { + token.tokenLength++; + readDigitsInto(token, source, true); + } + } + } else { + throw new CalculatorException("Unknown thing " + c, i, 1); + } + tokens.add(token); + i += token.tokenLength; + } + return tokens; + } + ///</editor-fold> + + ///<editor-fold desc="Shunting Time"> + static int getPrecedence(Token token) throws CalculatorException { + switch (token.operatorValue.intern()) { + case "+": + case "-": + return 0; + case "*": + case "/": + case "x": + return 1; + } + throw new CalculatorException("Unknown operator " + token.operatorValue, token.tokenStart, token.tokenLength); + } + + public static List<Token> shuntingYard(List<Token> toShunt) throws CalculatorException { + // IT'S SHUNTING TIME + // This is an implementation of the shunting yard algorithm + + Deque<Token> op = new ArrayDeque<>(); + List<Token> out = new ArrayList<>(); + + for (Token currentlyShunting : toShunt) { + switch (currentlyShunting.type) { + case NUMBER: + out.add(currentlyShunting); + break; + case BINOP: + int p = getPrecedence(currentlyShunting); + while (!op.isEmpty()) { + Token l = op.peek(); + if (l.type == TokenType.LPAREN) + break; + assert (l.type == TokenType.BINOP); + int pl = getPrecedence(l); + if (pl > p) { + out.add(op.pop()); + } else { + break; + } + } + op.push(currentlyShunting); + break; + case LPAREN: + op.push(currentlyShunting); + break; + case RPAREN: + while (1 > 0) { + if (op.isEmpty()) + throw new CalculatorException( + "Unbalanced right parenthesis", + currentlyShunting.tokenStart, + currentlyShunting.tokenLength + ); + Token l = op.pop(); + if (l.type == TokenType.LPAREN) { + break; + } + out.add(l); + } + break; + case POSTOP: + out.add(currentlyShunting); + break; + } + } + while (!op.isEmpty()) { + Token l = op.pop(); + if (l.type == TokenType.LPAREN) + throw new CalculatorException("Unbalanced left parenthesis", l.tokenStart, l.tokenLength); + out.add(l); + } + return out; + } + + /// </editor-fold> + + ///<editor-fold desc="Evaluating Time"> + + public static BigDecimal evaluate(List<Token> rpnTokens) throws CalculatorException { + Deque<BigDecimal> values = new ArrayDeque<>(); + try { + for (Token command : rpnTokens) { + switch (command.type) { + case NUMBER: + values.push(new BigDecimal(command.numericValue).scaleByPowerOfTen(command.exponent)); + break; + case BINOP: + BigDecimal right = values.pop().setScale(2, RoundingMode.HALF_UP); + BigDecimal left = values.pop().setScale(2, RoundingMode.HALF_UP); + switch (command.operatorValue.intern()) { + case "x": + case "*": + values.push(left.multiply(right).setScale(2, RoundingMode.HALF_UP)); + break; + case "/": + try { + values.push(left.divide(right, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP)); + } catch (ArithmeticException e) { + throw new CalculatorException("Encountered division by 0", command.tokenStart, command.tokenLength); + } + break; + case "+": + values.push(left.add(right).setScale(2, RoundingMode.HALF_UP)); + break; + case "-": + values.push(left.subtract(right).setScale(2, RoundingMode.HALF_UP)); + break; + default: + throw new CalculatorException( + "Unknown operation " + command.operatorValue, + command.tokenStart, + command.tokenLength + ); + } + break; + case LPAREN: + case RPAREN: + throw new CalculatorException( + "Did not expect unshunted token in RPN", + command.tokenStart, + command.tokenLength + ); + case POSTOP: + BigDecimal p = values.pop(); + switch (command.operatorValue.intern()) { + case "s": + values.push(p.multiply(new BigDecimal(64)).setScale(2, RoundingMode.HALF_UP)); + break; + case "k": + values.push(p.multiply(new BigDecimal(1_000)).setScale(2, RoundingMode.HALF_UP)); + break; + case "m": + values.push(p.multiply(new BigDecimal(1_000_000)).setScale(2, RoundingMode.HALF_UP)); + break; + case "b": + values.push(p.multiply(new BigDecimal(1_000_000_000)).setScale(2, RoundingMode.HALF_UP)); + break; + case "t": + values.push(p.multiply(new BigDecimal("1000000000000")).setScale(2, RoundingMode.HALF_UP)); + break; + default: + throw new CalculatorException( + "Unknown operation " + command.operatorValue, + command.tokenStart, + command.tokenLength + ); + } + break; + } + } + BigDecimal peek = values.pop(); + return peek.stripTrailingZeros(); + } catch (NoSuchElementException e) { + throw new CalculatorException("Unfinished expression", 0, 0); + } + } + + ///</editor-fold> + +} |