Created
July 2, 2018 06:08
-
-
Save dodgex/ed83688b29669ce82a95b492d1c3eefa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.math.BigDecimal; | |
import java.math.RoundingMode; | |
/** | |
* Based on: https://stackoverflow.com/a/26227947/1659588 | |
*/ | |
public class Parser { | |
private static final BigDecimal NEGATIVE_ONE = new BigDecimal("-1"); | |
private final String str; | |
private final ArgumentRetriver argRetriver; | |
private final int scale = 5; | |
private int pos = -1; | |
private int ch; | |
public Parser(String str) { | |
this(str, null); | |
} | |
public Parser(String str, ArgumentRetriver argRetriver) { | |
this.str = str; | |
this.argRetriver = argRetriver; | |
} | |
public BigDecimal parse() { | |
nextChar(); | |
Expression x = parseExpression(); | |
if (pos < str.length()) throw new RuntimeException("Unexpected: " + (char) ch); | |
return x.eval(); | |
} | |
private void nextChar() { | |
ch = (++pos < str.length()) ? str.charAt(pos) : -1; | |
} | |
private boolean eat(int charToEat) { | |
while (isWhitespace()) nextChar(); | |
if (ch == charToEat) { | |
nextChar(); | |
return true; | |
} | |
return false; | |
} | |
private boolean isWhitespace() { | |
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; | |
} | |
private Expression parseExpression() { | |
Expression x = parseTerm(); | |
for (; ; ) { | |
if (eat('+')) { // addition | |
Expression left = x, right = parseTerm(); | |
x = () -> left.eval().add(right.eval()); | |
} else if (eat('-')) { // subtraction | |
Expression left = x, right = parseTerm(); | |
x = () -> left.eval().subtract(right.eval()); | |
} else { | |
return x; | |
} | |
} | |
} | |
private Expression parseTerm() { | |
Expression x = parseFactor(); | |
for (; ; ) { | |
if (eat('*')) { | |
Expression left = x, right = parseTerm(); | |
x = () -> left.eval().multiply(right.eval()); | |
} else if (eat('/')) { | |
Expression left = x, right = parseTerm(); | |
x = () -> left.eval().divide(right.eval(), scale, RoundingMode.HALF_EVEN) | |
.stripTrailingZeros() | |
; | |
} else { | |
return x; | |
} | |
} | |
} | |
private Expression parseArgument() { | |
if (argRetriver == null) { | |
throw new IllegalStateException("provide argument retriver"); | |
} | |
if (!eat('(')) { | |
throw new IllegalStateException("'(' expected"); | |
} | |
int startPos = this.pos; | |
while (ch != ')') nextChar(); | |
String argument = str.substring(startPos, this.pos); | |
if (!argument.contains(":")) { | |
throw new IllegalStateException("expected ':' missing in argument: " + argument); | |
} | |
String[] split = argument.split(":", 2); | |
BigDecimal value = argRetriver.getArgumentValue(split[0], split[1]); | |
if (value == null) { | |
throw new IllegalStateException("no value available for argument: " + argument); | |
} | |
BigDecimal result = scaledValue(value, scale); | |
eat(')'); | |
return () -> result; | |
} | |
private Expression parseFactor() { | |
if (eat('+')) { | |
return parseFactor(); // unary plus | |
} | |
if (eat('-')) { | |
return parseFactor().negate(); // unary minus | |
} | |
Expression x; | |
int startPos = this.pos; | |
if (eat('(')) { // parentheses | |
x = parseExpression(); | |
eat(')'); | |
} else if ((ch >= '0' && ch <= '9') || ch == '.') { // numbers | |
while ((ch >= '0' && ch <= '9') || ch == '.') nextChar(); | |
x = fromString(str.substring(startPos, this.pos), scale); | |
} else if (ch >= 'a' && ch <= 'z') { // functions | |
while (ch >= 'a' && ch <= 'z') nextChar(); | |
String func = str.substring(startPos, this.pos); | |
// x = parseArgument(); | |
if (func.equals("cell")) x = parseArgument(); | |
// else if (func.equals("sin")) x = Math.sin(Math.toRadians(x)); | |
// else if (func.equals("cos")) x = Math.cos(Math.toRadians(x)); | |
// else if (func.equals("tan")) x = Math.tan(Math.toRadians(x)); | |
else throw new RuntimeException("Unknown function: " + func); | |
} else { | |
throw new RuntimeException("Unexpected: " + (char) ch); | |
} | |
return x; | |
} | |
private Expression fromString(String val, int minScale) { | |
BigDecimal result = scaledValue(val, minScale); | |
return () -> result; | |
} | |
private BigDecimal scaledValue(String val, int minScale) { | |
BigDecimal result = new BigDecimal(val); | |
return scaledValue(result, minScale); | |
} | |
private BigDecimal scaledValue(BigDecimal value, int minScale) { | |
if (value.scale() < minScale) { | |
value = value.setScale(minScale, RoundingMode.HALF_EVEN); | |
} | |
return value.stripTrailingZeros(); | |
} | |
@FunctionalInterface | |
public interface ArgumentRetriver { | |
BigDecimal getArgumentValue(String row, String col); | |
} | |
@FunctionalInterface | |
private interface Expression { | |
BigDecimal eval(); | |
default Expression negate() { | |
return () -> eval().multiply(NEGATIVE_ONE); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment