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