package core.framework.impl.template.expression; import core.framework.impl.code.CodeCompileException; import java.util.regex.Pattern; /** * @author neo */ class ExpressionParser { private static final Pattern NUMBER_PATTERN = Pattern.compile("[\\d\\.]+"); private static final Pattern METHOD_PATTERN = Pattern.compile("[a-z][a-zA-Z\\d]*"); private static final Pattern FIELD_PATTERN = Pattern.compile("[a-z][a-zA-Z\\d]*"); public Token parse(String expression) { int length = expression.length(); char firstChar = expression.charAt(0); if (firstChar == '"' || firstChar == '\'') { if (length <= 1 || expression.charAt(length - 1) != firstChar) throw new CodeCompileException("\" or \' is not closed, expression=" + expression); return new ValueToken("\"" + expression.substring(1, length - 1) + "\"", String.class); } for (int i = 0; i < length; i++) { char ch = expression.charAt(i); if (ch == '(') { return parseMethod(expression, i); } else if (ch == '.') { if (i == length - 1) { throw new CodeCompileException("expression must not end with '.'"); } String field = expression.substring(0, i); if (NUMBER_PATTERN.matcher(field).matches()) continue; if (!FIELD_PATTERN.matcher(field).matches()) throw new CodeCompileException("invalid field name, field=" + field); FieldToken token = new FieldToken(field); token.next = parse(expression.substring(i + 1)); if (token.next instanceof ValueToken) throw new CodeCompileException("value token must not follow '.'"); return token; } } if (NUMBER_PATTERN.matcher(expression).matches()) { return new ValueToken(expression, Number.class); } else { if (!FIELD_PATTERN.matcher(expression).matches()) throw new CodeCompileException("invalid field name, field=" + expression); return new FieldToken(expression); } } private Token parseMethod(String expression, int leftParenthesesIndex) { int length = expression.length(); String method = expression.substring(0, leftParenthesesIndex); if (!METHOD_PATTERN.matcher(method).matches()) throw new CodeCompileException("invalid method name, method=" + method); MethodToken token = new MethodToken(method); int endIndex = findMethodEnd(expression, leftParenthesesIndex + 1); parseMethodParams(token, expression.substring(leftParenthesesIndex + 1, endIndex)); if (endIndex < length - 1) { if (endIndex + 1 >= length) throw new CodeCompileException("method can only be followed by field or method"); if (expression.charAt(endIndex + 1) != '.') throw new CodeCompileException("method can only be followed by '.'"); token.next = parse(expression.substring(endIndex + 2)); if (token.next instanceof ValueToken) throw new CodeCompileException("value token must not be followed by '.'"); } return token; } private void parseMethodParams(MethodToken token, String params) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < params.length(); i++) { char ch = params.charAt(i); if (ch == ',') { String param = builder.toString().trim(); if (param.length() == 0) throw new CodeCompileException("expect param before ',', method=" + token.name); token.params.add(parse(param)); builder = new StringBuilder(); } else { builder.append(ch); } } String param = builder.toString().trim(); if (param.length() > 0) token.params.add(parse(param)); } private int findMethodEnd(String expression, int index) { int leftParentheses = 0; for (int i = index; i < expression.length(); i++) { char ch = expression.charAt(i); if (ch == ')') { if (leftParentheses == 0) return i; else leftParentheses--; } else if (ch == '(') { leftParentheses++; } } throw new CodeCompileException("parentheses is not closed properly"); } }