package expression.generic; import java.util.List; /** * @author Doschennikov Nikita (me@fymio.us) */ public class GenericParser { private static final List VARIABLES = List.of("x", "y", "z"); private String src; private int pos; public GenericExpr parse(String expression) { this.src = expression; this.pos = 0; GenericExpr result = parseMinMax(); skipWhitespace(); if (pos < src.length()) { throw new IllegalArgumentException( "Unexpected character '" + src.charAt(pos) + "' at position " + pos ); } return result; } private GenericExpr parseMinMax() { GenericExpr left = parseAddSub(); while (true) { skipWhitespace(); if (matchKeyword("min")) { GenericExpr right = parseAddSub(); GenericExpr l = left; left = (type, vars) -> type.min( l.evaluate(type, vars), right.evaluate(type, vars) ); } else if (matchKeyword("max")) { GenericExpr right = parseAddSub(); GenericExpr l = left; left = (type, vars) -> type.max( l.evaluate(type, vars), right.evaluate(type, vars) ); } else { break; } } return left; } private GenericExpr parseAddSub() { GenericExpr left = parseMulDiv(); while (true) { skipWhitespace(); if (pos < src.length() && src.charAt(pos) == '+') { pos++; GenericExpr right = parseMulDiv(); GenericExpr l = left; left = (type, vars) -> type.add( l.evaluate(type, vars), right.evaluate(type, vars) ); } else if ( pos < src.length() && src.charAt(pos) == '-' && !nextIsKeyword() ) { pos++; GenericExpr right = parseMulDiv(); GenericExpr l = left; left = (type, vars) -> type.subtract( l.evaluate(type, vars), right.evaluate(type, vars) ); } else { break; } } return left; } private GenericExpr parseMulDiv() { GenericExpr left = parseUnary(); while (true) { skipWhitespace(); if (pos < src.length() && src.charAt(pos) == '*') { pos++; GenericExpr right = parseUnary(); GenericExpr l = left; left = (type, vars) -> type.multiply( l.evaluate(type, vars), right.evaluate(type, vars) ); } else if (pos < src.length() && src.charAt(pos) == '/') { pos++; GenericExpr right = parseUnary(); GenericExpr l = left; left = (type, vars) -> type.divide( l.evaluate(type, vars), right.evaluate(type, vars) ); } else { break; } } return left; } private GenericExpr parseUnary() { skipWhitespace(); if (pos >= src.length()) { throw new IllegalArgumentException( "Unexpected end of expression at position " + pos ); } if (src.charAt(pos) == '-') { pos++; skipWhitespace(); if (pos < src.length() && Character.isDigit(src.charAt(pos))) { return parseNumber(true); } GenericExpr operand = parseUnary(); return (type, vars) -> type.negate(operand.evaluate(type, vars)); } if (matchKeyword("count")) { GenericExpr operand = parseUnary(); return (type, vars) -> type.count(operand.evaluate(type, vars)); } return parsePrimary(); } private GenericExpr parsePrimary() { skipWhitespace(); if (pos >= src.length()) { throw new IllegalArgumentException("Unexpected end of expression"); } char c = src.charAt(pos); if (c == '(') { pos++; GenericExpr inner = parseMinMax(); skipWhitespace(); expect(); return inner; } if (Character.isDigit(c)) { return parseNumber(false); } if (Character.isLetter(c)) { int start = pos; while ( pos < src.length() && Character.isLetterOrDigit(src.charAt(pos)) ) { pos++; } String name = src.substring(start, pos); int idx = VARIABLES.indexOf(name); if (idx >= 0) { return (type, vars) -> vars.get(idx); } throw new IllegalArgumentException( "Unknown variable '" + name + "' at position " + start ); } throw new IllegalArgumentException( "Unexpected character '" + c + "' at position " + pos ); } private GenericExpr 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); } int intVal = (int) val; return (type, vars) -> type.fromInt(intVal); } private boolean matchKeyword(String keyword) { int i = pos; for (char ch : keyword.toCharArray()) { if (i >= src.length() || src.charAt(i) != ch) return false; i++; } if ( i < src.length() && Character.isLetterOrDigit(src.charAt(i)) ) return false; pos = i; return true; } private boolean nextIsKeyword() { return false; } private void skipWhitespace() { while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) { pos++; } } 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++; } }