diff options
author | Marko Zajc <marko@zajc.eu.org> | 2023-11-29 01:11:21 +0100 |
---|---|---|
committer | Marko Zajc <marko@zajc.eu.org> | 2023-11-29 01:11:21 +0100 |
commit | e32323aaf0ba0d28ca54c103bb33e5ce3eea54ad (patch) | |
tree | 9559e9c5c005ab29e24a08543f19f9a8b570304a | |
parent | f2f8f5d24e430e85873546604eee88d6ae2edc53 (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.java | 118 |
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 @@ | |||
1 | package libot.commands; | 1 | package libot.commands; |
2 | 2 | ||
3 | import static java.lang.Byte.compare; | 3 | import static java.lang.Byte.*; |
4 | import static java.lang.System.*; | 4 | import static java.lang.System.*; |
5 | import static java.nio.charset.StandardCharsets.UTF_8; | 5 | import static java.nio.charset.StandardCharsets.UTF_8; |
6 | import static java.nio.file.Files.exists; | 6 | import static java.nio.file.Files.exists; |
7 | import static java.nio.file.Paths.get; | 7 | import static java.nio.file.Paths.get; |
8 | import static java.util.concurrent.TimeUnit.SECONDS; | 8 | import static java.util.concurrent.TimeUnit.SECONDS; |
9 | import static java.util.regex.Pattern.*; | ||
9 | import static java.util.stream.Collectors.joining; | 10 | import static java.util.stream.Collectors.joining; |
10 | import static libot.core.Constants.*; | 11 | import static libot.core.Constants.*; |
11 | import static libot.core.commands.CommandCategory.UTILITIES; | 12 | import static libot.core.commands.CommandCategory.UTILITIES; |
@@ -18,6 +19,7 @@ import static org.slf4j.LoggerFactory.getLogger; | |||
18 | import java.io.IOException; | 19 | import java.io.IOException; |
19 | import java.nio.file.Path; | 20 | import java.nio.file.Path; |
20 | import java.util.*; | 21 | import java.util.*; |
22 | import java.util.regex.*; | ||
21 | 23 | ||
22 | import javax.annotation.*; | 24 | import 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 |