From e859470b037a13f61f12eca8abbae8026313e1e5 Mon Sep 17 00:00:00 2001 From: pony Date: Sun, 10 May 2026 20:37:32 +0800 Subject: [PATCH 1/2] add comment --- .../aviator/AviatorEvaluatorInstance.java | 3420 +++++++------- .../aviator/code/asm/ASMCodeGenerator.java | 2 +- .../aviator/parser/ExpressionParser.java | 4122 +++++++++-------- 3 files changed, 3776 insertions(+), 3768 deletions(-) diff --git a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java index bbdff43b..79b67e60 100644 --- a/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java +++ b/src/main/java/com/googlecode/aviator/AviatorEvaluatorInstance.java @@ -1,15 +1,15 @@ /** * Copyright (C) 2010 dennis zhuang (killme2008@gmail.com) - * + *

* This library is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. - * + *

* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * @@ -44,6 +44,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.FutureTask; + import com.googlecode.aviator.Options.Value; import com.googlecode.aviator.annotation.Function; import com.googlecode.aviator.annotation.Ignore; @@ -185,1739 +186,1742 @@ */ public final class AviatorEvaluatorInstance { - private volatile AviatorClassLoader aviatorClassLoader = initAviatorClassLoader(); - - private OutputStream traceOutputStream = System.out; - - private FunctionMissing functionMissing; - - /** - * Generated java class version,default 1.7 - */ - private int bytecodeVersion = Opcodes.V1_7; - - private EnvProcessor envProcessor; - - /** - * Options - */ - private volatile Map options = new IdentityHashMap(); - - /** function loader list */ - private List functionLoaders; - - /** internal libs in main resources */ - private static final String[] libs = new String[] {"aviator.av"}; - - /** cached compiled internal ASM lib functions */ - private static volatile Map internalASMLibFunctions; - - /** cached compiled internal interpred lib functions */ - private static volatile Map internalInterpretedLibFunctions; - - /** - * alias operator token - */ - private final Map aliasOperatorTokens = new IdentityHashMap<>(); - - /** - * Set a alias token for the operator, only supports AND and OR operator right now. It's - * not thread-safe, and you must call it before using this instance - * - * @param type the operator type - * @param token the alias token - * @since 5.3.1 - */ - public void aliasOperator(final OperatorType type, final String token) { - // TODO more constraints on token - if ((type != OperatorType.AND && type != OperatorType.OR) || token == null - || !ExpressionParser.isJavaIdentifier(token)) { - throw new IllegalArgumentException(); - } - this.aliasOperatorTokens.put(type, token); - } - - public String getOperatorAliasToken(final OperatorType type) { - return this.aliasOperatorTokens.get(type); - } - - /** - * Create an ObjectInputStream from an input stream for deserialize an expression. - * - * @since 5.3.4 - * @return the stream - * @throws IOException - * - */ - public ObjectInputStream newObjectInputStream(InputStream in) throws IOException { - ensureEnableSerializableOption(); - return new AviatorObjectInputStream(in, this); - } - - private void ensureEnableSerializableOption() { - if (!getOptionValue(Options.SERIALIZABLE).bool) { - throw new IllegalStateException("Options.SERIALIZABLE is false"); - } - } - - /** - * Create an ObjectOutputStream to serialize an expression. - * - * @since 5.3.4 - * @return - * @throws IOException - */ - public ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException { - ensureEnableSerializableOption(); - return new AviatorObjectOutputStream(out); - } - - /** - * Adds a function loader - * - * @see FunctionLoader - * @since 4.0.0 - * @param loader - */ - public void addFunctionLoader(final FunctionLoader loader) { - if (this.functionLoaders == null) { - this.functionLoaders = new ArrayList(); - } - this.functionLoaders.add(loader); - } - - /** - * Retrieve current env processor, default is null. - * - * @since 5.1.3 - * @return - */ - public EnvProcessor getEnvProcessor() { - return this.envProcessor; - } - - /** - * Set an env processor. Note, this method should be called before using the evaluator instance. - * - * @since 5.1.3 - * - * @param envProcessor - */ - public void setEnvProcessor(final EnvProcessor envProcessor) { - this.envProcessor = envProcessor; - } - - /** - * Compile a script file into expression. - * - * @param path the script file path - * @param cached whether to cache the compiled result with key is script file's absolute path. - * @since 5.0.0 - * @return the compiled expression instance. - */ - public Expression compileScript(final String path, final boolean cached) throws IOException { - File file = tryFindScriptFile(path); - return compileScript(file.getAbsolutePath(), file, cached); - } - - /** - * Compile a script into expression. - * - * @param cacheKey caching key when cached is true. - * @param file the script file - * @param cached whether to cache the expression instance by cacheKey. - * @return the compiled expression instance. - * @since 5.0.0 - * @throws IOException - */ - public Expression compileScript(final String cacheKey, final File file, final boolean cached) - throws IOException { - try (InputStream in = new FileInputStream(file); - Reader reader = new InputStreamReader(in, Charset.forName("utf-8"));) { - - return compile(cacheKey, Utils.readFully(reader), file.getName(), cached); - } - } - - public File tryFindScriptFile(final String path) throws IOException { - // 1. absolute path - File file = new File(path); - if (file.exists()) { - return file; - } - // 2. from context classloader - ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); - file = tryFindFileFromClassLoader(path, contextLoader); - if (file != null) { - return file; - } - // 3. from current class loader - contextLoader = getClass().getClassLoader(); - file = tryFindFileFromClassLoader(path, contextLoader); - if (file != null) { - return file; - } - throw new FileNotFoundException("File not found: " + path); - } - - /** - * Set a custom aviator class loader - * - * @since 5.0.0 - * @param aviatorClassLoader - */ - public void setAviatorClassLoader(final AviatorClassLoader aviatorClassLoader) { - this.aviatorClassLoader = aviatorClassLoader; - } - - private File tryFindFileFromClassLoader(final String path, final ClassLoader contextLoader) { - URL url = contextLoader.getResource(path); - if (url != null) { - return new File(url.getPath()); - } - if (!path.startsWith("/")) { - url = contextLoader.getResource("/" + path); - } - return null; - } - - /** - * Loads a script from path and return its exports. - * - * @param path the script file path - * @throws IOException - * @return the exports map. - * @since 5.0.0 - */ - public Map loadScript(final String path) throws IOException { - ensureFeatureEnabled(Feature.Module); - final File file = tryFindScriptFile(path); - final String abPath = file.getAbsolutePath(); - return loadScript0(abPath); - } - - private Map loadScript0(final String abPath) throws IOException { - Expression exp = this.compileScript(abPath); - return executeModule(exp, abPath); - } - - @SuppressWarnings("unchecked") - private Map executeModule(final Expression exp, final String abPath) { - final Env exports = new Env(); - exports.configure(this, exp, -1, null); - final Map module = exp.newEnv("exports", exports, "path", abPath); - Map env = exp.newEnv("__MODULE__", module, "exports", exports); - ((BaseExpression) exp).execute(env, false); - - return (Map) module.get("exports"); - } - - /** - * Loads a script from path and return its exports with module caching. - * - * @param path the script file path - * @throws IOException - * @return the exports map - * @since 5.0.0 - */ - public Map requireScript(final String path) throws IOException { - ensureFeatureEnabled(Feature.Module); - if (!path.endsWith(".av")) { - // internal modules - Map exports = (Env) this.moduleCache.get(path); - if (exports != null) { - return exports; - } - } - File file = tryFindScriptFile(path); - final String abPath = file.getAbsolutePath(); - Map exports = (Env) this.moduleCache.get(abPath); - if (exports != null) { - return exports; - } else { - // TODO better lock - synchronized (abPath.intern()) { - exports = (Env) this.moduleCache.get(abPath); + private volatile AviatorClassLoader aviatorClassLoader = initAviatorClassLoader(); + + private OutputStream traceOutputStream = System.out; + + private FunctionMissing functionMissing; + + /** + * Generated java class version,default 1.7 + */ + private int bytecodeVersion = Opcodes.V1_7; + + private EnvProcessor envProcessor; + + /** + * Options + */ + private volatile Map options = new IdentityHashMap(); + + /** function loader list */ + private List functionLoaders; + + /** internal libs in main resources */ + private static final String[] libs = new String[]{"aviator.av"}; + + /** cached compiled internal ASM lib functions */ + private static volatile Map internalASMLibFunctions; + + /** cached compiled internal interpred lib functions */ + private static volatile Map internalInterpretedLibFunctions; + + /** + * alias operator token + */ + private final Map aliasOperatorTokens = new IdentityHashMap<>(); + + /** + * Set a alias token for the operator, only supports AND and OR operator right now. It's + * not thread-safe, and you must call it before using this instance + * + * @param type the operator type + * @param token the alias token + * @since 5.3.1 + */ + public void aliasOperator(final OperatorType type, final String token) { + // TODO more constraints on token + if ((type != OperatorType.AND && type != OperatorType.OR) || token == null + || !ExpressionParser.isJavaIdentifier(token)) { + throw new IllegalArgumentException(); + } + this.aliasOperatorTokens.put(type, token); + } + + public String getOperatorAliasToken(final OperatorType type) { + return this.aliasOperatorTokens.get(type); + } + + /** + * Create an ObjectInputStream from an input stream for deserialize an expression. + * + * @since 5.3.4 + * @return the stream + * @throws IOException + * + */ + public ObjectInputStream newObjectInputStream(InputStream in) throws IOException { + ensureEnableSerializableOption(); + return new AviatorObjectInputStream(in, this); + } + + private void ensureEnableSerializableOption() { + if (!getOptionValue(Options.SERIALIZABLE).bool) { + throw new IllegalStateException("Options.SERIALIZABLE is false"); + } + } + + /** + * Create an ObjectOutputStream to serialize an expression. + * + * @since 5.3.4 + * @return + * @throws IOException + */ + public ObjectOutputStream newObjectOutputStream(OutputStream out) throws IOException { + ensureEnableSerializableOption(); + return new AviatorObjectOutputStream(out); + } + + /** + * Adds a function loader + * + * @see FunctionLoader + * @since 4.0.0 + * @param loader + */ + public void addFunctionLoader(final FunctionLoader loader) { + if (this.functionLoaders == null) { + this.functionLoaders = new ArrayList(); + } + this.functionLoaders.add(loader); + } + + /** + * Retrieve current env processor, default is null. + * + * @since 5.1.3 + * @return + */ + public EnvProcessor getEnvProcessor() { + return this.envProcessor; + } + + /** + * Set an env processor. Note, this method should be called before using the evaluator instance. + * + * @since 5.1.3 + * + * @param envProcessor + */ + public void setEnvProcessor(final EnvProcessor envProcessor) { + this.envProcessor = envProcessor; + } + + /** + * Compile a script file into expression. + * + * @param path the script file path + * @param cached whether to cache the compiled result with key is script file's absolute path. + * @since 5.0.0 + * @return the compiled expression instance. + */ + public Expression compileScript(final String path, final boolean cached) throws IOException { + File file = tryFindScriptFile(path); + return compileScript(file.getAbsolutePath(), file, cached); + } + + /** + * Compile a script into expression. + * + * @param cacheKey caching key when cached is true. + * @param file the script file + * @param cached whether to cache the expression instance by cacheKey. + * @return the compiled expression instance. + * @since 5.0.0 + * @throws IOException + */ + public Expression compileScript(final String cacheKey, final File file, final boolean cached) + throws IOException { + try (InputStream in = new FileInputStream(file); + Reader reader = new InputStreamReader(in, Charset.forName("utf-8"));) { + + return compile(cacheKey, Utils.readFully(reader), file.getName(), cached); + } + } + + public File tryFindScriptFile(final String path) throws IOException { + // 1. absolute path + File file = new File(path); + if (file.exists()) { + return file; + } + // 2. from context classloader + ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); + file = tryFindFileFromClassLoader(path, contextLoader); + if (file != null) { + return file; + } + // 3. from current class loader + contextLoader = getClass().getClassLoader(); + file = tryFindFileFromClassLoader(path, contextLoader); + if (file != null) { + return file; + } + throw new FileNotFoundException("File not found: " + path); + } + + /** + * Set a custom aviator class loader + * + * @since 5.0.0 + * @param aviatorClassLoader + */ + public void setAviatorClassLoader(final AviatorClassLoader aviatorClassLoader) { + this.aviatorClassLoader = aviatorClassLoader; + } + + private File tryFindFileFromClassLoader(final String path, final ClassLoader contextLoader) { + URL url = contextLoader.getResource(path); + if (url != null) { + return new File(url.getPath()); + } + if (!path.startsWith("/")) { + url = contextLoader.getResource("/" + path); + } + return null; + } + + /** + * Loads a script from path and return its exports. + * + * @param path the script file path + * @throws IOException + * @return the exports map. + * @since 5.0.0 + */ + public Map loadScript(final String path) throws IOException { + ensureFeatureEnabled(Feature.Module); + final File file = tryFindScriptFile(path); + final String abPath = file.getAbsolutePath(); + return loadScript0(abPath); + } + + private Map loadScript0(final String abPath) throws IOException { + Expression exp = this.compileScript(abPath); + return executeModule(exp, abPath); + } + + @SuppressWarnings("unchecked") + private Map executeModule(final Expression exp, final String abPath) { + final Env exports = new Env(); + exports.configure(this, exp, -1, null); + final Map module = exp.newEnv("exports", exports, "path", abPath); + Map env = exp.newEnv("__MODULE__", module, "exports", exports); + ((BaseExpression) exp).execute(env, false); + + return (Map) module.get("exports"); + } + + /** + * Loads a script from path and return its exports with module caching. + * + * @param path the script file path + * @throws IOException + * @return the exports map + * @since 5.0.0 + */ + public Map requireScript(final String path) throws IOException { + ensureFeatureEnabled(Feature.Module); + if (!path.endsWith(".av")) { + // internal modules + Map exports = (Env) this.moduleCache.get(path); + if (exports != null) { + return exports; + } + } + File file = tryFindScriptFile(path); + final String abPath = file.getAbsolutePath(); + Map exports = (Env) this.moduleCache.get(abPath); if (exports != null) { - return exports; + return exports; + } else { + // TODO better lock + synchronized (abPath.intern()) { + exports = (Env) this.moduleCache.get(abPath); + if (exports != null) { + return exports; + } + exports = loadScript0(abPath); + this.moduleCache.put(abPath, exports); + return exports; + } } - exports = loadScript0(abPath); - this.moduleCache.put(abPath, exports); - return exports; - } - } - } - - /** - * Configure the evaluator into sandbox mode for security, it means: - *

- * - * For more information on security, please refer to the - * documentation - * - * @since 5.4.3 - * - */ - public void enableSandboxMode() { - disableFeature(Feature.Module); - disableFeature(Feature.NewInstance); - disableFeature(Feature.InternalVars); - disableFeature(Feature.StaticMethods); - setFunctionMissing(null); - setOption(Options.MAX_LOOP_COUNT, 65535); - setOption(Options.ALLOWED_CLASS_SET, Collections.emptySet()); - setOption(Options.ASSIGNABLE_ALLOWED_CLASS_SET, Collections.emptySet()); - setOption(Options.EVAL_TIMEOUT_MS, 1000L); - } - - /** - * Adds a module class and import it's public static methods as module's exports into module - * cache, return the exports map. - * - * @param moduleClazz - * @return the exports map - * @since 5.0.0 - */ - public Env addModule(final Class moduleClazz) - throws NoSuchMethodException, IllegalAccessException { - String namespace = moduleClazz.getSimpleName(); - - Import importAnt = moduleClazz.getAnnotation(Import.class); - - if (importAnt != null) { - namespace = importAnt.ns(); - if (namespace == null || namespace.isEmpty() - || !ExpressionParser.isJavaIdentifier(namespace)) { - throw new IllegalArgumentException("Invalid namespace in Import annotation: " + namespace); - } - } - - Env exports = null; - synchronized (namespace.intern()) { - exports = (Env) this.moduleCache.get(namespace); - if (exports != null) { + } + + /** + * Configure the evaluator into sandbox mode for security, it means: + * + * + * For more information on security, please refer to the + * documentation + * + * @since 5.4.3 + * + */ + public void enableSandboxMode() { + disableFeature(Feature.Module); + disableFeature(Feature.NewInstance); + disableFeature(Feature.InternalVars); + disableFeature(Feature.StaticMethods); + setFunctionMissing(null); + setOption(Options.MAX_LOOP_COUNT, 65535); + setOption(Options.ALLOWED_CLASS_SET, Collections.emptySet()); + setOption(Options.ASSIGNABLE_ALLOWED_CLASS_SET, Collections.emptySet()); + setOption(Options.EVAL_TIMEOUT_MS, 1000L); + } + + /** + * Adds a module class and import it's public static methods as module's exports into module + * cache, return the exports map. + * + * @param moduleClazz + * @return the exports map + * @since 5.0.0 + */ + public Env addModule(final Class moduleClazz) + throws NoSuchMethodException, IllegalAccessException { + String namespace = moduleClazz.getSimpleName(); + + Import importAnt = moduleClazz.getAnnotation(Import.class); + + if (importAnt != null) { + namespace = importAnt.ns(); + if (namespace == null || namespace.isEmpty() + || !ExpressionParser.isJavaIdentifier(namespace)) { + throw new IllegalArgumentException("Invalid namespace in Import annotation: " + namespace); + } + } + + Env exports = null; + synchronized (namespace.intern()) { + exports = (Env) this.moduleCache.get(namespace); + if (exports != null) { + return exports; + } + exports = loadModule(moduleClazz); + this.moduleCache.put(namespace, exports); + return exports; + } + } + + /** + * Remove a module by namespace name. + * + * @param ns + * @since 5.1.4 + */ + public void removeModule(final String ns) { + this.moduleCache.remove(ns); + } + + private Env loadModule(final Class moduleClazz) + throws IllegalAccessException, NoSuchMethodException { + Map> methodMap = Reflector.findMethodsFromClass(moduleClazz, true); + + if (methodMap == null || methodMap.isEmpty()) { + throw new IllegalArgumentException("Empty module"); + } + + Env exports = new Env(); + + for (Map.Entry> entry : methodMap.entrySet()) { + exports.put(entry.getKey(), new ClassMethodFunction(moduleClazz, true, entry.getKey(), + entry.getKey(), entry.getValue())); + } + exports.setInstance(this); return exports; - } - exports = loadModule(moduleClazz); - this.moduleCache.put(namespace, exports); - return exports; - } - } - - /** - * Remove a module by namespace name. - * - * @param ns - * @since 5.1.4 - */ - public void removeModule(final String ns) { - this.moduleCache.remove(ns); - } - - private Env loadModule(final Class moduleClazz) - throws IllegalAccessException, NoSuchMethodException { - Map> methodMap = Reflector.findMethodsFromClass(moduleClazz, true); - - if (methodMap == null || methodMap.isEmpty()) { - throw new IllegalArgumentException("Empty module"); - } - - Env exports = new Env(); - - for (Map.Entry> entry : methodMap.entrySet()) { - exports.put(entry.getKey(), new ClassMethodFunction(moduleClazz, true, entry.getKey(), - entry.getKey(), entry.getValue())); - } - exports.setInstance(this); - return exports; - } - - public Map getModuleCache() { - return this.moduleCache; - } - - /** - * Compile a script file into expression, it doesn't cache the compiled result. - * - * @param file the script file path - * @return - */ - public Expression compileScript(final String path) throws IOException { - return this.compileScript(path, this.cachedExpressionByDefault); - } - - /** - * Returns the function missing handler, null if not set. - * - * @since 4.2.5 - * @return - */ - public FunctionMissing getFunctionMissing() { - return this.functionMissing; - } - - /** - * Configure a function missing handler.the handler can be null. - * - * @since 4.2.5 - * @param functionMissing - */ - public void setFunctionMissing(final FunctionMissing functionMissing) { - this.functionMissing = functionMissing; - } - - /** - * Adds all public instance methods in the class as custom functions into evaluator except those - * have {@link Ignore} annotation, all these functions will keep the same name as method name, but - * prefixed with namespace, the function name can be renamed by {@link Function} annotation. And - * the function will have more than one argument than the method, the function's first argument is - * always the instance object(this pointer). - * - * @since 4.2.3 - * @see Ignore - * @see Function - * @param namespace the functions namespace - * @param clazz the class - * @return the added function list. - */ - public List addInstanceFunctions(final String namespace, final Class clazz) - throws IllegalAccessException, NoSuchMethodException { - return addMethodFunctions(namespace, false, clazz); - } - - private List addMethodFunctions(final String namespace, final boolean isStatic, - final Class clazz) throws IllegalAccessException, NoSuchMethodException { - Map> methodMap = Reflector.findMethodsFromClass(clazz, isStatic); - List added = new ArrayList<>(); - - for (Map.Entry> entry : methodMap.entrySet()) { - String methodName = entry.getKey(); - String name = namespace + "." + methodName; - this.addFunction( - new ClassMethodFunction(clazz, isStatic, name, methodName, entry.getValue())); - added.add(name); - } - return added; - } - - /** - * Adds all public static methods in the class as custom functions into evaluator except those - * have {@link Ignore} annotation, all these functions will keep the same name as method name, but - * prefixed with namespace, the function name can be renamed by {@link Function} annotation. - * - * @since 4.2.2 - * @see Ignore - * @see Function - * @param namespace the functions namespace - * @param clazz the class - * @return the added function list. - */ - public List addStaticFunctions(final String namespace, final Class clazz) - throws IllegalAccessException, NoSuchMethodException { - - return addMethodFunctions(namespace, true, clazz); - } - - /** - * Import the class public methods into aviator evaluator as custom functions. The function's - * namespace is the class name by default, and the scopes are both static and instance methods. - * The namespace and scope can be set by {@link Import} annotation. - * - * @since 4.2.2 - * @see Import - * @param clazz the class - * @return the added function list. - * @throws NoSuchMethodException - * @throws IllegalAccessException - */ - public List importFunctions(final Class clazz) - throws IllegalAccessException, NoSuchMethodException { - - String namespace = clazz.getSimpleName(); - ImportScope[] scopes = {ImportScope.Static, ImportScope.Instance}; - - Import importAnt = clazz.getAnnotation(Import.class); - - if (importAnt != null) { - namespace = importAnt.ns(); - if (namespace == null || namespace.isEmpty() - || !ExpressionParser.isJavaIdentifier(namespace)) { - throw new IllegalArgumentException("Invalid namespace in Import annotation: " + namespace); - } - scopes = importAnt.scopes(); - if (scopes == null || scopes.length == 0) { - throw new IllegalArgumentException("Empty scopes in Import annotation"); - } - } - - List result = new ArrayList<>(); - for (ImportScope scope : scopes) { - switch (scope) { - case Static: - result.addAll(addStaticFunctions(namespace, clazz)); - break; - case Instance: - result.addAll(addInstanceFunctions(namespace, clazz)); - break; - default: - throw new IllegalStateException("Invalid import scope: " + scope); - } - } - return result; - } - - /** - * Remove a function loader - * - * @since 4.0.0 - * @param loader - */ - public void removeFunctionLoader(final FunctionLoader loader) { - if (this.functionLoaders == null) { - return; - } - this.functionLoaders.remove(loader); - } - - /** - * Adds a evaluator option - * - * @since 2.3.4 - * @see Options - * @param opt - * @param val - */ - public void setOption(final Options opt, final Object val) { - if (opt == null || val == null) { - throw new IllegalArgumentException("Option and value should not be null."); - } - if (!opt.isValidValue(val)) { - throw new IllegalArgumentException("Invalid value for option:" + opt.name()); - } - Map newOpts = new IdentityHashMap<>(this.options); - newOpts.put(opt, opt.intoValue(val)); - if (opt == Options.FEATURE_SET) { - Set oldSet = new HashSet<>(getFeatures()); - @SuppressWarnings("unchecked") - Set newSet = (Set) val; - if (oldSet.removeAll(newSet)) { - // removed functions that feature is disabled. - for (Feature feat : oldSet) { - for (AviatorFunction fn : feat.getFunctions()) { + } + + public Map getModuleCache() { + return this.moduleCache; + } + + /** + * Compile a script file into expression, it doesn't cache the compiled result. + * + * @param file the script file path + * @return + */ + public Expression compileScript(final String path) throws IOException { + return this.compileScript(path, this.cachedExpressionByDefault); + } + + /** + * Returns the function missing handler, null if not set. + * + * @since 4.2.5 + * @return + */ + public FunctionMissing getFunctionMissing() { + return this.functionMissing; + } + + /** + * Configure a function missing handler.the handler can be null. + * + * @since 4.2.5 + * @param functionMissing + */ + public void setFunctionMissing(final FunctionMissing functionMissing) { + this.functionMissing = functionMissing; + } + + /** + * Adds all public instance methods in the class as custom functions into evaluator except those + * have {@link Ignore} annotation, all these functions will keep the same name as method name, but + * prefixed with namespace, the function name can be renamed by {@link Function} annotation. And + * the function will have more than one argument than the method, the function's first argument is + * always the instance object(this pointer). + * + * @since 4.2.3 + * @see Ignore + * @see Function + * @param namespace the functions namespace + * @param clazz the class + * @return the added function list. + */ + public List addInstanceFunctions(final String namespace, final Class clazz) + throws IllegalAccessException, NoSuchMethodException { + return addMethodFunctions(namespace, false, clazz); + } + + private List addMethodFunctions(final String namespace, final boolean isStatic, + final Class clazz) throws IllegalAccessException, NoSuchMethodException { + Map> methodMap = Reflector.findMethodsFromClass(clazz, isStatic); + List added = new ArrayList<>(); + + for (Map.Entry> entry : methodMap.entrySet()) { + String methodName = entry.getKey(); + String name = namespace + "." + methodName; + this.addFunction( + new ClassMethodFunction(clazz, isStatic, name, methodName, entry.getValue())); + added.add(name); + } + return added; + } + + /** + * Adds all public static methods in the class as custom functions into evaluator except those + * have {@link Ignore} annotation, all these functions will keep the same name as method name, but + * prefixed with namespace, the function name can be renamed by {@link Function} annotation. + * + * @since 4.2.2 + * @see Ignore + * @see Function + * @param namespace the functions namespace + * @param clazz the class + * @return the added function list. + */ + public List addStaticFunctions(final String namespace, final Class clazz) + throws IllegalAccessException, NoSuchMethodException { + + return addMethodFunctions(namespace, true, clazz); + } + + /** + * Import the class public methods into aviator evaluator as custom functions. The function's + * namespace is the class name by default, and the scopes are both static and instance methods. + * The namespace and scope can be set by {@link Import} annotation. + * + * @since 4.2.2 + * @see Import + * @param clazz the class + * @return the added function list. + * @throws NoSuchMethodException + * @throws IllegalAccessException + */ + public List importFunctions(final Class clazz) + throws IllegalAccessException, NoSuchMethodException { + + String namespace = clazz.getSimpleName(); + ImportScope[] scopes = {ImportScope.Static, ImportScope.Instance}; + + Import importAnt = clazz.getAnnotation(Import.class); + + if (importAnt != null) { + namespace = importAnt.ns(); + if (namespace == null || namespace.isEmpty() + || !ExpressionParser.isJavaIdentifier(namespace)) { + throw new IllegalArgumentException("Invalid namespace in Import annotation: " + namespace); + } + scopes = importAnt.scopes(); + if (scopes == null || scopes.length == 0) { + throw new IllegalArgumentException("Empty scopes in Import annotation"); + } + } + + List result = new ArrayList<>(); + for (ImportScope scope : scopes) { + switch (scope) { + case Static: + result.addAll(addStaticFunctions(namespace, clazz)); + break; + case Instance: + result.addAll(addInstanceFunctions(namespace, clazz)); + break; + default: + throw new IllegalStateException("Invalid import scope: " + scope); + } + } + return result; + } + + /** + * Remove a function loader + * + * @since 4.0.0 + * @param loader + */ + public void removeFunctionLoader(final FunctionLoader loader) { + if (this.functionLoaders == null) { + return; + } + this.functionLoaders.remove(loader); + } + + /** + * Adds a evaluator option + * + * @since 2.3.4 + * @see Options + * @param opt + * @param val + */ + public void setOption(final Options opt, final Object val) { + if (opt == null || val == null) { + throw new IllegalArgumentException("Option and value should not be null."); + } + if (!opt.isValidValue(val)) { + throw new IllegalArgumentException("Invalid value for option:" + opt.name()); + } + Map newOpts = new IdentityHashMap<>(this.options); + newOpts.put(opt, opt.intoValue(val)); + if (opt == Options.FEATURE_SET) { + Set oldSet = new HashSet<>(getFeatures()); + @SuppressWarnings("unchecked") + Set newSet = (Set) val; + if (oldSet.removeAll(newSet)) { + // removed functions that feature is disabled. + for (Feature feat : oldSet) { + for (AviatorFunction fn : feat.getFunctions()) { + this.removeFunction(fn); + } + } + } + } + this.options = newOpts; + if (opt == Options.FEATURE_SET) { + loadFeatureFunctions(); + } + } + + /** + * Enable a script engine feature. + * + * @see Feature + * @since 5.0.0 + * @param feature + */ + public void enableFeature(final Feature feature) { + Set featureSet = new HashSet<>(this.options.get(Options.FEATURE_SET).featureSet); + featureSet.add(feature); + featureSet.addAll(feature.getPrequires()); + setOption(Options.FEATURE_SET, featureSet); + loadFeatureFunctions(); + } + + /** + * Returns current valid syntax feature set. + * + * @return + */ + public Set getFeatures() { + return this.options.get(Options.FEATURE_SET).featureSet; + } + + /** + * Returns true when a syntax feature is enabled. + * + * @param feature + * @return + */ + public boolean isFeatureEnabled(final Feature feature) { + return this.options.get(Options.FEATURE_SET).featureSet.contains(feature); + } + + /** + * Disable a script engine feature. + * + * @see Feature + * @since 5.0.0 + * @param feature + */ + public void disableFeature(final Feature feature) { + Set featureSet = new HashSet<>(this.options.get(Options.FEATURE_SET).featureSet); + featureSet.remove(feature); + for (AviatorFunction fn : feature.getFunctions()) { this.removeFunction(fn); - } } - } - } - this.options = newOpts; - if (opt == Options.FEATURE_SET) { - loadFeatureFunctions(); - } - } - - /** - * Enable a script engine feature. - * - * @see Feature - * @since 5.0.0 - * @param feature - */ - public void enableFeature(final Feature feature) { - Set featureSet = new HashSet<>(this.options.get(Options.FEATURE_SET).featureSet); - featureSet.add(feature); - featureSet.addAll(feature.getPrequires()); - setOption(Options.FEATURE_SET, featureSet); - loadFeatureFunctions(); - } - - /** - * Returns current valid syntax feature set. - * - * @return - */ - public Set getFeatures() { - return this.options.get(Options.FEATURE_SET).featureSet; - } - - /** - * Returns true when a syntax feature is enabled. - * - * @param feature - * @return - */ - public boolean isFeatureEnabled(final Feature feature) { - return this.options.get(Options.FEATURE_SET).featureSet.contains(feature); - } - - /** - * Disable a script engine feature. - * - * @see Feature - * @since 5.0.0 - * @param feature - */ - public void disableFeature(final Feature feature) { - Set featureSet = new HashSet<>(this.options.get(Options.FEATURE_SET).featureSet); - featureSet.remove(feature); - for (AviatorFunction fn : feature.getFunctions()) { - this.removeFunction(fn); - } - this.setOption(Options.FEATURE_SET, featureSet); - loadFeatureFunctions(); - } - - /** - * Returns the current evaluator option value, returns null if missing. - * - * @param opt - * @return - */ - @Deprecated - @SuppressWarnings("unchecked") - public T getOption(final Options opt) { - Value val = this.options.get(opt); - if (val == null) { - val = opt.getDefaultValueObject(); - } - - return (T) opt.intoObject(val); - } - - /** - * Returns the current evaluator option value union, returns null if missing. - * - * @param opt - * @return the option value, null if missing. - */ - public Value getOptionValue(final Options opt) { - Value val = this.options.get(opt); - assert (val != null); - return val; - } - - /** - * Returns the generated java classes byte code version, 1.6 by defualt. - * - * @return the bytecode version. - */ - public int getBytecodeVersion() { - return this.bytecodeVersion; - } - - /** - * Set the generated java classes java byte code version. - * - * @see Opcodes#V1_6 - * @param bytecodeVersion - */ - public void setBytecodeVersion(final int bytecodeVersion) { - this.bytecodeVersion = bytecodeVersion; - } - - /** - * Get the evaluator instance options - * - * @return - */ - public Map getOptions() { - Map ret = new HashMap<>(); - for (Map.Entry entry : this.options.entrySet()) { - ret.put(entry.getKey(), entry.getKey().intoObject(entry.getValue())); - } - return ret; - } - - /** - * Returns the functions map - * - * @return - */ - public Map getFuncMap() { - return this.funcMap; - } - - /** - * Returns the operators map. - * - * @return - */ - public Map getOpsMap() { - return this.opsMap; - } - - /** - * Get current trace output stream,default is System.out - * - * @return - */ - public OutputStream getTraceOutputStream() { - return this.traceOutputStream; - } - - /** - * Set trace output stream - * - * @param traceOutputStream - */ - public void setTraceOutputStream(final OutputStream traceOutputStream) { - this.traceOutputStream = traceOutputStream; - } - - private AviatorClassLoader initAviatorClassLoader() { - return AccessController.doPrivileged(new PrivilegedAction() { - - @Override - public AviatorClassLoader run() { - return new AviatorClassLoader(AviatorEvaluatorInstance.class.getClassLoader()); - } - - }); - } - - private final Map funcMap = new HashMap(); - - private final ConcurrentHashMap moduleCache = - new ConcurrentHashMap<>(); - - private final Map opsMap = - new IdentityHashMap(); - - private void loadModule() { - try { - addModule(IoModule.class); - } catch (Exception e) { - System.err.println("Fail to load internal modules."); - System.exit(1); - } - } - - /** - * Set alias name for function specified by name - * - * @param name the origin function name - * @param aliasName the alias function name - * @since 5.0.0 - */ - public void aliasFunction(final String name, final String aliasName) { - this.addFunction(aliasName, getFunction(name)); - } - - private void loadLib() { - // Load internal functions - // load sys lib - loadSystemFunctions(); - - // load string lib - loadStringFunctions(); - - // load math lib - loadMathFunctions(); - - // seq lib - loadSeqFunctions(); - - // alias - aliasFunction("println", "p"); - aliasFunction("pst", "printStackTrace"); - - loadInternalLibs(); - } - - private void loadSeqFunctions() { - addFunction(SeqCollectorFunction.INSTANCE); - addFunction(SeqCollectorRawFunction.INSTANCE); - addFunction(SeqKeysFunction.INSTANCE); - addFunction(SeqValsFunction.INSTANCE); - addFunction(SeqReverseFunction.INSTANCE); - addFunction(SeqZipmapFunction.INSTANCE); - addFunction(new SeqNewArrayFunction()); - addFunction(new SeqArrayFunction()); - addFunction(new SeqNewListFunction()); - addFunction(new SeqNewMapFunction()); - addFunction(new SeqNewSetFunction()); - addFunction(new SeqMapEntryFunction()); - addFunction(new SeqIntoFunction()); - addFunction(new SeqAddFunction()); - addFunction(new SeqAddAllFunction()); - addFunction(new SeqRemoveFunction()); - addFunction(new SeqGetFunction()); - addFunction(new SeqPutFunction()); - addFunction(new SeqMinFunction()); - addFunction(new SeqMaxFunction()); - addFunction(new SeqMapFunction()); - addFunction(new SeqReduceFunction()); - addFunction(new SeqFilterFunction()); - addFunction(new SeqSortFunction()); - addFunction(new SeqIncludeFunction()); - addFunction(new SeqContainsKeyFunction()); - addFunction(new SeqCountFunction()); - addFunction(new SeqEveryFunction()); - addFunction(new SeqNotAnyFunction()); - addFunction(new SeqSomeFunction()); - addFunction(new SeqMakePredicateFunFunction("seq.eq", OperatorType.EQ)); - addFunction(new SeqMakePredicateFunFunction("seq.neq", OperatorType.NEQ)); - addFunction(new SeqMakePredicateFunFunction("seq.lt", OperatorType.LT)); - addFunction(new SeqMakePredicateFunFunction("seq.le", OperatorType.LE)); - addFunction(new SeqMakePredicateFunFunction("seq.gt", OperatorType.GT)); - addFunction(new SeqMakePredicateFunFunction("seq.ge", OperatorType.GE)); - addFunction(new SeqCompsitePredFunFunction("seq.and", LogicOp.AND)); - addFunction(new SeqCompsitePredFunFunction("seq.or", LogicOp.OR)); - addFunction(new SeqMakePredicateFunFunction("seq.true", OperatorType.EQ, AviatorBoolean.TRUE)); - addFunction( - new SeqMakePredicateFunFunction("seq.false", OperatorType.EQ, AviatorBoolean.FALSE)); - addFunction(new SeqMakePredicateFunFunction("seq.nil", OperatorType.EQ, AviatorNil.NIL)); - addFunction(new SeqMakePredicateFunFunction("seq.exists", OperatorType.NEQ, AviatorNil.NIL)); - } - - private void loadMathFunctions() { - addFunction(new MathAbsFunction()); - addFunction(new MathRoundFunction()); - addFunction(new MathPowFunction()); - addFunction(new MathSqrtFunction()); - addFunction(new MathLog10Function()); - addFunction(new MathLogFunction()); - addFunction(new MathSinFunction()); - addFunction(new MathCosFunction()); - addFunction(new MathTanFunction()); - addFunction(MathAsinFunction.INSTANCE); - addFunction(MathAcosFunction.INSTANCE); - addFunction(MathAtanFunction.INSTANCE); - addFunction(MathFloorFunction.INSTANCE); - addFunction(MathCeilFunction.INSTANCE); - } - - private void loadStringFunctions() { - addFunction(new StringContainsFunction()); - addFunction(new StringIndexOfFunction()); - addFunction(new StringStartsWithFunction()); - addFunction(new StringEndsWithFunction()); - addFunction(new StringSubStringFunction()); - addFunction(new StringLengthFunction()); - addFunction(new StringSplitFunction()); - addFunction(new StringJoinFunction()); - addFunction(new StringReplaceFirstFunction()); - addFunction(new StringReplaceAllFunction()); - } - - private void loadSystemFunctions() { - addFunction(ComparatorFunction.INSTANCE); - addFunction(new CompareFunction()); - addFunction(new SysDateFunction()); - addFunction(new PrintlnFunction()); - addFunction(new PrintFunction()); - addFunction(new PstFunction()); - addFunction(new RandomFunction()); - addFunction(new NowFunction()); - addFunction(new LongFunction()); - addFunction(new BooleanFunction()); - addFunction(new DoubleFunction()); - addFunction(new StrFunction()); - addFunction(new BigIntFunction()); - addFunction(new DecimalFunction()); - addFunction(new Date2StringFunction()); - addFunction(new String2DateFunction()); - addFunction(new BinaryFunction(OperatorType.ADD)); - addFunction(new BinaryFunction(OperatorType.Exponent)); - addFunction(new BinaryFunction(OperatorType.SUB)); - addFunction(new BinaryFunction(OperatorType.MULT)); - addFunction(new BinaryFunction(OperatorType.DIV)); - addFunction(new BinaryFunction(OperatorType.MOD)); - addFunction(new BinaryFunction(OperatorType.NEG)); - addFunction(new BinaryFunction(OperatorType.NOT)); - addFunction(new BinaryFunction(OperatorType.BIT_AND)); - addFunction(new BinaryFunction(OperatorType.BIT_OR)); - addFunction(new BinaryFunction(OperatorType.BIT_XOR)); - addFunction(new BinaryFunction(OperatorType.BIT_NOT)); - addFunction(new TupleFunction()); - addFunction(new MinFunction()); - addFunction(new MaxFunction()); - addFunction(new IdentityFunction()); - addFunction(new AssertFunction()); - addFunction(new RangeFunction()); - addFunction(new IsDefFunction()); - addFunction(new UndefFunction()); - addFunction(new TypeFunction()); - addFunction(SeqFunction.INSTANCE); - addFunction(EvalFunction.INSTANCE); - addFunction(IsAFunction.INSTANCE); - addFunction(MetaFunction.INSTANCE); - addFunction(WithMetaFunction.INSTANCE); - addFunction(WithoutMetaFunction.INSTANCE); - } - - private void loadInternalLibs() { - if (getEvalMode() == EvalMode.ASM) { - - if (internalASMLibFunctions == null) { - internalASMLibFunctions = loadInternalFunctions(); // cache it - } else { - for (Map.Entry entry : internalASMLibFunctions.entrySet()) { - addFunction(entry.getKey(), entry.getValue()); + this.setOption(Options.FEATURE_SET, featureSet); + loadFeatureFunctions(); + } + + /** + * Returns the current evaluator option value, returns null if missing. + * + * @param opt + * @return + */ + @Deprecated + @SuppressWarnings("unchecked") + public T getOption(final Options opt) { + Value val = this.options.get(opt); + if (val == null) { + val = opt.getDefaultValueObject(); } - } - } else { - if (internalInterpretedLibFunctions == null) { - internalInterpretedLibFunctions = loadInternalFunctions(); // cache it - } else { - for (Map.Entry entry : internalInterpretedLibFunctions - .entrySet()) { - addFunction(entry.getKey(), entry.getValue()); + + return (T) opt.intoObject(val); + } + + /** + * Returns the current evaluator option value union, returns null if missing. + * + * @param opt + * @return the option value, null if missing. + */ + public Value getOptionValue(final Options opt) { + Value val = this.options.get(opt); + assert (val != null); + return val; + } + + /** + * Returns the generated java classes byte code version, 1.6 by defualt. + * + * @return the bytecode version. + */ + public int getBytecodeVersion() { + return this.bytecodeVersion; + } + + /** + * Set the generated java classes java byte code version. + * + * @see Opcodes#V1_6 + * @param bytecodeVersion + */ + public void setBytecodeVersion(final int bytecodeVersion) { + this.bytecodeVersion = bytecodeVersion; + } + + /** + * Get the evaluator instance options + * + * @return + */ + public Map getOptions() { + Map ret = new HashMap<>(); + for (Map.Entry entry : this.options.entrySet()) { + ret.put(entry.getKey(), entry.getKey().intoObject(entry.getValue())); } - } - } - } - - private Map loadInternalFunctions() { - Map funcs = new HashMap<>(); - for (String lib : libs) { - try (final InputStream in = this.getClass().getResourceAsStream("/" + lib); - final BufferedInputStream bis = new BufferedInputStream(in); - final Reader reader = new InputStreamReader(bis)) { - Expression exp = this.compile(lib, Utils.readFully(reader), false); - Map exports = executeModule(exp, lib); - for (Map.Entry entry : exports.entrySet()) { - if (entry.getValue() instanceof AviatorFunction) { - final AviatorFunction fn = (AviatorFunction) entry.getValue(); - addFunction(entry.getKey(), fn); - funcs.put(entry.getKey(), fn); - } + return ret; + } + + /** + * Returns the functions map + * + * @return + */ + public Map getFuncMap() { + return this.funcMap; + } + + /** + * Returns the operators map. + * + * @return + */ + public Map getOpsMap() { + return this.opsMap; + } + + /** + * Get current trace output stream,default is System.out + * + * @return + */ + public OutputStream getTraceOutputStream() { + return this.traceOutputStream; + } + + /** + * Set trace output stream + * + * @param traceOutputStream + */ + public void setTraceOutputStream(final OutputStream traceOutputStream) { + this.traceOutputStream = traceOutputStream; + } + + private AviatorClassLoader initAviatorClassLoader() { + return AccessController.doPrivileged(new PrivilegedAction() { + + @Override + public AviatorClassLoader run() { + return new AviatorClassLoader(AviatorEvaluatorInstance.class.getClassLoader()); + } + + }); + } + + private final Map funcMap = new HashMap(); + + private final ConcurrentHashMap moduleCache = + new ConcurrentHashMap<>(); + + private final Map opsMap = + new IdentityHashMap(); + + private void loadModule() { + try { + addModule(IoModule.class); + } catch (Exception e) { + System.err.println("Fail to load internal modules."); + System.exit(1); } - } catch (IOException e) { - throw new IllegalStateException("Fail to load internal lib: " + lib, e); - } - } - return funcs; - } - - /** - * Compiled Expression cache - */ - private final ConcurrentHashMap/* - * Compiled - * expression - * task - */> expressionCache = - new ConcurrentHashMap>(); - - private LRUMap> expressionLRUCache; - - private boolean cachedExpressionByDefault; - - /** - * Create a aviator evaluator instance. - */ - AviatorEvaluatorInstance(final EvalMode evalMode) { - fillDefaultOpts(); - setOption(Options.EVAL_MODE, evalMode); - - // Load libs with Options.SERIALIZABLE=true - boolean serializable = this.getOptionValue(Options.SERIALIZABLE).bool; - try { - this.setOption(Options.SERIALIZABLE, true); - loadFeatureFunctions(); - loadLib(); - loadModule(); - addFunctionLoader(ClassPathConfigFunctionLoader.getInstance()); - } finally { - this.setOption(Options.SERIALIZABLE, serializable); - } - } - - private void fillDefaultOpts() { - this.cachedExpressionByDefault = false; - for (Options opt : Options.values()) { - this.options.put(opt, opt.getDefaultValueObject()); - } - } - - /** - * Returns true when caching compiled expression result by default. - * - * @since 5.2.2 - * @return - */ - public boolean isCachedExpressionByDefault() { - return this.cachedExpressionByDefault; - } - - /** - * Set true to cache the compiled expression result by default when invoke - * {@link #compile(String)}, {@link #compileScript(String)}, {@link #execute(String)} and - * {@link #execute(String, Map)}. Default is false. - * - * @param cachedExpressionByDefault - * @since 5.2.2 - */ - public void setCachedExpressionByDefault(final boolean cachedExpressionByDefault) { - this.cachedExpressionByDefault = cachedExpressionByDefault; - } - - private void loadFeatureFunctions() { - for (Feature feat : this.options.get(Options.FEATURE_SET).featureSet) { - for (AviatorFunction fn : feat.getFunctions()) { - if (!containsFunction(fn.getName())) { - this.addFunction(fn); + } + + /** + * Set alias name for function specified by name + * + * @param name the origin function name + * @param aliasName the alias function name + * @since 5.0.0 + */ + public void aliasFunction(final String name, final String aliasName) { + this.addFunction(aliasName, getFunction(name)); + } + + private void loadLib() { + // Load internal functions + // load sys lib + loadSystemFunctions(); + + // load string lib + loadStringFunctions(); + + // load math lib + loadMathFunctions(); + + // seq lib + loadSeqFunctions(); + + // alias + aliasFunction("println", "p"); + aliasFunction("pst", "printStackTrace"); + + loadInternalLibs(); + } + + private void loadSeqFunctions() { + addFunction(SeqCollectorFunction.INSTANCE); + addFunction(SeqCollectorRawFunction.INSTANCE); + addFunction(SeqKeysFunction.INSTANCE); + addFunction(SeqValsFunction.INSTANCE); + addFunction(SeqReverseFunction.INSTANCE); + addFunction(SeqZipmapFunction.INSTANCE); + addFunction(new SeqNewArrayFunction()); + addFunction(new SeqArrayFunction()); + addFunction(new SeqNewListFunction()); + addFunction(new SeqNewMapFunction()); + addFunction(new SeqNewSetFunction()); + addFunction(new SeqMapEntryFunction()); + addFunction(new SeqIntoFunction()); + addFunction(new SeqAddFunction()); + addFunction(new SeqAddAllFunction()); + addFunction(new SeqRemoveFunction()); + addFunction(new SeqGetFunction()); + addFunction(new SeqPutFunction()); + addFunction(new SeqMinFunction()); + addFunction(new SeqMaxFunction()); + addFunction(new SeqMapFunction()); + addFunction(new SeqReduceFunction()); + addFunction(new SeqFilterFunction()); + addFunction(new SeqSortFunction()); + addFunction(new SeqIncludeFunction()); + addFunction(new SeqContainsKeyFunction()); + addFunction(new SeqCountFunction()); + addFunction(new SeqEveryFunction()); + addFunction(new SeqNotAnyFunction()); + addFunction(new SeqSomeFunction()); + addFunction(new SeqMakePredicateFunFunction("seq.eq", OperatorType.EQ)); + addFunction(new SeqMakePredicateFunFunction("seq.neq", OperatorType.NEQ)); + addFunction(new SeqMakePredicateFunFunction("seq.lt", OperatorType.LT)); + addFunction(new SeqMakePredicateFunFunction("seq.le", OperatorType.LE)); + addFunction(new SeqMakePredicateFunFunction("seq.gt", OperatorType.GT)); + addFunction(new SeqMakePredicateFunFunction("seq.ge", OperatorType.GE)); + addFunction(new SeqCompsitePredFunFunction("seq.and", LogicOp.AND)); + addFunction(new SeqCompsitePredFunFunction("seq.or", LogicOp.OR)); + addFunction(new SeqMakePredicateFunFunction("seq.true", OperatorType.EQ, AviatorBoolean.TRUE)); + addFunction( + new SeqMakePredicateFunFunction("seq.false", OperatorType.EQ, AviatorBoolean.FALSE)); + addFunction(new SeqMakePredicateFunFunction("seq.nil", OperatorType.EQ, AviatorNil.NIL)); + addFunction(new SeqMakePredicateFunFunction("seq.exists", OperatorType.NEQ, AviatorNil.NIL)); + } + + private void loadMathFunctions() { + addFunction(new MathAbsFunction()); + addFunction(new MathRoundFunction()); + addFunction(new MathPowFunction()); + addFunction(new MathSqrtFunction()); + addFunction(new MathLog10Function()); + addFunction(new MathLogFunction()); + addFunction(new MathSinFunction()); + addFunction(new MathCosFunction()); + addFunction(new MathTanFunction()); + addFunction(MathAsinFunction.INSTANCE); + addFunction(MathAcosFunction.INSTANCE); + addFunction(MathAtanFunction.INSTANCE); + addFunction(MathFloorFunction.INSTANCE); + addFunction(MathCeilFunction.INSTANCE); + } + + private void loadStringFunctions() { + addFunction(new StringContainsFunction()); + addFunction(new StringIndexOfFunction()); + addFunction(new StringStartsWithFunction()); + addFunction(new StringEndsWithFunction()); + addFunction(new StringSubStringFunction()); + addFunction(new StringLengthFunction()); + addFunction(new StringSplitFunction()); + addFunction(new StringJoinFunction()); + addFunction(new StringReplaceFirstFunction()); + addFunction(new StringReplaceAllFunction()); + } + + private void loadSystemFunctions() { + addFunction(ComparatorFunction.INSTANCE); + addFunction(new CompareFunction()); + addFunction(new SysDateFunction()); + addFunction(new PrintlnFunction()); + addFunction(new PrintFunction()); + addFunction(new PstFunction()); + addFunction(new RandomFunction()); + addFunction(new NowFunction()); + addFunction(new LongFunction()); + addFunction(new BooleanFunction()); + addFunction(new DoubleFunction()); + addFunction(new StrFunction()); + addFunction(new BigIntFunction()); + addFunction(new DecimalFunction()); + addFunction(new Date2StringFunction()); + addFunction(new String2DateFunction()); + addFunction(new BinaryFunction(OperatorType.ADD)); + addFunction(new BinaryFunction(OperatorType.Exponent)); + addFunction(new BinaryFunction(OperatorType.SUB)); + addFunction(new BinaryFunction(OperatorType.MULT)); + addFunction(new BinaryFunction(OperatorType.DIV)); + addFunction(new BinaryFunction(OperatorType.MOD)); + addFunction(new BinaryFunction(OperatorType.NEG)); + addFunction(new BinaryFunction(OperatorType.NOT)); + addFunction(new BinaryFunction(OperatorType.BIT_AND)); + addFunction(new BinaryFunction(OperatorType.BIT_OR)); + addFunction(new BinaryFunction(OperatorType.BIT_XOR)); + addFunction(new BinaryFunction(OperatorType.BIT_NOT)); + addFunction(new TupleFunction()); + addFunction(new MinFunction()); + addFunction(new MaxFunction()); + addFunction(new IdentityFunction()); + addFunction(new AssertFunction()); + addFunction(new RangeFunction()); + addFunction(new IsDefFunction()); + addFunction(new UndefFunction()); + addFunction(new TypeFunction()); + addFunction(SeqFunction.INSTANCE); + addFunction(EvalFunction.INSTANCE); + addFunction(IsAFunction.INSTANCE); + addFunction(MetaFunction.INSTANCE); + addFunction(WithMetaFunction.INSTANCE); + addFunction(WithoutMetaFunction.INSTANCE); + } + + private void loadInternalLibs() { + if (getEvalMode() == EvalMode.ASM) { + + if (internalASMLibFunctions == null) { + internalASMLibFunctions = loadInternalFunctions(); // cache it + } else { + for (Map.Entry entry : internalASMLibFunctions.entrySet()) { + addFunction(entry.getKey(), entry.getValue()); + } + } + } else { + if (internalInterpretedLibFunctions == null) { + internalInterpretedLibFunctions = loadInternalFunctions(); // cache it + } else { + for (Map.Entry entry : internalInterpretedLibFunctions + .entrySet()) { + addFunction(entry.getKey(), entry.getValue()); + } + } } - } - } - } - - /** - * Use {@link LRUMap} as expression caching.It should be called when initializing the evaluator - * instance. - * - * @since 5.0.0 - * @param capacity - * @return the evaluator instance itself. - */ - public AviatorEvaluatorInstance useLRUExpressionCache(final int capacity) { - this.expressionLRUCache = new LRUMap<>(capacity); - return this; - } - - /** - * Clear all cached compiled expression - */ - public void clearExpressionCache() { - resetClassLoader(); - if (this.expressionLRUCache != null) { - synchronized (this.expressionLRUCache) { - this.expressionLRUCache.clear(); - } - } else { - this.expressionCache.clear(); - } - } - - /** - * Reset the classloader to a new instance. - * - * @since 5.0.0 - */ - public void resetClassLoader() { - this.aviatorClassLoader = initAviatorClassLoader(); - } - - /** - * Returns classloader - * - * @return - */ - public AviatorClassLoader getAviatorClassLoader() { - return getAviatorClassLoader(false); - } - - /** - * Returns classloader - * - * @return - */ - public AviatorClassLoader getAviatorClassLoader(final boolean cached) { - if (cached) { - return this.aviatorClassLoader; - } else { - return new AviatorClassLoader(this.getClass().getClassLoader()); - } - } - - /** - * Add an aviator function,it's not thread-safe. - * - * @param function - */ - public void addFunction(final AviatorFunction function) { - addFunction(function.getName(), function); - } - - /** - * Adds a function with the name - * - * @param name - * @param function - */ - public void addFunction(final String name, final AviatorFunction function) { - if (function == null) { - throw new IllegalArgumentException("Null function"); - } - if (SymbolTable.isReservedKeyword(name)) { - throw new IllegalArgumentException("Invalid function name, lambda is a keyword."); - } - if (this.funcMap.containsKey(name)) { - System.out.println("[Aviator WARN] The function '" + name - + "' is already exists, but is replaced with new one."); - } - this.funcMap.put(name, function); - } - - /** - * Define a function by name and expression. - * - * @param name the function name - * @param expression the expression to be executed and it's result must be a function. - * @since 4.0.0 - */ - public void defineFunction(final String name, final String expression) { - this.defineFunction(name, expression, null); - } - - /** - * Define a function by name and expression with the execution env. - * - * @param name the function name - * @param expression the expression to be executed and it's result must be a function. - * @param env the expression execution env - * @since 4.0.0 - */ - public void defineFunction(final String name, final String expression, - final Map env) { - AviatorFunction function = (AviatorFunction) this.execute(expression, env); - this.addFunction(name, function); - } - - /** - * Remove an aviator function by name,it's not thread-safe. - * - * @param name - * @return - */ - public AviatorFunction removeFunction(final String name) { - return (AviatorFunction) this.funcMap.remove(name); - } - - /** - * @see #getFunction(String, SymbolTable) - * @param name - * @return - */ - public AviatorFunction getFunction(final String name) { - return this.getFunction(name, null); - } - - /** - * Retrieve an aviator function by name,throw exception if not found or null.It's not thread-safe. - * - * @param name - * @param symbolTable - * @return - */ - public AviatorFunction getFunction(final String name, final SymbolTable symbolTable) { - AviatorFunction function = (AviatorFunction) this.funcMap.get(name); - if (function == null && this.functionLoaders != null) { - for (FunctionLoader loader : this.functionLoaders) { - if (loader != null) { - function = loader.onFunctionNotFound(name); + } + + private Map loadInternalFunctions() { + Map funcs = new HashMap<>(); + for (String lib : libs) { + try (final InputStream in = this.getClass().getResourceAsStream("/" + lib); + final BufferedInputStream bis = new BufferedInputStream(in); + final Reader reader = new InputStreamReader(bis)) { + Expression exp = this.compile(lib, Utils.readFully(reader), false); + Map exports = executeModule(exp, lib); + for (Map.Entry entry : exports.entrySet()) { + if (entry.getValue() instanceof AviatorFunction) { + final AviatorFunction fn = (AviatorFunction) entry.getValue(); + addFunction(entry.getKey(), fn); + funcs.put(entry.getKey(), fn); + } + } + } catch (IOException e) { + throw new IllegalStateException("Fail to load internal lib: " + lib, e); + } } - if (function != null) { - break; + return funcs; + } + + /** + * Compiled Expression cache + */ + private final ConcurrentHashMap/* + * Compiled + * expression + * task + */> expressionCache = + new ConcurrentHashMap>(); + + private LRUMap> expressionLRUCache; + + private boolean cachedExpressionByDefault; + + /** + * Create a aviator evaluator instance. + */ + AviatorEvaluatorInstance(final EvalMode evalMode) { + fillDefaultOpts(); + setOption(Options.EVAL_MODE, evalMode); + + // Load libs with Options.SERIALIZABLE=true + boolean serializable = this.getOptionValue(Options.SERIALIZABLE).bool; + try { + this.setOption(Options.SERIALIZABLE, true); + loadFeatureFunctions(); + loadLib(); + loadModule(); + addFunctionLoader(ClassPathConfigFunctionLoader.getInstance()); + } finally { + this.setOption(Options.SERIALIZABLE, serializable); } - } - } - if (function == null) { - // Returns a delegate function that will try to find the function from runtime - // env. - function = new RuntimeFunctionDelegator(name, symbolTable, this.functionMissing); - } - return function; - } - - /** - * Add an operator aviator function,it's not thread-safe. - * - * @param function - */ - public void addOpFunction(final OperatorType opType, final AviatorFunction function) { - this.opsMap.put(opType, function); - } - - /** - * Retrieve an operator aviator function by op type, return null if not found.It's not - * thread-safe. - * - * @since 3.3 - * @param opType - * @return - */ - public AviatorFunction getOpFunction(final OperatorType opType) { - return this.opsMap.get(opType); - } - - /** - * Remove an operator aviator function by op type, it's not thread-safe. - * - * @since 3.3 - * @param opType - * @return - */ - public AviatorFunction removeOpFunction(final OperatorType opType) { - return this.opsMap.remove(opType); - } - - /** - * Check if the function exists in the evaluator. Note: it doesn't check the runtime defined - * functions. - * - * @param name - * @return - */ - public boolean containsFunction(final String name) { - boolean exists = this.funcMap.containsKey(name); - if (!exists && this.functionLoaders != null) { - for (FunctionLoader loader : this.functionLoaders) { - if (loader != null && loader.onFunctionNotFound(name) != null) { - exists = true; - break; + } + + private void fillDefaultOpts() { + this.cachedExpressionByDefault = false; + for (Options opt : Options.values()) { + this.options.put(opt, opt.getDefaultValueObject()); } - } - } - - return exists; - } - - /** - * Remove a aviator function - * - * @param function - * @return - */ - public AviatorFunction removeFunction(final AviatorFunction function) { - return removeFunction(function.getName()); - } - - /** - * Returns a compiled expression in cache - * - * @param expression - * @return - */ - public Expression getCachedExpression(final String expression) { - return getCachedExpressionByKey(expression); - } - - /** - * Returns a compiled expression in cache by cacheKey. - * - * @param cacheKey - * @return - */ - public Expression getCachedExpressionByKey(final String cacheKey) { - FutureTask task = null; - if (this.expressionLRUCache != null) { - synchronized (this.expressionLRUCache) { - task = this.expressionLRUCache.get(cacheKey); - } - } else { - task = this.expressionCache.get(cacheKey); - } - if (task != null) { - return getCompiledExpression(cacheKey, task); - } else { - return null; - } - } - - /** - * Returns true when the expression is in cache. - * - * @param expression - * @return - * @since 4.0.0 - */ - public boolean isExpressionCached(final String expression) { - return getCachedExpression(expression) != null; - } - - /** - * Returns the number of cached expressions. - * - * @since 4.0.0 - * @return - */ - public int getExpressionCacheSize() { - if (this.expressionLRUCache != null) { - synchronized (this.expressionLRUCache) { - return this.expressionLRUCache.size(); - } - } - return this.expressionCache.size(); - } - - /** - * Compile a text expression to Expression object - * - * @param expression text expression - * @param cached Whether to cache the compiled result,make true to cache it. - * @return - */ - public Expression compile(final String expression, final boolean cached) { - return this.compile(expression, expression, cached); - } - - /** - * Compile a text expression to Expression object - * - * @param cacheKey unique key for caching. - * @param expression text expression - * @param cached Whether to cache the compiled result,make true to cache it. - * @return - */ - public Expression compile(final String cacheKey, final String expression, final boolean cached) { - return this.compile(cacheKey, expression, null, cached); - } - - private Expression compile(final String cacheKey, final String expression, - final String sourceFile, final boolean cached) { - if (expression == null || expression.trim().length() == 0) { - throw new CompileExpressionErrorException("Blank expression"); - } - if (cacheKey == null || cacheKey.trim().length() == 0) { - throw new CompileExpressionErrorException("Blank cacheKey"); - } - - if (cached) { - FutureTask existedTask = null; - if (this.expressionLRUCache != null) { - boolean runTask = false; - synchronized (this.expressionLRUCache) { - existedTask = this.expressionLRUCache.get(cacheKey); - if (existedTask == null) { - existedTask = newCompileTask(expression, sourceFile, cached); - runTask = true; - this.expressionLRUCache.put(cacheKey, existedTask); - } + } + + /** + * Returns true when caching compiled expression result by default. + * + * @since 5.2.2 + * @return + */ + public boolean isCachedExpressionByDefault() { + return this.cachedExpressionByDefault; + } + + /** + * Set true to cache the compiled expression result by default when invoke + * {@link #compile(String)}, {@link #compileScript(String)}, {@link #execute(String)} and + * {@link #execute(String, Map)}. Default is false. + * + * @param cachedExpressionByDefault + * @since 5.2.2 + */ + public void setCachedExpressionByDefault(final boolean cachedExpressionByDefault) { + this.cachedExpressionByDefault = cachedExpressionByDefault; + } + + private void loadFeatureFunctions() { + for (Feature feat : this.options.get(Options.FEATURE_SET).featureSet) { + for (AviatorFunction fn : feat.getFunctions()) { + if (!containsFunction(fn.getName())) { + this.addFunction(fn); + } + } } - if (runTask) { - existedTask.run(); + } + + /** + * Use {@link LRUMap} as expression caching.It should be called when initializing the evaluator + * instance. + * + * @since 5.0.0 + * @param capacity + * @return the evaluator instance itself. + */ + public AviatorEvaluatorInstance useLRUExpressionCache(final int capacity) { + this.expressionLRUCache = new LRUMap<>(capacity); + return this; + } + + /** + * Clear all cached compiled expression + */ + public void clearExpressionCache() { + resetClassLoader(); + if (this.expressionLRUCache != null) { + synchronized (this.expressionLRUCache) { + this.expressionLRUCache.clear(); + } + } else { + this.expressionCache.clear(); + } + } + + /** + * Reset the classloader to a new instance. + * + * @since 5.0.0 + */ + public void resetClassLoader() { + this.aviatorClassLoader = initAviatorClassLoader(); + } + + /** + * Returns classloader + * + * @return + */ + public AviatorClassLoader getAviatorClassLoader() { + return getAviatorClassLoader(false); + } + + /** + * Returns classloader + * + * @return + */ + public AviatorClassLoader getAviatorClassLoader(final boolean cached) { + if (cached) { + return this.aviatorClassLoader; + } else { + return new AviatorClassLoader(this.getClass().getClassLoader()); + } + } + + /** + * Add an aviator function,it's not thread-safe. + * + * @param function + */ + public void addFunction(final AviatorFunction function) { + addFunction(function.getName(), function); + } + + /** + * Adds a function with the name + * + * @param name + * @param function + */ + public void addFunction(final String name, final AviatorFunction function) { + if (function == null) { + throw new IllegalArgumentException("Null function"); + } + if (SymbolTable.isReservedKeyword(name)) { + throw new IllegalArgumentException("Invalid function name, lambda is a keyword."); + } + if (this.funcMap.containsKey(name)) { + System.out.println("[Aviator WARN] The function '" + name + + "' is already exists, but is replaced with new one."); + } + this.funcMap.put(name, function); + } + + /** + * Define a function by name and expression. + * + * @param name the function name + * @param expression the expression to be executed and it's result must be a function. + * @since 4.0.0 + */ + public void defineFunction(final String name, final String expression) { + this.defineFunction(name, expression, null); + } + + /** + * Define a function by name and expression with the execution env. + * + * @param name the function name + * @param expression the expression to be executed and it's result must be a function. + * @param env the expression execution env + * @since 4.0.0 + */ + public void defineFunction(final String name, final String expression, + final Map env) { + AviatorFunction function = (AviatorFunction) this.execute(expression, env); + this.addFunction(name, function); + } + + /** + * Remove an aviator function by name,it's not thread-safe. + * + * @param name + * @return + */ + public AviatorFunction removeFunction(final String name) { + return (AviatorFunction) this.funcMap.remove(name); + } + + /** + * @see #getFunction(String, SymbolTable) + * @param name + * @return + */ + public AviatorFunction getFunction(final String name) { + return this.getFunction(name, null); + } + + /** + * Retrieve an aviator function by name,throw exception if not found or null.It's not thread-safe. + * + * @param name + * @param symbolTable + * @return + */ + public AviatorFunction getFunction(final String name, final SymbolTable symbolTable) { + AviatorFunction function = (AviatorFunction) this.funcMap.get(name); + if (function == null && this.functionLoaders != null) { + for (FunctionLoader loader : this.functionLoaders) { + if (loader != null) { + function = loader.onFunctionNotFound(name); + } + if (function != null) { + break; + } + } + } + if (function == null) { + // Returns a delegate function that will try to find the function from runtime + // env. + function = new RuntimeFunctionDelegator(name, symbolTable, this.functionMissing); + } + return function; + } + + /** + * Add an operator aviator function,it's not thread-safe. + * + * @param function + */ + public void addOpFunction(final OperatorType opType, final AviatorFunction function) { + this.opsMap.put(opType, function); + } + + /** + * Retrieve an operator aviator function by op type, return null if not found.It's not + * thread-safe. + * + * @since 3.3 + * @param opType + * @return + */ + public AviatorFunction getOpFunction(final OperatorType opType) { + return this.opsMap.get(opType); + } + + /** + * Remove an operator aviator function by op type, it's not thread-safe. + * + * @since 3.3 + * @param opType + * @return + */ + public AviatorFunction removeOpFunction(final OperatorType opType) { + return this.opsMap.remove(opType); + } + + /** + * Check if the function exists in the evaluator. Note: it doesn't check the runtime defined + * functions. + * + * @param name + * @return + */ + public boolean containsFunction(final String name) { + boolean exists = this.funcMap.containsKey(name); + if (!exists && this.functionLoaders != null) { + for (FunctionLoader loader : this.functionLoaders) { + if (loader != null && loader.onFunctionNotFound(name) != null) { + exists = true; + break; + } + } + } + + return exists; + } + + /** + * Remove a aviator function + * + * @param function + * @return + */ + public AviatorFunction removeFunction(final AviatorFunction function) { + return removeFunction(function.getName()); + } + + /** + * Returns a compiled expression in cache + * + * @param expression + * @return + */ + public Expression getCachedExpression(final String expression) { + return getCachedExpressionByKey(expression); + } + + /** + * Returns a compiled expression in cache by cacheKey. + * + * @param cacheKey + * @return + */ + public Expression getCachedExpressionByKey(final String cacheKey) { + FutureTask task = null; + if (this.expressionLRUCache != null) { + synchronized (this.expressionLRUCache) { + task = this.expressionLRUCache.get(cacheKey); + } + } else { + task = this.expressionCache.get(cacheKey); } - } else { - FutureTask task = this.expressionCache.get(cacheKey); if (task != null) { - return getCompiledExpression(expression, task); + return getCompiledExpression(cacheKey, task); + } else { + return null; } - task = newCompileTask(expression, sourceFile, cached); - existedTask = this.expressionCache.putIfAbsent(cacheKey, task); - if (existedTask == null) { - existedTask = task; - existedTask.run(); + } + + /** + * Returns true when the expression is in cache. + * + * @param expression + * @return + * @since 4.0.0 + */ + public boolean isExpressionCached(final String expression) { + return getCachedExpression(expression) != null; + } + + /** + * Returns the number of cached expressions. + * + * @since 4.0.0 + * @return + */ + public int getExpressionCacheSize() { + if (this.expressionLRUCache != null) { + synchronized (this.expressionLRUCache) { + return this.expressionLRUCache.size(); + } } - } - return getCompiledExpression(cacheKey, existedTask); - - } else { - return innerCompile(expression, sourceFile, cached); - } - - } - - private FutureTask newCompileTask(final String expression, final String sourceFile, - final boolean cached) { - return new FutureTask<>(new Callable() { - @Override - public Expression call() throws Exception { - return innerCompile(expression, sourceFile, cached); - } - - }); - } - - private Expression getCompiledExpression(final String cacheKey, - final FutureTask task) { - try { - return task.get(); - } catch (Throwable t) { - invalidateCacheByKey(cacheKey); - final Throwable cause = t.getCause(); - if (cause instanceof ExpressionSyntaxErrorException - || cause instanceof CompileExpressionErrorException) { - throw Reflector.sneakyThrow(cause); - } - throw new CompileExpressionErrorException("Compile expression failure, cacheKey=" + cacheKey, - t); - } - } - - private Expression innerCompile(final String expression, final String sourceFile, - final boolean cached) { - ExpressionLexer lexer = new ExpressionLexer(this, expression); - CodeGenerator codeGenerator = newCodeGenerator(sourceFile, cached); - ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator); - Expression exp = parser.parse(); - if (getOptionValue(Options.TRACE_EVAL).bool) { - ((BaseExpression) exp).setExpression(expression); - } - return exp; - } - - private EvalMode getEvalMode() { - return getOptionValue(Options.EVAL_MODE).evalMode; - } - - private int getOptimizeLevel() { - return getOptionValue(Options.OPTIMIZE_LEVEL).number; - } - - public CodeGenerator newCodeGenerator(final String sourceFile, final boolean cached) { - AviatorClassLoader classLoader = getAviatorClassLoader(cached); - return newCodeGenerator(classLoader, sourceFile); - - } - - public EvalCodeGenerator newEvalCodeGenerator(final AviatorClassLoader classLoader, - final String sourceFile) { - switch (getEvalMode()) { - case ASM: - return new ASMCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream); - case INTERPRETER: - return new InterpretCodeGenerator(this, sourceFile, classLoader); - default: - throw new IllegalArgumentException("Unknown eval mode: " + getEvalMode()); - - } - } - - public CodeGenerator newCodeGenerator(final AviatorClassLoader classLoader, - final String sourceFile) { - switch (getOptimizeLevel()) { - case AviatorEvaluator.COMPILE: - final EvalCodeGenerator codeGen = newEvalCodeGenerator(classLoader, sourceFile); - codeGen.start(); - return codeGen; - case AviatorEvaluator.EVAL: - return new OptimizeCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream); - default: - throw new IllegalArgumentException("Unknow option " + getOptimizeLevel()); - } - } - - /** - * Compile a text expression to Expression Object without caching - * - * @param expression - * @return - */ - public Expression compile(final String expression) { - return compile(expression, this.cachedExpressionByDefault); - } - - /** - * Validate a script text whether is a legal aviatorscript text, throw exception if not. - * - * @since 5.0.2 - * @param script the script text - */ - public void validate(final String script) { - if (script == null || script.trim().length() == 0) { - throw new CompileExpressionErrorException("Blank script"); - } - ExpressionLexer lexer = new ExpressionLexer(this, script); - CodeGenerator codeGenerator = new NoneCodeGenerator(); - ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator); - parser.parse(); - } - - /** - * Execute a text expression with values that are variables order in the expression.It only runs - * in EVAL mode,and it will cache the compiled expression.It's deprecated, please use - * {@link #execute(String, Map)} instead. - * - * @param expression - * @param values - * @return - */ - @Deprecated - public Object exec(final String expression, final Object... values) { - if (getOptimizeLevel() != AviatorEvaluator.EVAL) { - throw new IllegalStateException("Aviator evaluator is not in EVAL mode."); - } - Expression compiledExpression = compile(expression, true); - if (compiledExpression != null) { - List vars = compiledExpression.getVariableNames(); - if (!vars.isEmpty()) { - int valLen = values == null ? 0 : values.length; - if (valLen != vars.size()) { - throw new IllegalArgumentException("Expect " + vars.size() + " values,but has " + valLen); + return this.expressionCache.size(); + } + + /** + * Compile a text expression to Expression object + * + * @param expression text expression + * @param cached Whether to cache the compiled result,make true to cache it. + * @return + */ + public Expression compile(final String expression, final boolean cached) { + return this.compile(expression, expression, cached); + } + + /** + * Compile a text expression to Expression object + * + * @param cacheKey unique key for caching. + * @param expression text expression + * @param cached Whether to cache the compiled result,make true to cache it. + * @return + */ + public Expression compile(final String cacheKey, final String expression, final boolean cached) { + return this.compile(cacheKey, expression, null, cached); + } + + private Expression compile(final String cacheKey, final String expression, + final String sourceFile, final boolean cached) { + if (expression == null || expression.trim().length() == 0) { + throw new CompileExpressionErrorException("Blank expression"); } - Map env = new HashMap(); - int i = 0; - for (String var : vars) { - env.put(var, values[i++]); + if (cacheKey == null || cacheKey.trim().length() == 0) { + throw new CompileExpressionErrorException("Blank cacheKey"); } - return compiledExpression.execute(env); - } else { - return compiledExpression.execute(); - } - } else { - throw new ExpressionNotFoundException("Null compiled expression for " + expression); - } - } - - /** - * Execute a text expression with environment - * - * @param cacheKey unique key for caching - * @param expression text expression - * @param env Binding variable environment - * @param cached Whether to cache the compiled result,make true to cache it. - */ - public Object execute(final String cacheKey, final String expression, - final Map env, final boolean cached) { - Expression compiledExpression = compile(cacheKey, expression, cached); - if (compiledExpression != null) { - return compiledExpression.execute(env); - } else { - throw new ExpressionNotFoundException("Null compiled expression for " + expression); - } - } - - /** - * Execute a text expression with environment - * - * @param expression text expression - * @param env Binding variable environment - * @param cached Whether to cache the compiled result,make true to cache it. - */ - public Object execute(final String expression, final Map env, - final boolean cached) { - return execute(expression, expression, env, cached); - } - - /** - * Execute a text expression without caching - * - * @param expression - * @param env - * @return - */ - public Object execute(final String expression, final Map env) { - return execute(expression, env, this.cachedExpressionByDefault); - } - - /** - * Invalidate expression cache - * - * @param expression - */ - public void invalidateCache(final String expression) { - invalidateCacheByKey(expression); - } - - /** - * Invalidate expression cache by cacheKey - * - * @param cacheKey - */ - public void invalidateCacheByKey(final String cacheKey) { - if (this.expressionLRUCache != null) { - synchronized (this.expressionLRUCache) { - this.expressionLRUCache.remove(cacheKey); - } - } else { - this.expressionCache.remove(cacheKey); - } - } - - /** - * Execute a text expression without caching and env map. - * - * @param expression - * @return - */ - public Object execute(final String expression) { - return execute(expression, (Map) null); - } - - public void ensureFeatureEnabled(final Feature feature) { - if (!getOptionValue(Options.FEATURE_SET).featureSet.contains(feature)) { - throw new UnsupportedFeatureException(feature); - } - } - - public static class StringSegments { - public final List segs; - public int hintLength; - - public StringSegments(final List segs, final int hintLength) { - super(); - this.segs = segs; - this.hintLength = hintLength; - } - - public boolean isEmpty() { - return this.segs.isEmpty(); - } - - public String toString(final Map env, final String lexeme) { - if (this.segs.isEmpty()) { - return lexeme; - } - StringBuilder sb = new StringBuilder(this.hintLength); - final int size = this.segs.size(); - for (int i = 0; i < size; i++) { - this.segs.get(i).appendTo(sb, env); - } - final String result = sb.toString(); - final int newLen = result.length(); - // Prevent hintLength too large. - if (newLen > this.hintLength && newLen < 10 * this.hintLength) { - this.hintLength = newLen; - } - return result; - } - } - - /** - * Compile a string to string segments, if string doesn't have a interpolation,returns an empty - * list. - * - * @param lexeme - * @return - */ - public StringSegments compileStringSegments(final String lexeme) { - return this.compileStringSegments(lexeme, null, 1); - } - - /** - * Compile a string to string segments, if string doesn't have a interpolation,returns an empty - * list. - * - * @param lexeme - * @param sourceFile - * @param lineNo; - * @return - */ - public StringSegments compileStringSegments(final String lexeme, final String sourceFile, - final int lineNo) { - List segs = new ArrayList(); - boolean hasInterpolationOrEscaped = false; - StringCharacterIterator it = new StringCharacterIterator(lexeme); - char ch = it.current(), prev = StringCharacterIterator.DONE; - int lastInterPos = 0; - int i = 1; - for (;;) { - if (ch == '#') { - if (prev == '\\') { - // # is escaped, skip the backslash. - final String segStr = lexeme.substring(lastInterPos, i - 2); - segs.add(new LiteralSegment(segStr)); - lastInterPos = i - 1; - hasInterpolationOrEscaped = true; + + if (cached) { + FutureTask existedTask = null; + if (this.expressionLRUCache != null) { + boolean runTask = false; + synchronized (this.expressionLRUCache) { + existedTask = this.expressionLRUCache.get(cacheKey); + if (existedTask == null) { + existedTask = newCompileTask(expression, sourceFile, cached); + runTask = true; + this.expressionLRUCache.put(cacheKey, existedTask); + } + } + if (runTask) { + existedTask.run(); + } + } else { + FutureTask task = this.expressionCache.get(cacheKey); + if (task != null) { + return getCompiledExpression(expression, task); + } + task = newCompileTask(expression, sourceFile, cached); + existedTask = this.expressionCache.putIfAbsent(cacheKey, task); + if (existedTask == null) { + existedTask = task; + existedTask.run(); + } + } + return getCompiledExpression(cacheKey, existedTask); + } else { - // # is not escaped. - prev = ch; - ch = it.next(); - i++; - if (ch == '{') { - // Find a interpolation position. - if (i - 2 > lastInterPos) { - final String segStr = lexeme.substring(lastInterPos, i - 2); - segs.add(new LiteralSegment(segStr)); + return innerCompile(expression, sourceFile, cached); + } + + } + + private FutureTask newCompileTask(final String expression, final String sourceFile, + final boolean cached) { + return new FutureTask<>(new Callable() { + @Override + public Expression call() throws Exception { + return innerCompile(expression, sourceFile, cached); } - try { - ExpressionLexer lexer = new ExpressionLexer(this, lexeme.substring(i)); - lexer.setLineNo(lineNo); - ExpressionParser parser = - new ExpressionParser(this, lexer, newCodeGenerator(sourceFile, false)); - - Expression exp = parser.parse(false); - final Token lookahead = parser.getLookahead(); - if (lookahead == null || (lookahead.getType() != TokenType.Char - || ((CharToken) lookahead).getCh() != '}')) { - parser.reportSyntaxError("expect '}' to complete string interpolation"); - } - int expStrLen = lookahead.getStartIndex() + 1; - while (expStrLen-- > 0) { - prev = ch; - ch = it.next(); - i++; - } - Token previousToken = null; - - if (parser.getParsedTokens() == 2 && (previousToken = parser.getPrevToken()) != null - && previousToken.getType() == TokenType.Variable) { - // special case for inline variable. - if (previousToken == Variable.TRUE) { - segs.add(new LiteralSegment("true")); - } else if (previousToken == Variable.FALSE) { - segs.add(new LiteralSegment("false")); - } else if (previousToken == Variable.NIL) { - segs.add(new LiteralSegment("null")); - } else { - segs.add(new VarSegment( - parser.getSymbolTable().reserve(previousToken.getLexeme()).getLexeme())); + }); + } + + private Expression getCompiledExpression(final String cacheKey, + final FutureTask task) { + try { + return task.get(); + } catch (Throwable t) { + invalidateCacheByKey(cacheKey); + final Throwable cause = t.getCause(); + if (cause instanceof ExpressionSyntaxErrorException + || cause instanceof CompileExpressionErrorException) { + throw Reflector.sneakyThrow(cause); + } + throw new CompileExpressionErrorException("Compile expression failure, cacheKey=" + cacheKey, + t); + } + } + + private Expression innerCompile(final String expression, final String sourceFile, + final boolean cached) { + // 词法扫描 + ExpressionLexer lexer = new ExpressionLexer(this, expression); + // 代码生成器 + CodeGenerator codeGenerator = newCodeGenerator(sourceFile, cached); + ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator); + // 触发解析动作。在这个方法执行完的一瞬间,字节码已经生成、匿名类已经加载、Expression 实例已经 new 出来了。 + Expression exp = parser.parse(); + if (getOptionValue(Options.TRACE_EVAL).bool) { + ((BaseExpression) exp).setExpression(expression); + } + return exp; + } + + private EvalMode getEvalMode() { + return getOptionValue(Options.EVAL_MODE).evalMode; + } + + private int getOptimizeLevel() { + return getOptionValue(Options.OPTIMIZE_LEVEL).number; + } + + public CodeGenerator newCodeGenerator(final String sourceFile, final boolean cached) { + AviatorClassLoader classLoader = getAviatorClassLoader(cached); + return newCodeGenerator(classLoader, sourceFile); + + } + + public EvalCodeGenerator newEvalCodeGenerator(final AviatorClassLoader classLoader, + final String sourceFile) { + switch (getEvalMode()) { + case ASM: + return new ASMCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream); + case INTERPRETER: + return new InterpretCodeGenerator(this, sourceFile, classLoader); + default: + throw new IllegalArgumentException("Unknown eval mode: " + getEvalMode()); + + } + } + + public CodeGenerator newCodeGenerator(final AviatorClassLoader classLoader, + final String sourceFile) { + switch (getOptimizeLevel()) { + case AviatorEvaluator.COMPILE: + final EvalCodeGenerator codeGen = newEvalCodeGenerator(classLoader, sourceFile); + codeGen.start(); + return codeGen; + case AviatorEvaluator.EVAL: + return new OptimizeCodeGenerator(this, sourceFile, classLoader, this.traceOutputStream); + default: + throw new IllegalArgumentException("Unknow option " + getOptimizeLevel()); + } + } + + /** + * Compile a text expression to Expression Object without caching + * + * @param expression + * @return + */ + public Expression compile(final String expression) { + return compile(expression, this.cachedExpressionByDefault); + } + + /** + * Validate a script text whether is a legal aviatorscript text, throw exception if not. + * + * @since 5.0.2 + * @param script the script text + */ + public void validate(final String script) { + if (script == null || script.trim().length() == 0) { + throw new CompileExpressionErrorException("Blank script"); + } + ExpressionLexer lexer = new ExpressionLexer(this, script); + CodeGenerator codeGenerator = new NoneCodeGenerator(); + ExpressionParser parser = new ExpressionParser(this, lexer, codeGenerator); + parser.parse(); + } + + /** + * Execute a text expression with values that are variables order in the expression.It only runs + * in EVAL mode,and it will cache the compiled expression.It's deprecated, please use + * {@link #execute(String, Map)} instead. + * + * @param expression + * @param values + * @return + */ + @Deprecated + public Object exec(final String expression, final Object... values) { + if (getOptimizeLevel() != AviatorEvaluator.EVAL) { + throw new IllegalStateException("Aviator evaluator is not in EVAL mode."); + } + Expression compiledExpression = compile(expression, true); + if (compiledExpression != null) { + List vars = compiledExpression.getVariableNames(); + if (!vars.isEmpty()) { + int valLen = values == null ? 0 : values.length; + if (valLen != vars.size()) { + throw new IllegalArgumentException("Expect " + vars.size() + " values,but has " + valLen); + } + Map env = new HashMap(); + int i = 0; + for (String var : vars) { + env.put(var, values[i++]); } - } else { - segs.add(new ExpressionSegment(exp)); - } - hasInterpolationOrEscaped = true; - lastInterPos = i; - } catch (Throwable t) { - throw new CompileExpressionErrorException( - "Fail to compile string interpolation: " + lexeme, t); + return compiledExpression.execute(env); + } else { + return compiledExpression.execute(); + } + } else { + throw new ExpressionNotFoundException("Null compiled expression for " + expression); + } + } + + /** + * Execute a text expression with environment + * + * @param cacheKey unique key for caching + * @param expression text expression + * @param env Binding variable environment + * @param cached Whether to cache the compiled result,make true to cache it. + */ + public Object execute(final String cacheKey, final String expression, + final Map env, final boolean cached) { + Expression compiledExpression = compile(cacheKey, expression, cached); + if (compiledExpression != null) { + return compiledExpression.execute(env); + } else { + throw new ExpressionNotFoundException("Null compiled expression for " + expression); + } + } + + /** + * Execute a text expression with environment + * + * @param expression text expression + * @param env Binding variable environment + * @param cached Whether to cache the compiled result,make true to cache it. + */ + public Object execute(final String expression, final Map env, + final boolean cached) { + return execute(expression, expression, env, cached); + } + + /** + * Execute a text expression without caching + * + * @param expression + * @param env + * @return + */ + public Object execute(final String expression, final Map env) { + return execute(expression, env, this.cachedExpressionByDefault); + } + + /** + * Invalidate expression cache + * + * @param expression + */ + public void invalidateCache(final String expression) { + invalidateCacheByKey(expression); + } + + /** + * Invalidate expression cache by cacheKey + * + * @param cacheKey + */ + public void invalidateCacheByKey(final String cacheKey) { + if (this.expressionLRUCache != null) { + synchronized (this.expressionLRUCache) { + this.expressionLRUCache.remove(cacheKey); + } + } else { + this.expressionCache.remove(cacheKey); + } + } + + /** + * Execute a text expression without caching and env map. + * + * @param expression + * @return + */ + public Object execute(final String expression) { + return execute(expression, (Map) null); + } + + public void ensureFeatureEnabled(final Feature feature) { + if (!getOptionValue(Options.FEATURE_SET).featureSet.contains(feature)) { + throw new UnsupportedFeatureException(feature); + } + } + + public static class StringSegments { + public final List segs; + public int hintLength; + + public StringSegments(final List segs, final int hintLength) { + super(); + this.segs = segs; + this.hintLength = hintLength; + } + + public boolean isEmpty() { + return this.segs.isEmpty(); + } + + public String toString(final Map env, final String lexeme) { + if (this.segs.isEmpty()) { + return lexeme; + } + StringBuilder sb = new StringBuilder(this.hintLength); + final int size = this.segs.size(); + for (int i = 0; i < size; i++) { + this.segs.get(i).appendTo(sb, env); } - // End of interpolation - } - // End of # is not escaped. + final String result = sb.toString(); + final int newLen = result.length(); + // Prevent hintLength too large. + if (newLen > this.hintLength && newLen < 10 * this.hintLength) { + this.hintLength = newLen; + } + return result; } - } + } + + /** + * Compile a string to string segments, if string doesn't have a interpolation,returns an empty + * list. + * + * @param lexeme + * @return + */ + public StringSegments compileStringSegments(final String lexeme) { + return this.compileStringSegments(lexeme, null, 1); + } + + /** + * Compile a string to string segments, if string doesn't have a interpolation,returns an empty + * list. + * + * @param lexeme + * @param sourceFile + * @param lineNo; + * @return + */ + public StringSegments compileStringSegments(final String lexeme, final String sourceFile, + final int lineNo) { + List segs = new ArrayList(); + boolean hasInterpolationOrEscaped = false; + StringCharacterIterator it = new StringCharacterIterator(lexeme); + char ch = it.current(), prev = StringCharacterIterator.DONE; + int lastInterPos = 0; + int i = 1; + for (; ; ) { + if (ch == '#') { + if (prev == '\\') { + // # is escaped, skip the backslash. + final String segStr = lexeme.substring(lastInterPos, i - 2); + segs.add(new LiteralSegment(segStr)); + lastInterPos = i - 1; + hasInterpolationOrEscaped = true; + } else { + // # is not escaped. + prev = ch; + ch = it.next(); + i++; + if (ch == '{') { + // Find a interpolation position. + if (i - 2 > lastInterPos) { + final String segStr = lexeme.substring(lastInterPos, i - 2); + segs.add(new LiteralSegment(segStr)); + } + + try { + ExpressionLexer lexer = new ExpressionLexer(this, lexeme.substring(i)); + lexer.setLineNo(lineNo); + ExpressionParser parser = + new ExpressionParser(this, lexer, newCodeGenerator(sourceFile, false)); + + Expression exp = parser.parse(false); + final Token lookahead = parser.getLookahead(); + if (lookahead == null || (lookahead.getType() != TokenType.Char + || ((CharToken) lookahead).getCh() != '}')) { + parser.reportSyntaxError("expect '}' to complete string interpolation"); + } + int expStrLen = lookahead.getStartIndex() + 1; + while (expStrLen-- > 0) { + prev = ch; + ch = it.next(); + i++; + } + Token previousToken = null; + + if (parser.getParsedTokens() == 2 && (previousToken = parser.getPrevToken()) != null + && previousToken.getType() == TokenType.Variable) { + // special case for inline variable. + if (previousToken == Variable.TRUE) { + segs.add(new LiteralSegment("true")); + } else if (previousToken == Variable.FALSE) { + segs.add(new LiteralSegment("false")); + } else if (previousToken == Variable.NIL) { + segs.add(new LiteralSegment("null")); + } else { + segs.add(new VarSegment( + parser.getSymbolTable().reserve(previousToken.getLexeme()).getLexeme())); + } + } else { + segs.add(new ExpressionSegment(exp)); + } + hasInterpolationOrEscaped = true; + lastInterPos = i; + } catch (Throwable t) { + throw new CompileExpressionErrorException( + "Fail to compile string interpolation: " + lexeme, t); + } + // End of interpolation + } + // End of # is not escaped. + } + } - if (ch == StringCharacterIterator.DONE) { - if (i - 1 > lastInterPos) { - final String segStr = lexeme.substring(lastInterPos, i - 1); - segs.add(new LiteralSegment(segStr)); + if (ch == StringCharacterIterator.DONE) { + if (i - 1 > lastInterPos) { + final String segStr = lexeme.substring(lastInterPos, i - 1); + segs.add(new LiteralSegment(segStr)); + } + break; + } + + prev = ch; + ch = it.next(); + i++; } - break; - } - - prev = ch; - ch = it.next(); - i++; - } - if (hasInterpolationOrEscaped) { - return new StringSegments(segs, lexeme.length() * 2 / 3); - } else { - return new StringSegments(Collections.emptyList(), 0); - } - } - - - /** - * check if class is in Options.ALLOWED_CLASS_SET - * - * @param checkIfAllow check or not - * @param clazz the class for check - * @return the class for check - */ - public Class checkIfClassIsAllowed(final boolean checkIfAllow, final Class clazz) { - if (checkIfAllow) { - Set> allowedList = this.getOptionValue(Options.ALLOWED_CLASS_SET).classes; - if (allowedList != null) { - // Null list means allowing all classes - if (!allowedList.contains(clazz)) { - throw new ExpressionRuntimeException( - "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET"); + if (hasInterpolationOrEscaped) { + return new StringSegments(segs, lexeme.length() * 2 / 3); + } else { + return new StringSegments(Collections.emptyList(), 0); } - } - Set> assignableList = - this.getOptionValue(Options.ASSIGNABLE_ALLOWED_CLASS_SET).classes; - if (assignableList != null) { - for (Class aClass : assignableList) { - if (aClass.isAssignableFrom(clazz)) { - return clazz; - } + } + + + /** + * check if class is in Options.ALLOWED_CLASS_SET + * + * @param checkIfAllow check or not + * @param clazz the class for check + * @return the class for check + */ + public Class checkIfClassIsAllowed(final boolean checkIfAllow, final Class clazz) { + if (checkIfAllow) { + Set> allowedList = this.getOptionValue(Options.ALLOWED_CLASS_SET).classes; + if (allowedList != null) { + // Null list means allowing all classes + if (!allowedList.contains(clazz)) { + throw new ExpressionRuntimeException( + "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET"); + } + } + Set> assignableList = + this.getOptionValue(Options.ASSIGNABLE_ALLOWED_CLASS_SET).classes; + if (assignableList != null) { + for (Class aClass : assignableList) { + if (aClass.isAssignableFrom(clazz)) { + return clazz; + } + } + throw new ExpressionRuntimeException( + "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET"); + } } - throw new ExpressionRuntimeException( - "`" + clazz + "` is not in allowed class set, check Options.ALLOWED_CLASS_SET"); - } + return clazz; } - return clazz; - } } diff --git a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java index e430d004..1ba4be9e 100644 --- a/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java +++ b/src/main/java/com/googlecode/aviator/code/asm/ASMCodeGenerator.java @@ -716,11 +716,11 @@ public void onNeg(final Token lookahead) { * (non-Javadoc) * * @see com.googlecode.aviator.code.CodeGenerator#getResult() + * 把内存中杂乱的二进制指令(Byte Array)正式转变为一个活生生、可执行的 Java 对象。 */ @Override public Expression getResult(final boolean unboxObject) { end(unboxObject); - byte[] bytes = this.classWriter.toByteArray(); try { boolean enableSerializable = this.instance.getOptionValue(Options.SERIALIZABLE).bool; diff --git a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java index 8472c8a0..cd127ab5 100644 --- a/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java +++ b/src/main/java/com/googlecode/aviator/parser/ExpressionParser.java @@ -1,14 +1,14 @@ /** * Copyright (C) 2010 dennis zhuang (killme2008@gmail.com) - * + *

* This library is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. - * + *

* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; + import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.Expression; import com.googlecode.aviator.Feature; @@ -49,7 +50,7 @@ * *

* Operator Precedence (lowest to highest): - * + * *

  *  1. parseTernary        ?:
  *  2. parseLogicalOr      ||        (logical or)
@@ -70,2249 +71,2252 @@
  * @author dennis
  */
 public class ExpressionParser implements Parser {
-  private final ExpressionLexer lexer;
-
-  /*
-   * lookahead token
-   */
-  private Token lookahead;
-
-  private final ArrayDeque> prevTokens = new ArrayDeque<>();
-
-  /** Maximum number of previous tokens to keep for lookback operations */
-  private static final int MAX_PREV_TOKENS = 256;
-
-  private CodeGenerator codeGenerator;
-
-  private ScopeInfo scope;
-
-  private int parsedTokens;
-
-
-  private boolean inPattern = false;
+    private final ExpressionLexer lexer;
 
-  private final AviatorEvaluatorInstance instance;
+    /*
+     * lookahead token
+     */
+    private Token lookahead;
 
-  private final boolean captureFuncArgs;
+    private final ArrayDeque> prevTokens = new ArrayDeque<>();
 
-  private final Set featureSet;
+    /** Maximum number of previous tokens to keep for lookback operations */
+    private static final int MAX_PREV_TOKENS = 256;
 
+    private CodeGenerator codeGenerator;
 
+    private ScopeInfo scope;
 
-  public Token getPrevToken() {
-    return this.prevTokens.peek();
-  }
+    private int parsedTokens;
 
 
+    private boolean inPattern = false;
 
-  /*
-   * (non-Javadoc)
-   *
-   * @see com.googlecode.aviator.parser.Parser#getCodeGenerator()
-   */
-  @Override
-  public CodeGenerator getCodeGenerator() {
-    return this.codeGenerator;
-  }
+    private final AviatorEvaluatorInstance instance;
 
+    private final boolean captureFuncArgs;
 
+    private final Set featureSet;
 
-  public Token getLookahead() {
-    return this.lookahead;
-  }
 
+    public Token getPrevToken() {
+        return this.prevTokens.peek();
+    }
 
 
-  @Override
-  public SymbolTable getSymbolTable() {
-    return this.lexer.getSymbolTable();
-  }
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.googlecode.aviator.parser.Parser#getCodeGenerator()
+     */
+    @Override
+    public CodeGenerator getCodeGenerator() {
+        return this.codeGenerator;
+    }
 
-  /*
-   * (non-Javadoc)
-   *
-   * @see com.googlecode.aviator.parser.Parser#setCodeGenerator(com.googlecode.aviator.code.
-   * CodeGenerator)
-   */
-  @Override
-  public void setCodeGenerator(final CodeGenerator codeGenerator) {
-    this.codeGenerator = codeGenerator;
-  }
 
-  /*
-   * (non-Javadoc)
-   *
-   * @see com.googlecode.aviator.parser.Parser#enterScope()
-   */
-  @Override
-  public ScopeInfo enterScope(final boolean inNewScope) {
-    ScopeInfo current = this.scope;
-    this.scope = new ScopeInfo(0, 0, 0, 0, inNewScope, new ArrayDeque());
-    return current;
-  }
+    public Token getLookahead() {
+        return this.lookahead;
+    }
 
-  /**
-   * (non-Javadoc)
-   *
-   * @see com.googlecode.aviator.parser.Parser#restoreScope(com.googlecode.aviator.parser.ExpressionParser.DepthInfo)
-   */
-  @Override
-  public void restoreScope(final ScopeInfo info) {
-    this.scope = info;
-  }
 
-  public ExpressionParser(final AviatorEvaluatorInstance instance, final ExpressionLexer lexer,
-      final CodeGenerator codeGenerator) {
-    super();
-    this.scope = new ScopeInfo(0, 0, 0, 0, false, new ArrayDeque());
-    this.instance = instance;
-    this.captureFuncArgs = instance.getOptionValue(Options.CAPTURE_FUNCTION_ARGS).bool;
-    this.lexer = lexer;
-    this.lookahead = this.lexer.scan();
-    if (this.lookahead != null) {
-      this.parsedTokens++;
+    @Override
+    public SymbolTable getSymbolTable() {
+        return this.lexer.getSymbolTable();
     }
-    this.featureSet = this.instance.getOptionValue(Options.FEATURE_SET).featureSet;
-    if (this.lookahead == null) {
-      reportSyntaxError("blank script");
+
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.googlecode.aviator.parser.Parser#setCodeGenerator(com.googlecode.aviator.code.
+     * CodeGenerator)
+     */
+    @Override
+    public void setCodeGenerator(final CodeGenerator codeGenerator) {
+        this.codeGenerator = codeGenerator;
     }
-    setCodeGenerator(codeGenerator);
-    getCodeGeneratorWithTimes().setParser(this);
-  }
 
-  private void ensureFeatureEnabled(final Feature feature) {
-    if (!this.featureSet.contains(feature)) {
-      throw new UnsupportedFeatureException(feature);
+    /*
+     * (non-Javadoc)
+     *
+     * @see com.googlecode.aviator.parser.Parser#enterScope()
+     */
+    @Override
+    public ScopeInfo enterScope(final boolean inNewScope) {
+        ScopeInfo current = this.scope;
+        this.scope = new ScopeInfo(0, 0, 0, 0, inNewScope, new ArrayDeque());
+        return current;
     }
-  }
-
-  /**
-   * Call __reducer_return(result)
-   */
-  public void returnStatement() {
-    move(true);
-    CodeGenerator cg = getCodeGeneratorWithTimes();
-    cg.onTernaryEnd(this.lookahead);
-    if (expectChar(';')) {
-      // 'return;' => 'return nil;'
-      if (this.scope.newLexicalScope) {
-        cg.onMethodName(Constants.ReducerReturnFn);
-        cg.onConstant(Variable.NIL);
-        cg.onMethodParameter(this.lookahead);
-        cg.onMethodInvoke(this.lookahead);
-      } else {
-        cg.onConstant(Variable.NIL);
-      }
-      move(true);
-      return;
-    } else {
-      if (this.scope.newLexicalScope) {
-        cg.onMethodName(Constants.ReducerReturnFn);
-        if (!parseTernary()) {
-          reportSyntaxError("invalid value for return, missing ';'?");
+
+    /**
+     * (non-Javadoc)
+     *
+     * @see com.googlecode.aviator.parser.Parser#restoreScope(com.googlecode.aviator.parser.ExpressionParser.DepthInfo)
+     */
+    @Override
+    public void restoreScope(final ScopeInfo info) {
+        this.scope = info;
+    }
+
+    public ExpressionParser(final AviatorEvaluatorInstance instance, final ExpressionLexer lexer,
+                            final CodeGenerator codeGenerator) {
+        super();
+        this.scope = new ScopeInfo(0, 0, 0, 0, false, new ArrayDeque());
+        this.instance = instance;
+        this.captureFuncArgs = instance.getOptionValue(Options.CAPTURE_FUNCTION_ARGS).bool;
+        this.lexer = lexer;
+        this.lookahead = this.lexer.scan();
+        if (this.lookahead != null) {
+            this.parsedTokens++;
         }
-        cg.onMethodParameter(this.lookahead);
-        cg.onMethodInvoke(this.lookahead);
-      } else {
-        if (!parseTernary()) {
-          reportSyntaxError("invalid value for return, missing ';'?");
+        this.featureSet = this.instance.getOptionValue(Options.FEATURE_SET).featureSet;
+        if (this.lookahead == null) {
+            reportSyntaxError("blank script");
         }
-      }
-    }
-
-    if (!expectChar(';')) {
-      reportSyntaxError("missing ';' for return statement");
+        setCodeGenerator(codeGenerator);
+        getCodeGeneratorWithTimes().setParser(this);
     }
-    move(true);
-  }
-
-  public boolean parseTernary() {
-    int gcTimes = this.getCGTimes;
 
-    if (this.lookahead == Variable.NEW) {
-      newStatement();
-      return true;
+    private void ensureFeatureEnabled(final Feature feature) {
+        if (!this.featureSet.contains(feature)) {
+            throw new UnsupportedFeatureException(feature);
+        }
     }
 
-    parseLogicalOr();
-    if (this.lookahead == null || expectChar(':') || expectChar(',')) {
-      return gcTimes < this.getCGTimes;
-    }
-    Token opToken = this.lookahead;
-    if (expectChar('?')) {
-      move(true);
-      CodeGenerator cg = getCodeGeneratorWithTimes();
-      cg.onTernaryBoolean(opToken);
-      if (!parseTernary()) {
-        reportSyntaxError("invalid token for ternary operator");
-      }
-      if (expectChar(':')) {
+    /**
+     * Call __reducer_return(result)
+     */
+    public void returnStatement() {
         move(true);
-        cg.onTernaryLeft(this.lookahead);
-        if (!parseTernary()) {
-          reportSyntaxError("invalid token for ternary operator");
+        CodeGenerator cg = getCodeGeneratorWithTimes();
+        cg.onTernaryEnd(this.lookahead);
+        if (expectChar(';')) {
+            // 'return;' => 'return nil;'
+            if (this.scope.newLexicalScope) {
+                cg.onMethodName(Constants.ReducerReturnFn);
+                cg.onConstant(Variable.NIL);
+                cg.onMethodParameter(this.lookahead);
+                cg.onMethodInvoke(this.lookahead);
+            } else {
+                cg.onConstant(Variable.NIL);
+            }
+            move(true);
+            return;
+        } else {
+            if (this.scope.newLexicalScope) {
+                cg.onMethodName(Constants.ReducerReturnFn);
+                if (!parseTernary()) {
+                    reportSyntaxError("invalid value for return, missing ';'?");
+                }
+                cg.onMethodParameter(this.lookahead);
+                cg.onMethodInvoke(this.lookahead);
+            } else {
+                if (!parseTernary()) {
+                    reportSyntaxError("invalid value for return, missing ';'?");
+                }
+            }
         }
-        cg.onTernaryRight(this.lookahead);
-      } else {
-        reportSyntaxError("expect ':'");
-      }
+
+        if (!expectChar(';')) {
+            reportSyntaxError("missing ';' for return statement");
+        }
+        move(true);
     }
-    return gcTimes < this.getCGTimes;
-  }
 
+    public boolean parseTernary() {
+        int gcTimes = this.getCGTimes;
 
-  public void parseLogicalOr() {
-    parseLogicalAnd();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('|')) {
-        getCodeGeneratorWithTimes().onJoinLeft(opToken);
-        move(true);
-        if (expectChar('|')) {
-          move(true);
-          parseLogicalAnd();
-          getCodeGeneratorWithTimes().onJoinRight(opToken);
-        } else {
-          reportSyntaxError("expect '|'");
-        }
-      } else {
-        // Process operator alias
-        String alias = this.instance.getOperatorAliasToken(OperatorType.OR);
-        if (alias != null) {
-          if (opToken != null && opToken.getType() == TokenType.Variable
-              && opToken.getLexeme().equals(alias)) {
-            CodeGenerator cg = getCodeGeneratorWithTimes();
-            cg.onJoinLeft(opToken);
+        if (this.lookahead == Variable.NEW) {
+            newStatement();
+            return true;
+        }
+
+        parseLogicalOr();
+        if (this.lookahead == null || expectChar(':') || expectChar(',')) {
+            return gcTimes < this.getCGTimes;
+        }
+        Token opToken = this.lookahead;
+        if (expectChar('?')) {
             move(true);
-            parseLogicalAnd();
-            cg.onJoinRight(opToken);
-            continue;
-          }
+            CodeGenerator cg = getCodeGeneratorWithTimes();
+            cg.onTernaryBoolean(opToken);
+            if (!parseTernary()) {
+                reportSyntaxError("invalid token for ternary operator");
+            }
+            if (expectChar(':')) {
+                move(true);
+                cg.onTernaryLeft(this.lookahead);
+                if (!parseTernary()) {
+                    reportSyntaxError("invalid token for ternary operator");
+                }
+                cg.onTernaryRight(this.lookahead);
+            } else {
+                reportSyntaxError("expect ':'");
+            }
         }
+        return gcTimes < this.getCGTimes;
+    }
+
+
+    public void parseLogicalOr() {
+        parseLogicalAnd();
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('|')) {
+                getCodeGeneratorWithTimes().onJoinLeft(opToken);
+                move(true);
+                if (expectChar('|')) {
+                    move(true);
+                    parseLogicalAnd();
+                    getCodeGeneratorWithTimes().onJoinRight(opToken);
+                } else {
+                    reportSyntaxError("expect '|'");
+                }
+            } else {
+                // Process operator alias
+                String alias = this.instance.getOperatorAliasToken(OperatorType.OR);
+                if (alias != null) {
+                    if (opToken != null && opToken.getType() == TokenType.Variable
+                            && opToken.getLexeme().equals(alias)) {
+                        CodeGenerator cg = getCodeGeneratorWithTimes();
+                        cg.onJoinLeft(opToken);
+                        move(true);
+                        parseLogicalAnd();
+                        cg.onJoinRight(opToken);
+                        continue;
+                    }
+                }
 
-        break;
-      }
+                break;
+            }
 
+        }
     }
-  }
 
 
-  private boolean expectChar(final char ch) {
-    if (this.lookahead == null) {
-      return false;
+    private boolean expectChar(final char ch) {
+        if (this.lookahead == null) {
+            return false;
+        }
+        return this.lookahead.getType() == TokenType.Char && ((CharToken) this.lookahead).getCh() == ch;
     }
-    return this.lookahead.getType() == TokenType.Char && ((CharToken) this.lookahead).getCh() == ch;
-  }
 
 
-  public void parseBitOr() {
-    parseBitXor();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('|')) {
-        move(true);
-        if (expectChar('|')) {
-          back();
-          break;
-        }
+    public void parseBitOr() {
         parseBitXor();
-        getCodeGeneratorWithTimes().onBitOr(opToken);
-      } else {
-        break;
-      }
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('|')) {
+                move(true);
+                if (expectChar('|')) {
+                    back();
+                    break;
+                }
+                parseBitXor();
+                getCodeGeneratorWithTimes().onBitOr(opToken);
+            } else {
+                break;
+            }
+        }
     }
-  }
 
 
-  public void parseBitXor() {
-    parseBitAnd();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('^')) {
-        move(true);
+    public void parseBitXor() {
         parseBitAnd();
-        getCodeGeneratorWithTimes().onBitXor(opToken);
-      } else {
-        break;
-      }
-    }
-  }
-
-
-  public void parseBitAnd() {
-    parseEquality();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('&')) {
-        move(true);
-        if (expectChar('&')) {
-          back();
-          break;
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('^')) {
+                move(true);
+                parseBitAnd();
+                getCodeGeneratorWithTimes().onBitXor(opToken);
+            } else {
+                break;
+            }
         }
-        parseEquality();
-        getCodeGeneratorWithTimes().onBitAnd(opToken);
-      } else {
-        break;
-      }
     }
-  }
-
 
-  public void parseLogicalAnd() {
-    parseBitOr();
-    while (true) {
-      Token opToken = this.lookahead;
 
-      if (expectChar('&')) {
-        CodeGenerator cg = getCodeGeneratorWithTimes();
-        cg.onAndLeft(opToken);
-        move(true);
-        if (expectChar('&')) {
-          move(true);
-          parseBitOr();
-          cg.onAndRight(opToken);
-        } else {
-          reportSyntaxError("expect '&'");
-        }
-      } else {
-        // Process operator alias
-        String alias = this.instance.getOperatorAliasToken(OperatorType.AND);
-        if (alias != null) {
-          if (opToken != null && opToken.getType() == TokenType.Variable
-              && opToken.getLexeme().equals(alias)) {
-            CodeGenerator cg = getCodeGeneratorWithTimes();
-            cg.onAndLeft(opToken);
-            move(true);
-            parseBitOr();
-            cg.onAndRight(opToken);
-            continue;
-          }
+    public void parseBitAnd() {
+        parseEquality();
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('&')) {
+                move(true);
+                if (expectChar('&')) {
+                    back();
+                    break;
+                }
+                parseEquality();
+                getCodeGeneratorWithTimes().onBitAnd(opToken);
+            } else {
+                break;
+            }
         }
-
-        break;
-      }
-
-
     }
 
-  }
-
 
-  public void parseEquality() {
-    parseRelational();
-    while (true) {
-      Token opToken = this.lookahead;
-      Token prevToken = getPrevToken();
-      if (expectChar('=')) {
-        move(true);
-        if (expectChar('=')) {
-          move(true);
-          parseRelational();
-          getCodeGeneratorWithTimes().onEq(opToken);
-        } else if (expectChar('~')) {
-          // It is a regular expression
-          move(true);
-          parseRelational();
-          getCodeGeneratorWithTimes().onMatch(opToken);
-        } else {
-          // this.back();
-          // assignment
-
-          boolean isVar = false;
-          if (prevToken.getType() == TokenType.Variable) {
-            isVar = true;
-          } else if (prevToken.getType() == TokenType.Char
-              && ((CharToken) prevToken).getCh() == ']') {
-            int depth = 1;
-            boolean beginSearch = false;
-            boolean found = false;
-            for (Token t : this.prevTokens) {
-              if (!beginSearch && t == prevToken) {
-                beginSearch = true;
-                continue;
-              }
+    public void parseLogicalAnd() {
+        parseBitOr();
+        while (true) {
+            Token opToken = this.lookahead;
 
-              if (beginSearch && t.getType() == TokenType.Char) {
-                CharToken chToken = (CharToken) t;
-                switch (chToken.getCh()) {
-                  case ']':
-                    depth++;
-                    break;
-                  case '[':
-                    depth--;
-                    break;
+            if (expectChar('&')) {
+                CodeGenerator cg = getCodeGeneratorWithTimes();
+                cg.onAndLeft(opToken);
+                move(true);
+                if (expectChar('&')) {
+                    move(true);
+                    parseBitOr();
+                    cg.onAndRight(opToken);
+                } else {
+                    reportSyntaxError("expect '&'");
                 }
-                if (depth == 0) {
-                  found = true;
-                  continue;
+            } else {
+                // Process operator alias
+                String alias = this.instance.getOperatorAliasToken(OperatorType.AND);
+                if (alias != null) {
+                    if (opToken != null && opToken.getType() == TokenType.Variable
+                            && opToken.getLexeme().equals(alias)) {
+                        CodeGenerator cg = getCodeGeneratorWithTimes();
+                        cg.onAndLeft(opToken);
+                        move(true);
+                        parseBitOr();
+                        cg.onAndRight(opToken);
+                        continue;
+                    }
                 }
 
-              }
-
-              if (found) {
-                if (t.getType() == TokenType.Variable) {
-                  t.withMeta(Constants.TYPE_META, CompileTypes.Array);
-                }
                 break;
-              }
             }
 
-          }
-
-          StatementType stmtType = statement();
 
-          // try to find var(prevToken) in right statement, it's not initialized if presents.
-          if (isVar) {
-            checkVarIsInit(prevToken, stmtType);
-          }
-
-          ensureFeatureEnabled(Feature.Assignment);
-          getCodeGeneratorWithTimes().onAssignment(opToken);
-        }
-      } else if (expectChar('!')) {
-        move(true);
-        if (expectChar('=')) {
-          move(true);
-          parseRelational();
-          getCodeGeneratorWithTimes().onNeq(opToken);
-        } else {
-          reportSyntaxError("expect '='");
         }
-      } else {
-        break;
-      }
-    }
-  }
 
+    }
 
 
-  private void checkVarIsInit(final Token prevToken, StatementType stmtType) {
-    boolean isInit = true;
-    for (Token t : this.prevTokens) {
-      if (t == prevToken) {
-        break;
-      }
-      // It's in right statement, so it's not initialized.
-      if (t.getType() == TokenType.Variable && t.getLexeme().equals(prevToken.getLexeme())) {
-        isInit = false;
-        break;
-      }
+    public void parseEquality() {
+        parseRelational();
+        while (true) {
+            Token opToken = this.lookahead;
+            Token prevToken = getPrevToken();
+            if (expectChar('=')) {
+                move(true);
+                if (expectChar('=')) {
+                    move(true);
+                    parseRelational();
+                    getCodeGeneratorWithTimes().onEq(opToken);
+                } else if (expectChar('~')) {
+                    // It is a regular expression
+                    move(true);
+                    parseRelational();
+                    getCodeGeneratorWithTimes().onMatch(opToken);
+                } else {
+                    // this.back();
+                    // assignment
+
+                    boolean isVar = false;
+                    if (prevToken.getType() == TokenType.Variable) {
+                        isVar = true;
+                    } else if (prevToken.getType() == TokenType.Char
+                            && ((CharToken) prevToken).getCh() == ']') {
+                        int depth = 1;
+                        boolean beginSearch = false;
+                        boolean found = false;
+                        for (Token t : this.prevTokens) {
+                            if (!beginSearch && t == prevToken) {
+                                beginSearch = true;
+                                continue;
+                            }
+
+                            if (beginSearch && t.getType() == TokenType.Char) {
+                                CharToken chToken = (CharToken) t;
+                                switch (chToken.getCh()) {
+                                    case ']':
+                                        depth++;
+                                        break;
+                                    case '[':
+                                        depth--;
+                                        break;
+                                }
+                                if (depth == 0) {
+                                    found = true;
+                                    continue;
+                                }
+
+                            }
+
+                            if (found) {
+                                if (t.getType() == TokenType.Variable) {
+                                    t.withMeta(Constants.TYPE_META, CompileTypes.Array);
+                                }
+                                break;
+                            }
+                        }
+
+                    }
+
+                    StatementType stmtType = statement();
+
+                    // try to find var(prevToken) in right statement, it's not initialized if presents.
+                    if (isVar) {
+                        checkVarIsInit(prevToken, stmtType);
+                    }
+
+                    ensureFeatureEnabled(Feature.Assignment);
+                    getCodeGeneratorWithTimes().onAssignment(opToken);
+                }
+            } else if (expectChar('!')) {
+                move(true);
+                if (expectChar('=')) {
+                    move(true);
+                    parseRelational();
+                    getCodeGeneratorWithTimes().onNeq(opToken);
+                } else {
+                    reportSyntaxError("expect '='");
+                }
+            } else {
+                break;
+            }
+        }
     }
-    prevToken.withMeta(Constants.INIT_META, isInit);
-  }
 
 
-  public void parseRelational() {
-    parseShift();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('<')) {
-        move(true);
-        if (expectChar('=')) {
-          move(true);
-          parseAdditive();
-          getCodeGeneratorWithTimes().onLe(opToken);
-        } else {
-          parseAdditive();
-          getCodeGeneratorWithTimes().onLt(opToken);
+    private void checkVarIsInit(final Token prevToken, StatementType stmtType) {
+        boolean isInit = true;
+        for (Token t : this.prevTokens) {
+            if (t == prevToken) {
+                break;
+            }
+            // It's in right statement, so it's not initialized.
+            if (t.getType() == TokenType.Variable && t.getLexeme().equals(prevToken.getLexeme())) {
+                isInit = false;
+                break;
+            }
         }
-      } else if (expectChar('>')) {
-        move(true);
-        if (expectChar('=')) {
-          move(true);
-          parseAdditive();
-          getCodeGeneratorWithTimes().onGe(opToken);
-        } else {
-          parseAdditive();
-          getCodeGeneratorWithTimes().onGt(opToken);
+        prevToken.withMeta(Constants.INIT_META, isInit);
+    }
+
+
+    public void parseRelational() {
+        parseShift();
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('<')) {
+                move(true);
+                if (expectChar('=')) {
+                    move(true);
+                    parseAdditive();
+                    getCodeGeneratorWithTimes().onLe(opToken);
+                } else {
+                    parseAdditive();
+                    getCodeGeneratorWithTimes().onLt(opToken);
+                }
+            } else if (expectChar('>')) {
+                move(true);
+                if (expectChar('=')) {
+                    move(true);
+                    parseAdditive();
+                    getCodeGeneratorWithTimes().onGe(opToken);
+                } else {
+                    parseAdditive();
+                    getCodeGeneratorWithTimes().onGt(opToken);
+                }
+            } else {
+                break;
+            }
         }
-      } else {
-        break;
-      }
     }
-  }
-
 
-  public void parseShift() {
-    parseAdditive();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('<')) {
-        move(true);
-        if (expectChar('<')) {
-          move(true);
-          parseAdditive();
-          getCodeGeneratorWithTimes().onShiftLeft(opToken);
-        } else {
-          back();
-          break;
-        }
-      } else if (expectChar('>')) {
-        move(true);
-        if (expectChar('>')) {
-          move(true);
-          if (expectChar('>')) {
-            move(true);
-            parseAdditive();
-            getCodeGeneratorWithTimes().onUnsignedShiftRight(opToken);
-          } else {
-            parseAdditive();
-            getCodeGeneratorWithTimes().onShiftRight(opToken);
-          }
 
-        } else {
-          back();
-          break;
+    public void parseShift() {
+        parseAdditive();
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('<')) {
+                move(true);
+                if (expectChar('<')) {
+                    move(true);
+                    parseAdditive();
+                    getCodeGeneratorWithTimes().onShiftLeft(opToken);
+                } else {
+                    back();
+                    break;
+                }
+            } else if (expectChar('>')) {
+                move(true);
+                if (expectChar('>')) {
+                    move(true);
+                    if (expectChar('>')) {
+                        move(true);
+                        parseAdditive();
+                        getCodeGeneratorWithTimes().onUnsignedShiftRight(opToken);
+                    } else {
+                        parseAdditive();
+                        getCodeGeneratorWithTimes().onShiftRight(opToken);
+                    }
+
+                } else {
+                    back();
+                    break;
+                }
+            } else {
+                break;
+            }
         }
-      } else {
-        break;
-      }
     }
-  }
 
 
-  public void parseAdditive() {
-    parseMultiplicative();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('+')) {
-        move(true);
+    public void parseAdditive() {
         parseMultiplicative();
-        getCodeGeneratorWithTimes().onAdd(opToken);
-      } else if (expectChar('-')) {
-        move(true);
-        parseMultiplicative();
-        getCodeGeneratorWithTimes().onSub(opToken);
-      } else {
-        break;
-      }
-    }
-  }
-
-  public void parseExponent() {
-    parseFactor();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('*')) {
-        move(true);
-        if (expectChar('*')) {
-          move(true);
-          parseUnary();
-          getCodeGeneratorWithTimes().onExponent(opToken);
-        } else {
-          back();
-          break;
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('+')) {
+                move(true);
+                parseMultiplicative();
+                getCodeGeneratorWithTimes().onAdd(opToken);
+            } else if (expectChar('-')) {
+                move(true);
+                parseMultiplicative();
+                getCodeGeneratorWithTimes().onSub(opToken);
+            } else {
+                break;
+            }
         }
-      } else {
-        break;
-      }
     }
-  }
 
+    public void parseExponent() {
+        parseFactor();
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('*')) {
+                move(true);
+                if (expectChar('*')) {
+                    move(true);
+                    parseUnary();
+                    getCodeGeneratorWithTimes().onExponent(opToken);
+                } else {
+                    back();
+                    break;
+                }
+            } else {
+                break;
+            }
+        }
+    }
 
-  public void parseMultiplicative() {
-    parseUnary();
-    while (true) {
-      Token opToken = this.lookahead;
-      if (expectChar('*')) {
-        move(true);
-        parseUnary();
-        getCodeGeneratorWithTimes().onMult(opToken);
-      } else if (expectChar('/')) {
-        move(true);
-        parseUnary();
-        getCodeGeneratorWithTimes().onDiv(opToken);
-      } else if (expectChar('%')) {
-        move(true);
+
+    public void parseMultiplicative() {
         parseUnary();
-        getCodeGeneratorWithTimes().onMod(opToken);
-      } else {
-        break;
-      }
+        while (true) {
+            Token opToken = this.lookahead;
+            if (expectChar('*')) {
+                move(true);
+                parseUnary();
+                getCodeGeneratorWithTimes().onMult(opToken);
+            } else if (expectChar('/')) {
+                move(true);
+                parseUnary();
+                getCodeGeneratorWithTimes().onDiv(opToken);
+            } else if (expectChar('%')) {
+                move(true);
+                parseUnary();
+                getCodeGeneratorWithTimes().onMod(opToken);
+            } else {
+                break;
+            }
+        }
     }
-  }
 
 
-  public void parseUnary() {
-    Token opToken = this.lookahead;
-    if (expectChar('!')) {
-      move(true);
-      // check if it is a seq function call,"!" as variable
-      if (expectChar(',') || expectChar(')')) {
-        back();
-        parseExponent();
-      } else {
-        parseUnary();
-        getCodeGeneratorWithTimes().onNot(opToken);
-      }
-    } else if (expectChar('-')) {
-      move(true);
-      // check if it is a seq function call,"!" as variable
-      if (expectChar(',') || expectChar(')')) {
-        back();
-        parseExponent();
-      } else {
-        parseUnary();
-        getCodeGeneratorWithTimes().onNeg(opToken);
-      }
-    } else if (expectChar('~')) {
-      move(true);
-      // check if it is a seq function call,"~" as variable
-      if (expectChar(',') || expectChar(')')) {
-        back();
-        parseExponent();
-      } else {
-        parseUnary();
-        getCodeGeneratorWithTimes().onBitNot(opToken);
-      }
-    } else {
-      parseExponent();
-    }
-  }
-
-  private int getLookaheadStartIndex() {
-    // We should calculate the lookahead token's start index, because the token may be reserved by
-    // symbol table and it's start index is wrong.
-    return this.lookahead != null ? (this.lexer.getCurrentIndex() - getLookaheadLexemeLength())
-        : -1;
-  }
-
-  private int getLookaheadLexemeLength() {
-    int len = this.lookahead.getLexeme().length();
-    if (this.lookahead.getType() == TokenType.String) {
-      // Must include quote symbols.
-      len += 2;
-    }
-    return len;
-  }
-
-  private String getParamExp(final int lastTokenIndex) {
-    if (lastTokenIndex >= 0 && getLookaheadStartIndex() >= 0) {
-      return this.lexer.getScanString().substring(lastTokenIndex, getLookaheadStartIndex());
-    } else {
-      return null;
-    }
-  }
-
-  public static final CharToken LEFT_PAREN = new CharToken('(', 0, -1);
-  public static final CharToken RIGHT_PAREN = new CharToken(')', 0, -1);
-
-
-  public boolean isOPVariable(final Token token) {
-    if (token.getType() != TokenType.Char) {
-      return false;
-    }
-    CharToken charToken = (CharToken) token;
-
-    move(true);
-    if (expectChar(',') || expectChar(')')) {
-      back();
-      String lexeme = String.valueOf(charToken.getCh());
-      if (lexeme.equals("-")) {
-        lexeme = "-sub";
-      }
-      return this.instance.containsFunction(lexeme);
-    } else {
-      back();
-      return false;
-    }
-  }
-
-  public void parseFactor() {
-    if (parseFactor0()) {
-      methodInvokeOrArrayAccess();
-    }
-  }
-
-  /**
-   * @deprecated Use {@link #parseTernary()} instead.
-   */
-  @Deprecated
-  public boolean ternary() {
-    return parseTernary();
-  }
-
-  /**
-   * @deprecated Use {@link #parseLogicalOr()} instead.
-   */
-  @Deprecated
-  public void join() {
-    parseLogicalOr();
-  }
-
-  /**
-   * @deprecated Use {@link #parseBitOr()} instead.
-   */
-  @Deprecated
-  public void bitOr() {
-    parseBitOr();
-  }
-
-  /**
-   * @deprecated Use {@link #parseBitXor()} instead.
-   */
-  @Deprecated
-  public void xor() {
-    parseBitXor();
-  }
-
-  /**
-   * @deprecated Use {@link #parseBitAnd()} instead.
-   */
-  @Deprecated
-  public void bitAnd() {
-    parseBitAnd();
-  }
-
-  /**
-   * @deprecated Use {@link #parseLogicalAnd()} instead.
-   */
-  @Deprecated
-  public void and() {
-    parseLogicalAnd();
-  }
-
-  /**
-   * @deprecated Use {@link #parseEquality()} instead.
-   */
-  @Deprecated
-  public void equality() {
-    parseEquality();
-  }
-
-  /**
-   * @deprecated Use {@link #parseRelational()} instead.
-   */
-  @Deprecated
-  public void rel() {
-    parseRelational();
-  }
-
-  /**
-   * @deprecated Use {@link #parseShift()} instead.
-   */
-  @Deprecated
-  public void shift() {
-    parseShift();
-  }
-
-  /**
-   * @deprecated Use {@link #parseAdditive()} instead.
-   */
-  @Deprecated
-  public void expr() {
-    parseAdditive();
-  }
-
-  /**
-   * @deprecated Use {@link #parseExponent()} instead.
-   */
-  @Deprecated
-  public void exponent() {
-    parseExponent();
-  }
-
-  /**
-   * @deprecated Use {@link #parseMultiplicative()} instead.
-   */
-  @Deprecated
-  public void term() {
-    parseMultiplicative();
-  }
-
-  /**
-   * @deprecated Use {@link #parseUnary()} instead.
-   */
-  @Deprecated
-  public void unary() {
-    parseUnary();
-  }
-
-  /**
-   * @deprecated Use {@link #parseFactor()} instead.
-   */
-  @Deprecated
-  public void factor() {
-    parseFactor();
-  }
-
-
-
-  private boolean parseFactor0() {
-    if (this.lookahead == null) {
-      reportSyntaxError("illegal token");
-    }
-    if (this.lookahead == Variable.END) {
-      return false;
-    }
-    if (expectChar('(')) {
-      move(true);
-      this.scope.enterParen();
-      parseTernary();
-      if (expectChar(')')) {
-        move(true);
-        this.scope.leaveParen();
-      }
-    } else if (this.lookahead.getType() == TokenType.Number
-        || this.lookahead.getType() == TokenType.String
-        || this.lookahead.getType() == TokenType.Variable || this.lookahead == Variable.TRUE
-        || this.lookahead == Variable.FALSE || isOPVariable(this.lookahead)) {
-      if (this.lookahead.getType() == TokenType.Variable) {
-        checkVariableName(this.lookahead);
-      }
-      // binary operation as variable for seq functions
-      if (this.lookahead.getType() == TokenType.Char) {
-        CharToken charToken = (CharToken) this.lookahead;
-        if (!ExpressionLexer.isBinaryOP(charToken.getCh())) {
-          reportSyntaxError("unexpect char '" + charToken.getCh() + "'");
-        }
-        // make it as variable
-        this.lookahead = this.lexer.getSymbolTable().reserve(
-            new Variable(charToken.getLexeme(), charToken.getLineNo(), charToken.getStartIndex()));
-      }
-      move(true);
-      // function
-      Token prev = getPrevToken();
-      if (prev.getType() == TokenType.Variable && expectChar('(')) {
-        if (prev == Variable.LAMBDA) {
-          lambda(false);
-        } else if (prev == Variable.FN) {
-          lambda(true);
+    public void parseUnary() {
+        Token opToken = this.lookahead;
+        if (expectChar('!')) {
+            move(true);
+            // check if it is a seq function call,"!" as variable
+            if (expectChar(',') || expectChar(')')) {
+                back();
+                parseExponent();
+            } else {
+                parseUnary();
+                getCodeGeneratorWithTimes().onNot(opToken);
+            }
+        } else if (expectChar('-')) {
+            move(true);
+            // check if it is a seq function call,"!" as variable
+            if (expectChar(',') || expectChar(')')) {
+                back();
+                parseExponent();
+            } else {
+                parseUnary();
+                getCodeGeneratorWithTimes().onNeg(opToken);
+            }
+        } else if (expectChar('~')) {
+            move(true);
+            // check if it is a seq function call,"~" as variable
+            if (expectChar(',') || expectChar(')')) {
+                back();
+                parseExponent();
+            } else {
+                parseUnary();
+                getCodeGeneratorWithTimes().onBitNot(opToken);
+            }
         } else {
-          method(prev);
-        }
-      } else if (prev.getType() == TokenType.Variable) {
-        if (!arrayAccess()) {
-          getCodeGeneratorWithTimes().onConstant(prev);
-        }
-      } else {
-        getCodeGeneratorWithTimes().onConstant(prev);
-      }
-    } else if (expectChar('/')) {
-      pattern();
-    } else if (expectChar('}')) {
-      return false;
-    } else {
-      reportSyntaxError("invalid token");
-    }
-    return true;
-  }
-
-
-  private void lambda(final boolean fn) {
-    ensureFeatureEnabled(Feature.Lambda);
-    this.scope.enterLambda();
-    getCodeGeneratorWithTimes().onLambdaDefineStart(getPrevToken());
-    this.scope.enterParen();
-    move(true);
-    int paramIndex = 0;
-    FunctionParam lastParam = null;
-    List variadicParams = new ArrayList<>(2);
-    if (!expectChar(')')) {
-      lastParam = lambdaArgument(paramIndex++);
-      if (lastParam.isVariadic()) {
-        variadicParams.add(lastParam);
-      }
-
-      while (expectChar(',')) {
-        move(true);
-        lastParam = lambdaArgument(paramIndex++);
-        if (lastParam.isVariadic()) {
-          variadicParams.add(lastParam);
+            parseExponent();
         }
-      }
     }
 
-    // assert only one variadic param and it's the last one.
-    if (variadicParams.size() > 1) {
-      reportSyntaxError("The variadic parameter must be the last parameter: `"
-          + variadicParams.get(0).getName() + "`");
-    }
-    if (variadicParams.size() > 0 && variadicParams.get(0) != lastParam) {
-      reportSyntaxError("The variadic parameter must be the last parameter: `"
-          + variadicParams.get(0).getName() + "`");
+    private int getLookaheadStartIndex() {
+        // We should calculate the lookahead token's start index, because the token may be reserved by
+        // symbol table and it's start index is wrong.
+        return this.lookahead != null ? (this.lexer.getCurrentIndex() - getLookaheadLexemeLength())
+                : -1;
     }
 
-    if (expectChar(')')) {
-      this.scope.leaveParen();
-      move(true);
-
-      if (fn) {
-        if (!expectChar('{')) {
-          reportSyntaxError("expect '{'");
+    private int getLookaheadLexemeLength() {
+        int len = this.lookahead.getLexeme().length();
+        if (this.lookahead.getType() == TokenType.String) {
+            // Must include quote symbols.
+            len += 2;
         }
-      } else {
-        if (!expectChar('-')) {
-          reportSyntaxError("expect '->' for lambda body");
-        }
-        move(true);
-        if (!expectChar('>')) {
-          reportSyntaxError("expect '->' for lambda body");
-        }
-      }
-
-      move(true);
-      getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead);
-      statements();
+        return len;
+    }
 
-      if (fn) {
-        if (!expectChar('}')) {
-          reportSyntaxError("missing '}' to close function body");
-        }
-      } else {
-        if (this.lookahead != Variable.END) {
-          reportSyntaxError("expect lambda 'end'");
+    private String getParamExp(final int lastTokenIndex) {
+        if (lastTokenIndex >= 0 && getLookaheadStartIndex() >= 0) {
+            return this.lexer.getScanString().substring(lastTokenIndex, getLookaheadStartIndex());
+        } else {
+            return null;
         }
-      }
-
-      getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead);
-      this.scope.leaveLambda();
-      move(true);
     }
-  }
-
-  private String currentTokenLexeme() {
-    return this.lookahead == null ? "END_OF_STRING" : this.lookahead.getLexeme();
-  }
 
+    public static final CharToken LEFT_PAREN = new CharToken('(', 0, -1);
+    public static final CharToken RIGHT_PAREN = new CharToken(')', 0, -1);
 
-  private FunctionParam lambdaArgument(final int index) {
-    if (expectChar('&')) {
-      move(true);
 
-      if (this.lookahead.getType() != TokenType.Variable) {
-        reportSyntaxError("expect argument name, but is: " + currentTokenLexeme());
-      }
+    public boolean isOPVariable(final Token token) {
+        if (token.getType() != TokenType.Char) {
+            return false;
+        }
+        CharToken charToken = (CharToken) token;
 
-      return lambdaArgument0(index, true);
-    } else if (this.lookahead.getType() == TokenType.Variable) {
-      return lambdaArgument0(index, false);
-    } else {
-      reportSyntaxError("expect argument name, but is: " + currentTokenLexeme());
-      return null;
+        move(true);
+        if (expectChar(',') || expectChar(')')) {
+            back();
+            String lexeme = String.valueOf(charToken.getCh());
+            if (lexeme.equals("-")) {
+                lexeme = "-sub";
+            }
+            return this.instance.containsFunction(lexeme);
+        } else {
+            back();
+            return false;
+        }
     }
-  }
 
-  private FunctionParam lambdaArgument0(final int index, final boolean isVariadic) {
-    if (!isJavaIdentifier(this.lookahead.getLexeme())) {
-      reportSyntaxError("illegal argument name: " + currentTokenLexeme());
+    public void parseFactor() {
+        if (parseFactor0()) {
+            methodInvokeOrArrayAccess();
+        }
     }
-    final FunctionParam param = new FunctionParam(index, this.lookahead.getLexeme(), isVariadic);
-    getCodeGeneratorWithTimes().onLambdaArgument(this.lookahead, param);
-    move(true);
-    return param;
-  }
 
-
-  private boolean arrayAccess() {
-    // check if it is a array index access
-    boolean hasArray = false;
-    while (expectChar('[')) {
-      if (!hasArray) {
-        getCodeGeneratorWithTimes().onArray(getPrevToken());
-        move(true);
-        hasArray = true;
-      } else {
-        move(true);
-      }
-      getCodeGeneratorWithTimes().onArrayIndexStart(getPrevToken());
-      array();
+    /**
+     * @deprecated Use {@link #parseTernary()} instead.
+     */
+    @Deprecated
+    public boolean ternary() {
+        return parseTernary();
     }
-    return hasArray;
-
-  }
 
-
-  private void array() {
-    this.scope.enterBracket();
-    Token prev = getPrevToken();
-    if (prev instanceof Variable && ((Variable) prev).isLiteralKeyword()) {
-      reportSyntaxError(prev.getLexeme() + " could not use [] operator");
-    }
-    if (!parseTernary()) {
-      reportSyntaxError("missing index for array access");
-    }
-    if (expectChar(']')) {
-      this.scope.leaveBracket();
-      move(true);
-      getCodeGeneratorWithTimes().onArrayIndexEnd(this.lookahead);
+    /**
+     * @deprecated Use {@link #parseLogicalOr()} instead.
+     */
+    @Deprecated
+    public void join() {
+        parseLogicalOr();
     }
-  }
 
-  private void checkVariableName(final Token token) {
-    if (token.getType() == TokenType.Delegate) {
-      return;
-    }
-    if (!((Variable) token).isQuote()) {
-      String[] names = token.getLexeme().split("\\.", -1);
-      for (int i = 0; i < names.length; i++) {
-        String name = names[i];
-        if (name.isEmpty() && i == names.length - 1) {
-          // Keep compatibility with existing trailing-dot syntax, e.g. "use java.util."
-          continue;
-        }
-        if (!isValidPropertySegment(name)) {
-          reportSyntaxError("illegal identifier: " + name);
-        }
-      }
+    /**
+     * @deprecated Use {@link #parseBitOr()} instead.
+     */
+    @Deprecated
+    public void bitOr() {
+        parseBitOr();
     }
-  }
 
-  private boolean isValidPropertySegment(final String segment) {
-    if (segment == null || segment.isEmpty()) {
-      return false;
+    /**
+     * @deprecated Use {@link #parseBitXor()} instead.
+     */
+    @Deprecated
+    public void xor() {
+        parseBitXor();
     }
 
-    int bracketIdx = segment.indexOf('[');
-    if (bracketIdx < 0) {
-      return isJavaIdentifier(segment);
+    /**
+     * @deprecated Use {@link #parseBitAnd()} instead.
+     */
+    @Deprecated
+    public void bitAnd() {
+        parseBitAnd();
     }
 
-    if (bracketIdx == 0) {
-      return segment.endsWith("]"); // "[0]" format
+    /**
+     * @deprecated Use {@link #parseLogicalAnd()} instead.
+     */
+    @Deprecated
+    public void and() {
+        parseLogicalAnd();
     }
 
-    // "bars[0]" format
-    String baseName = segment.substring(0, bracketIdx);
-    return isJavaIdentifier(baseName) && segment.endsWith("]");
-  }
+    /**
+     * @deprecated Use {@link #parseEquality()} instead.
+     */
+    @Deprecated
+    public void equality() {
+        parseEquality();
+    }
 
-  private void methodInvokeOrArrayAccess() {
-    while (expectChar('[') || expectChar('(')) {
-      if (isConstant(getPrevToken(), this.instance)) {
-        break;
-      }
-      if (expectChar('[')) {
-        arrayAccess();
-      } else if (expectChar('(')) {
-        method(anonymousMethodName());
-      }
+    /**
+     * @deprecated Use {@link #parseRelational()} instead.
+     */
+    @Deprecated
+    public void rel() {
+        parseRelational();
     }
-  }
 
-  private void method(final Token methodName) {
-    if (expectChar('(')) {
-      this.scope.enterParen();
-      checkVariableName(methodName);
-      checkFunctionName(methodName, false);
-      getCodeGeneratorWithTimes().onMethodName(methodName);
-      move(true);
-      int paramIndex = 0;
-      List params = null;
-      boolean unpackArguments = false;
-      if (this.captureFuncArgs) {
-        params = new ArrayList<>();
-      }
-      int lastTokenIndex = getLookaheadStartIndex();
-      if (!expectChar(')')) {
+    /**
+     * @deprecated Use {@link #parseShift()} instead.
+     */
+    @Deprecated
+    public void shift() {
+        parseShift();
+    }
+
+    /**
+     * @deprecated Use {@link #parseAdditive()} instead.
+     */
+    @Deprecated
+    public void expr() {
+        parseAdditive();
+    }
+
+    /**
+     * @deprecated Use {@link #parseExponent()} instead.
+     */
+    @Deprecated
+    public void exponent() {
+        parseExponent();
+    }
+
+    /**
+     * @deprecated Use {@link #parseMultiplicative()} instead.
+     */
+    @Deprecated
+    public void term() {
+        parseMultiplicative();
+    }
+
+    /**
+     * @deprecated Use {@link #parseUnary()} instead.
+     */
+    @Deprecated
+    public void unary() {
+        parseUnary();
+    }
+
+    /**
+     * @deprecated Use {@link #parseFactor()} instead.
+     */
+    @Deprecated
+    public void factor() {
+        parseFactor();
+    }
+
+
+    private boolean parseFactor0() {
+        if (this.lookahead == null) {
+            reportSyntaxError("illegal token");
+        }
+        if (this.lookahead == Variable.END) {
+            return false;
+        }
+        if (expectChar('(')) {
+            move(true);
+            this.scope.enterParen();
+            parseTernary();
+            if (expectChar(')')) {
+                move(true);
+                this.scope.leaveParen();
+            }
+        } else if (this.lookahead.getType() == TokenType.Number
+                || this.lookahead.getType() == TokenType.String
+                || this.lookahead.getType() == TokenType.Variable || this.lookahead == Variable.TRUE
+                || this.lookahead == Variable.FALSE || isOPVariable(this.lookahead)) {
+            if (this.lookahead.getType() == TokenType.Variable) {
+                checkVariableName(this.lookahead);
+            }
+            // binary operation as variable for seq functions
+            if (this.lookahead.getType() == TokenType.Char) {
+                CharToken charToken = (CharToken) this.lookahead;
+                if (!ExpressionLexer.isBinaryOP(charToken.getCh())) {
+                    reportSyntaxError("unexpect char '" + charToken.getCh() + "'");
+                }
+                // make it as variable
+                this.lookahead = this.lexer.getSymbolTable().reserve(
+                        new Variable(charToken.getLexeme(), charToken.getLineNo(), charToken.getStartIndex()));
+            }
+            move(true);
+            // function
+            Token prev = getPrevToken();
+            if (prev.getType() == TokenType.Variable && expectChar('(')) {
+                if (prev == Variable.LAMBDA) {
+                    lambda(false);
+                } else if (prev == Variable.FN) {
+                    lambda(true);
+                } else {
+                    method(prev);
+                }
+            } else if (prev.getType() == TokenType.Variable) {
+                if (!arrayAccess()) {
+                    getCodeGeneratorWithTimes().onConstant(prev);
+                }
+            } else {
+                getCodeGeneratorWithTimes().onConstant(prev);
+            }
+        } else if (expectChar('/')) {
+            pattern();
+        } else if (expectChar('}')) {
+            return false;
+        } else {
+            reportSyntaxError("invalid token");
+        }
+        return true;
+    }
+
+
+    private void lambda(final boolean fn) {
+        ensureFeatureEnabled(Feature.Lambda);
+        this.scope.enterLambda();
+        getCodeGeneratorWithTimes().onLambdaDefineStart(getPrevToken());
+        this.scope.enterParen();
+        move(true);
+        int paramIndex = 0;
+        FunctionParam lastParam = null;
+        List variadicParams = new ArrayList<>(2);
+        if (!expectChar(')')) {
+            lastParam = lambdaArgument(paramIndex++);
+            if (lastParam.isVariadic()) {
+                variadicParams.add(lastParam);
+            }
+
+            while (expectChar(',')) {
+                move(true);
+                lastParam = lambdaArgument(paramIndex++);
+                if (lastParam.isVariadic()) {
+                    variadicParams.add(lastParam);
+                }
+            }
+        }
+
+        // assert only one variadic param and it's the last one.
+        if (variadicParams.size() > 1) {
+            reportSyntaxError("The variadic parameter must be the last parameter: `"
+                    + variadicParams.get(0).getName() + "`");
+        }
+        if (variadicParams.size() > 0 && variadicParams.get(0) != lastParam) {
+            reportSyntaxError("The variadic parameter must be the last parameter: `"
+                    + variadicParams.get(0).getName() + "`");
+        }
+
+        if (expectChar(')')) {
+            this.scope.leaveParen();
+            move(true);
+
+            if (fn) {
+                if (!expectChar('{')) {
+                    reportSyntaxError("expect '{'");
+                }
+            } else {
+                if (!expectChar('-')) {
+                    reportSyntaxError("expect '->' for lambda body");
+                }
+                move(true);
+                if (!expectChar('>')) {
+                    reportSyntaxError("expect '->' for lambda body");
+                }
+            }
+
+            move(true);
+            getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead);
+            statements();
+
+            if (fn) {
+                if (!expectChar('}')) {
+                    reportSyntaxError("missing '}' to close function body");
+                }
+            } else {
+                if (this.lookahead != Variable.END) {
+                    reportSyntaxError("expect lambda 'end'");
+                }
+            }
+
+            getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead);
+            this.scope.leaveLambda();
+            move(true);
+        }
+    }
+
+    private String currentTokenLexeme() {
+        return this.lookahead == null ? "END_OF_STRING" : this.lookahead.getLexeme();
+    }
+
+
+    private FunctionParam lambdaArgument(final int index) {
+        if (expectChar('&')) {
+            move(true);
+
+            if (this.lookahead.getType() != TokenType.Variable) {
+                reportSyntaxError("expect argument name, but is: " + currentTokenLexeme());
+            }
+
+            return lambdaArgument0(index, true);
+        } else if (this.lookahead.getType() == TokenType.Variable) {
+            return lambdaArgument0(index, false);
+        } else {
+            reportSyntaxError("expect argument name, but is: " + currentTokenLexeme());
+            return null;
+        }
+    }
+
+    private FunctionParam lambdaArgument0(final int index, final boolean isVariadic) {
+        if (!isJavaIdentifier(this.lookahead.getLexeme())) {
+            reportSyntaxError("illegal argument name: " + currentTokenLexeme());
+        }
+        final FunctionParam param = new FunctionParam(index, this.lookahead.getLexeme(), isVariadic);
+        getCodeGeneratorWithTimes().onLambdaArgument(this.lookahead, param);
+        move(true);
+        return param;
+    }
+
+
+    private boolean arrayAccess() {
+        // check if it is a array index access
+        boolean hasArray = false;
+        while (expectChar('[')) {
+            if (!hasArray) {
+                getCodeGeneratorWithTimes().onArray(getPrevToken());
+                move(true);
+                hasArray = true;
+            } else {
+                move(true);
+            }
+            getCodeGeneratorWithTimes().onArrayIndexStart(getPrevToken());
+            array();
+        }
+        return hasArray;
+
+    }
+
+
+    private void array() {
+        this.scope.enterBracket();
+        Token prev = getPrevToken();
+        if (prev instanceof Variable && ((Variable) prev).isLiteralKeyword()) {
+            reportSyntaxError(prev.getLexeme() + " could not use [] operator");
+        }
+        if (!parseTernary()) {
+            reportSyntaxError("missing index for array access");
+        }
+        if (expectChar(']')) {
+            this.scope.leaveBracket();
+            move(true);
+            getCodeGeneratorWithTimes().onArrayIndexEnd(this.lookahead);
+        }
+    }
+
+    private void checkVariableName(final Token token) {
+        if (token.getType() == TokenType.Delegate) {
+            return;
+        }
+        if (!((Variable) token).isQuote()) {
+            String[] names = token.getLexeme().split("\\.", -1);
+            for (int i = 0; i < names.length; i++) {
+                String name = names[i];
+                if (name.isEmpty() && i == names.length - 1) {
+                    // Keep compatibility with existing trailing-dot syntax, e.g. "use java.util."
+                    continue;
+                }
+                if (!isValidPropertySegment(name)) {
+                    reportSyntaxError("illegal identifier: " + name);
+                }
+            }
+        }
+    }
+
+    private boolean isValidPropertySegment(final String segment) {
+        if (segment == null || segment.isEmpty()) {
+            return false;
+        }
+
+        int bracketIdx = segment.indexOf('[');
+        if (bracketIdx < 0) {
+            return isJavaIdentifier(segment);
+        }
+
+        if (bracketIdx == 0) {
+            return segment.endsWith("]"); // "[0]" format
+        }
+
+        // "bars[0]" format
+        String baseName = segment.substring(0, bracketIdx);
+        return isJavaIdentifier(baseName) && segment.endsWith("]");
+    }
+
+    private void methodInvokeOrArrayAccess() {
+        while (expectChar('[') || expectChar('(')) {
+            if (isConstant(getPrevToken(), this.instance)) {
+                break;
+            }
+            if (expectChar('[')) {
+                arrayAccess();
+            } else if (expectChar('(')) {
+                method(anonymousMethodName());
+            }
+        }
+    }
+
+    private void method(final Token methodName) {
+        if (expectChar('(')) {
+            this.scope.enterParen();
+            checkVariableName(methodName);
+            checkFunctionName(methodName, false);
+            getCodeGeneratorWithTimes().onMethodName(methodName);
+            move(true);
+            int paramIndex = 0;
+            List params = null;
+            boolean unpackArguments = false;
+            if (this.captureFuncArgs) {
+                params = new ArrayList<>();
+            }
+            int lastTokenIndex = getLookaheadStartIndex();
+            if (!expectChar(')')) {
+
+                boolean isPackArgs = false;
+                if (expectChar('*')) {
+                    move(true);
+                    if (expectChar('*') || expectChar(',')) {
+                        // binary operation as argument
+                        back();
+                    } else {
+                        unpackArguments = true;
+                        withMetaBegin();
+                        isPackArgs = true;
+                    }
+                }
+
+                parseTernary();
+
+                if (isPackArgs) {
+                    withMetaEnd(Constants.UNPACK_ARGS, true);
+                }
+
+                getCodeGeneratorWithTimes().onMethodParameter(this.lookahead);
+                if (this.captureFuncArgs) {
+                    params.add(new FunctionArgument(paramIndex++, getParamExp(lastTokenIndex)));
+                }
+                while (expectChar(',')) {
+                    move(true);
+                    isPackArgs = false;
+                    lastTokenIndex = getLookaheadStartIndex();
+                    if (expectChar('*')) {
+                        move(true);
+                        if (expectChar('*') || expectChar(',')) {
+                            // binary operation as argument
+                            back();
+                        } else {
+                            unpackArguments = true;
+                            withMetaBegin();
+                            isPackArgs = true;
+                        }
+                    }
+
+                    if (!parseTernary()) {
+                        reportSyntaxError("invalid argument");
+                    }
+
+                    if (isPackArgs) {
+                        withMetaEnd(Constants.UNPACK_ARGS, true);
+                    }
+
+                    getCodeGeneratorWithTimes().onMethodParameter(this.lookahead);
+                    if (this.captureFuncArgs) {
+                        params.add(new FunctionArgument(paramIndex++, getParamExp(lastTokenIndex)));
+                    }
+                }
+            }
+            if (unpackArguments) {
+                methodName.withMeta(Constants.UNPACK_ARGS, true);
+            }
+            if (expectChar(')')) {
+                getCodeGeneratorWithTimes()
+                        .onMethodInvoke(currentToken().withMeta(Constants.PARAMS_META, params));
+                move(true);
+                this.scope.leaveParen();
+            }
+        }
+    }
+
+
+    /**
+     * Test whether a given string is a valid Java identifier.
+     *
+     * @param id string which should be checked
+     * @return true if a valid identifier
+     */
+    public static final boolean isJavaIdentifier(final String id) {
+        if (id == null) {
+            return false;
+        }
+
+        if (id.equals("")) {
+            return false;
+        }
+
+        if (!java.lang.Character.isJavaIdentifierStart(id.charAt(0))) {
+            return false;
+        }
+
+        for (int i = 1; i < id.length(); i++) {
+            if (!java.lang.Character.isJavaIdentifierPart(id.charAt(i))) {
+                return false;
+            }
+        }
+        if (id.equals("null")) {
+            return false;
+        }
+        return true;
+    }
+
+
+    private void pattern() {
+        // It is a pattern
+        int startIndex = this.lookahead.getStartIndex();
+        move(true);
+        this.inPattern = true;
+        StringBuilder sb = new StringBuilder();
+        while (this.lookahead != null) {
+            while (!expectChar('/') && this.lookahead != null) {
+                sb.append(this.lookahead.getLexeme());
+                move(false);
+            }
+            if (getPrevToken().getType() == TokenType.Char
+                    && ((CharToken) getPrevToken()).getLexeme().equals("\\")) {
+                sb.append("/");
+                move(false);
+                continue;
+            }
+            this.inPattern = false;
+            break;
+        }
+        if (this.inPattern) {
+            reportSyntaxError("invalid regular pattern:" + sb.toString());
+        }
+        getCodeGeneratorWithTimes()
+                .onConstant(new PatternToken(sb.toString(), this.lexer.getLineNo(), startIndex));
+        move(true);
+    }
+
+
+    public void reportSyntaxError(final String message) {
+        int index = isValidLookahead() ? this.lookahead.getStartIndex() : this.lexer.getCurrentIndex();
+
+        if (this.lookahead != null) {
+            this.lexer.pushback(this.lookahead);
+        }
+
+        String msg = "Syntax error: " + message + //
+                " at " + index + //
+                ", lineNumber: " + this.lexer.getLineNo() + //
+                ", token : " + //
+                this.lookahead + ",\nwhile parsing expression: `\n" + //
+                this.lexer.getScanString() + "^^^\n`";
+
+        ExpressionSyntaxErrorException e = new ExpressionSyntaxErrorException(msg);
+        StackTraceElement[] traces = e.getStackTrace();
+        List filteredTraces = new ArrayList<>();
+        for (StackTraceElement t : traces) {
+            if (!this.instance.getOptionValue(Options.TRACE_EVAL).bool
+                    && t.getClassName().equals(this.getClass().getName())) {
+                continue;
+            }
+            filteredTraces.add(t);
+        }
+        e.setStackTrace(filteredTraces.toArray(new StackTraceElement[filteredTraces.size()]));
+        throw e;
+    }
+
+    private boolean isValidLookahead() {
+        return this.lookahead != null && this.lookahead.getStartIndex() > 0;
+    }
+
+    public void move(final boolean analyse) {
+        if (this.lookahead != null) {
+            this.prevTokens.push(this.lookahead);
+            // Limit memory usage by removing oldest tokens
+            if (this.prevTokens.size() > MAX_PREV_TOKENS) {
+                this.prevTokens.pollLast();
+            }
+            this.lookahead = this.lexer.scan(analyse);
+            if (this.lookahead != null) {
+                this.parsedTokens++;
+            }
+        } else {
+            reportSyntaxError("illegal token");
+        }
+    }
+
+    public int getParsedTokens() {
+        return this.parsedTokens;
+    }
+
+    public void back() {
+        if (this.lookahead != null) {
+            this.parsedTokens--;
+        }
+        this.lexer.pushback(this.lookahead);
+        this.lookahead = getPrevToken();
+    }
+
+
+    public Expression parse(final boolean reportErrorIfNotEOF) {
+        /**
+         * 不断从lexer那里拿单词(token)它会不断地调用 {codeGenerator} 的方法
+         * 当这一行运行完时,ASM 已经把大部分的字节码指令写到内存的缓冲区里了。
+         */
+        StatementType statementType = statements();
+        if (this.lookahead != null && reportErrorIfNotEOF) {
+            if (statementType == StatementType.Ternary) {
+                reportSyntaxError("unexpect token '" + currentTokenLexeme()
+                        + "', maybe forget to insert ';' to complete last expression ");
+            } else {
+                reportSyntaxError("unexpect token '" + currentTokenLexeme() + "'");
+            }
+        }
+        /**
+         * 打扫战场:写入 RETURN 字节码指令,完成方法的收尾。
+         * 化虚为实:调用 ClassWriter.toByteArray() 拿到二进制字节流。
+         * 点石成金:
+         *      通过 AviatorClassLoader 把这串字节流变成一个真正的 Java 类(Class)。
+         *      通过 clazz.getConstructor().newInstance() 反射实例化。
+         */
+        return getCodeGeneratorWithTimes().getResult(true);
+    }
+
+
+    public Expression parse() {
+        return parse(true);
+    }
+
+    static enum StatementType {
+        Ternary, Return, Empty, Other
+    }
+
+    /**
+     * Call __reducer_break()
+     */
+    private void breakStatement() {
+        if (!this.scope.newLexicalScope) {
+            reportSyntaxError("break only can be used in for-loop");
+        }
+        move(true);
+        getCodeGeneratorWithTimes().onMethodName(Constants.ReducerBreakFn);
+        getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead);
+        if (!expectChar(';')) {
+            reportSyntaxError("missing ';' for break");
+        }
+        move(true);
+    }
+
+    /**
+     * Call __reducer_cont(nil)
+     */
+    private void continueStatement() {
+        if (!this.scope.newLexicalScope) {
+            reportSyntaxError("continue only can be used in for-loop");
+        }
+        move(true);
+        getCodeGeneratorWithTimes().onMethodName(Constants.ReducerContFn);
+        getCodeGeneratorWithTimes().onConstant(Variable.NIL);
+        getCodeGeneratorWithTimes().onMethodParameter(this.lookahead);
+        getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead);
+        if (!expectChar(';')) {
+            reportSyntaxError("missing ';' for continue");
+        }
+        move(true);
+    }
+
+
+    /**
+     * 
+     *  while(test) {
+     *     ...body...
+     *  }
+     *  ...statements...
+     * 
+ * + * ===> + * + *
+     *  __reducer_callcc(__reducer_loop, lambda() ->
+     *       if(test) {
+     *          ...body...
+     *       }else {
+     *          break;
+     *       }
+     *  end, lambda()- >
+     *       ...statements...
+     *  end);
+     * 
+ */ + private void whileStatement() { + move(true); + + // prepare to call __reducer_callcc(LOOP, iterator, statements) + getCodeGeneratorWithTimes().onMethodName(Constants.ReducerFn); + getCodeGeneratorWithTimes().onConstant(Constants.REDUCER_LOOP); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + + // create a lambda function wraps while body(iterator) + boolean newLexicalScope = this.scope.newLexicalScope; + this.scope.newLexicalScope = true; + { + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + ifStatement(true, false); + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + getCodeGenerator().onMethodParameter(this.lookahead); + } + + if (expectChar(';')) { + // the statement is ended. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + // create a lambda function wraps statements after while(statements) + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, true)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + if (statements() == StatementType.Empty) { + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + } + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + + // call __reducer_callcc(LOOP, iterator, statements) + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + // restore newLexicalScope + this.scope.newLexicalScope = newLexicalScope; + + } + + private void letStatement() { + move(true); + Token var = this.lookahead; + checkVariableName(var); + getCodeGenerator().onConstant(var); + move(true); + if (!expectChar('=')) { + reportSyntaxError("expect '='"); + } + move(true); + StatementType stmtType = statement(); + if (stmtType == StatementType.Empty) { + reportSyntaxError("invalid value to define"); + } + checkVarIsInit(var, stmtType); + ensureFeatureEnabled(Feature.Assignment); + getCodeGeneratorWithTimes().onAssignment(currentToken().withMeta(Constants.DEFINE_META, true)); + if (!expectChar(';')) { + reportSyntaxError("missing ';' for let statement"); + } + move(true); + } + + private void fnStatement() { + move(true); + + if (expectChar('(')) { + // Anonymous function + lambda(true); + } else { + + checkVariableName(this.lookahead); + checkFunctionName(this.lookahead, true); + getCodeGeneratorWithTimes().onConstant(this.lookahead.withMeta(Constants.INIT_META, true) + .withMeta(Constants.TYPE_META, CompileTypes.Function)); + move(true); + if (!expectChar('(')) { + reportSyntaxError("expect '(' after function name"); + } + lambda(true); + ensureFeatureEnabled(Feature.Assignment); + getCodeGeneratorWithTimes() + .onAssignment(currentToken().withMeta(Constants.DEFINE_META, true)); + } + } + + private void checkFunctionName(final Token token, final boolean warnOnExists) { + String fnName = token.getLexeme(); + if (SymbolTable.isReservedKeyword(fnName)) { + reportSyntaxError("The function name `" + fnName + "` is a reserved keyword"); + } + if (warnOnExists && this.instance.getFuncMap().containsKey(fnName)) { + System.out.println("[Aviator WARN] The function '" + fnName + + "' is already exists, but is replaced with new one."); + } + } + + private Token currentToken() { + Token token = this.lookahead; + if (token == null) { + token = new CharToken((char) -1, this.lexer.getLineNo(), this.lexer.getCurrentIndex()); + } + return token; + } + + + private boolean scopeStatement() { + /** + *
+         *   {
+         *    ...body...
+         *   }
+         *   ...statements...
+         * 
+ * + * => + * + *
+         *  __if_callcc((lambda() -> ...body... end)(),  lambda() ->
+         *     ...statements...
+         *  end);
+         * 
+ */ + + boolean hasReturn = false; + boolean newLexicalScope = this.scope.newLexicalScope; + this.scope.newLexicalScope = true; + // prepare to call __if_callcc(result, statements) + getCodeGeneratorWithTimes().onMethodName(Constants.IfReturnFn); + + // Create a lambda to wrap the scope body. + { + move(true); + this.scope.enterBrace(); + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + hasReturn = statements() == StatementType.Return; + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + getCodeGeneratorWithTimes().onMethodName(anonymousMethodName()); + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + + if (!expectChar('}')) { + reportSyntaxError("missing '}' to close scope"); + } + move(true); + this.scope.leaveBrace(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + + if (expectChar(';')) { + // the statement is ended. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + // create a lambda function wraps statements after scope statement (statements) + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, true)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + if (statements() == StatementType.Empty) { + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + } + getCodeGenerator().onMethodParameter(this.lookahead); + + // call __if_callcc(result, statements) + getCodeGenerator().onMethodInvoke(this.lookahead); + this.scope.newLexicalScope = newLexicalScope; + + return hasReturn; + } + + private void tryStatement() { + getCodeGeneratorWithTimes().onMethodName(Constants.TRY_VAR); + move(true); + if (!expectChar('{')) { + reportSyntaxError("expect '{' after try"); + } + move(true); + + boolean newLexicalScope = this.scope.newLexicalScope; + this.scope.newLexicalScope = true; + // create a lambda function wraps try body + { + + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + statements(); + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + if (!expectChar('}')) { + reportSyntaxError("missing '}' for try body"); + } + move(true); + boolean hasCatch = false; + boolean hasFinally = false; + + while (this.lookahead == Variable.CATCH) { + if (!hasCatch) { + // create a handler list. + getCodeGeneratorWithTimes().onMethodName(Constants.SEQ_LIST_VAR); + hasCatch = true; + } - boolean isPackArgs = false; - if (expectChar('*')) { - move(true); - if (expectChar('*') || expectChar(',')) { - // binary operation as argument - back(); - } else { - unpackArguments = true; - withMetaBegin(); - isPackArgs = true; - } + move(true); + // create a lambda function wraps catch handlers + if (!expectChar('(')) { + reportSyntaxError("expect '(' after catch"); + } + move(true); + if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { + reportSyntaxError("invalid exception class name"); + } + checkVariableName(this.lookahead); + List> exceptionClasses = new ArrayList<>(); + exceptionClasses.add(this.lookahead); + move(true); + + Token boundVar = null; + if (expectChar(')')) { + // catch(e) to catch all. + boundVar = exceptionClasses.remove(0); + exceptionClasses.add(Constants.THROWABLE_VAR); + } else { + // catch multi exception + while (expectChar('|')) { + move(true); + if (this.lookahead.getType() != TokenType.Variable) { + reportSyntaxError("invalid exception class to catch"); + } + checkVariableName(this.lookahead); + exceptionClasses.add(this.lookahead); + move(true); + } + if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { + reportSyntaxError("invalid bound variable name for exception"); + } + checkVariableName(this.lookahead); + boundVar = this.lookahead; + move(true); + } + + if (!expectChar(')')) { + reportSyntaxError("missing ')' for catch caluse"); + } + move(true); + + if (!expectChar('{')) { + reportSyntaxError("missing '{' for catch block"); + } + move(true); + { + // create a catch handler + getCodeGeneratorWithTimes().onMethodName(Constants.CATCH_HANDLER_VAR); + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaArgument(boundVar, + new FunctionParam(0, boundVar.getLexeme(), false)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + statements(); + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + for (Token exceptionClass : exceptionClasses) { + getCodeGeneratorWithTimes().onConstant(exceptionClass); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + } + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + if (!expectChar('}')) { + reportSyntaxError("missing '}' for to complete catch block"); + } + move(true); + } + + if (hasCatch) { + // Invoke seq.list to create handler list + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } else { + getCodeGeneratorWithTimes().onConstant(Variable.NIL); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + + if (this.lookahead == Variable.FINALLY) { + hasFinally = true; + move(true); + if (!expectChar('{')) { + reportSyntaxError("missing '{' for finally block"); + } + move(true); + // create a lambda to + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + statements(); + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + if (!expectChar('}')) { + reportSyntaxError("missing '}' for finally block"); + } + move(true); + } else { + getCodeGeneratorWithTimes().onConstant(Variable.NIL); } - parseTernary(); + if (!hasCatch && !hasFinally) { + reportSyntaxError("missing catch or finally blocks for catch"); + } - if (isPackArgs) { - withMetaEnd(Constants.UNPACK_ARGS, true); + if (expectChar(';')) { + // The statement is ended. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + // create a lambda function wraps statements after try..catch + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, true)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + if (statements() == StatementType.Empty) { + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); } getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - if (this.captureFuncArgs) { - params.add(new FunctionArgument(paramIndex++, getParamExp(lastTokenIndex))); - } - while (expectChar(',')) { - move(true); - isPackArgs = false; - lastTokenIndex = getLookaheadStartIndex(); - if (expectChar('*')) { - move(true); - if (expectChar('*') || expectChar(',')) { - // binary operation as argument - back(); - } else { - unpackArguments = true; - withMetaBegin(); - isPackArgs = true; - } - } - - if (!parseTernary()) { - reportSyntaxError("invalid argument"); - } - - if (isPackArgs) { - withMetaEnd(Constants.UNPACK_ARGS, true); - } - - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - if (this.captureFuncArgs) { - params.add(new FunctionArgument(paramIndex++, getParamExp(lastTokenIndex))); - } - } - } - if (unpackArguments) { - methodName.withMeta(Constants.UNPACK_ARGS, true); - } - if (expectChar(')')) { + + this.scope.newLexicalScope = newLexicalScope; + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + } + + private void throwStatement() { + getCodeGeneratorWithTimes().onMethodName(Constants.THROW_VAR); + move(true); + statement(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + if (!expectChar(';')) { + reportSyntaxError("missing ';' for throw statement"); + } + } + + private void newStatement() { + ensureFeatureEnabled(Feature.NewInstance); + getCodeGeneratorWithTimes().onMethodName(Constants.NEW_VAR); + move(true); + + if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { + reportSyntaxError("invalid class name"); + } + checkVariableName(this.lookahead); getCodeGeneratorWithTimes() - .onMethodInvoke(currentToken().withMeta(Constants.PARAMS_META, params)); + .onConstant(this.lookahead.withMeta(Constants.TYPE_META, CompileTypes.Class)); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + move(true); + + if (!expectChar('(')) { + reportSyntaxError("missing '(' after class name"); + } + + this.scope.enterParen(); + move(true); + if (!expectChar(')')) { + parseTernary(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + while (expectChar(',')) { + move(true); + if (!parseTernary()) { + reportSyntaxError("invalid argument"); + } + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + } + if (!expectChar(')')) { + reportSyntaxError("missing ')' for new statement"); + } + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); move(true); this.scope.leaveParen(); - } } - } + + private void className() { + if (this.lookahead.getType() != TokenType.Variable && !expectChar('*')) { + reportSyntaxError("expect variable name or * to use"); + } + if (expectChar('*')) { + wildcard(); + } else { + checkVariableName(this.lookahead); + getCodeGenerator().onConstant(this.lookahead.withMeta(Constants.USE_CLASS_PKG, true)); + } + move(true); + } + + private void useStatement() { + getCodeGeneratorWithTimes().onMethodName(Constants.USE_VAR); + move(true); + className(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + + if (expectChar('*')) { + // wildcard + wildcard(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + move(true); + } else if (expectChar('{')) { + this.scope.enterBrace(); + move(true); + className(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + while (expectChar(',')) { + move(true); + className(); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + if (!expectChar('}')) { + reportSyntaxError("expect '}' to complete use statement"); + } else { + move(true); + this.scope.leaveBrace(); + } + } + + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + if (!expectChar(';')) { + reportSyntaxError("missing ';' for use statement"); + } + } - /** - * Test whether a given string is a valid Java identifier. - * - * @param id string which should be checked - * @return true if a valid identifier - */ - public static final boolean isJavaIdentifier(final String id) { - if (id == null) { - return false; + private void wildcard() { + getCodeGenerator() + .onConstant(new Variable("*", this.lookahead.getLineNo(), this.lookahead.getStartIndex())); } - if (id.equals("")) { - return false; + private StatementType statement() { + if (this.lookahead == Variable.IF) { + ensureFeatureEnabled(Feature.If); + if (ifStatement(false, false)) { + return StatementType.Return; + } else { + return StatementType.Other; + } + } else if (this.lookahead == Variable.FOR) { + ensureFeatureEnabled(Feature.ForLoop); + forStatement(); + return StatementType.Other; + } else if (this.lookahead == Variable.RETURN) { + ensureFeatureEnabled(Feature.Return); + returnStatement(); + return StatementType.Return; + } else if (this.lookahead == Variable.BREAK) { + breakStatement(); + return StatementType.Return; + } else if (this.lookahead == Variable.CONTINUE) { + continueStatement(); + return StatementType.Return; + } else if (this.lookahead == Variable.LET) { + ensureFeatureEnabled(Feature.Let); + letStatement(); + return StatementType.Other; + } else if (this.lookahead == Variable.WHILE) { + ensureFeatureEnabled(Feature.WhileLoop); + whileStatement(); + return StatementType.Other; + } else if (this.lookahead == Variable.FN) { + ensureFeatureEnabled(Feature.Fn); + fnStatement(); + return StatementType.Other; + } else if (this.lookahead == Variable.TRY) { + ensureFeatureEnabled(Feature.ExceptionHandle); + tryStatement(); + return StatementType.Other; + } else if (this.lookahead == Variable.THROW) { + ensureFeatureEnabled(Feature.ExceptionHandle); + throwStatement(); + return StatementType.Other; + } else if (expectChar('{')) { + ensureFeatureEnabled(Feature.LexicalScope); + if (scopeStatement()) { + return StatementType.Return; + } else { + return StatementType.Other; + } + } else if (this.lookahead == Variable.USE) { + ensureFeatureEnabled(Feature.Use); + useStatement(); + return StatementType.Other; + } else { + if (parseTernary()) { + return StatementType.Ternary; + } else { + return StatementType.Empty; + } + } } - if (!java.lang.Character.isJavaIdentifierStart(id.charAt(0))) { - return false; + private void withMetaBegin() { + getCodeGeneratorWithTimes().onMethodName(Constants.WithMetaFn); } - for (int i = 1; i < id.length(); i++) { - if (!java.lang.Character.isJavaIdentifierPart(id.charAt(i))) { - return false; - } - } - if (id.equals("null")) { - return false; - } - return true; - } - - - private void pattern() { - // It is a pattern - int startIndex = this.lookahead.getStartIndex(); - move(true); - this.inPattern = true; - StringBuilder sb = new StringBuilder(); - while (this.lookahead != null) { - while (!expectChar('/') && this.lookahead != null) { - sb.append(this.lookahead.getLexeme()); - move(false); - } - if (getPrevToken().getType() == TokenType.Char - && ((CharToken) getPrevToken()).getLexeme().equals("\\")) { - sb.append("/"); - move(false); - continue; - } - this.inPattern = false; - break; - } - if (this.inPattern) { - reportSyntaxError("invalid regular pattern:" + sb.toString()); - } - getCodeGeneratorWithTimes() - .onConstant(new PatternToken(sb.toString(), this.lexer.getLineNo(), startIndex)); - move(true); - } - - - public void reportSyntaxError(final String message) { - int index = isValidLookahead() ? this.lookahead.getStartIndex() : this.lexer.getCurrentIndex(); - - if (this.lookahead != null) { - this.lexer.pushback(this.lookahead); - } - - String msg = "Syntax error: " + message + // - " at " + index + // - ", lineNumber: " + this.lexer.getLineNo() + // - ", token : " + // - this.lookahead + ",\nwhile parsing expression: `\n" + // - this.lexer.getScanString() + "^^^\n`"; - - ExpressionSyntaxErrorException e = new ExpressionSyntaxErrorException(msg); - StackTraceElement[] traces = e.getStackTrace(); - List filteredTraces = new ArrayList<>(); - for (StackTraceElement t : traces) { - if (!this.instance.getOptionValue(Options.TRACE_EVAL).bool - && t.getClassName().equals(this.getClass().getName())) { - continue; - } - filteredTraces.add(t); - } - e.setStackTrace(filteredTraces.toArray(new StackTraceElement[filteredTraces.size()])); - throw e; - } - - private boolean isValidLookahead() { - return this.lookahead != null && this.lookahead.getStartIndex() > 0; - } - - public void move(final boolean analyse) { - if (this.lookahead != null) { - this.prevTokens.push(this.lookahead); - // Limit memory usage by removing oldest tokens - if (this.prevTokens.size() > MAX_PREV_TOKENS) { - this.prevTokens.pollLast(); - } - this.lookahead = this.lexer.scan(analyse); - if (this.lookahead != null) { - this.parsedTokens++; - } - } else { - reportSyntaxError("illegal token"); - } - } - - public int getParsedTokens() { - return this.parsedTokens; - } - - public void back() { - if (this.lookahead != null) { - this.parsedTokens--; - } - this.lexer.pushback(this.lookahead); - this.lookahead = getPrevToken(); - } - - - public Expression parse(final boolean reportErrorIfNotEOF) { - StatementType statementType = statements(); - if (this.lookahead != null && reportErrorIfNotEOF) { - if (statementType == StatementType.Ternary) { - reportSyntaxError("unexpect token '" + currentTokenLexeme() - + "', maybe forget to insert ';' to complete last expression "); - } else { - reportSyntaxError("unexpect token '" + currentTokenLexeme() + "'"); - } - } - return getCodeGeneratorWithTimes().getResult(true); - } - - - public Expression parse() { - return parse(true); - } - - static enum StatementType { - Ternary, Return, Empty, Other - } - - /** - * Call __reducer_break() - */ - private void breakStatement() { - if (!this.scope.newLexicalScope) { - reportSyntaxError("break only can be used in for-loop"); - } - move(true); - getCodeGeneratorWithTimes().onMethodName(Constants.ReducerBreakFn); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - if (!expectChar(';')) { - reportSyntaxError("missing ';' for break"); - } - move(true); - } - - /** - * Call __reducer_cont(nil) - */ - private void continueStatement() { - if (!this.scope.newLexicalScope) { - reportSyntaxError("continue only can be used in for-loop"); - } - move(true); - getCodeGeneratorWithTimes().onMethodName(Constants.ReducerContFn); - getCodeGeneratorWithTimes().onConstant(Variable.NIL); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - if (!expectChar(';')) { - reportSyntaxError("missing ';' for continue"); - } - move(true); - } - - - /** - *
-   *  while(test) {
-   *     ...body...
-   *  }
-   *  ...statements...
-   * 
- * - * ===> - * - *
-   *  __reducer_callcc(__reducer_loop, lambda() ->
-   *       if(test) {
-   *          ...body...
-   *       }else {
-   *          break;
-   *       }
-   *  end, lambda()- >
-   *       ...statements...
-   *  end);
-   * 
- */ - private void whileStatement() { - move(true); - - // prepare to call __reducer_callcc(LOOP, iterator, statements) - getCodeGeneratorWithTimes().onMethodName(Constants.ReducerFn); - getCodeGeneratorWithTimes().onConstant(Constants.REDUCER_LOOP); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - // create a lambda function wraps while body(iterator) - boolean newLexicalScope = this.scope.newLexicalScope; - this.scope.newLexicalScope = true; - { - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - ifStatement(true, false); - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - getCodeGenerator().onMethodParameter(this.lookahead); - } - - if (expectChar(';')) { - // the statement is ended. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - // create a lambda function wraps statements after while(statements) - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, true)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - if (statements() == StatementType.Empty) { - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - } - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - // call __reducer_callcc(LOOP, iterator, statements) - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - // restore newLexicalScope - this.scope.newLexicalScope = newLexicalScope; - - } - - private void letStatement() { - move(true); - Token var = this.lookahead; - checkVariableName(var); - getCodeGenerator().onConstant(var); - move(true); - if (!expectChar('=')) { - reportSyntaxError("expect '='"); - } - move(true); - StatementType stmtType = statement(); - if (stmtType == StatementType.Empty) { - reportSyntaxError("invalid value to define"); - } - checkVarIsInit(var, stmtType); - ensureFeatureEnabled(Feature.Assignment); - getCodeGeneratorWithTimes().onAssignment(currentToken().withMeta(Constants.DEFINE_META, true)); - if (!expectChar(';')) { - reportSyntaxError("missing ';' for let statement"); - } - move(true); - } - - private void fnStatement() { - move(true); - - if (expectChar('(')) { - // Anonymous function - lambda(true); - } else { - - checkVariableName(this.lookahead); - checkFunctionName(this.lookahead, true); - getCodeGeneratorWithTimes().onConstant(this.lookahead.withMeta(Constants.INIT_META, true) - .withMeta(Constants.TYPE_META, CompileTypes.Function)); - move(true); - if (!expectChar('(')) { - reportSyntaxError("expect '(' after function name"); - } - lambda(true); - ensureFeatureEnabled(Feature.Assignment); - getCodeGeneratorWithTimes() - .onAssignment(currentToken().withMeta(Constants.DEFINE_META, true)); - } - } - - private void checkFunctionName(final Token token, final boolean warnOnExists) { - String fnName = token.getLexeme(); - if (SymbolTable.isReservedKeyword(fnName)) { - reportSyntaxError("The function name `" + fnName + "` is a reserved keyword"); - } - if (warnOnExists && this.instance.getFuncMap().containsKey(fnName)) { - System.out.println("[Aviator WARN] The function '" + fnName - + "' is already exists, but is replaced with new one."); - } - } - - private Token currentToken() { - Token token = this.lookahead; - if (token == null) { - token = new CharToken((char) -1, this.lexer.getLineNo(), this.lexer.getCurrentIndex()); - } - return token; - } - - - private boolean scopeStatement() { + private void withMetaEnd(final Object key, final Object val) { + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + getCodeGeneratorWithTimes().onConstant(value2token(key)); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + + getCodeGeneratorWithTimes().onConstant(value2token(val)); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + } + + + private Token value2token(final Object val) { + if (val instanceof Token) { + return (Token) val; + } else if (val == null) { + return Variable.NIL; + } else if (val instanceof String) { + return (new StringToken((String) val, this.lexer.getLineNo(), + this.lookahead.getStartIndex())); + } else if (val instanceof Number) { + return (new NumberToken((Number) val, val.toString(), this.lexer.getLineNo(), + this.lookahead.getStartIndex())); + } else if (val instanceof Boolean) { + return (((boolean) val) ? Variable.TRUE : Variable.FALSE); + } else { + throw new ExpressionRuntimeException( + "Unsupported compiled-time metadata type: " + val.getClass()); + } + } + + /** *
-     *   {
-     *    ...body...
-     *   }
-     *   ...statements...
+     *  for x in coll {
+     *     ...body...
+     *  }
+     *  ...statements...
      * 
* - * => + * ===> * *
-     *  __if_callcc((lambda() -> ...body... end)(),  lambda() ->
-     *     ...statements...
+     *  __reducer_callcc(seq, lambda(x) ->
+     *      ...body...
+     *  end, lambda()- >
+     *       ...statements...
      *  end);
      * 
*/ + private void forStatement() { + move(true); - boolean hasReturn = false; - boolean newLexicalScope = this.scope.newLexicalScope; - this.scope.newLexicalScope = true; - // prepare to call __if_callcc(result, statements) - getCodeGeneratorWithTimes().onMethodName(Constants.IfReturnFn); - - // Create a lambda to wrap the scope body. - { - move(true); - this.scope.enterBrace(); - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - hasReturn = statements() == StatementType.Return; - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - getCodeGeneratorWithTimes().onMethodName(anonymousMethodName()); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - - if (!expectChar('}')) { - reportSyntaxError("missing '}' to close scope"); - } - move(true); - this.scope.leaveBrace(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - - if (expectChar(';')) { - // the statement is ended. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - // create a lambda function wraps statements after scope statement (statements) - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, true)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - if (statements() == StatementType.Empty) { - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - } - getCodeGenerator().onMethodParameter(this.lookahead); - - // call __if_callcc(result, statements) - getCodeGenerator().onMethodInvoke(this.lookahead); - this.scope.newLexicalScope = newLexicalScope; - - return hasReturn; - } - - private void tryStatement() { - getCodeGeneratorWithTimes().onMethodName(Constants.TRY_VAR); - move(true); - if (!expectChar('{')) { - reportSyntaxError("expect '{' after try"); - } - move(true); - - boolean newLexicalScope = this.scope.newLexicalScope; - this.scope.newLexicalScope = true; - // create a lambda function wraps try body - { - - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - statements(); - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - if (!expectChar('}')) { - reportSyntaxError("missing '}' for try body"); - } - move(true); - boolean hasCatch = false; - boolean hasFinally = false; - - while (this.lookahead == Variable.CATCH) { - if (!hasCatch) { - // create a handler list. - getCodeGeneratorWithTimes().onMethodName(Constants.SEQ_LIST_VAR); - hasCatch = true; - } - - move(true); - // create a lambda function wraps catch handlers - if (!expectChar('(')) { - reportSyntaxError("expect '(' after catch"); - } - move(true); - if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { - reportSyntaxError("invalid exception class name"); - } - checkVariableName(this.lookahead); - List> exceptionClasses = new ArrayList<>(); - exceptionClasses.add(this.lookahead); - move(true); - - Token boundVar = null; - if (expectChar(')')) { - // catch(e) to catch all. - boundVar = exceptionClasses.remove(0); - exceptionClasses.add(Constants.THROWABLE_VAR); - } else { - // catch multi exception - while (expectChar('|')) { - move(true); - if (this.lookahead.getType() != TokenType.Variable) { - reportSyntaxError("invalid exception class to catch"); - } - checkVariableName(this.lookahead); - exceptionClasses.add(this.lookahead); - move(true); + List> reducerArgs = new ArrayList<>(2); + + while (true) { + if (reducerArgs.size() > 2) { + reportSyntaxError("Too many variables in for statement: " + reducerArgs.size()); + } + reducerArgs.add(this.lookahead); + checkVariableName(this.lookahead); + move(true); + if (expectChar(',')) { + move(true); + continue; + } else { + break; + } } - if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { - reportSyntaxError("invalid bound variable name for exception"); + + if (this.lookahead == Variable.IN) { + move(true); + // prepare to call __reducer_callcc(seq, iterator, statements) + { + getCodeGeneratorWithTimes().onMethodName(Constants.ReducerFn); + // The seq + if (!parseTernary()) { + reportSyntaxError("missing collection"); + } + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + if (expectChar('{')) { + move(true); + this.scope.enterBrace(); + + boolean newLexicalScope = this.scope.newLexicalScope; + this.scope.newLexicalScope = true; + + // create a lambda function wraps for-loop body(iterator) + { + withMetaBegin(); + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + + for (Token reducerArg : reducerArgs) { + getCodeGeneratorWithTimes().onLambdaArgument(reducerArg, + new FunctionParam(0, reducerArg.getLexeme(), false)); + } + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + statements(); + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + withMetaEnd(Constants.ARITIES_META, Long.valueOf(reducerArgs.size())); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + + if (expectChar('}')) { + move(true); + this.scope.leaveBrace(); + } else { + reportSyntaxError("missing '}' in for-loop"); + } + + if (expectChar(';')) { + // The statement is ended. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + // create a lambda function wraps statements after for-loop(statements) + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, true)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + if (statements() == StatementType.Empty) { + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + } + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + // call __reducer_callcc(seq, iterator, statements) + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + // restore newLexicalScope + this.scope.newLexicalScope = newLexicalScope; + } else { + reportSyntaxError("expect '{' in for-loop"); + } + } else { + reportSyntaxError("expect 'in' keyword while using for-loop"); } - checkVariableName(this.lookahead); - boundVar = this.lookahead; - move(true); - } - - if (!expectChar(')')) { - reportSyntaxError("missing ')' for catch caluse"); - } - move(true); - - if (!expectChar('{')) { - reportSyntaxError("missing '{' for catch block"); - } - move(true); - { - // create a catch handler - getCodeGeneratorWithTimes().onMethodName(Constants.CATCH_HANDLER_VAR); - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - getCodeGeneratorWithTimes().onLambdaArgument(boundVar, - new FunctionParam(0, boundVar.getLexeme(), false)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - statements(); - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - for (Token exceptionClass : exceptionClasses) { - getCodeGeneratorWithTimes().onConstant(exceptionClass); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); + } + + + private StatementType statements() { + if (this.lookahead == null) { + return StatementType.Empty; } - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - } - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - if (!expectChar('}')) { - reportSyntaxError("missing '}' for to complete catch block"); - } - move(true); - } - - if (hasCatch) { - // Invoke seq.list to create handler list - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } else { - getCodeGeneratorWithTimes().onConstant(Variable.NIL); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - - if (this.lookahead == Variable.FINALLY) { - hasFinally = true; - move(true); - if (!expectChar('{')) { - reportSyntaxError("missing '{' for finally block"); - } - move(true); - // create a lambda to - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - statements(); - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - if (!expectChar('}')) { - reportSyntaxError("missing '}' for finally block"); - } - move(true); - } else { - getCodeGeneratorWithTimes().onConstant(Variable.NIL); - } - - if (!hasCatch && !hasFinally) { - reportSyntaxError("missing catch or finally blocks for catch"); - } - - if (expectChar(';')) { - // The statement is ended. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - // create a lambda function wraps statements after try..catch - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, true)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - if (statements() == StatementType.Empty) { - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - } - - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - this.scope.newLexicalScope = newLexicalScope; - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - } - - private void throwStatement() { - getCodeGeneratorWithTimes().onMethodName(Constants.THROW_VAR); - move(true); - statement(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - if (!expectChar(';')) { - reportSyntaxError("missing ';' for throw statement"); - } - } - - private void newStatement() { - ensureFeatureEnabled(Feature.NewInstance); - getCodeGeneratorWithTimes().onMethodName(Constants.NEW_VAR); - move(true); - - if (this.lookahead == null || this.lookahead.getType() != TokenType.Variable) { - reportSyntaxError("invalid class name"); - } - checkVariableName(this.lookahead); - getCodeGeneratorWithTimes() - .onConstant(this.lookahead.withMeta(Constants.TYPE_META, CompileTypes.Class)); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - move(true); - - if (!expectChar('(')) { - reportSyntaxError("missing '(' after class name"); - } - - this.scope.enterParen(); - move(true); - if (!expectChar(')')) { - parseTernary(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - while (expectChar(',')) { - move(true); - if (!parseTernary()) { - reportSyntaxError("invalid argument"); + + StatementType stmtType = statement(); + ensureDepthState(); + while (expectChar(';') || stmtType == StatementType.Other || stmtType == StatementType.Return) { + + ensureNoStatementAfterReturn(stmtType); + + if (this.lookahead != null && this.lookahead != Variable.END && !expectChar('}')) { + getCodeGeneratorWithTimes().onTernaryEnd(this.lookahead); + } + + if (expectChar(';')) { + move(true); + } + + if (this.lookahead == null) { + break; + } + + StatementType nextStmtType = statement(); + if (nextStmtType == StatementType.Empty) { + break; + } + stmtType = nextStmtType; + ensureDepthState(); } - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - } - if (!expectChar(')')) { - reportSyntaxError("missing ')' for new statement"); - } - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - move(true); - this.scope.leaveParen(); - } - - private void className() { - if (this.lookahead.getType() != TokenType.Variable && !expectChar('*')) { - reportSyntaxError("expect variable name or * to use"); - } - if (expectChar('*')) { - wildcard(); - } else { - checkVariableName(this.lookahead); - getCodeGenerator().onConstant(this.lookahead.withMeta(Constants.USE_CLASS_PKG, true)); - } - move(true); - } - - private void useStatement() { - getCodeGeneratorWithTimes().onMethodName(Constants.USE_VAR); - move(true); - className(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - if (expectChar('*')) { - // wildcard - wildcard(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - move(true); - } else if (expectChar('{')) { - this.scope.enterBrace(); - move(true); - className(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - while (expectChar(',')) { - move(true); - className(); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - if (!expectChar('}')) { - reportSyntaxError("expect '}' to complete use statement"); - } else { - move(true); - this.scope.leaveBrace(); - } - } - - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - if (!expectChar(';')) { - reportSyntaxError("missing ';' for use statement"); - } - } - - - - private void wildcard() { - getCodeGenerator() - .onConstant(new Variable("*", this.lookahead.getLineNo(), this.lookahead.getStartIndex())); - } - - private StatementType statement() { - if (this.lookahead == Variable.IF) { - ensureFeatureEnabled(Feature.If); - if (ifStatement(false, false)) { - return StatementType.Return; - } else { - return StatementType.Other; - } - } else if (this.lookahead == Variable.FOR) { - ensureFeatureEnabled(Feature.ForLoop); - forStatement(); - return StatementType.Other; - } else if (this.lookahead == Variable.RETURN) { - ensureFeatureEnabled(Feature.Return); - returnStatement(); - return StatementType.Return; - } else if (this.lookahead == Variable.BREAK) { - breakStatement(); - return StatementType.Return; - } else if (this.lookahead == Variable.CONTINUE) { - continueStatement(); - return StatementType.Return; - } else if (this.lookahead == Variable.LET) { - ensureFeatureEnabled(Feature.Let); - letStatement(); - return StatementType.Other; - } else if (this.lookahead == Variable.WHILE) { - ensureFeatureEnabled(Feature.WhileLoop); - whileStatement(); - return StatementType.Other; - } else if (this.lookahead == Variable.FN) { - ensureFeatureEnabled(Feature.Fn); - fnStatement(); - return StatementType.Other; - } else if (this.lookahead == Variable.TRY) { - ensureFeatureEnabled(Feature.ExceptionHandle); - tryStatement(); - return StatementType.Other; - } else if (this.lookahead == Variable.THROW) { - ensureFeatureEnabled(Feature.ExceptionHandle); - throwStatement(); - return StatementType.Other; - } else if (expectChar('{')) { - ensureFeatureEnabled(Feature.LexicalScope); - if (scopeStatement()) { - return StatementType.Return; - } else { - return StatementType.Other; - } - } else if (this.lookahead == Variable.USE) { - ensureFeatureEnabled(Feature.Use); - useStatement(); - return StatementType.Other; - } else { - if (parseTernary()) { - return StatementType.Ternary; - } else { - return StatementType.Empty; - } - } - } - - private void withMetaBegin() { - getCodeGeneratorWithTimes().onMethodName(Constants.WithMetaFn); - } - - private void withMetaEnd(final Object key, final Object val) { - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - getCodeGeneratorWithTimes().onConstant(value2token(key)); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - getCodeGeneratorWithTimes().onConstant(value2token(val)); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - } - - - - private Token value2token(final Object val) { - if (val instanceof Token) { - return (Token) val; - } else if (val == null) { - return Variable.NIL; - } else if (val instanceof String) { - return (new StringToken((String) val, this.lexer.getLineNo(), - this.lookahead.getStartIndex())); - } else if (val instanceof Number) { - return (new NumberToken((Number) val, val.toString(), this.lexer.getLineNo(), - this.lookahead.getStartIndex())); - } else if (val instanceof Boolean) { - return (((boolean) val) ? Variable.TRUE : Variable.FALSE); - } else { - throw new ExpressionRuntimeException( - "Unsupported compiled-time metadata type: " + val.getClass()); - } - } - - - /** - *
-   *  for x in coll {
-   *     ...body...
-   *  }
-   *  ...statements...
-   * 
- * - * ===> - * - *
-   *  __reducer_callcc(seq, lambda(x) ->
-   *      ...body...
-   *  end, lambda()- >
-   *       ...statements...
-   *  end);
-   * 
- */ - private void forStatement() { - move(true); - - List> reducerArgs = new ArrayList<>(2); - - while (true) { - if (reducerArgs.size() > 2) { - reportSyntaxError("Too many variables in for statement: " + reducerArgs.size()); - } - reducerArgs.add(this.lookahead); - checkVariableName(this.lookahead); - move(true); - if (expectChar(',')) { - move(true); - continue; - } else { - break; - } + ensureNoStatementAfterReturn(stmtType); + // If the last statement is ternary,it must be ended with END TOKEN such as null token, '}', + // 'end' keyword, or ';' + // Otherwise report syntax error. + if (stmtType == StatementType.Ternary) { + if (lookahead != null && !expectChar(';') && !expectChar('}') && lookahead != Variable.END) { + this.back(); + reportSyntaxError("unexpect token '" + currentTokenLexeme() + + "', maybe forget to insert ';' to complete last expression "); + } + } + + return stmtType; } - if (this.lookahead == Variable.IN) { - move(true); - // prepare to call __reducer_callcc(seq, iterator, statements) - { - getCodeGeneratorWithTimes().onMethodName(Constants.ReducerFn); - // The seq - if (!parseTernary()) { - reportSyntaxError("missing collection"); + private void ensureNoStatementAfterReturn(final StatementType statementType) { + if (statementType == StatementType.Return && this.lookahead != null) { + if (this.lookahead != Variable.END && !expectChar('}')) { + reportSyntaxError("unreachable code"); + } } - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - if (expectChar('{')) { - move(true); - this.scope.enterBrace(); + } + /** + *
+     *  if(test) {
+     *     ...if-body...
+     *  }else {
+     *     ...else-body...
+     *  }
+     *  ...statements...
+     * 
+ * + * ===> + * + *
+     *  __if_callcc(test ? (lambda() -> ...if-body... end)() :  (lambda() -> ...else-body... end)(),
+     *   lambda()- >
+     *       ...statements...
+     *  end);
+     * 
+ */ + private boolean ifStatement(final boolean isWhile, final boolean isElsif) { + if (!isWhile) { + move(true); + } + boolean ifBodyHasReturn = false; + boolean elseBodyHasReturn = false; boolean newLexicalScope = this.scope.newLexicalScope; this.scope.newLexicalScope = true; + // prepare to call __if_callcc(result, statements) + getCodeGeneratorWithTimes().onMethodName(Constants.IfReturnFn); - // create a lambda function wraps for-loop body(iterator) { - withMetaBegin(); - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); - - for (Token reducerArg : reducerArgs) { - getCodeGeneratorWithTimes().onLambdaArgument(reducerArg, - new FunctionParam(0, reducerArg.getLexeme(), false)); - } - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - statements(); - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - withMetaEnd(Constants.ARITIES_META, Long.valueOf(reducerArgs.size())); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - - if (expectChar('}')) { - move(true); - this.scope.leaveBrace(); - } else { - reportSyntaxError("missing '}' in for-loop"); + if (!parseTernary()) { + reportSyntaxError("missing test statement for if"); + } + + getCodeGeneratorWithTimes().onTernaryBoolean(this.lookahead); + if (expectChar('{')) { + move(true); + this.scope.enterBrace(); + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + ifBodyHasReturn = statements() == StatementType.Return; + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + getCodeGeneratorWithTimes().onMethodName(anonymousMethodName()); + getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); + getCodeGeneratorWithTimes().onTernaryLeft(this.lookahead); + } else { + reportSyntaxError("expect '{' for " + getLoopKeyword(isWhile) + " statement"); + } + if (!expectChar('}')) { + reportSyntaxError("missing '}' to close " + getLoopKeyword(isWhile) + " body"); + } + this.scope.leaveBrace(); + move(true); + + elseBodyHasReturn = elseStatement(isWhile, isElsif, ifBodyHasReturn); + getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); } - if (expectChar(';')) { - // The statement is ended. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - // create a lambda function wraps statements after for-loop(statements) - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, true)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - if (statements() == StatementType.Empty) { - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + { + // + if (isWhile || isElsif) { + // Load ReducerEmptyVal directly. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + + if (expectChar(';')) { + // the statement is ended. + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + // create a lambda function wraps statements after if statement (statements) + getCodeGeneratorWithTimes().onLambdaDefineStart( + getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, true)); + getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); + if (statements() == StatementType.Empty) { + getCodeGenerator().onConstant(Constants.ReducerEmptyVal); + } else { + if (ifBodyHasReturn && elseBodyHasReturn && !isElsif) { + reportSyntaxError("unreachable code"); + } + } + getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); + } + } + getCodeGenerator().onMethodParameter(this.lookahead); + // call __if_callcc(result, statements) + getCodeGenerator().onMethodInvoke(this.lookahead); + this.scope.newLexicalScope = newLexicalScope; } - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - // call __reducer_callcc(seq, iterator, statements) - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - // restore newLexicalScope - this.scope.newLexicalScope = newLexicalScope; - } else { - reportSyntaxError("expect '{' in for-loop"); - } - } else { - reportSyntaxError("expect 'in' keyword while using for-loop"); - } - } + return ifBodyHasReturn && elseBodyHasReturn; + } - private StatementType statements() { - if (this.lookahead == null) { - return StatementType.Empty; + private String getLoopKeyword(final boolean isWhile) { + return isWhile ? "while" : "if"; } - StatementType stmtType = statement(); - ensureDepthState(); - while (expectChar(';') || stmtType == StatementType.Other || stmtType == StatementType.Return) { + private boolean elseStatement(final boolean isWhile, boolean isElsif, + final boolean ifBodyHasReturn) { + if (isWhile) { + // Call __reducer_break(nil) + final CodeGenerator cg = getCodeGeneratorWithTimes(); + cg.onMethodName(Constants.ReducerBreakFn); + cg.onConstant(Variable.NIL); + cg.onMethodParameter(this.lookahead); + cg.onMethodInvoke(this.lookahead); + cg.onTernaryRight(this.lookahead); + return false; + } + + if (expectChar(';')) { + return withoutElse(); + } - ensureNoStatementAfterReturn(stmtType); + boolean hasReturn = false; + boolean hasElsif = this.lookahead == Variable.ELSIF; + boolean hasElse = this.lookahead == Variable.ELSE; + if (this.lookahead != null && (hasElse || hasElsif || ifBodyHasReturn)) { + if (hasElse) { + move(true); + if (expectChar('{')) { + this.scope.enterBrace(); + move(true); + hasReturn = elseBody(false); + if (expectChar('}')) { + this.scope.leaveBrace(); + move(true); + } else { + reportSyntaxError("missing '}' to close 'else' body"); + } + } else { + reportSyntaxError("expect '{' for else statement"); + } + } else if (hasElsif) { + hasReturn = ifStatement(false, true); + getCodeGenerator().onTernaryRight(this.lookahead); + } else if (ifBodyHasReturn && !isElsif) { + hasReturn = elseBody(true); + } else { + return withoutElse(); + } + return hasReturn; + } else { + // Missing else statement, always nil. + return withoutElse(); + } - if (this.lookahead != null && this.lookahead != Variable.END && !expectChar('}')) { - getCodeGeneratorWithTimes().onTernaryEnd(this.lookahead); - } + } - if (expectChar(';')) { - move(true); - } - - if (this.lookahead == null) { - break; - } - - StatementType nextStmtType = statement(); - if (nextStmtType == StatementType.Empty) { - break; - } - stmtType = nextStmtType; - ensureDepthState(); - } - ensureNoStatementAfterReturn(stmtType); - // If the last statement is ternary,it must be ended with END TOKEN such as null token, '}', - // 'end' keyword, or ';' - // Otherwise report syntax error. - if (stmtType == StatementType.Ternary) { - if (lookahead != null && !expectChar(';') && !expectChar('}') && lookahead != Variable.END) { - this.back(); - reportSyntaxError("unexpect token '" + currentTokenLexeme() - + "', maybe forget to insert ';' to complete last expression "); - } - } - - return stmtType; - } - - private void ensureNoStatementAfterReturn(final StatementType statementType) { - if (statementType == StatementType.Return && this.lookahead != null) { - if (this.lookahead != Variable.END && !expectChar('}')) { - reportSyntaxError("unreachable code"); - } - } - } - - /** - *
-   *  if(test) {
-   *     ...if-body...
-   *  }else {
-   *     ...else-body...
-   *  }
-   *  ...statements...
-   * 
- * - * ===> - * - *
-   *  __if_callcc(test ? (lambda() -> ...if-body... end)() :  (lambda() -> ...else-body... end)(),
-   *   lambda()- >
-   *       ...statements...
-   *  end);
-   * 
- */ - private boolean ifStatement(final boolean isWhile, final boolean isElsif) { - if (!isWhile) { - move(true); - } - boolean ifBodyHasReturn = false; - boolean elseBodyHasReturn = false; - boolean newLexicalScope = this.scope.newLexicalScope; - this.scope.newLexicalScope = true; - // prepare to call __if_callcc(result, statements) - getCodeGeneratorWithTimes().onMethodName(Constants.IfReturnFn); - - { - if (!parseTernary()) { - reportSyntaxError("missing test statement for if"); - } - - getCodeGeneratorWithTimes().onTernaryBoolean(this.lookahead); - if (expectChar('{')) { - move(true); - this.scope.enterBrace(); - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope)); + private boolean withoutElse() { + final CodeGenerator cg = getCodeGeneratorWithTimes(); + cg.onConstant(Variable.NIL); + cg.onTernaryRight(this.lookahead); + return false; + } + + private boolean elseBody(final boolean inheritEnv) { + getCodeGeneratorWithTimes().onLambdaDefineStart(this.lookahead // + .withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // + .withMeta(Constants.INHERIT_ENV_META, inheritEnv) // + ); getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - ifBodyHasReturn = statements() == StatementType.Return; + boolean hasReturn = statements() == StatementType.Return; getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); getCodeGeneratorWithTimes().onMethodName(anonymousMethodName()); getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - getCodeGeneratorWithTimes().onTernaryLeft(this.lookahead); - } else { - reportSyntaxError("expect '{' for " + getLoopKeyword(isWhile) + " statement"); - } - if (!expectChar('}')) { - reportSyntaxError("missing '}' to close " + getLoopKeyword(isWhile) + " body"); - } - this.scope.leaveBrace(); - move(true); - - elseBodyHasReturn = elseStatement(isWhile, isElsif, ifBodyHasReturn); - getCodeGeneratorWithTimes().onMethodParameter(this.lookahead); - } - - { - // - if (isWhile || isElsif) { - // Load ReducerEmptyVal directly. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - - if (expectChar(';')) { - // the statement is ended. - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - // create a lambda function wraps statements after if statement (statements) - getCodeGeneratorWithTimes().onLambdaDefineStart( - getPrevToken().withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, true)); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - if (statements() == StatementType.Empty) { - getCodeGenerator().onConstant(Constants.ReducerEmptyVal); - } else { - if (ifBodyHasReturn && elseBodyHasReturn && !isElsif) { - reportSyntaxError("unreachable code"); - } - } - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - } - } - getCodeGenerator().onMethodParameter(this.lookahead); - // call __if_callcc(result, statements) - getCodeGenerator().onMethodInvoke(this.lookahead); - this.scope.newLexicalScope = newLexicalScope; + getCodeGeneratorWithTimes().onTernaryRight(this.lookahead); + return hasReturn; } - return ifBodyHasReturn && elseBodyHasReturn; - } - - private String getLoopKeyword(final boolean isWhile) { - return isWhile ? "while" : "if"; - } - - private boolean elseStatement(final boolean isWhile, boolean isElsif, - final boolean ifBodyHasReturn) { - if (isWhile) { - // Call __reducer_break(nil) - final CodeGenerator cg = getCodeGeneratorWithTimes(); - cg.onMethodName(Constants.ReducerBreakFn); - cg.onConstant(Variable.NIL); - cg.onMethodParameter(this.lookahead); - cg.onMethodInvoke(this.lookahead); - cg.onTernaryRight(this.lookahead); - return false; + private DelegateToken anonymousMethodName() { + return new DelegateToken(this.lookahead, DelegateTokenType.Method_Name); } - if (expectChar(';')) { - return withoutElse(); + + private void ensureDepthState() { + DepthState state = this.scope.depthState.peekLast(); + if (state != null) { + back(); + switch (state) { + case Parent: + if (this.scope.parenDepth > 0) { + reportSyntaxError("insert ')' to complete statement"); + } + break; + case Bracket: + if (this.scope.bracketDepth > 0) { + reportSyntaxError("insert ']' to complete statement"); + } + break; + case Lambda: + if (this.scope.lambdaDepth > 0) { + reportSyntaxError("insert 'end' to complete lambda statement"); + } + break; + case Brace: + if (this.scope.braceDepth > 0) { + reportSyntaxError("insert '}' to complete statement"); + } + break; + } + } } - boolean hasReturn = false; - boolean hasElsif = this.lookahead == Variable.ELSIF; - boolean hasElse = this.lookahead == Variable.ELSE; - if (this.lookahead != null && (hasElse || hasElsif || ifBodyHasReturn)) { - if (hasElse) { - move(true); - if (expectChar('{')) { - this.scope.enterBrace(); - move(true); - hasReturn = elseBody(false); - if (expectChar('}')) { - this.scope.leaveBrace(); - move(true); - } else { - reportSyntaxError("missing '}' to close 'else' body"); - } - } else { - reportSyntaxError("expect '{' for else statement"); - } - } else if (hasElsif) { - hasReturn = ifStatement(false, true); - getCodeGenerator().onTernaryRight(this.lookahead); - } else if (ifBodyHasReturn && !isElsif) { - hasReturn = elseBody(true); - } else { - return withoutElse(); - } - return hasReturn; - } else { - // Missing else statement, always nil. - return withoutElse(); - } - - } - - private boolean withoutElse() { - final CodeGenerator cg = getCodeGeneratorWithTimes(); - cg.onConstant(Variable.NIL); - cg.onTernaryRight(this.lookahead); - return false; - } - - private boolean elseBody(final boolean inheritEnv) { - getCodeGeneratorWithTimes().onLambdaDefineStart(this.lookahead // - .withMeta(Constants.SCOPE_META, this.scope.newLexicalScope) // - .withMeta(Constants.INHERIT_ENV_META, inheritEnv) // - ); - getCodeGeneratorWithTimes().onLambdaBodyStart(this.lookahead); - boolean hasReturn = statements() == StatementType.Return; - getCodeGeneratorWithTimes().onLambdaBodyEnd(this.lookahead); - getCodeGeneratorWithTimes().onMethodName(anonymousMethodName()); - getCodeGeneratorWithTimes().onMethodInvoke(this.lookahead); - getCodeGeneratorWithTimes().onTernaryRight(this.lookahead); - return hasReturn; - } - - private DelegateToken anonymousMethodName() { - return new DelegateToken(this.lookahead, DelegateTokenType.Method_Name); - } - - - private void ensureDepthState() { - DepthState state = this.scope.depthState.peekLast(); - if (state != null) { - back(); - switch (state) { - case Parent: - if (this.scope.parenDepth > 0) { - reportSyntaxError("insert ')' to complete statement"); - } - break; - case Bracket: - if (this.scope.bracketDepth > 0) { - reportSyntaxError("insert ']' to complete statement"); - } - break; - case Lambda: - if (this.scope.lambdaDepth > 0) { - reportSyntaxError("insert 'end' to complete lambda statement"); - } - break; - case Brace: - if (this.scope.braceDepth > 0) { - reportSyntaxError("insert '}' to complete statement"); - } - break; - } - } - } - - public static boolean isConstant(final Token token, final AviatorEvaluatorInstance instance) { - switch (token.getType()) { - case Number: - case Pattern: - return true; - case String: - return !instance.isFeatureEnabled(Feature.StringInterpolation); - default: - return false; + public static boolean isConstant(final Token token, final AviatorEvaluatorInstance instance) { + switch (token.getType()) { + case Number: + case Pattern: + return true; + case String: + return !instance.isFeatureEnabled(Feature.StringInterpolation); + default: + return false; + } } - } - public static boolean isLiteralToken(final Token token, - final AviatorEvaluatorInstance instance) { - switch (token.getType()) { - case Variable: - return ((Variable) token).isLiteralKeyword(); - case Char: - case Number: - case Pattern: - return true; - case String: - return !instance.isFeatureEnabled(Feature.StringInterpolation); - default: - return false; + public static boolean isLiteralToken(final Token token, + final AviatorEvaluatorInstance instance) { + switch (token.getType()) { + case Variable: + return ((Variable) token).isLiteralKeyword(); + case Char: + case Number: + case Pattern: + return true; + case String: + return !instance.isFeatureEnabled(Feature.StringInterpolation); + default: + return false; + } } - } - private int getCGTimes; + private int getCGTimes; - private final CodeGenerator getCodeGeneratorWithTimes() { - this.getCGTimes++; - return this.codeGenerator; - } + private final CodeGenerator getCodeGeneratorWithTimes() { + this.getCGTimes++; + return this.codeGenerator; + } } From c5e3b85ec300749e874cfaea7b22fdec641929d8 Mon Sep 17 00:00:00 2001 From: pony Date: Sun, 10 May 2026 21:13:49 +0800 Subject: [PATCH 2/2] code format --- .../aviator/AviatorEvaluatorUnitTest.java | 419 +++++++++--------- 1 file changed, 210 insertions(+), 209 deletions(-) diff --git a/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java b/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java index ce673dc6..71d762b3 100644 --- a/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java +++ b/src/test/java/com/googlecode/aviator/AviatorEvaluatorUnitTest.java @@ -1,14 +1,14 @@ /** * Copyright (C) 2010 dennis zhuang (killme2008@gmail.com) - * + *

* This library is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either version * 2.1 of the License, or (at your option) any later version. - * + *

* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. - * + *

* You should have received a copy of the GNU Lesser General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * @@ -21,220 +21,221 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + import java.math.MathContext; import java.util.HashMap; import java.util.Map; + import com.googlecode.aviator.utils.Utils; import org.junit.Test; import com.googlecode.aviator.exception.CompileExpressionErrorException; public class AviatorEvaluatorUnitTest { - @Test - public void testCompileWithoutCache() { - Expression exp1 = AviatorEvaluator.compile("1+3"); - Expression exp2 = AviatorEvaluator.compile("1+3"); - assertNotNull(exp1); - assertNotNull(exp2); - assertNotSame(exp1, exp2); - - assertEquals(4, exp1.execute(null)); - assertEquals(4, exp2.execute(null)); - } - - @Test - public void testIssue597() { - String s = "use java.lang.Thread;\n" + "Thread.sleep(2000);\n" + "return 1 > 0;"; - - Expression exp = AviatorEvaluator.compile(s); - assertTrue(exp.getVariableNames().isEmpty()); - } - - @Test - public void testNewEnv() { - Map env = AviatorEvaluator.newEnv(); - assertTrue(env.isEmpty()); - env = AviatorEvaluator.newEnv("a", 1, "b", "hello", "c", 9.9d); - assertEquals(3, env.size()); - assertEquals(1, env.get("a")); - assertEquals("hello", env.get("b")); - assertEquals(9.9d, (double) env.get("c"), 0.001); - - try { - AviatorEvaluator.newEnv("a", 1, "b", "hello", 3, 9.9d); - fail(); - } catch (ClassCastException e) { - - } - - try { - AviatorEvaluator.newEnv("a", 1, "b", "hello", "c"); - fail(); - } catch (IllegalArgumentException e) { - - } - } - - @Test - public void testDefaultOptionValues() { - assertEquals(AviatorEvaluator.getOption(Options.TRACE_EVAL), false); - assertEquals(AviatorEvaluator.getOption(Options.FEATURE_SET), Feature.getFullFeatures()); - assertEquals( - AviatorEvaluator.getOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL), false); - assertEquals(AviatorEvaluator.getOption(Options.OPTIMIZE_LEVEL), AviatorEvaluator.EVAL); - assertEquals(AviatorEvaluator.getOption(Options.MATH_CONTEXT), MathContext.DECIMAL128); - } - - - @Test - public void testSetOptions() { - try { - AviatorEvaluator.setOption(Options.FEATURE_SET, Feature.getCompatibleFeatures()); - assertEquals(AviatorEvaluator.getOption(Options.FEATURE_SET), - Feature.getCompatibleFeatures()); - AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.COMPILE); - assertEquals(AviatorEvaluator.getOption(Options.OPTIMIZE_LEVEL), AviatorEvaluator.COMPILE); - } finally { - AviatorEvaluator.setOption(Options.FEATURE_SET, Feature.getFullFeatures()); - AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); - } - } - - - - @Test - public void testExec() { - String exp1 = "b-c+a"; - String exp2 = "2*3.14*(R-r)+b/c"; - String exp3 = "f>d?k:a"; - String exp4 = "map.a > map.b ? list[0][1]: y"; - assertEquals(8, AviatorEvaluator.exec(exp1, 6, 2, 4)); - assertEquals(104, AviatorEvaluator.exec(exp1, 99, 3, 8)); - assertEquals(1.14d, AviatorEvaluator.exec(exp1, 3.14, 3, 1)); - assertEquals(6.28, AviatorEvaluator.exec(exp2, 4, 3, 0, 100)); - assertEquals(13.304d, AviatorEvaluator.exec(exp2, 5, 3.2, 4, 2.0)); - assertEquals(3.14d, AviatorEvaluator.exec(exp3, 2, 1, 3.14, 9)); - assertEquals(9, AviatorEvaluator.exec(exp3, 1, 2, 3.14, 9)); - - Map map = new HashMap(); - map.put("a", 100); - map.put("b", 99); - int[][] list = new int[1][2]; - list[0] = new int[2]; - list[0][1] = 2000; - int y = 999; - assertEquals(2000L, AviatorEvaluator.exec(exp4, map, list, y)); - map.put("a", 99); - assertEquals(999L, AviatorEvaluator.exec(exp4, map, list, y)); - } - - - @Test(expected = IllegalArgumentException.class) - public void testExecIllegalArguments() { - AviatorEvaluator.exec("a-b+c", 1, 2); - } - - @Test - public void testCompileCacheWithSpecifiedCacheKey() { - String expression = "1+3"; - Expression exp1 = AviatorEvaluator.compile(Utils.md5sum(expression), expression, true); - Expression exp2 = AviatorEvaluator.compile(Utils.md5sum(expression), expression, true); - assertNotNull(exp1); - assertNotNull(exp2); - assertSame(exp1, exp2); - - assertEquals(4, exp1.execute(null)); - assertEquals(4, exp2.execute(null)); - } - - @Test - public void testCompileCache() { - Expression exp1 = AviatorEvaluator.compile("1+3", true); - Expression exp2 = AviatorEvaluator.compile("1+3", true); - assertNotNull(exp1); - assertNotNull(exp2); - assertSame(exp1, exp2); - - assertEquals(4, exp1.execute(null)); - assertEquals(4, exp2.execute(null)); - } - - - @Test - public void testInvalidateCache() { - Expression exp1 = AviatorEvaluator.compile("1+3", true); - Expression exp2 = AviatorEvaluator.compile("1+3", true); - assertNotNull(exp1); - assertNotNull(exp2); - assertSame(exp1, exp2); - - AviatorEvaluator.invalidateCache("1+3"); - Expression exp3 = AviatorEvaluator.compile("1+3", true); - assertNotSame(exp1, exp3); - - assertEquals(4, exp1.execute(null)); - assertEquals(4, exp2.execute(null)); - - assertEquals(4, exp3.execute(null)); - - } - - - @Test - public void evaluatorWithoutCache() { - Map env = new HashMap(); - env.put("a", "hello"); - env.put("b", " world"); - assertEquals("hello world", AviatorEvaluator.execute("a+b", env)); - } - - - @Test - public void evaluatorWithCache() { - Map env = new HashMap(); - env.put("a", "hello"); - env.put("b", " world"); - assertEquals("hello world", AviatorEvaluator.execute("a+b", env, true)); - } - - @Test - public void evaluatorWithSpecifiedCacheKey() { - Map env = new HashMap<>(); - env.put("a", "hello"); - env.put("b", " world"); - assertEquals("hello world", AviatorEvaluator.execute("key", "a + b", env, true)); - final Expression expression = AviatorEvaluator.getInstance().getCachedExpressionByKey("key"); - assertNotNull(expression); - assertEquals("hello world", expression.execute(env)); - } - - - @Test(expected = CompileExpressionErrorException.class) - public void compileBlankExpression1() { - AviatorEvaluator.compile(""); - } - - - @Test(expected = IllegalArgumentException.class) - public void testSetNullMathContext() { - AviatorEvaluator.setMathContext(null); - } - - - @Test(expected = CompileExpressionErrorException.class) - public void compileBlankExpression2() { - AviatorEvaluator.compile(" "); - } - + @Test + public void testCompileWithoutCache() { + Expression exp1 = AviatorEvaluator.compile("1+3"); + Expression exp2 = AviatorEvaluator.compile("1+3"); + assertNotNull(exp1); + assertNotNull(exp2); + assertNotSame(exp1, exp2); + + assertEquals(4, exp1.execute(null)); + assertEquals(4, exp2.execute(null)); + } + + @Test + public void testIssue597() { + String s = "use java.lang.Thread;\n" + "Thread.sleep(2000);\n" + "return 1 > 0;"; + + Expression exp = AviatorEvaluator.compile(s); + assertTrue(exp.getVariableNames().isEmpty()); + } + + @Test + public void testNewEnv() { + Map env = AviatorEvaluator.newEnv(); + assertTrue(env.isEmpty()); + env = AviatorEvaluator.newEnv("a", 1, "b", "hello", "c", 9.9d); + assertEquals(3, env.size()); + assertEquals(1, env.get("a")); + assertEquals("hello", env.get("b")); + assertEquals(9.9d, (double) env.get("c"), 0.001); + + try { + AviatorEvaluator.newEnv("a", 1, "b", "hello", 3, 9.9d); + fail(); + } catch (ClassCastException e) { + + } + + try { + AviatorEvaluator.newEnv("a", 1, "b", "hello", "c"); + fail(); + } catch (IllegalArgumentException e) { + + } + } + + @Test + public void testDefaultOptionValues() { + assertEquals(AviatorEvaluator.getOption(Options.TRACE_EVAL), false); + assertEquals(AviatorEvaluator.getOption(Options.FEATURE_SET), Feature.getFullFeatures()); + assertEquals( + AviatorEvaluator.getOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL), false); + assertEquals(AviatorEvaluator.getOption(Options.OPTIMIZE_LEVEL), AviatorEvaluator.EVAL); + assertEquals(AviatorEvaluator.getOption(Options.MATH_CONTEXT), MathContext.DECIMAL128); + } + + + @Test + public void testSetOptions() { + try { + AviatorEvaluator.setOption(Options.FEATURE_SET, Feature.getCompatibleFeatures()); + assertEquals(AviatorEvaluator.getOption(Options.FEATURE_SET), + Feature.getCompatibleFeatures()); + AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.COMPILE); + assertEquals(AviatorEvaluator.getOption(Options.OPTIMIZE_LEVEL), AviatorEvaluator.COMPILE); + } finally { + AviatorEvaluator.setOption(Options.FEATURE_SET, Feature.getFullFeatures()); + AviatorEvaluator.setOption(Options.OPTIMIZE_LEVEL, AviatorEvaluator.EVAL); + } + } + + + @Test + public void testExec() { + String exp1 = "b-c+a"; + String exp2 = "2*3.14*(R-r)+b/c"; + String exp3 = "f>d?k:a"; + String exp4 = "map.a > map.b ? list[0][1]: y"; + assertEquals(8, AviatorEvaluator.exec(exp1, 6, 2, 4)); + assertEquals(104, AviatorEvaluator.exec(exp1, 99, 3, 8)); + assertEquals(1.14d, AviatorEvaluator.exec(exp1, 3.14, 3, 1)); + assertEquals(6.28, AviatorEvaluator.exec(exp2, 4, 3, 0, 100)); + assertEquals(13.304d, AviatorEvaluator.exec(exp2, 5, 3.2, 4, 2.0)); + assertEquals(3.14d, AviatorEvaluator.exec(exp3, 2, 1, 3.14, 9)); + assertEquals(9, AviatorEvaluator.exec(exp3, 1, 2, 3.14, 9)); + + Map map = new HashMap(); + map.put("a", 100); + map.put("b", 99); + int[][] list = new int[1][2]; + list[0] = new int[2]; + list[0][1] = 2000; + int y = 999; + assertEquals(2000L, AviatorEvaluator.exec(exp4, map, list, y)); + map.put("a", 99); + assertEquals(999L, AviatorEvaluator.exec(exp4, map, list, y)); + } + + + @Test(expected = IllegalArgumentException.class) + public void testExecIllegalArguments() { + AviatorEvaluator.exec("a-b+c", 1, 2); + } + + @Test + public void testCompileCacheWithSpecifiedCacheKey() { + String expression = "1+3"; + Expression exp1 = AviatorEvaluator.compile(Utils.md5sum(expression), expression, true); + Expression exp2 = AviatorEvaluator.compile(Utils.md5sum(expression), expression, true); + assertNotNull(exp1); + assertNotNull(exp2); + assertSame(exp1, exp2); + + assertEquals(4, exp1.execute(null)); + assertEquals(4, exp2.execute(null)); + } + + @Test + public void testCompileCache() { + Expression exp1 = AviatorEvaluator.compile("1+3", true); + Expression exp2 = AviatorEvaluator.compile("1+3", true); + assertNotNull(exp1); + assertNotNull(exp2); + assertSame(exp1, exp2); + + assertEquals(4, exp1.execute(null)); + assertEquals(4, exp2.execute(null)); + } + - @Test(expected = CompileExpressionErrorException.class) - public void executeBlankExpression1() { - AviatorEvaluator.execute("", null); - } - - - @Test(expected = CompileExpressionErrorException.class) - public void executeBlankExpression2() { - AviatorEvaluator.execute(" "); - } + @Test + public void testInvalidateCache() { + Expression exp1 = AviatorEvaluator.compile("1+3", true); + Expression exp2 = AviatorEvaluator.compile("1+3", true); + assertNotNull(exp1); + assertNotNull(exp2); + assertSame(exp1, exp2); + + AviatorEvaluator.invalidateCache("1+3"); + Expression exp3 = AviatorEvaluator.compile("1+3", true); + assertNotSame(exp1, exp3); + + assertEquals(4, exp1.execute(null)); + assertEquals(4, exp2.execute(null)); + + assertEquals(4, exp3.execute(null)); + + } + + + @Test + public void evaluatorWithoutCache() { + Map env = new HashMap(); + env.put("a", "hello"); + env.put("b", " world"); + assertEquals("hello world", AviatorEvaluator.execute("a+b", env)); + } + + + @Test + public void evaluatorWithCache() { + Map env = new HashMap(); + env.put("a", "hello"); + env.put("b", " world"); + assertEquals("hello world", AviatorEvaluator.execute("a+b", env, true)); + } + + @Test + public void evaluatorWithSpecifiedCacheKey() { + Map env = new HashMap<>(); + env.put("a", "hello"); + env.put("b", " world"); + assertEquals("hello world", AviatorEvaluator.execute("key", "a + b", env, true)); + final Expression expression = AviatorEvaluator.getInstance().getCachedExpressionByKey("key"); + assertNotNull(expression); + assertEquals("hello world", expression.execute(env)); + } + + + @Test(expected = CompileExpressionErrorException.class) + public void compileBlankExpression1() { + AviatorEvaluator.compile(""); + } + + + @Test(expected = IllegalArgumentException.class) + public void testSetNullMathContext() { + AviatorEvaluator.setMathContext(null); + } + + + @Test(expected = CompileExpressionErrorException.class) + public void compileBlankExpression2() { + AviatorEvaluator.compile(" "); + } + + + @Test(expected = CompileExpressionErrorException.class) + public void executeBlankExpression1() { + AviatorEvaluator.execute("", null); + } + + + @Test(expected = CompileExpressionErrorException.class) + public void executeBlankExpression2() { + AviatorEvaluator.execute(" "); + } }