package com.metaweb.gridworks.expr; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.metaweb.gridworks.expr.Scanner.NumberToken; import com.metaweb.gridworks.expr.Scanner.Token; import com.metaweb.gridworks.expr.Scanner.TokenType; import com.metaweb.gridworks.expr.controls.ForEach; import com.metaweb.gridworks.expr.controls.ForNonBlank; import com.metaweb.gridworks.expr.controls.If; import com.metaweb.gridworks.expr.functions.And; import com.metaweb.gridworks.expr.functions.Get; import com.metaweb.gridworks.expr.functions.IsBlank; import com.metaweb.gridworks.expr.functions.IsNotBlank; import com.metaweb.gridworks.expr.functions.IsNotNull; import com.metaweb.gridworks.expr.functions.IsNull; import com.metaweb.gridworks.expr.functions.Length; import com.metaweb.gridworks.expr.functions.Not; import com.metaweb.gridworks.expr.functions.Or; import com.metaweb.gridworks.expr.functions.Replace; import com.metaweb.gridworks.expr.functions.Slice; import com.metaweb.gridworks.expr.functions.Split; import com.metaweb.gridworks.expr.functions.ToLowercase; import com.metaweb.gridworks.expr.functions.ToTitlecase; import com.metaweb.gridworks.expr.functions.ToUppercase; public class Parser { protected Scanner _scanner; protected Token _token; protected Evaluable _root; static public Map functionTable = new HashMap(); static public Map controlTable = new HashMap(); static { functionTable.put("toUppercase", new ToUppercase()); functionTable.put("toLowercase", new ToLowercase()); functionTable.put("toTitlecase", new ToTitlecase()); functionTable.put("get", new Get()); functionTable.put("slice", new Slice()); functionTable.put("substring", new Slice()); functionTable.put("replace", new Replace()); functionTable.put("split", new Split()); functionTable.put("length", new Length()); functionTable.put("and", new And()); functionTable.put("or", new Or()); functionTable.put("not", new Not()); functionTable.put("isNull", new IsNull()); functionTable.put("isNotNull", new IsNotNull()); functionTable.put("isBlank", new IsBlank()); functionTable.put("isNotBlank", new IsNotBlank()); controlTable.put("if", new If()); controlTable.put("forEach", new ForEach()); controlTable.put("forNonBlank", new ForNonBlank()); } public Parser(String s) throws Exception { this(s, 0, s.length()); } public Parser(String s, int from, int to) throws Exception { _scanner = new Scanner(s, from, to); _token = _scanner.next(); _root = parseExpression(); } public Evaluable getExpression() { return _root; } protected void next() { _token = _scanner.next(); } protected Exception makeException(String desc) { int index = _token != null ? _token.start : _scanner.getIndex(); return new Exception("Parsing error at offset " + index + ": " + desc); } protected Evaluable parseExpression() throws Exception { Evaluable sub = parseSubExpression(); while (_token != null && _token.type == TokenType.Operator && ">=<==!=".indexOf(_token.text) >= 0) { String op = _token.text; next(); Evaluable sub2 = parseSubExpression(); sub = new OperatorCallExpr(new Evaluable[] { sub, sub2 }, op); } return sub; } protected Evaluable parseSubExpression() throws Exception { Evaluable sub = parseTerm(); while (_token != null && _token.type == TokenType.Operator && "+-".indexOf(_token.text) >= 0) { String op = _token.text; next(); Evaluable sub2 = parseSubExpression(); sub = new OperatorCallExpr(new Evaluable[] { sub, sub2 }, op); } return sub; } protected Evaluable parseTerm() throws Exception { Evaluable factor = parseFactor(); while (_token != null && _token.type == TokenType.Operator && "*/".indexOf(_token.text) >= 0) { String op = _token.text; next(); Evaluable factor2 = parseFactor(); factor = new OperatorCallExpr(new Evaluable[] { factor, factor2 }, op); } return factor; } protected Evaluable parseFactor() throws Exception { if (_token == null) { throw makeException("Expression ends too early"); } Evaluable eval = null; if (_token.type == TokenType.String) { eval = new LiteralExpr(_token.text); next(); } else if (_token.type == TokenType.Number) { eval = new LiteralExpr(((NumberToken)_token).value); next(); } else if (_token.type == TokenType.Operator && _token.text.equals("-")) { // unary minus? next(); if (_token != null && _token.type == TokenType.Number) { eval = new LiteralExpr(-((NumberToken)_token).value); next(); } else { throw makeException("Bad negative number"); } } else if (_token.type == TokenType.Identifier) { String text = _token.text; next(); if (_token == null || _token.type != TokenType.Delimiter || !_token.text.equals("(")) { eval = new VariableExpr(text); } else { Function f = functionTable.get(text); Control c = controlTable.get(text); if (f == null && c == null) { throw makeException("Unknown function or control named " + text); } next(); // swallow ( List args = parseExpressionList(")"); if (c != null) { eval = new ControlCallExpr(makeArray(args), c); } else { eval = new FunctionCallExpr(makeArray(args), f); } } } else if (_token.type == TokenType.Delimiter && _token.text.equals("(")) { next(); eval = parseExpression(); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(")")) { next(); } else { throw makeException("Missing )"); } } else { throw makeException("Missing number, string, identifier, or parenthesized expression"); } while (_token != null) { if (_token.type == TokenType.Operator && _token.text.equals(".")) { next(); // swallow . if (_token == null || _token.type != TokenType.Identifier) { throw makeException("Missing function name"); } String identifier = _token.text; next(); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals("(")) { next(); // swallow ( Function f = functionTable.get(identifier); if (f == null) { throw makeException("Unknown function " + identifier); } List args = parseExpressionList(")"); args.add(0, eval); eval = new FunctionCallExpr(makeArray(args), f); } else { eval = new FieldAccessorExpr(eval, identifier); } } else if (_token.type == TokenType.Delimiter && _token.text.equals("[")) { next(); // swallow [ List args = parseExpressionList("]"); args.add(0, eval); eval = new FunctionCallExpr(makeArray(args), functionTable.get("get")); } else { break; } } return eval; } protected List parseExpressionList(String closingDelimiter) throws Exception { List l = new LinkedList(); if (_token != null && (_token.type != TokenType.Delimiter || !_token.text.equals(closingDelimiter))) { while (_token != null) { Evaluable eval = parseExpression(); l.add(eval); if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(",")) { next(); // swallow comma, loop back for more } else { break; } } } if (_token != null && _token.type == TokenType.Delimiter && _token.text.equals(closingDelimiter)) { next(); // swallow closing delimiter } else { throw makeException("Missing " + closingDelimiter); } return l; } protected Evaluable[] makeArray(List l) { Evaluable[] a = new Evaluable[l.size()]; l.toArray(a); return a; } }