From 0d8552f8daceed1666812f99f97afa7c6bcf8dc5 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Mon, 27 May 2024 22:10:26 +0100 Subject: [PATCH 01/10] feat: Start work on interactive Shell --- runshell.sh | 1 + .../marcellperger/mathexpr/interactive/Shell.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 runshell.sh create mode 100644 src/main/java/net/marcellperger/mathexpr/interactive/Shell.java diff --git a/runshell.sh b/runshell.sh new file mode 100644 index 0000000..8ad619e --- /dev/null +++ b/runshell.sh @@ -0,0 +1 @@ +java -classpath ./target/classes/ net.marcellperger.mathexpr.interactive.Shell diff --git a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java new file mode 100644 index 0000000..93de25d --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java @@ -0,0 +1,14 @@ +package net.marcellperger.mathexpr.interactive; + +import java.util.Arrays; +import java.util.Scanner; + +public class Shell { + public static void main(String[] args) { + Scanner sc = new Scanner(System.in); + System.out.println("args = " + Arrays.toString(args)); + System.out.print("Enter something: "); + String s = sc.nextLine(); + System.out.println("You entered " + s); + } +} From 29f82ac41de9739af33b86e00beb292d76b3ae07 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 2 Jun 2024 21:38:48 +0100 Subject: [PATCH 02/10] refactor: Add util.Input --- .../mathexpr/interactive/Shell.java | 7 +- .../marcellperger/mathexpr/util/Input.java | 69 +++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/marcellperger/mathexpr/util/Input.java diff --git a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java index 93de25d..75ed6c7 100644 --- a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java +++ b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java @@ -1,14 +1,13 @@ package net.marcellperger.mathexpr.interactive; import java.util.Arrays; -import java.util.Scanner; + +import static net.marcellperger.mathexpr.util.Input.input; public class Shell { public static void main(String[] args) { - Scanner sc = new Scanner(System.in); System.out.println("args = " + Arrays.toString(args)); System.out.print("Enter something: "); - String s = sc.nextLine(); - System.out.println("You entered " + s); + System.out.println("You entered " + input()); } } diff --git a/src/main/java/net/marcellperger/mathexpr/util/Input.java b/src/main/java/net/marcellperger/mathexpr/util/Input.java new file mode 100644 index 0000000..3c378ce --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/Input.java @@ -0,0 +1,69 @@ +package net.marcellperger.mathexpr.util; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Objects; +import java.util.Scanner; + +public class Input { + @NotNull Scanner sc; + @Nullable PrintStream ps; + + public Input() { + this(System.in, System.out); + } + public Input(@NotNull InputStream iStream) { + this(new Scanner(iStream)); + } + public Input(Readable readable) { + this(new Scanner(readable)); + } + public Input(String s) { + this(new Scanner(s)); + } + public Input(@NotNull Scanner scanner) { + this(scanner, null); + } + public Input(InputStream iStream, @Nullable OutputStream oStream) { + this(new Scanner(iStream), oStream); + } + public Input(@Nullable OutputStream oStream) { + this(System.in, oStream); + } + public Input(@NotNull Scanner scanner, @Nullable OutputStream oStream) { + this(scanner, oStream == null ? null : new PrintStream(oStream)); + } + public Input(@NotNull Scanner scanner, @Nullable PrintStream pStream) { + sc = scanner; + ps = pStream; + } + + public String getInput() { + return sc.nextLine(); + } + public String getInput(String prompt) { + Objects.requireNonNull(ps, "An OutputStream is required to use Input.getInput(prompt)").println(prompt); + return sc.nextLine(); + } + + public static String input() { + // Don't cache `new Input()` - System.in could be changed + return new Input().getInput(); + } + public static String input(@NotNull InputStream iStream) { + return new Input(iStream).getInput(); + } + public static String input(String prompt) { + return new Input().getInput(prompt); + } + public static String input(String prompt, @NotNull OutputStream oStream) { + return new Input(oStream).getInput(prompt); + } + public static String input(String prompt, @NotNull InputStream iStream, @NotNull OutputStream oStream) { + return new Input(iStream, oStream).getInput(prompt); + } +} From 5c560b442233360f5c97ed0d99937d198fa49204 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Sun, 2 Jun 2024 22:09:26 +0100 Subject: [PATCH 03/10] feat: Do basic Shell --- .../mathexpr/interactive/Shell.java | 49 +++++++++++++++++-- .../marcellperger/mathexpr/util/Input.java | 2 +- .../marcellperger/mathexpr/util/MathUtil.java | 20 ++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 src/main/java/net/marcellperger/mathexpr/util/MathUtil.java diff --git a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java index 75ed6c7..93659d1 100644 --- a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java +++ b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java @@ -1,13 +1,56 @@ package net.marcellperger.mathexpr.interactive; +import net.marcellperger.mathexpr.MathSymbol; +import net.marcellperger.mathexpr.parser.ExprParseException; +import net.marcellperger.mathexpr.parser.Parser; +import net.marcellperger.mathexpr.util.Input; +import net.marcellperger.mathexpr.util.MathUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.PrintStream; import java.util.Arrays; import static net.marcellperger.mathexpr.util.Input.input; public class Shell { + Input in; + PrintStream out; + + public Shell() { + in = new Input(); + out = System.out; + } + public static void main(String[] args) { - System.out.println("args = " + Arrays.toString(args)); - System.out.print("Enter something: "); - System.out.println("You entered " + input()); + new Shell().run(); + } + + // TODO run() until exit better - parse exit command/Ctrl+C + // TODO fails badly with unchecked exception on parsing `12 + (` + + public void run() { + //noinspection InfiniteLoopStatement + while (true) getAndRunCommand(); + } + + public void getAndRunCommand() { + runCommand(in.getInput(">? ")); + } + public void runCommand(String cmd) { + @Nullable MathSymbol sym = parseCmdOrPrintError(cmd); + if(sym == null) return; + double value = sym.calculateValue(); + out.println(MathUtil.roundToSigFigs(value, 10)); + } + public @Nullable MathSymbol parseCmdOrPrintError(String cmd) { + // No special commands so pass straight to parser + Parser p = new Parser(cmd); + try { + return p.parse(); + } catch (ExprParseException exc) { + out.println(exc); + return null; + } } } diff --git a/src/main/java/net/marcellperger/mathexpr/util/Input.java b/src/main/java/net/marcellperger/mathexpr/util/Input.java index 3c378ce..5036053 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/Input.java +++ b/src/main/java/net/marcellperger/mathexpr/util/Input.java @@ -46,7 +46,7 @@ public String getInput() { return sc.nextLine(); } public String getInput(String prompt) { - Objects.requireNonNull(ps, "An OutputStream is required to use Input.getInput(prompt)").println(prompt); + Objects.requireNonNull(ps, "An OutputStream is required to use Input.getInput(prompt)").print(prompt); return sc.nextLine(); } diff --git a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java new file mode 100644 index 0000000..aaa210f --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java @@ -0,0 +1,20 @@ +package net.marcellperger.mathexpr.util; + +import org.jetbrains.annotations.Range; + +public class MathUtil { + protected MathUtil() {} + + public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_VALUE) int sigFigs) { + int mostSignificantDigit = (int)Math.floor(Math.log10(value)); + int roundToDigit = mostSignificantDigit - sigFigs + 1; + return roundToDP(value, roundToDigit); + } + + public static double roundToNearest(double value, double nearest) { + return Math.round(value / nearest) * nearest; + } + public static double roundToDP(double value, int decimalPlaces) { + return roundToNearest(value, Math.pow(10, decimalPlaces)); + } +} From e169e569544d388049aeb6c021a0fe6f8b15ecee Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Mon, 3 Jun 2024 21:35:47 +0100 Subject: [PATCH 04/10] fix: Fix EOF issues in Parser --- .../mathexpr/interactive/Shell.java | 5 --- .../parser/ExprParseEofException.java | 18 +++++++++++ .../mathexpr/parser/ExprParseException.java | 2 -- .../marcellperger/mathexpr/parser/Parser.java | 32 +++++++++++++------ .../marcellperger/mathexpr/util/MathUtil.java | 1 + 5 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 src/main/java/net/marcellperger/mathexpr/parser/ExprParseEofException.java diff --git a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java index 93659d1..edc018c 100644 --- a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java +++ b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java @@ -5,13 +5,9 @@ import net.marcellperger.mathexpr.parser.Parser; import net.marcellperger.mathexpr.util.Input; import net.marcellperger.mathexpr.util.MathUtil; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.PrintStream; -import java.util.Arrays; - -import static net.marcellperger.mathexpr.util.Input.input; public class Shell { Input in; @@ -27,7 +23,6 @@ public static void main(String[] args) { } // TODO run() until exit better - parse exit command/Ctrl+C - // TODO fails badly with unchecked exception on parsing `12 + (` public void run() { //noinspection InfiniteLoopStatement diff --git a/src/main/java/net/marcellperger/mathexpr/parser/ExprParseEofException.java b/src/main/java/net/marcellperger/mathexpr/parser/ExprParseEofException.java new file mode 100644 index 0000000..10feafa --- /dev/null +++ b/src/main/java/net/marcellperger/mathexpr/parser/ExprParseEofException.java @@ -0,0 +1,18 @@ +package net.marcellperger.mathexpr.parser; + +public class ExprParseEofException extends ExprParseException { + public ExprParseEofException() { + } + + public ExprParseEofException(String message) { + super(message); + } + + public ExprParseEofException(String message, Throwable cause) { + super(message, cause); + } + + public ExprParseEofException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/net/marcellperger/mathexpr/parser/ExprParseException.java b/src/main/java/net/marcellperger/mathexpr/parser/ExprParseException.java index 551eaa9..0d81cc3 100644 --- a/src/main/java/net/marcellperger/mathexpr/parser/ExprParseException.java +++ b/src/main/java/net/marcellperger/mathexpr/parser/ExprParseException.java @@ -1,7 +1,5 @@ package net.marcellperger.mathexpr.parser; - -@SuppressWarnings("unused") // I want all the constructors public class ExprParseException extends Exception { public ExprParseException() { super(); diff --git a/src/main/java/net/marcellperger/mathexpr/parser/Parser.java b/src/main/java/net/marcellperger/mathexpr/parser/Parser.java index 7c9ac2b..d781c77 100644 --- a/src/main/java/net/marcellperger/mathexpr/parser/Parser.java +++ b/src/main/java/net/marcellperger/mathexpr/parser/Parser.java @@ -53,7 +53,7 @@ public MathSymbol parseExpr() throws ExprParseException { public @NotNull MathSymbol parseParensOrLiteral() throws ExprParseException { discardWhitespace(); - return peek() == '(' ? parseParens() : parseDoubleLiteral(); + return peekExpect() == '(' ? parseParens() : parseDoubleLiteral(); } public MathSymbol parseParens() throws ExprParseException { @@ -115,21 +115,35 @@ protected CharSequence strFromHere() { return CharBuffer.wrap(src, idx, src.length()); } - boolean notEof() { + public boolean notEof() { return idx < src.length(); } - boolean isEof() { + public boolean isEof() { return idx >= src.length(); } - char peek() { + protected char peekAssert() { return src.charAt(idx); } - char advance() { + protected char peekExpect() throws ExprParseEofException { + if(isEof()) throw new ExprParseEofException("Unexpected end of input"); + return src.charAt(idx); + } + protected char advanceAssert() { + return src.charAt(idx++); + } + protected char advanceExpect() throws ExprParseEofException { + if(isEof()) throw new ExprParseEofException("Unexpected end of input"); return src.charAt(idx++); } - boolean advanceIf(@NotNull Function predicate) { - boolean doAdvance = predicate.apply(peek()); + @SuppressWarnings("unused") + protected boolean advanceIf(@NotNull Function predicate) throws ExprParseEofException { + boolean doAdvance = predicate.apply(peekExpect()); + if(doAdvance) ++idx; + return doAdvance; + } + protected boolean advanceIfAssert(@NotNull Function predicate) { + boolean doAdvance = predicate.apply(peekAssert()); if(doAdvance) ++idx; return doAdvance; } @@ -140,7 +154,7 @@ boolean advanceIf(@NotNull Function predicate) { */ protected int advanceWhile(@NotNull Function predicate) { int n = 0; - while (notEof() && advanceIf(predicate)) ++n; + while (notEof() && advanceIfAssert(predicate)) ++n; return n; } @@ -153,7 +167,7 @@ protected void discardWhitespace() { } protected void advanceExpectNext(char expected) throws ExprParseException { - char actual = advance(); + char actual = advanceExpect(); if(actual != expected) throw new ExprParseException("Expected '%c', got '%c'".formatted(expected, actual)); } protected void advanceExpectNext_ignoreWs(char expected) throws ExprParseException { diff --git a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java index aaa210f..7fba989 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java +++ b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java @@ -5,6 +5,7 @@ public class MathUtil { protected MathUtil() {} + // TODO this doesn't work: roundToSigFigs(1.22222 + 0.3, 10) == 1.5222200000000001 public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_VALUE) int sigFigs) { int mostSignificantDigit = (int)Math.floor(Math.log10(value)); int roundToDigit = mostSignificantDigit - sigFigs + 1; From f06324dbb320f974dc4d55850705bdcad6dfc073 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Mon, 3 Jun 2024 22:11:00 +0100 Subject: [PATCH 05/10] fix: Fix FP issues --- .../java/net/marcellperger/mathexpr/util/MathUtil.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java index 7fba989..974b82c 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java +++ b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java @@ -2,10 +2,12 @@ import org.jetbrains.annotations.Range; +import java.math.BigDecimal; +import java.math.RoundingMode; + public class MathUtil { protected MathUtil() {} - // TODO this doesn't work: roundToSigFigs(1.22222 + 0.3, 10) == 1.5222200000000001 public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_VALUE) int sigFigs) { int mostSignificantDigit = (int)Math.floor(Math.log10(value)); int roundToDigit = mostSignificantDigit - sigFigs + 1; @@ -13,7 +15,9 @@ public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_ } public static double roundToNearest(double value, double nearest) { - return Math.round(value / nearest) * nearest; + BigDecimal nearestD = BigDecimal.valueOf(nearest); + return BigDecimal.valueOf(value).divide(nearestD, RoundingMode.HALF_UP) + .setScale(0, RoundingMode.HALF_UP).multiply(nearestD).doubleValue(); } public static double roundToDP(double value, int decimalPlaces) { return roundToNearest(value, Math.pow(10, decimalPlaces)); From 6682ff0af31cc047c9a547d71ce85fed7f28061c Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Mon, 3 Jun 2024 22:14:13 +0100 Subject: [PATCH 06/10] fix: Fix Ctrl+C to stop it --- .../mathexpr/interactive/Shell.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java index edc018c..93a748c 100644 --- a/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java +++ b/src/main/java/net/marcellperger/mathexpr/interactive/Shell.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import java.io.PrintStream; +import java.util.NoSuchElementException; public class Shell { Input in; @@ -22,15 +23,22 @@ public static void main(String[] args) { new Shell().run(); } - // TODO run() until exit better - parse exit command/Ctrl+C + // TODO run() until exit better - parse exit command, more robust/extensible command handling system public void run() { - //noinspection InfiniteLoopStatement - while (true) getAndRunCommand(); + //noinspection StatementWithEmptyBody + while (getAndRunCommand()) {} } - public void getAndRunCommand() { - runCommand(in.getInput(">? ")); + public /*returns wantMore */boolean getAndRunCommand() { + String inp; + try { + inp = in.getInput(">? "); + } catch (NoSuchElementException e) { + return false; + } + runCommand(inp); + return true; } public void runCommand(String cmd) { @Nullable MathSymbol sym = parseCmdOrPrintError(cmd); From 56dc44a8f9c22e10622dc55379e0ef332a6b9515 Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Tue, 4 Jun 2024 21:02:00 +0100 Subject: [PATCH 07/10] fix: Fix rounding 0, inf, NaN --- .../marcellperger/mathexpr/util/MathUtil.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java index 974b82c..0a0aec5 100644 --- a/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java +++ b/src/main/java/net/marcellperger/mathexpr/util/MathUtil.java @@ -9,17 +9,27 @@ public class MathUtil { protected MathUtil() {} public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_VALUE) int sigFigs) { - int mostSignificantDigit = (int)Math.floor(Math.log10(value)); + if(value == 0) return 0; // log10 would give Infinity so special-case it + int mostSignificantDigit = (int)Math.floor(Math.log10(Math.abs(value))); int roundToDigit = mostSignificantDigit - sigFigs + 1; return roundToDP(value, roundToDigit); } + public static double roundToDP(double value, int decimalPlaces) { + return roundToNearest(value, Math.pow(10, decimalPlaces)); + } + public static double roundToNearest(double value, double nearest) { + if(Double.isNaN(nearest) || Double.isNaN(value)) return Double.NaN; + // 0==0*n and inf==inf*n => 0 and 0 are multiples of everything + if(value == 0.0 || Double.isInfinite(value)) return value; + // The nearest infinity if the one with the same sign + if(Double.isInfinite(nearest)) return Math.copySign(nearest, value); + return roundRealToNearest(value, nearest); + } + protected static double roundRealToNearest(double value, double nearest) { BigDecimal nearestD = BigDecimal.valueOf(nearest); return BigDecimal.valueOf(value).divide(nearestD, RoundingMode.HALF_UP) .setScale(0, RoundingMode.HALF_UP).multiply(nearestD).doubleValue(); } - public static double roundToDP(double value, int decimalPlaces) { - return roundToNearest(value, Math.pow(10, decimalPlaces)); - } } From 9de22e8af3fc66be3c859061502571e41f826b6b Mon Sep 17 00:00:00 2001 From: Marcell Perger Date: Tue, 4 Jun 2024 21:03:46 +0100 Subject: [PATCH 08/10] refactor: Add `ShellCommandHandler` interface --- .idea/inspectionProfiles/Project_Default.xml | 1 + .../interactive/ExitCommandHandler.java | 8 ++++++ .../interactive/MathCommandHandler.java | 28 +++++++++++++++++++ .../interactive/ShellCommandHandler.java | 11 ++++++++ .../mathexpr/util/ControlFlowBreak.java | 9 ++++++ 5 files changed, 57 insertions(+) create mode 100644 src/main/java/net/marcellperger/mathexpr/interactive/ExitCommandHandler.java create mode 100644 src/main/java/net/marcellperger/mathexpr/interactive/MathCommandHandler.java create mode 100644 src/main/java/net/marcellperger/mathexpr/interactive/ShellCommandHandler.java create mode 100644 src/main/java/net/marcellperger/mathexpr/util/ControlFlowBreak.java diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index a4e99ea..637f013 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,7 @@