浏览代码

Field、Method 表达式支持可选链操作符 ?.

James 3 年之前
父节点
当前提交
f85f11ec53

+ 7 - 3
src/main/java/com/jfinal/template/expr/ExprLexer.java

@@ -118,7 +118,7 @@ class ExprLexer {
 	 * + - * / % ++ --
 	 * = == != < <= > >=
 	 * ! && ||
-	 * ? ?: ?!
+	 * ? ?? ?.
 	 * . .. : :: , ;
 	 * ( ) [ ] { }
 	 */
@@ -205,10 +205,14 @@ class ExprLexer {
 				throw new ParseException("Unsupported operator: '|'", location);
 			}
 			return ok(tok);
-		case '?':		// ? ??
-			if (next() == '?') {
+		case '?':		// ? ?? ?.
+			char c = next();
+			if (c == '?') {
 				tok = new Tok(Sym.NULL_SAFE, beginRow);
 				next();
+			} else if (c == '.') {
+				tok = new Tok(Sym.OPTIONAL_CHAIN, beginRow);
+				next();
 			} else {
 				tok = new Tok(Sym.QUESTION, beginRow);
 			}

+ 11 - 5
src/main/java/com/jfinal/template/expr/ExprParser.java

@@ -390,17 +390,23 @@ public class ExprParser {
 				expr = new Index(expr, index, location);
 				continue;
 			}
-			if (tok.sym != Sym.DOT) {
+			if (tok.sym != Sym.DOT && tok.sym != Sym.OPTIONAL_CHAIN) {
 				return expr;
 			}
-			if ((tok = move()).sym != Sym.ID) {
+			
+			Tok id = move();
+			if (id.sym != Sym.ID) {
 				resetForward(forward - 1);
 				return expr;
 			}
 			
+			// 可选链操作符 ?.
+			boolean optionalChain = (tok.sym == Sym.OPTIONAL_CHAIN);
+			
 			move();
+			// expr '.' ID
 			if (peek().sym != Sym.LPAREN) {
-				expr = new Field(expr, tok.value(), location);
+				expr = new Field(expr, id.value(), optionalChain, location);
 				continue;
 			}
 			
@@ -408,14 +414,14 @@ public class ExprParser {
 			// expr '.' ID '(' ')'
 			if (peek().sym == Sym.RPAREN) {
 				move();
-				expr = new Method(expr, tok.value(), location);
+				expr = new Method(expr, id.value(), optionalChain, location);
 				continue;
 			}
 			
 			// expr '.' ID '(' exprList ')'
 			ExprList exprList = exprList();
 			match(Sym.RPAREN);
-			expr = new Method(expr, tok.value(), exprList, location);
+			expr = new Method(expr, id.value(), exprList, optionalChain, location);
 		}
 	}
 	

+ 2 - 0
src/main/java/com/jfinal/template/expr/Sym.java

@@ -36,6 +36,8 @@ public enum Sym {
 	QUESTION("?"),
 	NULL_SAFE("??"),
 	
+	OPTIONAL_CHAIN("?."),
+	
 	ID("ID"),
 	
 	STR("STR"), TRUE("TRUE"), FALSE("FALSE"), NULL("NULL"),

+ 8 - 1
src/main/java/com/jfinal/template/expr/ast/Field.java

@@ -40,7 +40,10 @@ public class Field extends Expr {
 	private String getterName;
 	private long getterNameHash;
 	
-	public Field(Expr expr, String fieldName, Location location) {
+	// 可选链操作符 ?.
+	private boolean optionalChain;
+	
+	public Field(Expr expr, String fieldName, boolean optionalChain, Location location) {
 		if (expr == null) {
 			throw new ParseException("The object for field access can not be null", location);
 		}
@@ -49,12 +52,16 @@ public class Field extends Expr {
 		this.getterName = "get" + StrKit.firstCharToUpperCase(fieldName);
 		// fnv1a64 hash 到比 String.hashCode() 更大的 long 值范围
 		this.getterNameHash = HashKit.fnv1a64(getterName);
+		this.optionalChain = optionalChain;
 		this.location = location;
 	}
 	
 	public Object eval(Scope scope) {
 		Object target = expr.eval(scope);
 		if (target == null) {
+			if (optionalChain) {
+				return null;
+			}
 			if (scope.getCtrl().isNullSafe()) {
 				return null;
 			}

+ 12 - 5
src/main/java/com/jfinal/template/expr/ast/Method.java

@@ -41,19 +41,22 @@ public class Method extends Expr {
 	private Expr expr;
 	private String methodName;
 	private ExprList exprList;
+	
+	// 可选链操作符 ?.
+	private boolean optionalChain;
 
-	public Method(Expr expr, String methodName, ExprList exprList, Location location) {
+	public Method(Expr expr, String methodName, ExprList exprList, boolean optionalChain, Location location) {
 		if (exprList == null || exprList.length() == 0) {
 			throw new ParseException("The parameter of method can not be blank", location);
 		}
-		init(expr, methodName, exprList, location);
+		init(expr, methodName, exprList, optionalChain, location);
 	}
 
-	public Method(Expr expr, String methodName, Location location) {
-		init(expr, methodName, ExprList.NULL_EXPR_LIST, location);
+	public Method(Expr expr, String methodName, boolean optionalChain, Location location) {
+		init(expr, methodName, ExprList.NULL_EXPR_LIST, optionalChain, location);
 	}
 
-	private void init(Expr expr, String methodName, ExprList exprList, Location location) {
+	private void init(Expr expr, String methodName, ExprList exprList, boolean optionalChain, Location location) {
 		if (expr == null) {
 			throw new ParseException("The target for method invoking can not be blank", location);
 		}
@@ -63,12 +66,16 @@ public class Method extends Expr {
 		this.expr = expr;
 		this.methodName = methodName;
 		this.exprList = exprList;
+		this.optionalChain = optionalChain;
 		this.location = location;
 	}
 
 	public Object eval(Scope scope) {
 		Object target = expr.eval(scope);
 		if (target == null) {
+			if (optionalChain) {
+				return null;
+			}
 			if (scope.getCtrl().isNullSafe()) {
 				return null;
 			}