Prechádzať zdrojové kódy

lambda 单参数调用支持(e->)

kangjie 5 rokov pred
rodič
commit
ac9711ddc6

+ 9 - 9
src/main/java/com/ssssssss/expression/ExpressionEngine.java

@@ -1,11 +1,9 @@
 package com.ssssssss.expression;
 
-<<<<<<< HEAD:src/main/java/com/ssssssss/expression/DefaultExpressionEngine.java
 import org.springframework.stereotype.Component;
 
+import java.util.ArrayList;
 import java.util.HashMap;
-=======
->>>>>>> bea407944d46c13c7683b957cafd4061088611a7:src/main/java/com/ssssssss/expression/ExpressionEngine.java
 import java.util.Map;
 
 public class ExpressionEngine {
@@ -15,22 +13,24 @@ public class ExpressionEngine {
 		return ExpressionTemplate.create(expression).render(context);
 	}
 
-<<<<<<< HEAD:src/main/java/com/ssssssss/expression/DefaultExpressionEngine.java
 	public static void main(String[] args) {
 
-		DefaultExpressionEngine engine = new DefaultExpressionEngine();
+		ExpressionEngine engine = new ExpressionEngine();
 
 		Map<String, Object> params = new HashMap<>();
-		params.put("abc", "");
-		engine.execute("${}", params);
+		params.put("abc", "xxx");
+		ArrayList<Object> list = new ArrayList<>();
+		list.add("987654321");
+		list.add("");
+		params.put("arr", list);
+		Object result = engine.execute("${arr.map(e->abc.hashCode())}", params);
+		System.out.println(result);
 
 
 	}
 	
-=======
 	public Object executeWrap(String expression, Map<String, Object> variables) {
 		return execute("${" + expression + "}", variables);
 	}
 
->>>>>>> bea407944d46c13c7683b957cafd4061088611a7:src/main/java/com/ssssssss/expression/ExpressionEngine.java
 }

+ 28 - 0
src/main/java/com/ssssssss/expression/parsing/ArrayLikeLambdaExecutor.java

@@ -0,0 +1,28 @@
+package com.ssssssss.expression.parsing;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Supplier;
+
+public class ArrayLikeLambdaExecutor {
+
+
+
+    @SuppressWarnings("unchecked")
+    public static Object map(Object arrayLike, Object... arguments) {
+//        System.err.println("ArrayLikeLambdaExecutor:11 " + arrayLike);
+        List<Object> results = null;
+        Object argument = arguments[0];
+        List<Object> args = (List<Object>) argument;
+        results = new ArrayList<>(args.size());
+        for (int j = 0; j < args.size(); j++) {
+            Object result = ((Supplier) args.get(j)).get();
+            results.add(result);
+        }
+        if (arrayLike instanceof Collection) {
+            return results;
+        }
+        throw new RuntimeException("未实现");
+    }
+}

+ 137 - 3
src/main/java/com/ssssssss/expression/parsing/Ast.java

@@ -16,6 +16,9 @@ import java.io.IOException;
 import java.lang.reflect.Array;
 import java.lang.reflect.InvocationTargetException;
 import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 
 /** Templates are parsed into an abstract syntax tree (AST) nodes by a Parser. This class contains all AST node types. */
@@ -981,6 +984,124 @@ public abstract class Ast {
 		}
 	}
 
+	public static class LambdaAccess extends Expression {
+		private final Expression element;
+		private final Expression function;
+		private MemberAccess arrayLike;
+
+		public LambdaAccess (Span span, Expression element, Expression function) {
+			super(span);
+			this.element = element;
+			this.function = function;
+		}
+
+		/** Returns an expression that must evaluate to a map or array. **/
+		public Expression getElement() {
+			return element;
+		}
+
+		/** Returns an expression that is used as the key or index to fetch a map or array element. **/
+		public Expression getFunction() {
+			return function;
+		}
+		@SuppressWarnings("rawtypes")
+		@Override
+		public Object evaluate (ExpressionTemplate template, ExpressionTemplateContext context) throws IOException {
+			if ("map".equals(arrayLike.getName().getText())) {
+				Expression object = arrayLike.getObject();
+				if (object instanceof VariableAccess) {
+					VariableAccess arrLike = (VariableAccess) object;
+					String parName = arrLike.getVariableName().getText();
+					Object arrLikeObj = context.get(parName);
+					if (arrLikeObj instanceof Collection) {
+						Collection<?> coll = (Collection<?>) arrLikeObj;
+
+						if (function instanceof MethodCall) {
+							List<Object> collect = coll.stream().map(o -> {
+								Object result = null;
+								//TODO need multiple params
+								return ((Supplier) () -> {
+									try {
+										context.push();
+										context.setOnCurrentScope(getElement().getSpan().getText(), o);
+										Object res = function.evaluate(template, context);
+										context.pop();
+										return res;
+									} catch (IOException e) {
+										e.printStackTrace();
+										throw new RuntimeException(e);
+									}
+								});
+
+							}).collect(Collectors.toList());
+							return collect;
+						} else {
+							ExpressionError.error("err : function not instanceof MethodCall.", function.getSpan());
+						}
+						return null;
+					}
+				}
+			} else {
+				ExpressionError.error("只支持 map。不支持的lambda函数: " + arrayLike.getName().getText(), arrayLike.getSpan());
+			}
+			Set<String> variables = context.getVariables();
+
+
+			Object mapOrArray = getElement().evaluate(template, context);
+			if (mapOrArray == null) {
+				return null;
+			}
+			Object keyOrIndex = getFunction().evaluate(template, context);
+			if (keyOrIndex == null) {
+				return null;
+			}
+
+			if (mapOrArray instanceof Map) {
+				return ((Map)mapOrArray).get(keyOrIndex);
+			} else if (mapOrArray instanceof List) {
+				if (!(keyOrIndex instanceof Number)) {
+					ExpressionError.error("List index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getFunction().getSpan());
+				}
+				int index = ((Number)keyOrIndex).intValue();
+				return ((List)mapOrArray).get(index);
+			} else {
+				if (!(keyOrIndex instanceof Number)) {
+					ExpressionError.error("Array index must be an integer, but was " + keyOrIndex.getClass().getSimpleName(), getFunction().getSpan());
+				}
+				int index = ((Number)keyOrIndex).intValue();
+				if (mapOrArray instanceof int[]) {
+					return ((int[])mapOrArray)[index];
+				} else if (mapOrArray instanceof float[]) {
+					return ((float[])mapOrArray)[index];
+				} else if (mapOrArray instanceof double[]) {
+					return ((double[])mapOrArray)[index];
+				} else if (mapOrArray instanceof boolean[]) {
+					return ((boolean[])mapOrArray)[index];
+				} else if (mapOrArray instanceof char[]) {
+					return ((char[])mapOrArray)[index];
+				} else if (mapOrArray instanceof short[]) {
+					return ((short[])mapOrArray)[index];
+				} else if (mapOrArray instanceof long[]) {
+					return ((long[])mapOrArray)[index];
+				} else if (mapOrArray instanceof byte[]) {
+					return ((byte[])mapOrArray)[index];
+				} else if (mapOrArray instanceof String) {
+					return Character.toString(((String)mapOrArray).charAt(index));
+				} else {
+					return ((Object[])mapOrArray)[index];
+				}
+			}
+		}
+
+		public void setArrayLike(MemberAccess arrayLike) {
+			this.arrayLike = arrayLike;
+		}
+
+		public MemberAccess getArrayLike() {
+			return arrayLike;
+		}
+	}
+
 	/** Represents a call to a top-level function. A function may either be a {@link FunctionalInterface} stored in a
 	 * {@link ExpressionTemplateContext}, or a {@link Macro} defined in a template. */
 	public static class FunctionCall extends Expression {
@@ -1097,6 +1218,7 @@ public abstract class Ast {
 		private final List<Expression> arguments;
 		private Object cachedMethod;
 		private final ThreadLocal<Object[]> cachedArguments;
+		private boolean cachedMethodStatic;
 
 		public MethodCall (Span span, MemberAccess method, List<Expression> arguments) {
 			super(span);
@@ -1170,12 +1292,16 @@ public abstract class Ast {
 				Object method = getCachedMethod();
 				if (method != null) {
 					try {
+						if (isCachedMethodStatic()) {
+							return AbstractReflection.getInstance().callMethod(null, method, object, argumentValues);
+						}
 						return AbstractReflection.getInstance().callMethod(object, method, argumentValues);
 					} catch (Throwable t) {
+						t.printStackTrace();
 						// fall through
 					}
 				}
-				
+
 				method = AbstractReflection.getInstance().getMethod(object, getMethod().getName().getText(), argumentValues);
 				if (method != null) {
 					// found the method on the object, call it
@@ -1186,7 +1312,7 @@ public abstract class Ast {
 						ExpressionError.error(t.getMessage(), getSpan(), t);
 						return null; // never reached
 					}
-				} 
+				}
 				method = AbstractReflection.getInstance().getExtensionMethod(object, getMethod().getName().getText(), argumentValues);
 				if(method != null){
 					try {
@@ -1223,7 +1349,7 @@ public abstract class Ast {
 					if (method == null){
 						ExpressionError.error("在'" + object.getClass() + "'中找不到方法 " + getMethod().getName().getText() + "("+ StringUtils.join(JavaReflection.getStringTypes(argumentValues),",") +")",
 								getSpan());
-					} 
+					}
 					try {
 						return AbstractReflection.getInstance().callMethod(function, method, argumentValues);
 					} catch (Throwable t) {
@@ -1235,6 +1361,14 @@ public abstract class Ast {
 				clearCachedArguments();
 			}
 		}
+
+		public void setCachedMethodStatic(boolean cachedMethodStatic) {
+			this.cachedMethodStatic = cachedMethodStatic;
+		}
+
+		public boolean isCachedMethodStatic() {
+			return cachedMethodStatic;
+		}
 	}
 
 	/** Represents a map literal of the form <code>{ key: value, key2: value, ... }</code> which can be nested. */

+ 23 - 2
src/main/java/com/ssssssss/expression/parsing/Parser.java

@@ -179,7 +179,7 @@ public class Parser {
 		Span identifier = stream.expect(tokenType).getSpan();
 		Expression result = tokenType == TokenType.StringLiteral ? new StringLiteral(identifier) :new VariableAccess(identifier);
 
-		while (stream.hasMore() && stream.match(false, TokenType.LeftParantheses, TokenType.LeftBracket, TokenType.Period)) {
+		while (stream.hasMore() && stream.match(false, TokenType.LeftParantheses, TokenType.LeftBracket, TokenType.Period, TokenType.Lambda)) {
 
 			// function or method call
 			if (stream.match(TokenType.LeftParantheses, false)) {
@@ -188,7 +188,22 @@ public class Parser {
 				if (result instanceof VariableAccess || result instanceof MapOrArrayAccess) {
 					result = new FunctionCall(new Span(result.getSpan(), closingSpan), result, arguments);
 				} else if (result instanceof MemberAccess) {
-					result = new MethodCall(new Span(result.getSpan(), closingSpan), (MemberAccess)result, arguments);
+					for (Expression expression : arguments) {
+						if (expression instanceof LambdaAccess) {
+							LambdaAccess lambdaAccess = (LambdaAccess) expression;
+							lambdaAccess.setArrayLike((MemberAccess) result);
+						}
+					}
+					MethodCall methodCall = new MethodCall(new Span(result.getSpan(), closingSpan), (MemberAccess) result, arguments);
+					if ("map".equals(((MemberAccess) result).getName().getText())) {
+						try {
+							methodCall.setCachedMethod(ArrayLikeLambdaExecutor.class.getMethod("map", Object.class, Object[].class));
+						} catch (NoSuchMethodException e) {
+							e.printStackTrace();
+						}
+						methodCall.setCachedMethodStatic(true);
+					}
+					result = methodCall;
 				} else {
 					ExpressionError.error("Expected a variable, field or method.", stream);
 				}
@@ -206,6 +221,12 @@ public class Parser {
 				identifier = stream.expect(TokenType.Identifier).getSpan();
 				result = new MemberAccess(result, identifier);
 			}
+
+			else if (stream.match(TokenType.Lambda, true)) {
+				Expression key = parseExpression(stream);
+//				Span closingSpan = stream.expect(TokenType.RightParantheses).getSpan();
+				result = new LambdaAccess(new Span(result.getSpan(), key.getSpan()), result, key);
+			}
 		}
 
 		return result;

+ 1 - 0
src/main/java/com/ssssssss/expression/parsing/TokenType.java

@@ -11,6 +11,7 @@ public enum TokenType {
 	// @off
 	TextBlock("a text block"),
 	Period(".", "."),
+	Lambda("->", "->"),
 	Comma(",", ","),
 	Semicolon(";", ";"),
 	Colon(":", ":"),