aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Zajc <marko@zajc.eu.org>2023-11-29 01:11:21 +0100
committerMarko Zajc <marko@zajc.eu.org>2023-11-29 01:11:21 +0100
commite32323aaf0ba0d28ca54c103bb33e5ce3eea54ad (patch)
tree9559e9c5c005ab29e24a08543f19f9a8b570304a
parentf2f8f5d24e430e85873546604eee88d6ae2edc53 (diff)
Add base conversions to *calc and improve its mode parsing
This requires updating qalculate-helper
-rw-r--r--module-utilities/src/main/java/libot/commands/CalculatorCommand.java118
1 files changed, 91 insertions, 27 deletions
diff --git a/module-utilities/src/main/java/libot/commands/CalculatorCommand.java b/module-utilities/src/main/java/libot/commands/CalculatorCommand.java
index 84d1a21..82ad973 100644
--- a/module-utilities/src/main/java/libot/commands/CalculatorCommand.java
+++ b/module-utilities/src/main/java/libot/commands/CalculatorCommand.java
@@ -1,11 +1,12 @@
1package libot.commands; 1package libot.commands;
2 2
3import static java.lang.Byte.compare; 3import static java.lang.Byte.*;
4import static java.lang.System.*; 4import static java.lang.System.*;
5import static java.nio.charset.StandardCharsets.UTF_8; 5import static java.nio.charset.StandardCharsets.UTF_8;
6import static java.nio.file.Files.exists; 6import static java.nio.file.Files.exists;
7import static java.nio.file.Paths.get; 7import static java.nio.file.Paths.get;
8import static java.util.concurrent.TimeUnit.SECONDS; 8import static java.util.concurrent.TimeUnit.SECONDS;
9import static java.util.regex.Pattern.*;
9import static java.util.stream.Collectors.joining; 10import static java.util.stream.Collectors.joining;
10import static libot.core.Constants.*; 11import static libot.core.Constants.*;
11import static libot.core.commands.CommandCategory.UTILITIES; 12import static libot.core.commands.CommandCategory.UTILITIES;
@@ -18,6 +19,7 @@ import static org.slf4j.LoggerFactory.getLogger;
18import java.io.IOException; 19import java.io.IOException;
19import java.nio.file.Path; 20import java.nio.file.Path;
20import java.util.*; 21import java.util.*;
22import java.util.regex.*;
21 23
22import javax.annotation.*; 24import javax.annotation.*;
23 25
@@ -60,15 +62,16 @@ public class CalculatorCommand extends Command {
60 } 62 }
61 } 63 }
62 64
65 private static final Pattern REGEX_BASE =
66 compile("(?:\\s+convert)?\\s+to\\s+(?:(b(?:ase)?\\s*(?:[\\d]+))|([^\\s]+))", UNICODE_CHARACTER_CLASS);
67 private static final Pattern REGEX_MODE =
68 compile("mode\\s+((?:high\\s*)?precision|exact)", UNICODE_CHARACTER_CLASS);
69
63 private static final String EMOJI_INFO = "\u2139"; 70 private static final String EMOJI_INFO = "\u2139";
64 private static final String EMOJI_WARN = "\u26A0"; 71 private static final String EMOJI_WARN = "\u26A0";
65 private static final String EMOJI_ERROR = "<:e:988959163579269130>"; 72 private static final String EMOJI_ERROR = "<:e:988959163579269130>";
66 private static final String EMOJI_UNKNOWN = "\u2699"; 73 private static final String EMOJI_UNKNOWN = "\u2699";
67 74
68 private static final String MODE_HIGH_PRECISION = "precision";
69 private static final String MODE_EXACT = "exact";
70 private static final String MODE_NORMAL = "";
71
72 private static final byte SEPARATOR = 0x00; 75 private static final byte SEPARATOR = 0x00;
73 76
74 private static final byte TYPE_MESSAGE = 0x01; 77 private static final byte TYPE_MESSAGE = 0x01;
@@ -93,16 +96,26 @@ public class CalculatorCommand extends Command {
93 96
94 c.typing(); 97 c.typing();
95 String expression = c.params().get(0); 98 String expression = c.params().get(0);
96 String mode; 99
97 if (expression.startsWith(MODE_HIGH_PRECISION)) 100 byte mode = 1;
98 mode = MODE_HIGH_PRECISION; 101 var modeMatcher = REGEX_MODE.matcher(expression);
99 else if (expression.startsWith(MODE_EXACT)) 102 if (modeMatcher.find()) {
100 mode = MODE_EXACT; 103 mode = getMode(modeMatcher);
101 else 104 expression = modeMatcher.replaceFirst("");
102 mode = MODE_NORMAL; 105 }
106
107 byte base = 10;
108 var baseMatcher = REGEX_BASE.matcher(expression);
109 if (baseMatcher.find()) {
110 base = getBase(baseMatcher);
111 if (base == 0)
112 base = 10; // base invalid, leave expression as-is and set base back to 10
113 else
114 expression = baseMatcher.replaceFirst(""); // base valid, remove conversion string
115 }
103 116
104 var messages = new ArrayList<QalcMessage>(5); 117 var messages = new ArrayList<QalcMessage>(5);
105 var result = evaluate(c, messages, expression, mode); 118 var result = evaluate(c, messages, expression, mode, base);
106 119
107 var m = new MessageBuilder(); 120 var m = new MessageBuilder();
108 121
@@ -120,13 +133,13 @@ public class CalculatorCommand extends Command {
120 resultFile = resultString.getBytes(UTF_8); 133 resultFile = resultString.getBytes(UTF_8);
121 134
122 if (resultFile.length > Message.MAX_FILE_SIZE) 135 if (resultFile.length > Message.MAX_FILE_SIZE)
123 throw c.error("The result is too long (> 8 MiB)", FAILURE); 136 throw c.error("The result is too long (> 8 MiB)", FAILURE); // TODO this will need to be changed
124 } 137 }
125 } 138 }
126 139
127 if (!m.isEmpty()) { 140 if (!m.isEmpty()) {
128 if (resultFile != null) 141 if (resultFile != null)
129 c.replyraw(m).addFile(resultFile, mode).queue(); 142 c.replyraw(m).addFile(resultFile, "result.txt").queue();
130 else 143 else
131 c.reply(m); 144 c.reply(m);
132 } else { 145 } else {
@@ -137,11 +150,62 @@ public class CalculatorCommand extends Command {
137 } 150 }
138 } 151 }
139 152
153 public static byte getMode(Matcher matcher) {
154 var mode = matcher.group().toLowerCase();
155 if (mode.endsWith("precision"))
156 return 2;
157 else
158 return 1;
159 }
160
161 public static byte getBase(Matcher matcher) {
162 var numeric = matcher.group(1); // base N
163
164 if (numeric != null) {
165 try {
166 var base = parseByte(numeric);
167 if (base < 2 || base > 36) // qalculate's own limit
168 return 0;
169 else
170 return base;
171
172 } catch (NumberFormatException e) {
173 return 0;
174 }
175 }
176
177 var textual = matcher.group(2); // hex, dec, bin, ...
178 return switch (textual.toLowerCase()) { // loosely copied from qalc.cc
179 case "fp80" /* qalc.cc also lacks binary80 */ -> -34; // BASE_FP80
180 case "fp128", "binary128" -> -33; // BASE_FP128
181 case "fp64", "binary64", "double" -> -32; // BASE_FP64
182 case "fp32", "binary32", "float" -> -31; // BASE_FP32
183 case "fp16", "binary16" -> -30; // BASE_FP16
184 case "bijective" -> -26; // BASE_BIJECTIVE_26
185 case "bcd" -> -20;
186 case "unicode" -> -4; // BASE_UNICODE
187 case "time" -> -2; // BASE_TIME
188 case "roman" -> -1; // BASE_ROMAN_NUMERALS
189 case "bin", "binary" -> 2;
190 case "oct", "octal" -> 8;
191 case "dec", "decimal" -> 10;
192 case "doz", "dozenal", "duo", "duodecimal" -> 12;
193 case "hex", "hexadecimal" -> 16;
194 case "sexa", "sexagesimal" -> 60; // BASE_SEXAGESIMAL
195 case "sexa2", "sexagesimal2" -> 62; // BASE_SEXAGESIMAL_2
196 case "sexa3", "sexagesimal3" -> 63; // BASE_SEXAGESIMAL_3
197 case "latitude" -> 70; // BASE_LATITUDE
198 case "latitude2" -> 71; // BASE_LATITUDE_2
199 case "longitude" -> 72; // BASE_LONGITUDE
200 case "longitude2" -> 73; // BASE_LONGITUDE_2
201 default -> 0;
202 };
203 }
204
140 @Nullable 205 @Nullable
141 private static Result evaluate(@Nonnull CommandContext c, @Nonnull List<QalcMessage> messages, 206 private static Result evaluate(@Nonnull CommandContext c, @Nonnull List<QalcMessage> messages, String expression,
142 @Nonnull String expression, 207 byte mode, byte base) throws IOException, InterruptedException {
143 @Nonnull String mode) throws IOException, InterruptedException { 208 byte[] output = runCalculatorProcess(c, expression, mode, base);
144 byte[] output = runCalculatorProcess(c, expression, mode);
145 209
146 int i = -1; 210 int i = -1;
147 String value = null; 211 String value = null;
@@ -171,9 +235,10 @@ public class CalculatorCommand extends Command {
171 235
172 @Nonnull 236 @Nonnull
173 @SuppressWarnings("null") 237 @SuppressWarnings("null")
174 private static byte[] runCalculatorProcess(@Nonnull CommandContext c, @Nonnull String expression, 238 private static byte[] runCalculatorProcess(@Nonnull CommandContext c, String expression, byte mode,
175 @Nonnull String mode) throws IOException, InterruptedException { 239 byte base) throws IOException, InterruptedException {
176 var p = executeQalculate(expression.substring(mode.length()), mode); 240 var p = executeQalculate(expression, new String(new byte[] { mode }, UTF_8),
241 new String(new byte[] { base }, UTF_8));
177 242
178 if (!p.waitFor(TIMEOUT_EVALUATE, SECONDS) || p.exitValue() == EXIT_TIMEOUT) { 243 if (!p.waitFor(TIMEOUT_EVALUATE, SECONDS) || p.exitValue() == EXIT_TIMEOUT) {
179 if (p.isAlive()) 244 if (p.isAlive())
@@ -271,27 +336,26 @@ public class CalculatorCommand extends Command {
271 } 336 }
272 337
273 @Override 338 @Override
274 @SuppressWarnings("null")
275 public String getInfo() { 339 public String getInfo() {
276 return """ 340 return """
277 Evaluates an expression. Check out the lists of supported \ 341 Evaluates an expression. Check out the lists of supported \
278 [functions](https://qalculate.github.io/manual/qalculate-definitions-functions.html), \ 342 [functions](https://qalculate.github.io/manual/qalculate-definitions-functions.html), \
279 [units](https://qalculate.github.io/manual/qalculate-definitions-units.html), and \ 343 [units](https://qalculate.github.io/manual/qalculate-definitions-units.html), and \
280 [constants](https://qalculate.github.io/manual/qalculate-definitions-variables.html). \ 344 [constants](https://qalculate.github.io/manual/qalculate-definitions-variables.html). \
281 Begin your expression with `%s` for high precision mode, or `%s` for exact evaluation mode. 345 Add `mode precision` to the expression for high precision mode, or `mode exact` for exact evaluation mode.
282 Powered by [Qalculate!](https://qalculate.github.io/) 346 Powered by [Qalculate!](https://qalculate.github.io/)
283 _Note: Qalculate! input is multi-line. You can specify variable assignments (x := y) on separate lines._ 347 _Note: Qalculate! input is multi-line. You can specify variable assignments (x := y) on separate lines._
284 """.formatted(MODE_HIGH_PRECISION, MODE_EXACT); 348 """;
285 } 349 }
286 350
287 @Override 351 @Override
288 public String[] getParameters() { 352 public String[] getParameters() {
289 return array("[mode]", "expression"); 353 return array("expression");
290 } 354 }
291 355
292 @Override 356 @Override
293 public String[] getParameterInfo() { 357 public String[] getParameterInfo() {
294 return array("evaluation mode (precision/exact)", "expression to evaluate"); 358 return array("expression to evaluate");
295 } 359 }
296 360
297 @Override 361 @Override