first commit
This commit is contained in:
215
java/expression/exceptions/ExpressionParser.java
Normal file
215
java/expression/exceptions/ExpressionParser.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package expression.exceptions;
|
||||
|
||||
import expression.*;
|
||||
|
||||
/**
|
||||
* @author Doschennikov Nikita (me@fymio.us)
|
||||
*/
|
||||
public class ExpressionParser implements ListParser {
|
||||
|
||||
private String src;
|
||||
private int pos;
|
||||
private java.util.List<String> variables;
|
||||
|
||||
@Override
|
||||
public ListExpression parse(String expression, java.util.List<String> variables) {
|
||||
// System.err.println("PARSE: [" + expression + "]");
|
||||
this.src = expression;
|
||||
this.pos = 0;
|
||||
this.variables = variables;
|
||||
AbstractExpression result = parseMinMax();
|
||||
skipWhitespace();
|
||||
if (pos < src.length()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unexpected character '" + src.charAt(pos) + "' at position " + pos);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private AbstractExpression parseMinMax() {
|
||||
AbstractExpression left = parseAddSub();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (tryConsume("min")) left = new Min(left, parseAddSub());
|
||||
else if (tryConsume("max")) left = new Max(left, parseAddSub());
|
||||
else if (tryConsume("set")) left = new SetBit(left, parseAddSub());
|
||||
else if (tryConsume("clear")) left = new Clear(left, parseAddSub());
|
||||
else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseAddSub() {
|
||||
AbstractExpression left = parseMulDiv();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '+') {
|
||||
pos++;
|
||||
left = new CheckedAdd(left, parseMulDiv());
|
||||
} else if (pos < src.length() && src.charAt(pos) == '-') {
|
||||
pos++;
|
||||
left = new CheckedSubtract(left, parseMulDiv());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseMulDiv() {
|
||||
AbstractExpression left = parsePower();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '*' && nextCharIs('*')) {
|
||||
pos++;
|
||||
left = new CheckedMultiply(left, parsePower());
|
||||
} else if (pos < src.length() && src.charAt(pos) == '/' && nextCharIs('/')) {
|
||||
pos++;
|
||||
left = new CheckedDivide(left, parsePower());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parsePower() {
|
||||
AbstractExpression left = parseUnary();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (pos + 1 < src.length() && src.charAt(pos) == '*' && src.charAt(pos + 1) == '*') {
|
||||
pos += 2;
|
||||
left = new Power(left, parseUnary());
|
||||
} else if (pos + 1 < src.length() && src.charAt(pos) == '/' && src.charAt(pos + 1) == '/') {
|
||||
pos += 2;
|
||||
left = new Log(left, parseUnary());
|
||||
} else break;
|
||||
}
|
||||
return left;
|
||||
}
|
||||
|
||||
private AbstractExpression parseUnary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length())
|
||||
throw new IllegalArgumentException("Unexpected end of expression at position " + pos);
|
||||
|
||||
if (src.charAt(pos) == '-') {
|
||||
pos++;
|
||||
if (pos < src.length() && Character.isDigit(src.charAt(pos))) return parsePostfix(parseNumber(true));
|
||||
return new CheckedNegate(parseUnary());
|
||||
}
|
||||
|
||||
// abs: ||x|| (U+2016 DOUBLE VERTICAL LINE)
|
||||
if (src.charAt(pos) == '‖') {
|
||||
pos++;
|
||||
AbstractExpression inner = parseMinMax();
|
||||
skipWhitespace();
|
||||
if (pos >= src.length() || src.charAt(pos) != '‖')
|
||||
throw new IllegalArgumentException("Expected ‖ at position " + pos);
|
||||
pos++;
|
||||
return parsePostfix(new Abs(inner));
|
||||
}
|
||||
|
||||
// prefix unary: sqrt U+221A, cbrt U+221B
|
||||
if (src.charAt(pos) == '√') { pos++; return parsePostfix(new Sqrt(parseUnary())); }
|
||||
if (src.charAt(pos) == '∛') { pos++; return parsePostfix(new Cbrt(parseUnary())); }
|
||||
|
||||
if (tryConsume("reverse")) return parsePostfix(new Reverse(parseUnary()));
|
||||
if (tryConsume("digits")) return parsePostfix(new Digits(parseUnary()));
|
||||
if (tryConsume("floor")) return parsePostfix(new Floor(parseUnary()));
|
||||
if (tryConsume("ceiling")) return parsePostfix(new Ceiling(parseUnary()));
|
||||
if (tryConsume("log₂") || tryConsume("log2")) return parsePostfix(new Log2(parseUnary()));
|
||||
if (tryConsume("pow₂") || tryConsume("pow2")) return parsePostfix(new Pow2(parseUnary()));
|
||||
if (tryConsume("low")) return parsePostfix(new Low(parseUnary()));
|
||||
if (tryConsume("high")) return parsePostfix(new High(parseUnary()));
|
||||
|
||||
return parsePostfix(parsePrimary());
|
||||
}
|
||||
|
||||
private AbstractExpression parsePostfix(AbstractExpression base) {
|
||||
skipWhitespace();
|
||||
if (pos < src.length() && src.charAt(pos) == '²') { pos++; return parsePostfix(new Square(base)); }
|
||||
if (pos < src.length() && src.charAt(pos) == '³') { pos++; return parsePostfix(new Cube(base)); }
|
||||
return base;
|
||||
}
|
||||
|
||||
private AbstractExpression parsePrimary() {
|
||||
skipWhitespace();
|
||||
if (pos >= src.length()) throw new IllegalArgumentException("Unexpected end of expression");
|
||||
char c = src.charAt(pos);
|
||||
|
||||
if (c == '(') {
|
||||
pos++;
|
||||
AbstractExpression inner = parseMinMax();
|
||||
skipWhitespace();
|
||||
expect();
|
||||
return inner;
|
||||
}
|
||||
if (c == '$') { pos++; return new Variable(parseIndex()); }
|
||||
if (Character.isDigit(c)) return parseNumber(false);
|
||||
|
||||
if (Character.isLetter(c)) {
|
||||
int start = pos;
|
||||
while (pos < src.length() && (Character.isLetterOrDigit(src.charAt(pos)) || src.charAt(pos) == '_'))
|
||||
pos++;
|
||||
String name = src.substring(start, pos);
|
||||
int idx = variables != null ? variables.indexOf(name) : -1;
|
||||
if (idx >= 0) return new Variable(idx, name);
|
||||
throw new IllegalArgumentException("Unknown identifier '" + name + "' at position " + start);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unexpected character '" + c + "' at position " + pos);
|
||||
}
|
||||
|
||||
private void skipWhitespace() {
|
||||
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) pos++;
|
||||
}
|
||||
|
||||
private boolean nextCharIs(char next) {
|
||||
return pos + 1 >= src.length() || src.charAt(pos + 1) != next;
|
||||
}
|
||||
|
||||
private boolean tryConsume(String keyword) {
|
||||
skipWhitespace();
|
||||
if (!src.startsWith(keyword, pos)) return false;
|
||||
int end = pos + keyword.length();
|
||||
if (end < src.length()) {
|
||||
char next = src.charAt(end);
|
||||
if (Character.isLetterOrDigit(next) || next == '_') return false;
|
||||
}
|
||||
if (variables != null && variables.contains(keyword)) return false;
|
||||
pos = end;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void expect() {
|
||||
if (pos >= src.length() || src.charAt(pos) != ')')
|
||||
throw new IllegalArgumentException("Expected '" + ')' + "' at position " + pos
|
||||
+ (pos < src.length() ? ", got '" + src.charAt(pos) + "'" : ", got end of input"));
|
||||
pos++;
|
||||
}
|
||||
|
||||
private AbstractExpression parseNumber(boolean negative) {
|
||||
int start = pos;
|
||||
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
|
||||
if (start == pos) throw new IllegalArgumentException("Expected digit at position " + pos);
|
||||
String numStr = src.substring(start, pos);
|
||||
int result = 0;
|
||||
for (int i = 0; i < numStr.length(); i++) {
|
||||
int digit = numStr.charAt(i) - '0';
|
||||
if (!negative) {
|
||||
if (result > (Integer.MAX_VALUE - digit) / 10)
|
||||
throw new OverflowException("constant " + numStr);
|
||||
result = result * 10 + digit;
|
||||
} else {
|
||||
if (result < (Integer.MIN_VALUE + digit) / 10)
|
||||
throw new OverflowException("constant -" + numStr);
|
||||
result = result * 10 - digit;
|
||||
}
|
||||
}
|
||||
return new Const(result);
|
||||
}
|
||||
|
||||
private int parseIndex() {
|
||||
int start = pos;
|
||||
while (pos < src.length() && Character.isDigit(src.charAt(pos))) pos++;
|
||||
if (start == pos) throw new IllegalArgumentException("Expected digit after '$' at position " + pos);
|
||||
return Integer.parseInt(src.substring(start, pos));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user