kangjie 5 anni fa
parent
commit
0364888e33

+ 12 - 0
src/main/java/com/ssssssss/expression/DefaultExpressionEngine.java

@@ -2,6 +2,7 @@ package com.ssssssss.expression;
 
 import org.springframework.stereotype.Component;
 
+import java.util.HashMap;
 import java.util.Map;
 
 @Component
@@ -12,5 +13,16 @@ public class DefaultExpressionEngine {
 		ExpressionTemplateContext context = new ExpressionTemplateContext(variables);
 		return ExpressionTemplate.create(expression).render(context);
 	}
+
+	public static void main(String[] args) {
+
+		DefaultExpressionEngine engine = new DefaultExpressionEngine();
+
+		Map<String, Object> params = new HashMap<>();
+		params.put("abc", "");
+		engine.execute("${}", params);
+
+
+	}
 	
 }

+ 18 - 15
src/main/java/com/ssssssss/expression/ExpressionError.java

@@ -20,14 +20,15 @@ public class ExpressionError {
 	 * </p>
 	 */
 	public static void error (String message, TokenStream stream) {
-		if (stream.hasMore())
-			error(message, stream.consume().getSpan());
-		else {
+		if (stream.hasMore()) {
+            error(message, stream.consume().getSpan());
+        } else {
 			String source = stream.getSource();
-			if (source == null)
-				error(message, new Span(" ", 0, 1));
-			else
-				error(message, new Span(source, source.length() - 1, source.length()));
+			if (source == null) {
+                error(message, new Span(" ", 0, 1));
+            } else {
+                error(message, new Span(source, source.length() - 1, source.length()));
+            }
 		}
 	}
 
@@ -47,10 +48,11 @@ public class ExpressionError {
 			message += i >= errorStart && i <= errorEnd ? "^" : useTab ? "\t" : " ";
 		}
 
-		if (cause == null)
-			throw new TemplateException(message, location);
-		else
-			throw new TemplateException(message, location, cause);
+		if (cause == null) {
+            throw new TemplateException(message, location);
+        } else {
+            throw new TemplateException(message, location, cause);
+        }
 	}
 
 	/** Create an error message based on the provided message and location, highlighting the location in the line on which the
@@ -98,10 +100,11 @@ public class ExpressionError {
 			while (cause != null && cause != this) {
 				if (cause instanceof TemplateException) {
 					TemplateException ex = (TemplateException)cause;
-					if (ex.getCause() == null || ex.getCause() == ex)
-						builder.append(ex.errorMessage);
-					else
-						builder.append(ex.errorMessage.substring(0, ex.errorMessage.indexOf('\n')));
+					if (ex.getCause() == null || ex.getCause() == ex) {
+                        builder.append(ex.errorMessage);
+                    } else {
+                        builder.append(ex.errorMessage.substring(0, ex.errorMessage.indexOf('\n')));
+                    }
 					builder.append("\n");
 				}
 				cause = cause.getCause();

+ 9 - 3
src/main/java/com/ssssssss/expression/ExpressionTemplateContext.java

@@ -54,7 +54,9 @@ public class ExpressionTemplateContext {
 	public ExpressionTemplateContext set (String name, Object value) {
 		for (int i = scopes.size() - 1; i >= 0; i--) {
 			Map<String, Object> ctx = scopes.get(i);
-			if (ctx.isEmpty()) continue;
+			if (ctx.isEmpty()) {
+				continue;
+			}
 			if (ctx.containsKey(name)) {
 				ctx.put(name, value);
 				return this;
@@ -76,9 +78,13 @@ public class ExpressionTemplateContext {
 	public Object get (String name) {
 		for (int i = scopes.size() - 1; i >= 0; i--) {
 			Map<String, Object> ctx = scopes.get(i);
-			if (ctx.isEmpty()) continue;
+			if (ctx.isEmpty()) {
+				continue;
+			}
 			Object value = ctx.get(name);
-			if (value != null) return value;
+			if (value != null) {
+				return value;
+			}
 		}
 		return null;
 	}

+ 6 - 6
src/main/java/com/ssssssss/expression/interpreter/Reflection.java → src/main/java/com/ssssssss/expression/interpreter/AbstractReflection.java

@@ -2,18 +2,18 @@
 package com.ssssssss.expression.interpreter;
 
 /** Used by {@link AstInterpreter} to access fields and methods of objects. This is a singleton class used by all
- * {@link AstInterpreter} instances. Replace the default implementation via {@link #setInstance(Reflection)}. The implementation
+ * {@link AstInterpreter} instances. Replace the default implementation via {@link #setInstance(AbstractReflection)}. The implementation
  * must be thread-safe. */
-public abstract class Reflection {
-	private static Reflection instance = new JavaReflection();
+public abstract class AbstractReflection {
+	private static AbstractReflection instance = new JavaReflection();
 
 	/** Sets the Reflection instance to be used by all Template interpreters **/
-	public synchronized static void setInstance (Reflection reflection) {
-		instance = reflection;
+	public synchronized static void setInstance (AbstractReflection abstractReflection) {
+		instance = abstractReflection;
 	}
 
 	/** Returns the Reflection instance used to fetch field and call methods **/
-	public synchronized static Reflection getInstance () {
+	public synchronized static AbstractReflection getInstance () {
 		return instance;
 	}
 

+ 4 - 4
src/main/java/com/ssssssss/expression/interpreter/AstInterpreter.java

@@ -15,7 +15,7 @@ import java.util.List;
 /**
  * <p>
  * Interprets a Template given a TemplateContext to lookup variable values in and writes the evaluation results to an output
- * stream. Uses the global {@link Reflection} instance as returned by {@link Reflection#getInstance()} to access members and call
+ * stream. Uses the global {@link AbstractReflection} instance as returned by {@link AbstractReflection#getInstance()} to access members and call
  * methods.
  * </p>
  *
@@ -30,9 +30,9 @@ public class AstInterpreter {
 		try {
 			return interpretNodeList(template.getNodes(), template, context);
 		} catch (Throwable t) {
-			if (t instanceof TemplateException)
-				throw (TemplateException)t;
-			else {
+			if (t instanceof TemplateException) {
+                throw (TemplateException)t;
+            } else {
 				ExpressionError.error("执行表达式出错 " + t.getMessage(), template.getNodes().get(0).getSpan(),t);
 				return null; // never reached
 			}

+ 55 - 21
src/main/java/com/ssssssss/expression/interpreter/JavaReflection.java

@@ -7,7 +7,7 @@ import java.lang.reflect.Modifier;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
-public class JavaReflection extends Reflection {
+public class JavaReflection extends AbstractReflection {
 	private final Map<Class<?>, Map<String, Field>> fieldCache = new ConcurrentHashMap<Class<?>, Map<String, Field>>();
 	private final Map<Class<?>, Map<MethodSignature, Method>> methodCache = new ConcurrentHashMap<Class<?>, Map<MethodSignature, Method>>();
 	private final Map<Class<?>, Map<String,List<Method>>> extensionmethodCache = new ConcurrentHashMap<>();
@@ -159,9 +159,9 @@ public class JavaReflection extends Reflection {
 				Class<?> parentClass = cls.getSuperclass();
 				while (parentClass != Object.class && parentClass != null) {
 					try {
-						if (name == null)
-							method = findApply(parentClass);
-						else {
+						if (name == null) {
+                            method = findApply(parentClass);
+                        } else {
 							method = findMethod(parentClass, name, parameterTypes);
 						}
 						method.setAccessible(true);
@@ -180,7 +180,9 @@ public class JavaReflection extends Reflection {
 	/** Returns the <code>apply()</code> method of a functional interface. **/
 	private static Method findApply (Class<?> cls) {
 		for (Method method : cls.getDeclaredMethods()) {
-			if (method.getName().equals("apply")) return method;
+			if ("apply".equals(method.getName())) {
+                return method;
+            }
 		}
 		return null;
 	}
@@ -239,8 +241,12 @@ public class JavaReflection extends Reflection {
 			Method method = methods[i];
 
 			// if neither name or parameter list size match, bail on this method
-			if (!method.getName().equals(name)) continue;
-			if (method.getParameterTypes().length != parameterTypes.length) continue;
+			if (!method.getName().equals(name)) {
+                continue;
+            }
+			if (method.getParameterTypes().length != parameterTypes.length) {
+                continue;
+            }
 			methodList.add(method);
 		}
 		return findMethod(methodList,parameterTypes);
@@ -250,14 +256,30 @@ public class JavaReflection extends Reflection {
 	 * relax the type constraint a little, as we'll invoke a method via reflection. That means the from type will always be boxed,
 	 * as the {@link Method#invoke(Object, Object...)} method takes objects. **/
 	private static boolean isPrimitiveAssignableFrom (Class<?> from, Class<?> to) {
-		if ((from == Boolean.class || from == boolean.class) && (to == boolean.class || to == Boolean.class)) return true;
-		if ((from == Integer.class || from == int.class) && (to == int.class || to == Integer.class)) return true;
-		if ((from == Float.class || from == float.class) && (to == float.class || to == Float.class)) return true;
-		if ((from == Double.class || from == double.class) && (to == double.class || to == Double.class)) return true;
-		if ((from == Byte.class || from == byte.class) && (to == byte.class || to == Byte.class)) return true;
-		if ((from == Short.class || from == short.class) && (to == short.class || to == Short.class)) return true;
-		if ((from == Long.class || from == long.class) && (to == long.class || to == Long.class)) return true;
-		if ((from == Character.class || from == char.class) && (to == char.class || to == Character.class)) return true;
+		if ((from == Boolean.class || from == boolean.class) && (to == boolean.class || to == Boolean.class)) {
+            return true;
+        }
+		if ((from == Integer.class || from == int.class) && (to == int.class || to == Integer.class)) {
+            return true;
+        }
+		if ((from == Float.class || from == float.class) && (to == float.class || to == Float.class)) {
+            return true;
+        }
+		if ((from == Double.class || from == double.class) && (to == double.class || to == Double.class)) {
+            return true;
+        }
+		if ((from == Byte.class || from == byte.class) && (to == byte.class || to == Byte.class)) {
+            return true;
+        }
+		if ((from == Short.class || from == short.class) && (to == short.class || to == Short.class)) {
+            return true;
+        }
+		if ((from == Long.class || from == long.class) && (to == long.class || to == Long.class)) {
+            return true;
+        }
+		if ((from == Character.class || from == char.class) && (to == char.class || to == Character.class)) {
+            return true;
+        }
 		return false;
 	}
 
@@ -347,14 +369,26 @@ public class JavaReflection extends Reflection {
 
 		@Override
 		public boolean equals (Object obj) {
-			if (this == obj) return true;
-			if (obj == null) return false;
-			if (getClass() != obj.getClass()) return false;
+			if (this == obj) {
+                return true;
+            }
+			if (obj == null) {
+                return false;
+            }
+			if (getClass() != obj.getClass()) {
+                return false;
+            }
 			JavaReflection.MethodSignature other = (JavaReflection.MethodSignature)obj;
 			if (name == null) {
-				if (other.name != null) return false;
-			} else if (!name.equals(other.name)) return false;
-			if (!Arrays.equals(parameters, other.parameters)) return false;
+				if (other.name != null) {
+                    return false;
+                }
+			} else if (!name.equals(other.name)) {
+                return false;
+            }
+			if (!Arrays.equals(parameters, other.parameters)) {
+                return false;
+            }
 			return true;
 		}
 	}

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

@@ -7,7 +7,7 @@ import com.ssssssss.expression.ExpressionTemplate;
 import com.ssssssss.expression.ExpressionTemplateContext;
 import com.ssssssss.expression.interpreter.AstInterpreter;
 import com.ssssssss.expression.interpreter.JavaReflection;
-import com.ssssssss.expression.interpreter.Reflection;
+import com.ssssssss.expression.interpreter.AbstractReflection;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 
@@ -893,14 +893,14 @@ public abstract class Ast {
 			return name;
 		}
 
-		/** Returns the cached member descriptor as returned by {@link Reflection#getField(Object, String)} or
-		 * {@link Reflection#getMethod(Object, String, Object...)}. See {@link #setCachedMember(Object)}. **/
+		/** Returns the cached member descriptor as returned by {@link AbstractReflection#getField(Object, String)} or
+		 * {@link AbstractReflection#getMethod(Object, String, Object...)}. See {@link #setCachedMember(Object)}. **/
 		public Object getCachedMember () {
 			return cachedMember;
 		}
 
-		/** Sets the member descriptor as returned by {@link Reflection#getField(Object, String)} or
-		 * {@link Reflection#getMethod(Object, String, Object...)} for faster member lookups. Called by {@link AstInterpreter} the
+		/** Sets the member descriptor as returned by {@link AbstractReflection#getField(Object, String)} or
+		 * {@link AbstractReflection#getMethod(Object, String, Object...)} for faster member lookups. Called by {@link AstInterpreter} the
 		 * first time this node is evaluated. Subsequent evaluations can use the cached descriptor, avoiding a costly reflective
 		 * lookup. **/
 		public void setCachedMember (Object cachedMember) {
@@ -916,7 +916,7 @@ public abstract class Ast {
 			}
 
 			// special case for array.length
-			if (object.getClass().isArray() && getName().getText().equals("length")) {
+			if (object.getClass().isArray() && "length".equals(getName().getText())) {
 				return Array.getLength(object);
 			}
 
@@ -929,13 +929,13 @@ public abstract class Ast {
 			Object field = getCachedMember();
 			if (field != null) {
 				try {
-					return Reflection.getInstance().getFieldValue(object, field);
+					return AbstractReflection.getInstance().getFieldValue(object, field);
 				} catch (Throwable t) {
 					// fall through
 				}
 			}
 			String text = getName().getText();
-			field = Reflection.getInstance().getField(object, text);
+			field = AbstractReflection.getInstance().getField(object, text);
 			if (field == null) {
 				String methodName = null;
 				if(text.length() > 1){
@@ -974,7 +974,7 @@ public abstract class Ast {
 				}
 			}
 			setCachedMember(field);
-			return Reflection.getInstance().getFieldValue(object, field);
+			return AbstractReflection.getInstance().getFieldValue(object, field);
 		}
 	}
 
@@ -1003,13 +1003,13 @@ public abstract class Ast {
 			return arguments;
 		}
 
-		/** Returns the cached "function" descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} or the
+		/** Returns the cached "function" descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} or the
 		 * {@link Macro}. See {@link #setCachedFunction(Object)}. **/
 		public Object getCachedFunction () {
 			return cachedFunction;
 		}
 
-		/** Sets the "function" descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} for faster
+		/** Sets the "function" descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} for faster
 		 * lookups, or the {@link Macro} to be called. Called by {@link AstInterpreter} the first time this node is evaluated.
 		 * Subsequent evaluations can use the cached descriptor, avoiding a costly reflective lookup. **/
 		public void setCachedFunction (Object cachedFunction) {
@@ -1062,18 +1062,18 @@ public abstract class Ast {
 					Object method = getCachedFunction();
 					if (method != null) {
 						try {
-							return Reflection.getInstance().callMethod(function, method, argumentValues);
+							return AbstractReflection.getInstance().callMethod(function, method, argumentValues);
 						} catch (Throwable t) {
 							// fall through
 						}
 					}
-					method = Reflection.getInstance().getMethod(function, null, argumentValues);
+					method = AbstractReflection.getInstance().getMethod(function, null, argumentValues);
 					if (method == null) {
 						ExpressionError.error("Couldn't find function.", getSpan());
 					}
 					setCachedFunction(method);
 					try {
-						return Reflection.getInstance().callMethod(function, method, argumentValues);
+						return AbstractReflection.getInstance().callMethod(function, method, argumentValues);
 					} catch (Throwable t) {
 						ExpressionError.error(t.getMessage(), getSpan(), t);
 						return null; // never reached
@@ -1117,13 +1117,13 @@ public abstract class Ast {
 			return arguments;
 		}
 
-		/** Returns the cached member descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)}. See
+		/** Returns the cached member descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)}. See
 		 * {@link #setCachedMember(Object)}. **/
 		public Object getCachedMethod () {
 			return cachedMethod;
 		}
 
-		/** Sets the method descriptor as returned by {@link Reflection#getMethod(Object, String, Object...)} for faster lookups.
+		/** Sets the method descriptor as returned by {@link AbstractReflection#getMethod(Object, String, Object...)} for faster lookups.
 		 * Called by {@link AstInterpreter} the first time this node is evaluated. Subsequent evaluations can use the cached
 		 * descriptor, avoiding a costly reflective lookup. **/
 		public void setCachedMethod (Object cachedMethod) {
@@ -1167,24 +1167,24 @@ public abstract class Ast {
 				Object method = getCachedMethod();
 				if (method != null) {
 					try {
-						return Reflection.getInstance().callMethod(object, method, argumentValues);
+						return AbstractReflection.getInstance().callMethod(object, method, argumentValues);
 					} catch (Throwable t) {
 						// fall through
 					}
 				}
 				
-				method = Reflection.getInstance().getMethod(object, getMethod().getName().getText(), argumentValues);
+				method = AbstractReflection.getInstance().getMethod(object, getMethod().getName().getText(), argumentValues);
 				if (method != null) {
 					// found the method on the object, call it
 					setCachedMethod(method);
 					try {
-						return Reflection.getInstance().callMethod(object, method, argumentValues);
+						return AbstractReflection.getInstance().callMethod(object, method, argumentValues);
 					} catch (Throwable t) {
 						ExpressionError.error(t.getMessage(), getSpan(), t);
 						return null; // never reached
 					}
 				} 
-				method = Reflection.getInstance().getExtensionMethod(object, getMethod().getName().getText(), argumentValues);
+				method = AbstractReflection.getInstance().getExtensionMethod(object, getMethod().getName().getText(), argumentValues);
 				if(method != null){
 					try {
 						int argumentLength = argumentValues == null ? 0 : argumentValues.length;
@@ -1202,7 +1202,7 @@ public abstract class Ast {
 							}
 							parameters[0] = objs;
 						}
-						return Reflection.getInstance().callMethod(object, method, parameters);
+						return AbstractReflection.getInstance().callMethod(object, method, parameters);
 					} catch (Throwable t) {
 						ExpressionError.error(t.getMessage(), getSpan(), t);
 						// fall through
@@ -1210,19 +1210,19 @@ public abstract class Ast {
 					}
 				}else {
 					// didn't find the method on the object, try to find a field pointing to a lambda
-					Object field = Reflection.getInstance().getField(object, getMethod().getName().getText());
+					Object field = AbstractReflection.getInstance().getField(object, getMethod().getName().getText());
 					if (field == null){
 						ExpressionError.error("在'" + object.getClass() + "'中找不到方法 " + getMethod().getName().getText() + "(" + StringUtils.join(JavaReflection.getStringTypes(argumentValues),",") + ")",
 							getSpan());
 					}
-					Object function = Reflection.getInstance().getFieldValue(object, field);
-					method = Reflection.getInstance().getMethod(function, null, argumentValues);
+					Object function = AbstractReflection.getInstance().getFieldValue(object, field);
+					method = AbstractReflection.getInstance().getMethod(function, null, argumentValues);
 					if (method == null){
 						ExpressionError.error("在'" + object.getClass() + "'中找不到方法 " + getMethod().getName().getText() + "("+ StringUtils.join(JavaReflection.getStringTypes(argumentValues),",") +")",
 								getSpan());
 					} 
 					try {
-						return Reflection.getInstance().callMethod(function, method, argumentValues);
+						return AbstractReflection.getInstance().callMethod(function, method, argumentValues);
 					} catch (Throwable t) {
 						ExpressionError.error(t.getMessage(), getSpan(), t);
 						return null; // never reached

+ 48 - 16
src/main/java/com/ssssssss/expression/parsing/CharacterStream.java

@@ -17,10 +17,18 @@ public class CharacterStream {
 	}
 
 	public CharacterStream (String source, int start, int end) {
-		if (start > end) throw new IllegalArgumentException("Start must be <= end.");
-		if (start < 0) throw new IndexOutOfBoundsException("Start must be >= 0.");
-		if (start > Math.max(0, source.length() - 1)) throw new IndexOutOfBoundsException("Start outside of string.");
-		if (end > source.length()) throw new IndexOutOfBoundsException("End outside of string.");
+		if (start > end) {
+            throw new IllegalArgumentException("Start must be <= end.");
+        }
+		if (start < 0) {
+            throw new IndexOutOfBoundsException("Start must be >= 0.");
+        }
+		if (start > Math.max(0, source.length() - 1)) {
+            throw new IndexOutOfBoundsException("Start outside of string.");
+        }
+		if (end > source.length()) {
+            throw new IndexOutOfBoundsException("End outside of string.");
+        }
 
 		this.source = source;
 		this.index = start;
@@ -34,13 +42,17 @@ public class CharacterStream {
 
 	/** Returns the next character without advancing the stream **/
 	public char peek () {
-		if (!hasMore()) throw new RuntimeException("No more characters in stream.");
+		if (!hasMore()) {
+            throw new RuntimeException("No more characters in stream.");
+        }
 		return source.charAt(index++);
 	}
 
 	/** Returns the next character and advance the stream **/
 	public char consume () {
-		if (!hasMore()) throw new RuntimeException("No more characters in stream.");
+		if (!hasMore()) {
+            throw new RuntimeException("No more characters in stream.");
+        }
 		return source.charAt(index++);
 	}
 
@@ -52,19 +64,29 @@ public class CharacterStream {
 			return false;
 		}
 		for (int i = 0, j = index; i < needleLength; i++, j++) {
-			if (index >= end) return false;
-			if (needle.charAt(i) != source.charAt(j)) return false;
+			if (index >= end) {
+                return false;
+            }
+			if (needle.charAt(i) != source.charAt(j)) {
+                return false;
+            }
 		}
-		if (consume) index += needleLength;
+		if (consume) {
+            index += needleLength;
+        }
 		return true;
 	}
 
 	/** Returns whether the next character is a digit and optionally consumes it. **/
 	public boolean matchDigit (boolean consume) {
-		if (index >= end) return false;
+		if (index >= end) {
+            return false;
+        }
 		char c = source.charAt(index);
 		if (Character.isDigit(c)) {
-			if (consume) index++;
+			if (consume) {
+                index++;
+            }
 			return true;
 		}
 		return false;
@@ -73,10 +95,14 @@ public class CharacterStream {
 	/** Returns whether the next character is the start of an identifier and optionally consumes it. Adheres to
 	 * {@link Character#isJavaIdentifierStart(char)}. **/
 	public boolean matchIdentifierStart (boolean consume) {
-		if (index >= end) return false;
+		if (index >= end) {
+            return false;
+        }
 		char c = source.charAt(index);
 		if (Character.isJavaIdentifierStart(c) || c == '@') {
-			if (consume) index++;
+			if (consume) {
+                index++;
+            }
 			return true;
 		}
 		return false;
@@ -85,10 +111,14 @@ public class CharacterStream {
 	/** Returns whether the next character is the start of an identifier and optionally consumes it. Adheres to
 	 * {@link Character#isJavaIdentifierPart(char)}. **/
 	public boolean matchIdentifierPart (boolean consume) {
-		if (index >= end) return false;
+		if (index >= end) {
+            return false;
+        }
 		char c = source.charAt(index);
 		if (Character.isJavaIdentifierPart(c)) {
-			if (consume) index++;
+			if (consume) {
+                index++;
+            }
 			return true;
 		}
 		return false;
@@ -97,7 +127,9 @@ public class CharacterStream {
 	/** Skips any number of successive whitespace characters. **/
 	public void skipWhiteSpace () {
 		while (true) {
-			if (index >= end) return;
+			if (index >= end) {
+                return;
+            }
 			char c = source.charAt(index);
 			if (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
 				index++;

+ 16 - 8
src/main/java/com/ssssssss/expression/parsing/Parser.java

@@ -29,14 +29,16 @@ public class Parser {
 	private static Node parseStatement (TokenStream tokens) {
 		Node result = null;
 
-		if (tokens.match(TokenType.TextBlock, false))
+		if (tokens.match(TokenType.TextBlock, false)) {
 			result = new Text(tokens.consume().getSpan());
-		else
+		} else {
 			result = parseExpression(tokens);
+		}
 
 		// consume semi-colons as statement delimiters
-		while (tokens.match(";", true))
+		while (tokens.match(";", true)) {
 			;
+		}
 
 		return result;
 	}
@@ -148,7 +150,9 @@ public class Parser {
 			
 			stream.expect(":");
 			values.add(parseExpression(stream));
-			if (!stream.match("}", false)) stream.expect(TokenType.Comma);
+			if (!stream.match("}", false)) {
+				stream.expect(TokenType.Comma);
+			}
 		}
 		Span closeCurly = stream.expect("}").getSpan();
 		return new MapLiteral(new Span(openCurly, closeCurly), keys, values);
@@ -160,7 +164,9 @@ public class Parser {
 		List<Expression> values = new ArrayList<>();
 		while (stream.hasMore() && !stream.match(TokenType.RightBracket, false)) {
 			values.add(parseExpression(stream));
-			if (!stream.match(TokenType.RightBracket, false)) stream.expect(TokenType.Comma);
+			if (!stream.match(TokenType.RightBracket, false)) {
+				stream.expect(TokenType.Comma);
+			}
 		}
 
 		Span closeBracket = stream.expect(TokenType.RightBracket).getSpan();
@@ -179,9 +185,9 @@ public class Parser {
 			if (stream.match(TokenType.LeftParantheses, false)) {
 				List<Expression> arguments = parseArguments(stream);
 				Span closingSpan = stream.expect(TokenType.RightParantheses).getSpan();
-				if (result instanceof VariableAccess || result instanceof MapOrArrayAccess)
+				if (result instanceof VariableAccess || result instanceof MapOrArrayAccess) {
 					result = new FunctionCall(new Span(result.getSpan(), closingSpan), result, arguments);
-				else if (result instanceof MemberAccess) {
+				} else if (result instanceof MemberAccess) {
 					result = new MethodCall(new Span(result.getSpan(), closingSpan), (MemberAccess)result, arguments);
 				} else {
 					ExpressionError.error("Expected a variable, field or method.", stream);
@@ -211,7 +217,9 @@ public class Parser {
 		List<Expression> arguments = new ArrayList<Expression>();
 		while (stream.hasMore() && !stream.match(TokenType.RightParantheses, false)) {
 			arguments.add(parseExpression(stream));
-			if (!stream.match(TokenType.RightParantheses, false)) stream.expect(TokenType.Comma);
+			if (!stream.match(TokenType.RightParantheses, false)) {
+				stream.expect(TokenType.Comma);
+			}
 		}
 		return arguments;
 	}

+ 36 - 13
src/main/java/com/ssssssss/expression/parsing/Span.java

@@ -20,11 +20,18 @@ public class Span {
 	}
 
 	public Span (String source, int start, int end) {
-		if (start > end) throw new IllegalArgumentException("Start must be <= end.");
-		if (start < 0) throw new IndexOutOfBoundsException("Start must be >= 0.");
-		if (start > source.length() - 1) 
-			throw new IndexOutOfBoundsException("Start outside of string.");
-		if (end >source.length()) throw new IndexOutOfBoundsException("End outside of string.");
+		if (start > end) {
+            throw new IllegalArgumentException("Start must be <= end.");
+        }
+		if (start < 0) {
+            throw new IndexOutOfBoundsException("Start must be >= 0.");
+        }
+		if (start > source.length() - 1) {
+            throw new IndexOutOfBoundsException("Start outside of string.");
+        }
+		if (end >source.length()) {
+            throw new IndexOutOfBoundsException("End outside of string.");
+        }
 
 		this.source = source;
 		this.start = start;
@@ -33,11 +40,21 @@ public class Span {
 	}
 
 	public Span (Span start, Span end) {
-		if (!start.source.equals(end.source)) throw new IllegalArgumentException("The two spans do not reference the same source.");
-		if (start.start > end.end) throw new IllegalArgumentException("Start must be <= end.");
-		if (start.start < 0) throw new IndexOutOfBoundsException("Start must be >= 0.");
-		if (start.start > start.source.length() - 1) throw new IndexOutOfBoundsException("Start outside of string.");
-		if (end.end > start.source.length()) throw new IndexOutOfBoundsException("End outside of string.");
+		if (!start.source.equals(end.source)) {
+            throw new IllegalArgumentException("The two spans do not reference the same source.");
+        }
+		if (start.start > end.end) {
+            throw new IllegalArgumentException("Start must be <= end.");
+        }
+		if (start.start < 0) {
+            throw new IndexOutOfBoundsException("Start must be >= 0.");
+        }
+		if (start.start > start.source.length() - 1) {
+            throw new IndexOutOfBoundsException("Start outside of string.");
+        }
+		if (end.end > start.source.length()) {
+            throw new IndexOutOfBoundsException("End outside of string.");
+        }
 
 		this.source = start.source;
 		this.start = start.start;
@@ -74,7 +91,9 @@ public class Span {
 	public Line getLine () {
 		int lineStart = start;
 		while (true) {
-			if (lineStart < 0) break;
+			if (lineStart < 0) {
+                break;
+            }
 			char c = source.charAt(lineStart);
 			if (c == '\n') {
 				lineStart = lineStart + 1;
@@ -82,11 +101,15 @@ public class Span {
 			}
 			lineStart--;
 		}
-		if (lineStart < 0) lineStart = 0;
+		if (lineStart < 0) {
+            lineStart = 0;
+        }
 
 		int lineEnd = end;
 		while (true) {
-			if (lineEnd > source.length() - 1) break;
+			if (lineEnd > source.length() - 1) {
+                break;
+            }
 			char c = source.charAt(lineEnd);
 			if (c == '\n') {
 				break;

+ 37 - 17
src/main/java/com/ssssssss/expression/parsing/TokenStream.java

@@ -35,12 +35,16 @@ public class TokenStream {
 
 	/** Consumes the next token and returns it. **/
 	public Token consume () {
-		if (!hasMore()) throw new RuntimeException("Reached the end of the source.");
+		if (!hasMore()) {
+            throw new RuntimeException("Reached the end of the source.");
+        }
 		return tokens.get(index++);
 	}
 	
 	public Token next(){
-		if (!hasMore()) throw new RuntimeException("Reached the end of the source.");
+		if (!hasMore()) {
+            throw new RuntimeException("Reached the end of the source.");
+        }
 		return tokens.get(++index);
 	}
 	
@@ -58,10 +62,11 @@ public class TokenStream {
 		if (!result) {
 			Token token = index < tokens.size() ? tokens.get(index) : null;
 			Span span = token != null ? token.getSpan() : null;
-			if (span == null)
-				ExpressionError.error("Expected '" + type.getError() + "', but reached the end of the source.", this);
-			else
-				ExpressionError.error("Expected '" + type.getError() + "', but got '" + token.getText() + "'", span);
+			if (span == null) {
+                ExpressionError.error("Expected '" + type.getError() + "', but reached the end of the source.", this);
+            } else {
+                ExpressionError.error("Expected '" + type.getError() + "', but got '" + token.getText() + "'", span);
+            }
 			return null; // never reached
 		} else {
 			return tokens.get(index - 1);
@@ -75,10 +80,11 @@ public class TokenStream {
 		if (!result) {
 			Token token = index < tokens.size() ? tokens.get(index) : null;
 			Span span = token != null ? token.getSpan() : null;
-			if (span == null)
-				ExpressionError.error("Expected '" + text + "', but reached the end of the source.", this);
-			else
-				ExpressionError.error("Expected '" + text + "', but got '" + token.getText() + "'", span);
+			if (span == null) {
+                ExpressionError.error("Expected '" + text + "', but reached the end of the source.", this);
+            } else {
+                ExpressionError.error("Expected '" + text + "', but got '" + token.getText() + "'", span);
+            }
 			return null; // never reached
 		} else {
 			return tokens.get(index - 1);
@@ -87,9 +93,13 @@ public class TokenStream {
 
 	/** Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */
 	public boolean match (TokenType type, boolean consume) {
-		if (index >= end) return false;
+		if (index >= end) {
+            return false;
+        }
 		if (tokens.get(index).getType() == type) {
-			if (consume) index++;
+			if (consume) {
+                index++;
+            }
 			return true;
 		}
 		return false;
@@ -97,9 +107,13 @@ public class TokenStream {
 
 	/** Matches and optionally consumes the next token in case of a match. Returns whether the token matched. */
 	public boolean match (String text, boolean consume) {
-		if (index >= end) return false;
+		if (index >= end) {
+            return false;
+        }
 		if (tokens.get(index).getText().equals(text)) {
-			if (consume) index++;
+			if (consume) {
+                index++;
+            }
 			return true;
 		}
 		return false;
@@ -109,7 +123,9 @@ public class TokenStream {
 	 * matched. */
 	public boolean match (boolean consume, TokenType... types) {
 		for (TokenType type : types) {
-			if (match(type, consume)) return true;
+			if (match(type, consume)) {
+                return true;
+            }
 		}
 		return false;
 	}
@@ -118,14 +134,18 @@ public class TokenStream {
 	 * matched. */
 	public boolean match (boolean consume, String... tokenTexts) {
 		for (String text : tokenTexts) {
-			if (match(text, consume)) return true;
+			if (match(text, consume)) {
+                return true;
+            }
 		}
 		return false;
 	}
 
 	/** Returns the {@link Source} this stream wraps. */
 	public String getSource () {
-		if (tokens.size() == 0) return null;
+		if (tokens.size() == 0) {
+            return null;
+        }
 		return tokens.get(0).getSpan().getSource();
 	}
 }

+ 9 - 3
src/main/java/com/ssssssss/expression/parsing/TokenType.java

@@ -62,9 +62,15 @@ public enum TokenType {
 		Arrays.sort(values, new Comparator<TokenType>() {
 			@Override
 			public int compare (TokenType o1, TokenType o2) {
-				if (o1.literal == null && o2.literal == null) return 0;
-				if (o1.literal == null && o2.literal != null) return 1;
-				if (o1.literal != null && o2.literal == null) return -1;
+				if (o1.literal == null && o2.literal == null) {
+                    return 0;
+                }
+				if (o1.literal == null && o2.literal != null) {
+                    return 1;
+                }
+				if (o1.literal != null && o2.literal == null) {
+                    return -1;
+                }
 				return o2.literal.length() - o1.literal.length();
 			}
 		});

+ 47 - 20
src/main/java/com/ssssssss/expression/parsing/Tokenizer.java

@@ -16,19 +16,25 @@ public class Tokenizer {
 	 * tokens this tokenizer understands. */
 	public List<Token> tokenize (String source) {
 		List<Token> tokens = new ArrayList<Token>();
-		if (source.length() == 0) return tokens;
+		if (source.length() == 0) {
+            return tokens;
+        }
 		CharacterStream stream = new CharacterStream(source);
 		stream.startSpan();
 
 		RuntimeException re = null;
 		while (stream.hasMore()) {
 			if (stream.match("${", false)) {
-				if (!stream.isSpanEmpty()) tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));
+				if (!stream.isSpanEmpty()) {
+                    tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));
+                }
 				stream.startSpan();
 				boolean isContinue = false;
 				do{
 					while (!stream.match("}", true)) {
-						if (!stream.hasMore()) ExpressionError.error("Did not find closing }.", stream.endSpan());
+						if (!stream.hasMore()) {
+                            ExpressionError.error("Did not find closing }.", stream.endSpan());
+                        }
 						stream.consume();
 					}
 					try{
@@ -51,7 +57,9 @@ public class Tokenizer {
 				stream.consume();
 			}
 		}
-		if (!stream.isSpanEmpty()) tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));
+		if (!stream.isSpanEmpty()) {
+            tokens.add(new Token(TokenType.TextBlock, stream.endSpan()));
+        }
 		return tokens;
 	}
 
@@ -61,7 +69,9 @@ public class Tokenizer {
 		List<Token> tokens = new ArrayList<Token>();
 
 		// match opening tag and throw it away
-		if (!stream.match("${", true)) ExpressionError.error("Expected ${", new Span(source, stream.getPosition(), stream.getPosition() + 1));
+		if (!stream.match("${", true)) {
+            ExpressionError.error("Expected ${", new Span(source, stream.getPosition(), stream.getPosition() + 1));
+        }
 		int leftCount = 0;
 		int rightCount = 0;
 		outer:
@@ -73,21 +83,29 @@ public class Tokenizer {
 			if (stream.matchDigit(false)) {
 				TokenType type = TokenType.IntegerLiteral;
 				stream.startSpan();
-				while (stream.matchDigit(true))
-					;
+				while (stream.matchDigit(true)) {
+                    ;
+                }
 				if (stream.match(TokenType.Period.getLiteral(), true)) {
 					type = TokenType.FloatLiteral;
-					while (stream.matchDigit(true))
-						;
+					while (stream.matchDigit(true)) {
+                        ;
+                    }
 				}
 				if (stream.match("b", true) || stream.match("B", true)) {
-					if (type == TokenType.FloatLiteral) ExpressionError.error("Byte literal can not have a decimal point.", stream.endSpan());
+					if (type == TokenType.FloatLiteral) {
+                        ExpressionError.error("Byte literal can not have a decimal point.", stream.endSpan());
+                    }
 					type = TokenType.ByteLiteral;
 				} else if (stream.match("s", true) || stream.match("S", true)) {
-					if (type == TokenType.FloatLiteral) ExpressionError.error("Short literal can not have a decimal point.", stream.endSpan());
+					if (type == TokenType.FloatLiteral) {
+                        ExpressionError.error("Short literal can not have a decimal point.", stream.endSpan());
+                    }
 					type = TokenType.ShortLiteral;
 				} else if (stream.match("l", true) || stream.match("L", true)) {
-					if (type == TokenType.FloatLiteral) ExpressionError.error("Long literal can not have a decimal point.", stream.endSpan());
+					if (type == TokenType.FloatLiteral) {
+                        ExpressionError.error("Long literal can not have a decimal point.", stream.endSpan());
+                    }
 					type = TokenType.LongLiteral;
 				} else if (stream.match("f", true) || stream.match("F", true)) {
 					type = TokenType.FloatLiteral;
@@ -114,7 +132,9 @@ public class Tokenizer {
 					}
 					stream.consume();
 				}
-				if (!matchedEndQuote) ExpressionError.error("字符串没有结束符\'", stream.endSpan(),new StringLiteralException());
+				if (!matchedEndQuote) {
+                    ExpressionError.error("字符串没有结束符\'", stream.endSpan(),new StringLiteralException());
+                }
 				Span stringSpan = stream.endSpan();
 				stringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());
 				tokens.add(new Token(TokenType.StringLiteral, stringSpan));
@@ -136,7 +156,9 @@ public class Tokenizer {
 					}
 					stream.consume();
 				}
-				if (!matchedEndQuote) ExpressionError.error("字符串没有结束符\"", stream.endSpan(),new StringLiteralException());
+				if (!matchedEndQuote) {
+                    ExpressionError.error("字符串没有结束符\"", stream.endSpan(),new StringLiteralException());
+                }
 				Span stringSpan = stream.endSpan();
 				stringSpan = new Span(stringSpan.getSource(), stringSpan.getStart() - 1, stringSpan.getEnd());
 				tokens.add(new Token(TokenType.StringLiteral, stringSpan));
@@ -146,14 +168,15 @@ public class Tokenizer {
 			// Identifier, keyword, boolean literal, or null literal
 			if (stream.matchIdentifierStart(true)) {
 				stream.startSpan();
-				while (stream.matchIdentifierPart(true))
-					;
+				while (stream.matchIdentifierPart(true)) {
+                    ;
+                }
 				Span identifierSpan = stream.endSpan();
 				identifierSpan = new Span(identifierSpan.getSource(), identifierSpan.getStart() - 1, identifierSpan.getEnd());
 
-				if (identifierSpan.getText().equals("true") || identifierSpan.getText().equals("false")) {
+				if ("true".equals(identifierSpan.getText()) || "false".equals(identifierSpan.getText())) {
 					tokens.add(new Token(TokenType.BooleanLiteral, identifierSpan));
-				} else if (identifierSpan.getText().equals("null")) {
+				} else if ("null".equals(identifierSpan.getText())) {
 					tokens.add(new Token(TokenType.NullLiteral, identifierSpan));
 				} else {
 					tokens.add(new Token(TokenType.Identifier, identifierSpan));
@@ -179,13 +202,17 @@ public class Tokenizer {
 				continue outer;
 			}
 			// match closing tag
-			if (stream.match("}", false)) break;
+			if (stream.match("}", false)) {
+                break;
+            }
 
 			ExpressionError.error("Unknown token", new Span(source, stream.getPosition(), stream.getPosition() + 1));
 		}
 
 		// code spans must end with }
-		if (!stream.match("}", true)) ExpressionError.error("Expected }", new Span(source, stream.getPosition(), stream.getPosition() + 1));
+		if (!stream.match("}", true)) {
+            ExpressionError.error("Expected }", new Span(source, stream.getPosition(), stream.getPosition() + 1));
+        }
 		return tokens;
 	}
 }