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 variables; @Override public ListExpression parse(String expression, java.util.List 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)); } }