248 lines
7.1 KiB
Java
248 lines
7.1 KiB
Java
package expression.parser;
|
|
|
|
import expression.*;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* @author Doschennikov Nikita (me@fymio.us)
|
|
*/
|
|
public class ExpressionParser implements ListParser {
|
|
|
|
private String src;
|
|
private int pos;
|
|
private List<String> variables;
|
|
|
|
@Override
|
|
public ListExpression parse(String expression, List<String> variables) {
|
|
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 Add(left, parseMulDiv());
|
|
} else if (
|
|
pos < src.length() &&
|
|
src.charAt(pos) == '-' &&
|
|
!isUnaryContext()
|
|
) {
|
|
pos++;
|
|
left = new Subtract(left, parseMulDiv());
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
return left;
|
|
}
|
|
|
|
private AbstractExpression parseMulDiv() {
|
|
AbstractExpression left = parseUnary();
|
|
while (true) {
|
|
skipWhitespace();
|
|
if (pos < src.length() && src.charAt(pos) == '*') {
|
|
pos++;
|
|
left = new Multiply(left, parseUnary());
|
|
} else if (pos < src.length() && src.charAt(pos) == '/') {
|
|
pos++;
|
|
left = new Divide(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 parseNumber(true);
|
|
}
|
|
return new Negate(parseUnary());
|
|
}
|
|
|
|
if (tryConsume("reverse")) {
|
|
requireSeparatorOrParen();
|
|
return new Reverse(parseUnary());
|
|
}
|
|
if (tryConsume("digits")) {
|
|
requireSeparatorOrParen();
|
|
return new Digits(parseUnary());
|
|
}
|
|
if (tryConsume("floor")) {
|
|
requireSeparatorOrParen();
|
|
return new Floor(parseUnary());
|
|
}
|
|
if (tryConsume("ceiling")) {
|
|
requireSeparatorOrParen();
|
|
return new Ceiling(parseUnary());
|
|
}
|
|
|
|
return parsePrimary();
|
|
}
|
|
|
|
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.indexOf(name);
|
|
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 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 requireSeparatorOrParen() {
|
|
skipWhitespace();
|
|
}
|
|
|
|
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);
|
|
long val = Long.parseLong(numStr);
|
|
if (negative) val = -val;
|
|
if (val < Integer.MIN_VALUE || val > Integer.MAX_VALUE) {
|
|
throw new IllegalArgumentException("Integer overflow: " + val);
|
|
}
|
|
return new Const((int) val);
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
private boolean isUnaryContext() {
|
|
return false;
|
|
}
|
|
}
|