Skip to content
Merged
1 change: 1 addition & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions runshell.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
java -classpath ./target/classes/ net.marcellperger.mathexpr.interactive.Shell
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.marcellperger.mathexpr.interactive;

public class ExitCommandHandler implements ShellCommandHandler {
@Override
public boolean run(String cmd, Shell sh) {
return false; // Do not continue
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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.ControlFlowBreak;
import net.marcellperger.mathexpr.util.MathUtil;

public class MathCommandHandler implements ShellCommandHandler {
@Override
public boolean run(String cmd, Shell sh) {
try {
MathSymbol sym = parseOrPrintError(cmd, sh);
sh.out.println(MathUtil.roundToSigFigs(sym.calculateValue(), 12));
} catch (ControlFlowBreak _parseErrorAlreadyPrinted) {}
return true; // Always continue
}

MathSymbol parseOrPrintError(String cmd, Shell sh) throws ControlFlowBreak {
try {
return new Parser(cmd).parse();
} catch (ExprParseException e) {
sh.out.println(e);
throw new ControlFlowBreak();
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/net/marcellperger/mathexpr/interactive/Shell.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package net.marcellperger.mathexpr.interactive;

import net.marcellperger.mathexpr.util.Input;
import net.marcellperger.mathexpr.util.InputClosedException;

import java.io.PrintStream;

public class Shell {
public Input in;
public PrintStream out;
ShellCommandParser commandParser;

public Shell() {
in = new Input();
out = System.out;
commandParser = new ShellCommandParser(this);
}

public static void main(String[] args) {
new Shell().run();
}

public void run() {
displayStartupMessage();
//noinspection StatementWithEmptyBody
while (getAndRunCommand()) {}
}

public void displayStartupMessage() {
out.println("MathExpr console v0.1.0-alpha.1 (type \"!exit\" to exit).");
}

public /*returns wantMore */boolean getAndRunCommand() {
String inp;
try {
inp = in.getInput(">? ");
} catch (InputClosedException e) {
return false;
}
// This .strip() is surprisingly important - it allows the .split("\\s") to work properly
return runCommand(inp.strip());
}

public boolean runCommand(String cmd) {
return commandParser.dispatchCommand(cmd).run(cmd, this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package net.marcellperger.mathexpr.interactive;

@FunctionalInterface
public interface ShellCommandHandler {
/**
* @param cmd The command to run as a {@link String}
* @param sh The {@link Shell} object
* @return {@code false} to exit, {@code true} to continue
*/
boolean run(String cmd, Shell sh);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package net.marcellperger.mathexpr.interactive;

import net.marcellperger.mathexpr.util.Pair;
import net.marcellperger.mathexpr.util.UnreachableError;
import org.jetbrains.annotations.NotNull;

public class ShellCommandParser {
Shell sh;

public ShellCommandParser(Shell shell) {
sh = shell;
}

public ShellCommandHandler dispatchCommand(String cmd) {
// (There will be more...)
//noinspection SwitchStatementWithTooFewBranches
return switch (getCommandName(cmd)) {
case "!exit" -> new ExitCommandHandler();
default -> new MathCommandHandler();
};
}

protected @NotNull String getCommandName(String cmd) {
return splitCommand(cmd).left;
}

/**
* Splits {@code cmd} into command and arguments
*/
protected @NotNull Pair<@NotNull String, @NotNull String> splitCommand(@NotNull String cmd) {
String[] words = cmd.split("\\s+", 2);
return switch (words.length) {
case 0 -> new Pair<>("", "");
case 1 -> new Pair<>(words[0], "");
case 2 -> new Pair<>(words[0], words[1]);
default -> throw new UnreachableError();
};
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package net.marcellperger.mathexpr.parser;


@SuppressWarnings("unused") // I want all the constructors
public class ExprParseException extends Exception {
public ExprParseException() {
super();
Expand Down
32 changes: 23 additions & 9 deletions src/main/java/net/marcellperger/mathexpr/parser/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Character, Boolean> predicate) {
boolean doAdvance = predicate.apply(peek());
@SuppressWarnings("unused")
protected boolean advanceIf(@NotNull Function<Character, Boolean> predicate) throws ExprParseEofException {
boolean doAdvance = predicate.apply(peekExpect());
if(doAdvance) ++idx;
return doAdvance;
}
protected boolean advanceIfAssert(@NotNull Function<Character, Boolean> predicate) {
boolean doAdvance = predicate.apply(peekAssert());
if(doAdvance) ++idx;
return doAdvance;
}
Expand All @@ -140,7 +154,7 @@ boolean advanceIf(@NotNull Function<Character, Boolean> predicate) {
*/
protected int advanceWhile(@NotNull Function<Character, Boolean> predicate) {
int n = 0;
while (notEof() && advanceIf(predicate)) ++n;
while (notEof() && advanceIfAssert(predicate)) ++n;
return n;
}

Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.marcellperger.mathexpr.util;

/**
* The equivalent to Rust's {@code std::ops::ControlFlow::Break}.
* This is an exception that signals an operation exiting early.
*/
public class ControlFlowBreak extends Throwable {
public ControlFlowBreak() {}
}
74 changes: 74 additions & 0 deletions src/main/java/net/marcellperger/mathexpr/util/Input.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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.NoSuchElementException;
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() throws InputClosedException {
try {
return sc.nextLine();
} catch (NoSuchElementException e) {
throw new InputClosedException("Input was closed (Ctrl+C, Ctrl+D, etc.)", e);
}
}
public String getInput(String prompt) throws InputClosedException {
Objects.requireNonNull(ps, "An OutputStream is required to use Input.getInput(prompt)").print(prompt);
return getInput();
}

public static String input() throws InputClosedException {
// Don't cache `new Input()` - System.in could be changed
return new Input().getInput();
}
public static String input(@NotNull InputStream iStream) throws InputClosedException {
return new Input(iStream).getInput();
}
public static String input(String prompt) throws InputClosedException {
return new Input().getInput(prompt);
}
public static String input(String prompt, @NotNull OutputStream oStream) throws InputClosedException {
return new Input(oStream).getInput(prompt);
}
public static String input(String prompt, @NotNull InputStream iStream, @NotNull OutputStream oStream) throws InputClosedException {
return new Input(iStream, oStream).getInput(prompt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package net.marcellperger.mathexpr.util;

import java.util.NoSuchElementException;

public class InputClosedException extends NoSuchElementException {
public InputClosedException() {
}

public InputClosedException(String s, Throwable cause) {
super(s, cause);
}

public InputClosedException(Throwable cause) {
super(cause);
}

public InputClosedException(String s) {
super(s);
}
}
35 changes: 35 additions & 0 deletions src/main/java/net/marcellperger/mathexpr/util/MathUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package net.marcellperger.mathexpr.util;

import org.jetbrains.annotations.Range;

import java.math.BigDecimal;
import java.math.RoundingMode;

public class MathUtil {
protected MathUtil() {}

public static double roundToSigFigs(double value, @Range(from=1, to=Integer.MAX_VALUE) int sigFigs) {
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();
}
}
Loading