This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-jcasbin.git


The following commit(s) were added to refs/heads/master by this push:
     new e8e973d  feat: replace Aviator with a built-in matcher expression 
evaluator
e8e973d is described below

commit e8e973dee7417ab80477638545beeb591050875d
Author: Yang Luo <[email protected]>
AuthorDate: Thu Nov 5 10:50:22 2026 +0800

    feat: replace Aviator with a built-in matcher expression evaluator
---
 README.md                                          |  56 ++--
 pom.xml                                            |   6 -
 .../EvaluationContext.java}                        |  35 +--
 .../org/casbin/jcasbin/expression/Expression.java  |  87 ++++++
 .../jcasbin/expression/ExpressionException.java    |  31 ++
 .../jcasbin/expression/ExpressionFunction.java     |  45 +++
 .../java/org/casbin/jcasbin/expression/Lexer.java  | 205 +++++++++++++
 .../org/casbin/jcasbin/expression/Operations.java  | 215 +++++++++++++
 .../java/org/casbin/jcasbin/expression/Parser.java | 335 +++++++++++++++++++++
 .../casbin/jcasbin/expression/PropertyReader.java  | 124 ++++++++
 .../java/org/casbin/jcasbin/main/CoreEnforcer.java |  92 ++----
 .../casbin/jcasbin/main/ManagementEnforcer.java    |   9 +-
 .../java/org/casbin/jcasbin/model/FunctionMap.java |  34 +--
 .../org/casbin/jcasbin/util/BuiltInFunctions.java  | 187 +++++-------
 .../casbin/jcasbin/util/ExpressionValidator.java   |  67 -----
 src/main/java/org/casbin/jcasbin/util/Util.java    |  40 ---
 .../jcasbin/util/function/CustomFunction.java      |  38 +--
 .../org/casbin/jcasbin/util/function/EvalFunc.java |  26 +-
 .../casbin/jcasbin/util/function/FunctionArgs.java |  60 ++++
 .../jcasbin/util/function/GlobMatchFunc.java       |  19 +-
 .../casbin/jcasbin/util/function/IPMatchFunc.java  |  19 +-
 .../casbin/jcasbin/util/function/KeyGet2Func.java  |  21 +-
 .../casbin/jcasbin/util/function/KeyGet3Func.java  |  21 +-
 .../casbin/jcasbin/util/function/KeyGetFunc.java   |  19 +-
 .../jcasbin/util/function/KeyMatch2Func.java       |  19 +-
 .../jcasbin/util/function/KeyMatch3Func.java       |  19 +-
 .../jcasbin/util/function/KeyMatch4Func.java       |  19 +-
 .../jcasbin/util/function/KeyMatch5Func.java       |  19 +-
 .../casbin/jcasbin/util/function/KeyMatchFunc.java |  19 +-
 .../jcasbin/util/function/RegexMatchFunc.java      |  20 +-
 .../jcasbin/main/BuiltInFunctionsUnitTest.java     |  13 +-
 .../org/casbin/jcasbin/main/ExpressionTest.java    | 228 ++++++++++++++
 .../jcasbin/main/ExpressionValidatorTest.java      | 234 --------------
 .../java/org/casbin/jcasbin/main/FunctionTest.java |  12 +-
 .../casbin/jcasbin/main/ManagementAPIUnitTest.java |  23 --
 .../org/casbin/jcasbin/main/ModelUnitTest.java     |  12 +-
 .../jcasbin/main/SyncedEnforcerUnitTest.java       |   6 +-
 .../casbin/jcasbin/main/TestAviatorValidation.java |  64 ----
 .../java/org/casbin/jcasbin/main/TestUtil.java     |   6 +-
 .../java/org/casbin/jcasbin/main/UtilTest.java     |  26 --
 40 files changed, 1617 insertions(+), 913 deletions(-)

diff --git a/README.md b/README.md
index 33bf0f4..563f128 100644
--- a/README.md
+++ b/README.md
@@ -215,43 +215,45 @@ https://casbin.apache.org/docs/adapters
 
 https://casbin.apache.org/docs/role-managers
 
-## Expression Validation and Cross-Platform Compatibility
+## Matcher expressions
 
-Starting from version 1.98.1, jCasbin validates expressions to ensure 
cross-platform compatibility with other Casbin implementations (Go, Node.js, 
Python, .NET, etc.).
+jCasbin evaluates matchers and `eval()` rules with its own expression 
evaluator (`org.casbin.jcasbin.expression`). It has the same syntax and 
operator precedence as the Go implementation, so a model gives the same 
decisions in every Casbin implementation.
 
-### Restricted Syntax
+- **Literals**: `'text'` or `"text"`, numbers, `true`, `false`, `nil`
+- **Variables**: `r.sub`, `p.obj`, and attributes such as `r.sub.Age`. 
Attributes are read from map entries, getters (`getAge()`, `isAdmin()`), record 
accessors and public fields; no other methods are called.
+- **Arrays**: `('admin', 'editor')` or `['admin', 'editor']`, usually with `in`
+- **Operators**, from the lowest precedence: `? :` and `??`, `||`, `&&`, `== 
!= < > <= >= =~ !~ in`, `|`, `^`, `&`, `<< >>`, `+ -`, `* / %`, `**`, unary `- 
! ~`
+- **Built-in functions**: `g()`, `keyMatch()`, `keyMatch2()` to `keyMatch5()`, 
`keyGet()`, `keyGet2()`, `keyGet3()`, `regexMatch()`, `ipMatch()`, 
`globMatch()`, `eval()`
 
-The following AviatorScript-specific features are **not allowed** in `eval()` 
expressions and policy rules:
-
-- **Namespace methods**: `seq.list()`, `string.startsWith()`, 
`string.endsWith()`, `math.sqrt()`, etc.
-- **Advanced control structures**: `lambda`, `let`, `fn`, `for`, `while`, 
`return`, `if-then-else`, `->`
-
-These features are restricted because they are specific to AviatorScript and 
would make policies incompatible with other Casbin implementations.
-
-### Allowed Syntax
+```java
+"g(r.sub, p.sub) && keyMatch(r.path, p.path)"
+"r.role in ('admin', 'editor')"
+"eval(p.sub_rule)"  // with a policy rule such as: r.sub.Age > 18 && r.sub.Age 
< 65
+```
 
-The following standard Casbin syntax is fully supported:
+Anything else, such as AviatorScript's `seq.list()`, `string.startsWith()` or 
`lambda`, is a syntax error. A matcher with a syntax error throws an 
`ExpressionException`; an `eval()` rule with one is logged as a warning and 
evaluates to `false`.
 
-- **Operators**: `&&`, `||`, `==`, `!=`, `<`, `>`, `<=`, `>=`, `+`, `-`, `*`, 
`/`, `!`, `in`
-- **Built-in functions**: `g()`, `keyMatch()`, `keyMatch2-5()`, 
`regexMatch()`, `ipMatch()`, `globMatch()`, `timeMatch()`, `eval()`
-- **Custom functions**: Users can still register custom functions using 
`enforcer.addFunction()`
-- **Variable access**: `r.attr`, `p.attr` (automatically escaped to `r_attr`, 
`p_attr`)
+### Custom functions
 
-### Example
+A custom function receives the evaluated arguments (`String`, `Long`, 
`Double`, `Boolean`, `List`, or the objects passed to `enforce()`) and returns 
the result:
 
 ```java
-// ❌ NOT allowed - AviatorScript-specific syntax
-"eval(seq.list('admin', 'editor'))"
-"eval(string.startsWith(r.path, '/admin'))"
-
-// ✅ Allowed - Standard Casbin syntax
-"eval(r.age > 18 && r.age < 65)"
-"r.role in ('admin', 'editor')"  // Converted to include(tuple(...), ...)
-"g(r.sub, p.sub) && keyMatch(r.path, p.path)"
+enforcer.addFunction("isOwner", args -> args[0].equals(args[1]));
+
+enforcer.addFunction("isPublic", new CustomFunction() {
+    @Override
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("isPublic", args, 1);
+        return FunctionArgs.getString(args, 0).startsWith("/public/");
+    }
+
+    @Override
+    public String getName() {
+        return "isPublic";
+    }
+});
 ```
 
-If an expression contains restricted syntax, it will be logged as a warning 
and return `false`.
-
 ## Examples
 
 | Model                     | Model file                                       
                                                                                
 | Policy file                                                                  
                                                     |
diff --git a/pom.xml b/pom.xml
index 05727ea..280d8ba 100644
--- a/pom.xml
+++ b/pom.xml
@@ -261,12 +261,6 @@ under the License.
             <version>4.11.0</version>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>io.github.aviatorscript</groupId>
-            <artifactId>aviator</artifactId>
-            <version>5.9.0</version>
-        </dependency>
-
         <dependency>
             <groupId>com.github.seancfoley</groupId>
             <artifactId>ipaddress</artifactId>
diff --git a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java 
b/src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
similarity index 51%
copy from src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
copy to src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
index 2889fcb..2176106 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
+++ b/src/main/java/org/casbin/jcasbin/expression/EvaluationContext.java
@@ -15,32 +15,29 @@
 // specific language governing permissions and limitations
 // under the License.
 
-package org.casbin.jcasbin.util.function;
-
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import org.casbin.jcasbin.util.BuiltInFunctions;
+package org.casbin.jcasbin.expression;
 
+import java.util.Collections;
 import java.util.Map;
 
 /**
- * EvalFunc is the wrapper for eval.
- * It extends CustomFunction, so it can be used in matcher and policy rule.
- *
- * @author shink
+ * EvaluationContext holds the variables (such as r_sub, p_obj) and the 
functions
+ * an expression is evaluated with.
  */
-public class EvalFunc extends CustomFunction {
+public class EvaluationContext {
+    private final Map<String, Object> variables;
+    private final Map<String, ? extends ExpressionFunction> functions;
+
+    public EvaluationContext(Map<String, Object> variables, Map<String, ? 
extends ExpressionFunction> functions) {
+        this.variables = variables == null ? Collections.emptyMap() : 
variables;
+        this.functions = functions == null ? Collections.emptyMap() : 
functions;
+    }
 
-    @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
-        String eval = FunctionUtils.getStringValue(arg1, env);
-        eval = replaceTargets(eval, env);
-        return AviatorBoolean.valueOf(BuiltInFunctions.eval(eval, env, 
getAviatorEval()));
+    public Map<String, Object> getVariables() {
+        return variables;
     }
 
-    @Override
-    public String getName() {
-        return "eval";
+    public Map<String, ? extends ExpressionFunction> getFunctions() {
+        return functions;
     }
 }
diff --git a/src/main/java/org/casbin/jcasbin/expression/Expression.java 
b/src/main/java/org/casbin/jcasbin/expression/Expression.java
new file mode 100644
index 0000000..da50f42
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Expression.java
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.util.Map;
+
+/**
+ * Expression is a compiled matcher expression, such as
+ * {@code g(r_sub, p_sub) && keyMatch(r_obj, p_obj) && r_act == p_act}.
+ * It is immutable and can be evaluated concurrently with different variables.
+ *
+ * <p>The language is the one shared by all Casbin implementations: string, 
number,
+ * boolean and null literals, arrays like {@code ('a', 'b')} or {@code ['a', 
'b']},
+ * variables with attribute access like {@code r_sub.Age}, function calls, and 
the
+ * operators {@code ?: ?? || && == != < > <= >= =~ !~ in | ^ & << >> + - * / % 
! ~ **}.
+ * Attribute access reads map entries, getters, record accessors and public 
fields only.
+ */
+public final class Expression {
+    private final String source;
+    private final Parser.Node root;
+
+    private Expression(String source, Parser.Node root) {
+        this.source = source;
+        this.root = root;
+    }
+
+    /**
+     * compile parses an expression.
+     *
+     * @param source the expression text.
+     * @return the compiled expression.
+     * @throws ExpressionException if the expression is not valid.
+     */
+    public static Expression compile(String source) {
+        if (source == null) {
+            throw new ExpressionException("expression is null");
+        }
+        return new Expression(source, Parser.parse(source));
+    }
+
+    /**
+     * evaluate evaluates the expression.
+     *
+     * @param variables the variables, such as r_sub and p_obj.
+     * @param functions the functions the expression can call.
+     * @return the result.
+     * @throws ExpressionException if the evaluation fails.
+     */
+    public Object evaluate(Map<String, Object> variables, Map<String, ? 
extends ExpressionFunction> functions) {
+        return evaluate(new EvaluationContext(variables, functions));
+    }
+
+    /**
+     * evaluate evaluates the expression.
+     *
+     * @param context the variables and functions.
+     * @return the result.
+     * @throws ExpressionException if the evaluation fails.
+     */
+    public Object evaluate(EvaluationContext context) {
+        return root.eval(context);
+    }
+
+    public String getSource() {
+        return source;
+    }
+
+    @Override
+    public String toString() {
+        return source;
+    }
+}
diff --git 
a/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java 
b/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java
new file mode 100644
index 0000000..1b5e24d
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/ExpressionException.java
@@ -0,0 +1,31 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+/**
+ * ExpressionException is thrown when a matcher expression cannot be parsed or 
evaluated.
+ */
+public class ExpressionException extends RuntimeException {
+    public ExpressionException(String message) {
+        super(message);
+    }
+
+    public ExpressionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git 
a/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java 
b/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java
new file mode 100644
index 0000000..caf7c92
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/ExpressionFunction.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+/**
+ * ExpressionFunction is a function that can be called from a matcher, such as 
keyMatch(r.obj, p.obj).
+ */
+@FunctionalInterface
+public interface ExpressionFunction {
+    /**
+     * call invokes the function.
+     *
+     * @param args the evaluated arguments: String, Long, Double, Boolean, 
null,
+     *             List for array literals, or any object passed in the 
request.
+     * @return the result, usually a Boolean or a String.
+     */
+    Object call(Object... args);
+
+    /**
+     * call invokes the function with access to the variables and functions of 
the
+     * current evaluation. Override it when the function needs them, like 
eval() does.
+     *
+     * @param context the current evaluation context.
+     * @param args    the evaluated arguments.
+     * @return the result.
+     */
+    default Object call(EvaluationContext context, Object... args) {
+        return call(args);
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Lexer.java 
b/src/main/java/org/casbin/jcasbin/expression/Lexer.java
new file mode 100644
index 0000000..4e5fcd2
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Lexer.java
@@ -0,0 +1,205 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class Lexer {
+    enum Kind {
+        NUMBER, STRING, IDENTIFIER, OPERATOR, EOF
+    }
+
+    static class Token {
+        final Kind kind;
+        final String text;
+        final Object value;
+        final int position;
+
+        Token(Kind kind, String text, Object value, int position) {
+            this.kind = kind;
+            this.text = text;
+            this.value = value;
+            this.position = position;
+        }
+
+        boolean is(String operator) {
+            return kind == Kind.OPERATOR && text.equals(operator);
+        }
+
+        @Override
+        public String toString() {
+            return kind == Kind.EOF ? "end of expression" : "'" + text + "'";
+        }
+    }
+
+    private static final String[] OPERATORS = {
+        "**", "==", "!=", ">=", "<=", "=~", "!~", "&&", "||", "<<", ">>", "??",
+        "+", "-", "*", "/", "%", ">", "<", "!", "~", "&", "|", "^", "?", ":", 
"(", ")", "[", "]", ","
+    };
+
+    private final String source;
+    private int pos;
+
+    private Lexer(String source) {
+        this.source = source;
+    }
+
+    static List<Token> tokenize(String source) {
+        Lexer lexer = new Lexer(source);
+        List<Token> tokens = new ArrayList<>();
+        Token token;
+        do {
+            token = lexer.next();
+            tokens.add(token);
+        } while (token.kind != Kind.EOF);
+        return tokens;
+    }
+
+    private Token next() {
+        while (pos < source.length() && 
Character.isWhitespace(source.charAt(pos))) {
+            pos++;
+        }
+        if (pos >= source.length()) {
+            return new Token(Kind.EOF, "", null, pos);
+        }
+
+        char c = source.charAt(pos);
+        if (Character.isDigit(c)) {
+            return number();
+        }
+        if (c == '\'' || c == '"') {
+            return string(c);
+        }
+        if (Character.isJavaIdentifierStart(c)) {
+            return identifier();
+        }
+        for (String op : OPERATORS) {
+            if (source.startsWith(op, pos)) {
+                Token token = new Token(Kind.OPERATOR, op, null, pos);
+                pos += op.length();
+                return token;
+            }
+        }
+        throw new ExpressionException("unexpected character '" + c + "' at 
position " + pos + " in: " + source);
+    }
+
+    private Token number() {
+        int start = pos;
+        boolean decimal = false;
+        while (pos < source.length() && Character.isDigit(source.charAt(pos))) 
{
+            pos++;
+        }
+        if (pos + 1 < source.length() && source.charAt(pos) == '.' && 
Character.isDigit(source.charAt(pos + 1))) {
+            decimal = true;
+            pos++;
+            while (pos < source.length() && 
Character.isDigit(source.charAt(pos))) {
+                pos++;
+            }
+        }
+        if (pos < source.length() && (source.charAt(pos) == 'e' || 
source.charAt(pos) == 'E')) {
+            int mark = pos;
+            pos++;
+            if (pos < source.length() && (source.charAt(pos) == '+' || 
source.charAt(pos) == '-')) {
+                pos++;
+            }
+            if (pos < source.length() && 
Character.isDigit(source.charAt(pos))) {
+                decimal = true;
+                while (pos < source.length() && 
Character.isDigit(source.charAt(pos))) {
+                    pos++;
+                }
+            } else {
+                pos = mark;
+            }
+        }
+        String text = source.substring(start, pos);
+        Object value;
+        if (decimal) {
+            value = Double.parseDouble(text);
+        } else {
+            try {
+                value = Long.parseLong(text);
+            } catch (NumberFormatException e) {
+                value = Double.parseDouble(text);
+            }
+        }
+        return new Token(Kind.NUMBER, text, value, start);
+    }
+
+    private Token string(char quote) {
+        int start = pos;
+        pos++;
+        StringBuilder sb = new StringBuilder();
+        while (pos < source.length()) {
+            char c = source.charAt(pos);
+            if (c == quote) {
+                pos++;
+                return new Token(Kind.STRING, source.substring(start, pos), 
sb.toString(), start);
+            }
+            if (c == '\\' && pos + 1 < source.length()) {
+                char e = source.charAt(pos + 1);
+                pos += 2;
+                switch (e) {
+                    case 'n': sb.append('\n'); break;
+                    case 't': sb.append('\t'); break;
+                    case 'r': sb.append('\r'); break;
+                    case 'b': sb.append('\b'); break;
+                    case 'f': sb.append('\f'); break;
+                    case '\\': sb.append('\\'); break;
+                    case '\'': sb.append('\''); break;
+                    case '"': sb.append('"'); break;
+                    case 'u':
+                        if (pos + 4 <= source.length()) {
+                            try {
+                                sb.append((char) 
Integer.parseInt(source.substring(pos, pos + 4), 16));
+                                pos += 4;
+                                break;
+                            } catch (NumberFormatException ignored) {
+                            }
+                        }
+                        sb.append("\\u");
+                        break;
+                    default:
+                        // Keep unknown escapes such as \d as written, so 
regex patterns work.
+                        sb.append('\\').append(e);
+                }
+                continue;
+            }
+            sb.append(c);
+            pos++;
+        }
+        throw new ExpressionException("unterminated string starting at 
position " + start + " in: " + source);
+    }
+
+    private Token identifier() {
+        int start = pos;
+        pos++;
+        while (pos < source.length()) {
+            char c = source.charAt(pos);
+            if (Character.isJavaIdentifierPart(c)) {
+                pos++;
+            } else if (c == '.' && pos + 1 < source.length() && 
Character.isJavaIdentifierStart(source.charAt(pos + 1))) {
+                pos += 2;
+            } else {
+                break;
+            }
+        }
+        String text = source.substring(start, pos);
+        return new Token(Kind.IDENTIFIER, text, null, start);
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Operations.java 
b/src/main/java/org/casbin/jcasbin/expression/Operations.java
new file mode 100644
index 0000000..269b308
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Operations.java
@@ -0,0 +1,215 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+final class Operations {
+    private static final int MAX_CACHED_PATTERNS = 1000;
+    private static final Map<String, Pattern> PATTERNS = new 
ConcurrentHashMap<>();
+
+    private Operations() {
+    }
+
+    static boolean toBoolean(Object value, String operator) {
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        }
+        throw new ExpressionException("value " + describe(value) + " cannot be 
used with '" + operator + "', it is not a boolean");
+    }
+
+    static boolean equal(Object left, Object right) {
+        if (left == null || right == null) {
+            return left == right;
+        }
+        if (left instanceof Number && right instanceof Number) {
+            return compareNumbers((Number) left, (Number) right) == 0;
+        }
+        if (left instanceof Character) {
+            left = left.toString();
+        }
+        if (right instanceof Character) {
+            right = right.toString();
+        }
+        return Objects.equals(left, right);
+    }
+
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    static int compare(Object left, Object right, String operator) {
+        if (left instanceof Number && right instanceof Number) {
+            return compareNumbers((Number) left, (Number) right);
+        }
+        if (isText(left) && isText(right)) {
+            return left.toString().compareTo(right.toString());
+        }
+        if (left instanceof Comparable && right != null && left.getClass() == 
right.getClass()) {
+            return ((Comparable) left).compareTo(right);
+        }
+        throw new ExpressionException("values " + describe(left) + " and " + 
describe(right) + " cannot be compared with '" + operator + "'");
+    }
+
+    static boolean in(Object needle, Object haystack) {
+        if (haystack instanceof Collection) {
+            for (Object item : (Collection<?>) haystack) {
+                if (equal(needle, item)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        if (haystack != null && haystack.getClass().isArray()) {
+            int length = Array.getLength(haystack);
+            for (int i = 0; i < length; i++) {
+                if (equal(needle, Array.get(haystack, i))) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        throw new ExpressionException("the right side of 'in' must be an array 
or a collection, got " + describe(haystack));
+    }
+
+    static boolean regexMatch(Object text, Object pattern) {
+        if (!isText(text) || !isText(pattern)) {
+            throw new ExpressionException("'=~' needs a string and a pattern, 
got " + describe(text) + " and " + describe(pattern));
+        }
+        String regex = pattern.toString();
+        Pattern compiled = PATTERNS.get(regex);
+        if (compiled == null) {
+            try {
+                compiled = Pattern.compile(regex);
+            } catch (PatternSyntaxException e) {
+                throw new ExpressionException("invalid regular expression '" + 
regex + "'", e);
+            }
+            if (PATTERNS.size() >= MAX_CACHED_PATTERNS) {
+                PATTERNS.clear();
+            }
+            PATTERNS.put(regex, compiled);
+        }
+        return compiled.matcher(text.toString()).find();
+    }
+
+    static Object arithmetic(String operator, Object left, Object right) {
+        if ("+".equals(operator) && (isText(left) || isText(right))) {
+            return String.valueOf(left) + right;
+        }
+        if (!(left instanceof Number) || !(right instanceof Number)) {
+            throw new ExpressionException("values " + describe(left) + " and " 
+ describe(right) + " cannot be used with '" + operator + "', they are not 
numbers");
+        }
+        Number a = (Number) left;
+        Number b = (Number) right;
+        // Division is always floating point, as in Go.
+        if (isIntegral(a) && isIntegral(b) && !"/".equals(operator)) {
+            long x = a.longValue();
+            long y = b.longValue();
+            switch (operator) {
+                case "+": return x + y;
+                case "-": return x - y;
+                case "*": return x * y;
+                case "%":
+                    return y == 0 ? (Object) Double.NaN : (Object) (x % y);
+                case "**":
+                    if (y >= 0) {
+                        double power = Math.pow(x, y);
+                        if (Math.abs(power) < 0x1p63) {
+                            return (long) power;
+                        }
+                        return power;
+                    }
+                    return Math.pow(x, y);
+                case "&": return x & y;
+                case "|": return x | y;
+                case "^": return x ^ y;
+                case "<<": return x << y;
+                case ">>": return x >> y;
+                default:
+                    break;
+            }
+        } else {
+            double x = a.doubleValue();
+            double y = b.doubleValue();
+            switch (operator) {
+                case "+": return x + y;
+                case "-": return x - y;
+                case "*": return x * y;
+                case "/": return x / y;
+                case "%": return x % y;
+                case "**": return Math.pow(x, y);
+                case "&": return a.longValue() & b.longValue();
+                case "|": return a.longValue() | b.longValue();
+                case "^": return a.longValue() ^ b.longValue();
+                case "<<": return a.longValue() << b.longValue();
+                case ">>": return a.longValue() >> b.longValue();
+                default:
+                    break;
+            }
+        }
+        throw new ExpressionException("unknown operator '" + operator + "'");
+    }
+
+    static Object negate(Object value) {
+        if (value instanceof Number) {
+            Number n = (Number) value;
+            return isIntegral(n) ? (Object) (-n.longValue()) : (Object) 
(-n.doubleValue());
+        }
+        throw new ExpressionException("value " + describe(value) + " cannot be 
negated, it is not a number");
+    }
+
+    static Object bitwiseNot(Object value) {
+        if (value instanceof Number) {
+            return ~((Number) value).longValue();
+        }
+        throw new ExpressionException("value " + describe(value) + " cannot be 
used with '~', it is not a number");
+    }
+
+    private static int compareNumbers(Number a, Number b) {
+        if (isIntegral(a) && isIntegral(b)) {
+            return Long.compare(a.longValue(), b.longValue());
+        }
+        if (a instanceof BigDecimal || b instanceof BigDecimal || a instanceof 
BigInteger || b instanceof BigInteger) {
+            return new BigDecimal(a.toString()).compareTo(new 
BigDecimal(b.toString()));
+        }
+        return Double.compare(a.doubleValue(), b.doubleValue());
+    }
+
+    private static boolean isIntegral(Number n) {
+        return n instanceof Long || n instanceof Integer || n instanceof Short 
|| n instanceof Byte;
+    }
+
+    private static boolean isText(Object value) {
+        return value instanceof String || value instanceof Character;
+    }
+
+    static String describe(Object value) {
+        if (value == null) {
+            return "null";
+        }
+        if (value instanceof String) {
+            return "'" + value + "' (String)";
+        }
+        return value + " (" + value.getClass().getSimpleName() + ")";
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/Parser.java 
b/src/main/java/org/casbin/jcasbin/expression/Parser.java
new file mode 100644
index 0000000..632da0c
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/Parser.java
@@ -0,0 +1,335 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import org.casbin.jcasbin.expression.Lexer.Kind;
+import org.casbin.jcasbin.expression.Lexer.Token;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Parser builds the syntax tree of a matcher. Operator precedence follows the 
Go
+ * implementation (govaluate), from lowest to highest:
+ * ?: and ??, ||, &amp;&amp;, comparisons (== != &lt; &gt; &lt;= &gt;= =~ !~ 
in), |, ^, &amp;,
+ * &lt;&lt; &gt;&gt;, + -, * / %, **, unary - ! ~. Binary operators are 
left-associative.
+ */
+class Parser {
+    interface Node {
+        Object eval(EvaluationContext context);
+    }
+
+    private static final Set<String> COMPARATORS = new 
HashSet<>(Arrays.asList("==", "!=", "<", ">", "<=", ">=", "=~", "!~"));
+
+    private final String source;
+    private final List<Token> tokens;
+    private int index;
+
+    private Parser(String source) {
+        this.source = source;
+        this.tokens = Lexer.tokenize(source);
+    }
+
+    static Node parse(String source) {
+        Parser parser = new Parser(source);
+        Node node = parser.ternary();
+        if (parser.peek().kind != Kind.EOF) {
+            throw parser.error("unexpected " + parser.peek());
+        }
+        return node;
+    }
+
+    private Token peek() {
+        return tokens.get(index);
+    }
+
+    private Token advance() {
+        return tokens.get(index++);
+    }
+
+    private boolean accept(String operator) {
+        if (peek().is(operator)) {
+            index++;
+            return true;
+        }
+        return false;
+    }
+
+    private void expect(String operator) {
+        if (!accept(operator)) {
+            throw error("expected '" + operator + "' but found " + peek());
+        }
+    }
+
+    private ExpressionException error(String message) {
+        return new ExpressionException(message + " at position " + 
peek().position + " in: " + source);
+    }
+
+    private Node ternary() {
+        Node condition = or();
+        if (accept("?")) {
+            Node whenTrue = ternary();
+            expect(":");
+            Node whenFalse = ternary();
+            return ctx -> Operations.toBoolean(condition.eval(ctx), "?") ? 
whenTrue.eval(ctx) : whenFalse.eval(ctx);
+        }
+        if (accept("??")) {
+            Node fallback = ternary();
+            return ctx -> {
+                Object value = condition.eval(ctx);
+                return value != null ? value : fallback.eval(ctx);
+            };
+        }
+        return condition;
+    }
+
+    private Node or() {
+        Node left = and();
+        while (accept("||")) {
+            Node l = left;
+            Node r = and();
+            left = ctx -> Operations.toBoolean(l.eval(ctx), "||") || 
Operations.toBoolean(r.eval(ctx), "||");
+        }
+        return left;
+    }
+
+    private Node and() {
+        Node left = comparison();
+        while (accept("&&")) {
+            Node l = left;
+            Node r = comparison();
+            left = ctx -> Operations.toBoolean(l.eval(ctx), "&&") && 
Operations.toBoolean(r.eval(ctx), "&&");
+        }
+        return left;
+    }
+
+    private Node comparison() {
+        Node left = binary(0);
+        while (true) {
+            Token token = peek();
+            String op;
+            if (token.kind == Kind.OPERATOR && 
COMPARATORS.contains(token.text)) {
+                op = token.text;
+            } else if (token.kind == Kind.IDENTIFIER && 
token.text.equals("in")) {
+                op = "in";
+            } else {
+                return left;
+            }
+            index++;
+            Node l = left;
+            Node r = op.equals("in") && peek().is("(") ? inList() : binary(0);
+            left = comparator(op, l, r);
+        }
+    }
+
+    // After 'in', a parenthesized single value such as ('admin') is a 
one-element list, as in Go.
+    private Node inList() {
+        expect("(");
+        List<Node> items = list(")");
+        if (items.size() != 1 || tokens.get(index - 2).is(",")) {
+            return array(items);
+        }
+        Node item = items.get(0);
+        return ctx -> {
+            Object value = item.eval(ctx);
+            if (value instanceof Collection || (value != null && 
value.getClass().isArray())) {
+                return value;
+            }
+            return Collections.singletonList(value);
+        };
+    }
+
+    private static Node comparator(String op, Node l, Node r) {
+        switch (op) {
+            case "==": return ctx -> Operations.equal(l.eval(ctx), 
r.eval(ctx));
+            case "!=": return ctx -> !Operations.equal(l.eval(ctx), 
r.eval(ctx));
+            case "<": return ctx -> Operations.compare(l.eval(ctx), 
r.eval(ctx), op) < 0;
+            case ">": return ctx -> Operations.compare(l.eval(ctx), 
r.eval(ctx), op) > 0;
+            case "<=": return ctx -> Operations.compare(l.eval(ctx), 
r.eval(ctx), op) <= 0;
+            case ">=": return ctx -> Operations.compare(l.eval(ctx), 
r.eval(ctx), op) >= 0;
+            case "=~": return ctx -> Operations.regexMatch(l.eval(ctx), 
r.eval(ctx));
+            case "!~": return ctx -> !Operations.regexMatch(l.eval(ctx), 
r.eval(ctx));
+            default: return ctx -> Operations.in(l.eval(ctx), r.eval(ctx));
+        }
+    }
+
+    // Left-associative binary levels between comparisons and unary operators, 
lowest first.
+    private static final String[][] BINARY_LEVELS = {
+        {"|"}, {"^"}, {"&"}, {"<<", ">>"}, {"+", "-"}, {"*", "/", "%"}
+    };
+
+    private Node binary(int level) {
+        if (level == BINARY_LEVELS.length) {
+            return power();
+        }
+        Node left = binary(level + 1);
+        while (true) {
+            String op = null;
+            for (String candidate : BINARY_LEVELS[level]) {
+                if (peek().is(candidate)) {
+                    op = candidate;
+                    break;
+                }
+            }
+            if (op == null) {
+                return left;
+            }
+            index++;
+            Node l = left;
+            Node r = binary(level + 1);
+            String operator = op;
+            left = ctx -> Operations.arithmetic(operator, l.eval(ctx), 
r.eval(ctx));
+        }
+    }
+
+    private Node power() {
+        Node left = unary();
+        while (accept("**")) {
+            Node l = left;
+            Node r = unary();
+            left = ctx -> Operations.arithmetic("**", l.eval(ctx), 
r.eval(ctx));
+        }
+        return left;
+    }
+
+    private Node unary() {
+        if (accept("!")) {
+            Node operand = unary();
+            return ctx -> !Operations.toBoolean(operand.eval(ctx), "!");
+        }
+        if (accept("-")) {
+            Node operand = unary();
+            return ctx -> Operations.negate(operand.eval(ctx));
+        }
+        if (accept("~")) {
+            Node operand = unary();
+            return ctx -> Operations.bitwiseNot(operand.eval(ctx));
+        }
+        return primary();
+    }
+
+    private Node primary() {
+        Token token = advance();
+        switch (token.kind) {
+            case NUMBER:
+            case STRING:
+                Object value = token.value;
+                return ctx -> value;
+            case IDENTIFIER:
+                return identifier(token);
+            case OPERATOR:
+                if (token.text.equals("(")) {
+                    List<Node> items = list(")");
+                    if (items.size() == 1 && !tokens.get(index - 2).is(",")) {
+                        return items.get(0);
+                    }
+                    return array(items);
+                }
+                if (token.text.equals("[")) {
+                    return array(list("]"));
+                }
+                break;
+            default:
+                break;
+        }
+        index--;
+        throw error("unexpected " + token);
+    }
+
+    private Node identifier(Token token) {
+        String name = token.text;
+        switch (name) {
+            case "true":
+                return ctx -> Boolean.TRUE;
+            case "false":
+                return ctx -> Boolean.FALSE;
+            case "nil":
+            case "null":
+                return ctx -> null;
+            case "in":
+                index--;
+                throw error("unexpected 'in'");
+            default:
+                break;
+        }
+
+        if (accept("(")) {
+            if (name.indexOf('.') >= 0) {
+                index--;
+                throw error("unknown function '" + name + "'");
+            }
+            List<Node> args = list(")");
+            Node[] argNodes = args.toArray(new Node[0]);
+            return ctx -> {
+                ExpressionFunction function = ctx.getFunctions().get(name);
+                if (function == null) {
+                    throw new ExpressionException("unknown function '" + name 
+ "' in: " + source);
+                }
+                Object[] values = new Object[argNodes.length];
+                for (int i = 0; i < argNodes.length; i++) {
+                    values[i] = argNodes[i].eval(ctx);
+                }
+                return function.call(ctx, values);
+            };
+        }
+
+        String[] path = name.split("\\.");
+        if (path.length == 1) {
+            return ctx -> ctx.getVariables().get(name);
+        }
+        return ctx -> {
+            Object value = ctx.getVariables().get(path[0]);
+            for (int i = 1; i < path.length; i++) {
+                value = PropertyReader.get(value, path[i]);
+            }
+            return value;
+        };
+    }
+
+    // Parses comma-separated items up to the closing bracket, which is 
consumed.
+    private List<Node> list(String close) {
+        List<Node> items = new ArrayList<>();
+        if (accept(close)) {
+            return items;
+        }
+        do {
+            if (peek().is(close)) {
+                break;
+            }
+            items.add(ternary());
+        } while (accept(","));
+        expect(close);
+        return items;
+    }
+
+    private static Node array(List<Node> items) {
+        Node[] nodes = items.toArray(new Node[0]);
+        return ctx -> {
+            List<Object> values = new ArrayList<>(nodes.length);
+            for (Node node : nodes) {
+                values.add(node.eval(ctx));
+            }
+            return Collections.unmodifiableList(values);
+        };
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java 
b/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java
new file mode 100644
index 0000000..d20b744
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/expression/PropertyReader.java
@@ -0,0 +1,124 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.expression;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * PropertyReader reads attributes such as r.sub.Age from request objects: map 
entries,
+ * JavaBean getters, record accessors and public fields. It never calls other 
methods.
+ */
+final class PropertyReader {
+    private interface Accessor {
+        Object get(Object target) throws ReflectiveOperationException;
+    }
+
+    private static final Accessor MISSING = target -> null;
+    private static final Map<Class<?>, Map<String, Accessor>> ACCESSORS = new 
ConcurrentHashMap<>();
+
+    private PropertyReader() {
+    }
+
+    static Object get(Object target, String name) {
+        if (target == null) {
+            return null;
+        }
+        if (target instanceof Map) {
+            return ((Map<?, ?>) target).get(name);
+        }
+
+        Class<?> type = target.getClass();
+        Accessor accessor = ACCESSORS.computeIfAbsent(type, k -> new 
ConcurrentHashMap<>())
+            .computeIfAbsent(name, k -> find(type, name));
+        if (accessor == MISSING) {
+            throw new ExpressionException("no property '" + name + "' on " + 
type.getName());
+        }
+        try {
+            return accessor.get(target);
+        } catch (InvocationTargetException e) {
+            throw new ExpressionException("failed to read property '" + name + 
"' on " + type.getName(), e.getCause());
+        } catch (ReflectiveOperationException e) {
+            throw new ExpressionException("failed to read property '" + name + 
"' on " + type.getName(), e);
+        }
+    }
+
+    private static Accessor find(Class<?> type, String name) {
+        if (name.isEmpty()) {
+            return MISSING;
+        }
+        String capitalized = Character.toUpperCase(name.charAt(0)) + 
name.substring(1);
+        Method method = getter(type, "get" + capitalized);
+        if (method == null) {
+            method = getter(type, "is" + capitalized);
+            if (method != null && method.getReturnType() != boolean.class && 
method.getReturnType() != Boolean.class) {
+                method = null;
+            }
+        }
+        if (method == null && isRecord(type)) {
+            method = getter(type, name);
+        }
+        if (method != null) {
+            if (method.getName().equals("getClass")) {
+                return MISSING;
+            }
+            Method m = method;
+            return m::invoke;
+        }
+
+        try {
+            Field field = type.getField(name);
+            if (!Modifier.isStatic(field.getModifiers())) {
+                makeAccessible(field);
+                return field::get;
+            }
+        } catch (NoSuchFieldException ignored) {
+        }
+        return MISSING;
+    }
+
+    private static Method getter(Class<?> type, String methodName) {
+        try {
+            Method method = type.getMethod(methodName);
+            if (Modifier.isStatic(method.getModifiers()) || 
method.getReturnType() == void.class) {
+                return null;
+            }
+            makeAccessible(method);
+            return method;
+        } catch (NoSuchMethodException e) {
+            return null;
+        }
+    }
+
+    private static boolean isRecord(Class<?> type) {
+        Class<?> superclass = type.getSuperclass();
+        return superclass != null && 
"java.lang.Record".equals(superclass.getName());
+    }
+
+    // Public members of non-public classes, such as a private nested class, 
still need this.
+    private static void makeAccessible(java.lang.reflect.AccessibleObject 
member) {
+        try {
+            member.setAccessible(true);
+        } catch (RuntimeException ignored) {
+        }
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java 
b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
index 119c628..fec11c4 100644
--- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java
@@ -20,11 +20,6 @@ package org.casbin.jcasbin.main;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.Expression;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorFunction;
 import org.casbin.jcasbin.effect.DefaultEffector;
 import org.casbin.jcasbin.effect.Effect;
 import org.casbin.jcasbin.effect.Effector;
@@ -32,6 +27,8 @@ import org.casbin.jcasbin.effect.StreamEffector;
 import org.casbin.jcasbin.exception.CasbinAdapterException;
 import org.casbin.jcasbin.exception.CasbinEffectorException;
 import org.casbin.jcasbin.exception.CasbinMatcherException;
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.model.Assertion;
 import org.casbin.jcasbin.model.FunctionMap;
 import org.casbin.jcasbin.model.Model;
@@ -55,6 +52,8 @@ import java.util.regex.Pattern;
 public class CoreEnforcer {
     private static final Pattern STRING_LITERAL = 
Pattern.compile("'(?:\\\\.|[^'\\\\])*'|\"(?:\\\\.|[^\"\\\\])*\"");
     private static final Pattern MATCHER_FUNCTION_CALL = 
Pattern.compile("(?<![\\w.])(\\w+)\\s*\\(");
+    // A custom matcher may be written as a model line, such as "m = r.sub == 
p.sub".
+    private static final Pattern MATCHER_KEY_PREFIX = 
Pattern.compile("^\\s*m\\w*\\s*=(?![=~])");
 
     String modelPath;
     Model model;
@@ -74,8 +73,9 @@ public class CoreEnforcer {
     boolean autoNotifyDispatcher = true;
     boolean acceptJsonRequest = false;
 
-    private AviatorEvaluatorInstance aviatorEval;
-    private final Map<String, Map<String, AviatorBoolean>> gFunctionCache = 
new ConcurrentHashMap<>();
+    private static final int MAX_CACHED_EXPRESSIONS = 1000;
+    private final Map<String, Expression> expressionCache = new 
ConcurrentHashMap<>();
+    private final Map<String, Map<String, Boolean>> gFunctionCache = new 
ConcurrentHashMap<>();
 
     void initialize() {
         rmMap = new HashMap<>();
@@ -87,9 +87,7 @@ public class CoreEnforcer {
         autoSave = adapter instanceof FileAdapter ? false : true;
         autoBuildRoleLinks = true;
         dispatcher = null;
-        aviatorEval = AviatorEvaluator.newInstance();
         initRmMap();
-        initBuiltInFunction();
     }
 
     /**
@@ -167,24 +165,6 @@ public class CoreEnforcer {
         fm = FunctionMap.loadFunctionMap();
     }
 
-    /**
-     * set the aviator evaluator
-     *
-     * @param evaluator aviator evaluator
-     */
-    public void setAviatorEvaluator(AviatorEvaluatorInstance evaluator) {
-        this.aviatorEval = Objects.requireNonNull(evaluator, "The aviator 
evaluator cannot be null.");
-    }
-
-    /**
-     * gets the current Aviator Evaluator instance
-     *
-     * @return Aviator Evaluator instance of enforcer
-     */
-    public AviatorEvaluatorInstance getAviatorEval() {
-        return aviatorEval;
-    }
-
     /**
      * getAdapter gets the current adapter.
      *
@@ -464,17 +444,6 @@ public class CoreEnforcer {
         registerDomainMatchingFunc(ptype);
     }
 
-    private void initBuiltInFunction() {
-        for (Map.Entry<String, AviatorFunction> entry : fm.fm.entrySet()) {
-            AviatorFunction function = entry.getValue();
-
-            if (aviatorEval.containsFunction(function.getName())) {
-                aviatorEval.removeFunction(function.getName());
-            }
-            aviatorEval.addFunction(function);
-        }
-    }
-
     /**
      * clearRmMap clears rmMap.
      */
@@ -574,13 +543,7 @@ public class CoreEnforcer {
             return new EnforceResult(true, new 
ArrayList<>(Collections.singletonList("The enforcer is not enabled, allow all 
requests")));
         }
 
-        boolean compileCached = true;
-        if (fm.isModify) {
-            compileCached = false;
-            initBuiltInFunction();
-            fm.isModify = false;
-        }
-        Map<String, AviatorFunction> gFunctions = new HashMap<>();
+        Map<String, ExpressionFunction> functions = new HashMap<>(fm.fm);
         if (model.model.containsKey("g")) {
             for (Map.Entry<String, Assertion> entry : 
model.model.get("g").entrySet()) {
                 String key = entry.getKey();
@@ -588,25 +551,16 @@ public class CoreEnforcer {
 
                 RoleManager rm = ast.rm;
                 if (rm != null){
-                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm, 
gFunctionCache);
-                    gFunctions.put(key, aviatorFunction);
+                    functions.put(key, 
BuiltInFunctions.GenerateGFunctionClass.generateGFunction(key, rm, 
gFunctionCache));
                 }
 
                 ConditionalRoleManager condRM = ast.condRM;
                 if (condRM != null){
-                    AviatorFunction aviatorFunction = 
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
 condRM, gFunctionCache);
-                    gFunctions.put(key, aviatorFunction);
+                    functions.put(key, 
BuiltInFunctions.GenerateConditionalGFunctionClass.generateConditionalGFunction(key,
 condRM, gFunctionCache));
                 }
 
             }
         }
-        for (AviatorFunction f : gFunctions.values()) {
-            if (!aviatorEval.containsFunction(f.getName())) {
-                aviatorEval.addFunction(f);
-                compileCached = false;
-            }
-        }
-        fm.setAviatorEval(aviatorEval);
 
         String rType = "r", pType = "p", eType = "e", mType = "m";
         if (rvals.length != 0) {
@@ -624,7 +578,7 @@ public class CoreEnforcer {
         if (matcher == null || "".equals(matcher)) {
             expString = model.model.get("m").get(mType).value;
         } else {
-            expString = Util.removeComments(Util.escapeAssertion(matcher));
+            expString = 
Util.removeComments(Util.escapeAssertion(MATCHER_KEY_PREFIX.matcher(matcher).replaceFirst("")));
         }
 
         // json process
@@ -649,9 +603,7 @@ public class CoreEnforcer {
             }
         }
 
-        expString = Util.convertInSyntax(expString);
-        // Use md5 encryption as cacheKey to prevent expString from being too 
long
-        Expression expression = aviatorEval.compile(Util.md5(expString), 
expString, compileCached);
+        Expression expression = compileMatcher(expString);
 
         StreamEffector streamEffector = null;
         try {
@@ -679,7 +631,7 @@ public class CoreEnforcer {
                 getPTokens(parameters, pType, pvals, pTokens);
                 getRTokens(parameters, rType, rvals);
 
-                Object result = expression.execute(parameters);
+                Object result = expression.evaluate(parameters, functions);
 
                 if (result instanceof Boolean) {
                     if (!((boolean) result)) {
@@ -690,7 +642,7 @@ public class CoreEnforcer {
                     if (streamEffector == null) {
                         continue;
                     }
-                } else if (result instanceof Double || result instanceof Long) 
{
+                } else if (result instanceof Number) {
                     if (((Number) result).floatValue() == 0) {
                         policyEffects[i] = Effect.Indeterminate;
                     } else {
@@ -740,7 +692,7 @@ public class CoreEnforcer {
                 parameters.put(token, "");
             }
 
-            Object result = expression.execute(parameters);
+            Object result = expression.evaluate(parameters, functions);
 
             if (streamEffector != null) {
                 if ((boolean) result) {
@@ -1010,7 +962,19 @@ public class CoreEnforcer {
      * need to call it explicitly if you manipulate directly Model.
      */
     public void resetExpressionEvaluator() {
-        fm.setAviatorEval(null);
+        expressionCache.clear();
+    }
+
+    private Expression compileMatcher(String expString) {
+        Expression expression = expressionCache.get(expString);
+        if (expression == null) {
+            expression = Expression.compile(expString);
+            if (expressionCache.size() >= MAX_CACHED_EXPRESSIONS) {
+                expressionCache.clear();
+            }
+            expressionCache.put(expString, expression);
+        }
+        return expression;
     }
 
     public boolean isAutoNotifyWatcher() {
diff --git a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java 
b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
index 663533d..c3cb17d 100644
--- a/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
+++ b/src/main/java/org/casbin/jcasbin/main/ManagementEnforcer.java
@@ -18,9 +18,9 @@
 package org.casbin.jcasbin.main;
 
 import org.casbin.jcasbin.effect.Effect;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.model.Assertion;
 import org.casbin.jcasbin.util.Util;
-import org.casbin.jcasbin.util.function.CustomFunction;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -629,7 +629,6 @@ public class ManagementEnforcer extends InternalEnforcer {
     public boolean addNamedGroupingPolicy(String ptype, List<String> params) {
         boolean ruleAdded = addPolicy("g", ptype, params);
 
-        fm.setAviatorEval(null);
         return ruleAdded;
     }
 
@@ -784,7 +783,6 @@ public class ManagementEnforcer extends InternalEnforcer {
     public boolean removeNamedGroupingPolicy(String ptype, List<String> 
params) {
         boolean ruleRemoved = removePolicy("g", ptype, params);
 
-        fm.setAviatorEval(null);
         return ruleRemoved;
     }
 
@@ -833,7 +831,6 @@ public class ManagementEnforcer extends InternalEnforcer {
     public boolean removeFilteredNamedGroupingPolicy(String ptype, int 
fieldIndex, String... fieldValues) {
         boolean ruleRemoved = removeFilteredPolicy("g", ptype, fieldIndex, 
fieldValues);
 
-        fm.setAviatorEval(null);
         return ruleRemoved;
     }
 
@@ -843,10 +840,8 @@ public class ManagementEnforcer extends InternalEnforcer {
      * @param name the name of the function.
      * @param function the custom function.
      */
-    public void addFunction(String name, CustomFunction function) {
+    public void addFunction(String name, ExpressionFunction function) {
         fm.addFunction(name, function);
-
-        fm.setAviatorEval(null);
     }
 
     /**
diff --git a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java 
b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
index 5651daa..7586663 100644
--- a/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
+++ b/src/main/java/org/casbin/jcasbin/model/FunctionMap.java
@@ -17,8 +17,7 @@
 
 package org.casbin.jcasbin.model;
 
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.type.AviatorFunction;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.function.*;
 
 import java.util.HashMap;
@@ -29,9 +28,9 @@ import java.util.Map;
  */
 public class FunctionMap {
     /**
-     * AviatorFunction represents a function that is used in the matchers, 
used to get attributes in ABAC.
+     * fm maps the function names to the functions that can be used in the 
matchers.
      */
-    public Map<String, AviatorFunction> fm;
+    public Map<String, ExpressionFunction> fm;
 
     public boolean isModify = false;
 
@@ -41,36 +40,11 @@ public class FunctionMap {
      * @param name the name of the new function.
      * @param function the function.
      */
-    public void addFunction(String name, AviatorFunction function) {
+    public void addFunction(String name, ExpressionFunction function) {
         fm.put(name, function);
         isModify=true;
     }
 
-    /**
-     * setAviatorEval adds AviatorEvaluatorInstance to the custom function.
-     *
-     * @param name        the name of the custom function.
-     * @param aviatorEval the AviatorEvaluatorInstance object.
-     */
-    public void setAviatorEval(String name, AviatorEvaluatorInstance 
aviatorEval) {
-        if (fm.containsKey(name) && fm.get(name) instanceof CustomFunction) {
-            ((CustomFunction) fm.get(name)).setAviatorEval(aviatorEval);
-        }
-    }
-
-    /**
-     * setAviatorEval adds AviatorEvaluatorInstance to all the custom function.
-     *
-     * @param aviatorEval the AviatorEvaluatorInstance object.
-     */
-    public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
-        for (AviatorFunction function : fm.values()) {
-            if (function instanceof CustomFunction) {
-                ((CustomFunction) function).setAviatorEval(aviatorEval);
-            }
-        }
-    }
-
     /**
      * loadFunctionMap loads an initial function map.
      *
diff --git a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java 
b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
index 318d5b5..09fe1d2 100644
--- a/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
+++ b/src/main/java/org/casbin/jcasbin/util/BuiltInFunctions.java
@@ -17,16 +17,15 @@
 
 package org.casbin.jcasbin.util;
 
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.function.AbstractVariadicFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.*;
 import inet.ipaddr.AddressStringException;
 import inet.ipaddr.IPAddress;
 import inet.ipaddr.IPAddressString;
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.model.FunctionMap;
 import org.casbin.jcasbin.rbac.ConditionalRoleManager;
 import org.casbin.jcasbin.rbac.RoleManager;
+import org.casbin.jcasbin.util.function.FunctionArgs;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -41,6 +40,8 @@ public class BuiltInFunctions {
     private static final Pattern KEY_MATCH2_PATTERN = 
Pattern.compile(":[^/]+");
     private static final Pattern KEY_MATCH3_PATTERN = 
Pattern.compile("\\{[^/]+\\}");
     private static final Pattern KEY_MATCH5_PATTERN = 
Pattern.compile("\\{[^/]+\\}");
+    private static final int MAX_EVAL_EXPRESSIONS = 10000;
+    private static final Map<String, Expression> EVAL_EXPRESSIONS = new 
ConcurrentHashMap<>();
 
     /**
      * validate the variadic string parameter size
@@ -407,7 +408,7 @@ public class BuiltInFunctions {
          * @param rm   the role manager used by the function.
          * @return the function.
          */
-        public static AviatorFunction generateGFunction(String name, 
RoleManager rm) {
+        public static ExpressionFunction generateGFunction(String name, 
RoleManager rm) {
             return generateGFunction(name, rm, new ConcurrentHashMap<>());
         }
 
@@ -419,49 +420,34 @@ public class BuiltInFunctions {
          * @param memorizedMap the cache of g(_, _) results owned by the 
caller, key: name such as g, g2.
          * @return the function.
          */
-        public static AviatorFunction generateGFunction(String name, 
RoleManager rm, Map<String, Map<String, AviatorBoolean>> memorizedMap) {
+        public static ExpressionFunction generateGFunction(String name, 
RoleManager rm, Map<String, Map<String, Boolean>> memorizedMap) {
             memorizedMap.put(name, new ConcurrentHashMap<>());
 
-            return new AbstractVariadicFunction() {
-                @Override
-                public AviatorObject variadicCall(Map<String, Object> env, 
AviatorObject... args) {
-                    Map<String, AviatorBoolean> memorized = 
memorizedMap.get(name);
-                    int len = args.length;
-                    if (len < 2) {
-                        return AviatorBoolean.valueOf(false);
-                    }
-                    String name1 = FunctionUtils.getStringValue(args[0], env);
-                    String name2 = FunctionUtils.getStringValue(args[1], env);
-
-                    String key = "";
-                    for (AviatorObject arg : args) {
-                        String name = FunctionUtils.getStringValue(arg, env);
-                        key += ";" + name;
-                    }
-
-                    AviatorBoolean value = memorized.get(key);
-                    if (value != null) {
-                        return value;
-                    }
-
-                    if (rm == null) {
-                        value = AviatorBoolean.valueOf(name1.equals(name2));
-                    } else if (len == 2) {
-                        value = AviatorBoolean.valueOf(rm.hasLink(name1, 
name2));
-                    } else if (len >= 3) {
-                        String domain = FunctionUtils.getStringValue(args[2], 
env);
-                        value = AviatorBoolean.valueOf(rm.hasLink(name1, 
name2, domain));
-                    } else {
-                        value = AviatorBoolean.valueOf(false);
-                    }
-                    memorized.put(key, value);
+            return args -> {
+                Map<String, Boolean> memorized = memorizedMap.get(name);
+                int len = args.length;
+                if (len < 2) {
+                    return false;
+                }
+                String name1 = FunctionArgs.getString(args, 0);
+                String name2 = FunctionArgs.getString(args, 1);
+
+                String key = gFunctionCacheKey(args);
+                Boolean value = memorized.get(key);
+                if (value != null) {
                     return value;
                 }
 
-                @Override
-                public String getName() {
-                    return name;
+                if (rm == null) {
+                    value = name1.equals(name2);
+                } else if (len == 2) {
+                    value = rm.hasLink(name1, name2);
+                } else {
+                    String domain = FunctionArgs.getString(args, 2);
+                    value = rm.hasLink(name1, name2, domain);
                 }
+                memorized.put(key, value);
+                return value;
             };
         }
     }
@@ -474,7 +460,7 @@ public class BuiltInFunctions {
          * @param condRm  the conditional role manager used by the function.
          * @return the function.
          */
-        public static AviatorFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm) {
+        public static ExpressionFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm) {
             return generateConditionalGFunction(name, condRm, new 
ConcurrentHashMap<>());
         }
 
@@ -486,85 +472,74 @@ public class BuiltInFunctions {
          * @param memorizedMap the cache of g(_, _) results owned by the 
caller, key: name such as g, g2.
          * @return the function.
          */
-        public static AviatorFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm, Map<String, Map<String, AviatorBoolean>> 
memorizedMap) {
+        public static ExpressionFunction generateConditionalGFunction(String 
name, ConditionalRoleManager condRm, Map<String, Map<String, Boolean>> 
memorizedMap) {
             memorizedMap.put(name, new ConcurrentHashMap<>());
 
-            return new AbstractVariadicFunction() {
-                @Override
-                public AviatorObject variadicCall(Map<String, Object> env, 
AviatorObject... args) {
-                    Map<String, AviatorBoolean> memorized = 
memorizedMap.get(name);
-                    int len = args.length;
-                    if (len < 2) {
-                        return AviatorBoolean.valueOf(false);
-                    }
-                    String name1 = FunctionUtils.getStringValue(args[0], env);
-                    String name2 = FunctionUtils.getStringValue(args[1], env);
-
-                    String key = "";
-                    for (AviatorObject arg : args) {
-                        String name = FunctionUtils.getStringValue(arg, env);
-                        key += ";" + name;
-                    }
-
-                    AviatorBoolean value = memorized.get(key);
-                    if (value != null) {
-                        return value;
-                    }
-
-                    boolean hasLink;
-                    if (condRm == null) {
-                        hasLink = name1.equals(name2);
-                    } else if (len == 2) {
-                        hasLink = condRm.hasLink(name1, name2);
-                    } else if (len >= 3) {
-                        String domain = FunctionUtils.getStringValue(args[2], 
env);
-                        hasLink = condRm.hasLink(name1, name2, domain);
-                    } else {
-                        hasLink = false;
-                    }
-                    value = AviatorBoolean.valueOf(hasLink);
-                    memorized.put(key, value);
+            return args -> {
+                Map<String, Boolean> memorized = memorizedMap.get(name);
+                int len = args.length;
+                if (len < 2) {
+                    return false;
+                }
+                String name1 = FunctionArgs.getString(args, 0);
+                String name2 = FunctionArgs.getString(args, 1);
+
+                String key = gFunctionCacheKey(args);
+                Boolean value = memorized.get(key);
+                if (value != null) {
                     return value;
                 }
 
-                @Override
-                public String getName() {
-                    return name;
+                if (condRm == null) {
+                    value = name1.equals(name2);
+                } else if (len == 2) {
+                    value = condRm.hasLink(name1, name2);
+                } else {
+                    String domain = FunctionArgs.getString(args, 2);
+                    value = condRm.hasLink(name1, name2, domain);
                 }
+                memorized.put(key, value);
+                return value;
             };
         }
     }
 
+    private static String gFunctionCacheKey(Object[] args) {
+        StringBuilder key = new StringBuilder();
+        for (int i = 0; i < args.length; i++) {
+            key.append(';').append(FunctionArgs.getString(args, i));
+        }
+        return key.toString();
+    }
+
     /**
      * eval calculates the stringified boolean expression and return its 
result.
      *
-     * @param eval        the stringified boolean expression.
-     * @param env         the key-value pair of the parameters in the 
expression.
-     * @param aviatorEval the AviatorEvaluatorInstance object which contains 
built-in functions and custom functions.
-     * @return the result of the eval.
+     * @param eval      the stringified boolean expression.
+     * @param env       the key-value pair of the parameters in the expression.
+     * @param functions the built-in functions and custom functions the 
expression can call,
+     *                  null means only the built-in functions.
+     * @return the result of the eval, false if the expression is invalid or 
fails.
      */
-    public static boolean eval(String eval, Map<String, Object> env, 
AviatorEvaluatorInstance aviatorEval) {
-        // Validate expression to block AviatorScript-specific features
-        // that break cross-platform compatibility
-        try {
-            ExpressionValidator.validateExpression(eval);
-        } catch (IllegalArgumentException e) {
-            Util.logPrintfWarn("Expression validation failed: {}", 
e.getMessage());
-            return false;
+    public static boolean eval(String eval, Map<String, Object> env, 
Map<String, ? extends ExpressionFunction> functions) {
+        if (functions == null) {
+            functions = FunctionMap.loadFunctionMap().fm;
         }
-        
-        boolean res;
-        if (aviatorEval != null) {
-            try {
-                res = (boolean) aviatorEval.execute(eval, env);
-            } catch (Exception e) {
-                Util.logPrintfWarn("Execute 'eval' function error, nested 
exception is: {}", e.getMessage());
-                res = false;
+        try {
+            Expression expression = EVAL_EXPRESSIONS.get(eval);
+            if (expression == null) {
+                expression = Expression.compile(eval);
+                if (EVAL_EXPRESSIONS.size() >= MAX_EVAL_EXPRESSIONS) {
+                    EVAL_EXPRESSIONS.clear();
+                }
+                EVAL_EXPRESSIONS.put(eval, expression);
             }
-        } else {
-            res = (boolean) AviatorEvaluator.execute(eval, env);
+            Object res = expression.evaluate(env, functions);
+            return res instanceof Boolean && (Boolean) res;
+        } catch (Exception e) {
+            Util.logPrintfWarn("Execute 'eval' function error, nested 
exception is: {}", e.getMessage());
+            return false;
         }
-        return res;
     }
 
     // builtin LinkConditionFunc
diff --git a/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java 
b/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java
deleted file mode 100644
index c91d280..0000000
--- a/src/main/java/org/casbin/jcasbin/util/ExpressionValidator.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.casbin.jcasbin.util;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * ExpressionValidator validates expressions to ensure they only use standard 
Casbin syntax
- * and don't expose AviatorScript-specific features that would break 
cross-platform compatibility.
- */
-public class ExpressionValidator {
-    
-    // Patterns for AviatorScript-specific syntax that should be blocked
-    private static final Pattern[] DISALLOWED_PATTERNS = {
-        Pattern.compile("\\bseq\\."),           // seq.list(), seq.map(), etc.
-        Pattern.compile("\\bstring\\."),        // string.startsWith(), 
string.endsWith(), etc.
-        Pattern.compile("\\bmath\\."),          // math.sqrt(), math.pow(), 
etc.
-        Pattern.compile("\\blambda\\b"),        // lambda expressions
-        Pattern.compile("\\blet\\b"),           // variable binding
-        Pattern.compile("\\bfn\\b"),            // function definitions
-        Pattern.compile("->"),                  // lambda arrow
-        Pattern.compile("=>"),                  // alternative lambda arrow
-        Pattern.compile("\\bfor\\b"),           // for loops
-        Pattern.compile("\\bwhile\\b"),         // while loops
-        Pattern.compile("\\breturn\\b"),        // return statements
-        Pattern.compile("\\bif\\b.*\\bthen\\b.*\\belse\\b"), // if-then-else 
(aviator style)
-    };
-    
-    /**
-     * Validates that an expression only uses standard Casbin syntax.
-     * 
-     * @param expression the expression to validate
-     * @throws IllegalArgumentException if the expression contains 
non-standard syntax
-     */
-    public static void validateExpression(String expression) {
-        if (expression == null || expression.trim().isEmpty()) {
-            return;
-        }
-        
-        // Check for disallowed AviatorScript-specific patterns
-        for (Pattern pattern : DISALLOWED_PATTERNS) {
-            Matcher matcher = pattern.matcher(expression);
-            if (matcher.find()) {
-                throw new IllegalArgumentException(
-                    "Expression contains non-standard syntax: '" + 
matcher.group() + 
-                    "'. This AviatorScript-specific feature is not part of 
Casbin's standard specification."
-                );
-            }
-        }
-    }
-}
diff --git a/src/main/java/org/casbin/jcasbin/util/Util.java 
b/src/main/java/org/casbin/jcasbin/util/Util.java
index fb52d0f..0cc61e8 100644
--- a/src/main/java/org/casbin/jcasbin/util/Util.java
+++ b/src/main/java/org/casbin/jcasbin/util/Util.java
@@ -38,8 +38,6 @@ public class Util {
     public static boolean enableLog = true;
     private static Pattern evalReg = Pattern.compile("\\beval\\(([^),]*)\\)");
 
-    private static final Pattern IN_SYNTAX_PATTERN = 
Pattern.compile("([^\\s]+?) +in +(\\([^)]*\\)|\\[[^\\]]*\\]|\\S+)");
-
     private static Pattern escapeAssertionRegex = 
Pattern.compile("\\b(r|p)[0-9]*\\.");
 
     private static Logger LOGGER = 
LoggerFactory.getLogger("org.casbin.jcasbin");
@@ -189,44 +187,6 @@ public class Util {
         return sb.toString();
     }
 
-    /**
-     * convertInSyntax Convert 'in' to 'include' to fit aviatorscript,because
-     * aviatorscript don't support native 'in' syntax
-     *
-     * @param expString the value of the matcher
-     * @return the 'include' expression.
-     */
-    public static String convertInSyntax(String expString) {
-        if (expString.contains(" in ")) {
-            Matcher matcher = IN_SYNTAX_PATTERN.matcher(expString);
-            if (matcher.find()) {
-                return replaceInSyntaxWithInclude(matcher);
-            }
-        }
-
-        return expString;
-    }
-
-    private static String replaceInSyntaxWithInclude(Matcher matcher) {
-        StringBuffer sb = new StringBuffer();
-        do {
-            String leftOperand = matcher.group(1);
-            String rightOperand = matcher.group(2);
-            
-            // If right operand is a parenthesized expression with commas 
(tuple literal),
-            // convert to tuple() function. Otherwise, keep parentheses as-is 
for grouping.
-            if (rightOperand.startsWith("(") && rightOperand.endsWith(")") && 
rightOperand.contains(",")) {
-                rightOperand = "tuple" + rightOperand;
-            } else if (rightOperand.startsWith("[") && 
rightOperand.endsWith("]")) {
-                rightOperand = "tuple(" + rightOperand.substring(1, 
rightOperand.length() - 1) + ")";
-            }
-            
-            matcher.appendReplacement(sb, Matcher.quoteReplacement("include(" 
+ rightOperand + ", " + leftOperand + ")"));
-        } while (matcher.find());
-        matcher.appendTail(sb);
-        return sb.toString();
-    }
-
     /**
      * removeComments removes the comments starting with # in the text.
      *
diff --git a/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java 
b/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
index 298d039..a990003 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/CustomFunction.java
@@ -17,34 +17,20 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.AviatorEvaluatorInstance;
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-
-import java.util.Map;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 
 /**
+ * CustomFunction is the base class of named functions added with 
addFunction().
+ * Implement call(Object... args); the arguments are already evaluated, and
+ * FunctionArgs helps to check and read them.
+ *
  * @author: shink
  */
-public abstract class CustomFunction extends AbstractFunction {
-
-    private AviatorEvaluatorInstance aviatorEval;
-
-    public String replaceTargets(String exp, Map<String, Object> env) {
-        //Replace the first dot, because it can't be recognized by the 'reg' 
below.
-        if (exp.startsWith( "r") || exp.startsWith( "p")) {
-            exp = exp.replaceFirst("\\.","_");
-        }
-        //match example: "&&r.","||r.","=r."
-        String reg = "([| =)(&<>,+\\-*/!])((r|p)[0-9]*)\\.";
-        exp = exp.replaceAll(reg,"$1$2_");
-        return exp;
-    }
-
-    public AviatorEvaluatorInstance getAviatorEval() {
-        return aviatorEval;
-    }
-
-    public void setAviatorEval(AviatorEvaluatorInstance aviatorEval) {
-        this.aviatorEval = aviatorEval;
-    }
+public abstract class CustomFunction implements ExpressionFunction {
+    /**
+     * getName returns the name the function is called by in matchers.
+     *
+     * @return the function name.
+     */
+    public abstract String getName();
 }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
index 2889fcb..62f9f62 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/EvalFunc.java
@@ -17,29 +17,33 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.EvaluationContext;
+import org.casbin.jcasbin.expression.ExpressionException;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
-
-import java.util.Map;
+import org.casbin.jcasbin.util.Util;
 
 /**
  * EvalFunc is the wrapper for eval.
- * It extends CustomFunction, so it can be used in matcher and policy rule.
+ * It evaluates a rule stored in the policy, such as "r.sub.Age > 18",
+ * with the variables and functions of the current matcher.
  *
  * @author shink
  */
-public class EvalFunc extends CustomFunction {
+public class EvalFunc implements ExpressionFunction {
 
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1) {
-        String eval = FunctionUtils.getStringValue(arg1, env);
-        eval = replaceTargets(eval, env);
-        return AviatorBoolean.valueOf(BuiltInFunctions.eval(eval, env, 
getAviatorEval()));
+    public Object call(Object... args) {
+        throw new ExpressionException("eval can only be called from a 
matcher");
     }
 
     @Override
+    public Object call(EvaluationContext context, Object... args) {
+        FunctionArgs.checkCount("eval", args, 1);
+        String eval = Util.escapeAssertion(FunctionArgs.getString(args, 0));
+        return BuiltInFunctions.eval(eval, context.getVariables(), 
context.getFunctions());
+    }
+
     public String getName() {
         return "eval";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java 
b/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java
new file mode 100644
index 0000000..3e0f00a
--- /dev/null
+++ b/src/main/java/org/casbin/jcasbin/util/function/FunctionArgs.java
@@ -0,0 +1,60 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.util.function;
+
+import org.casbin.jcasbin.expression.ExpressionException;
+
+/**
+ * FunctionArgs checks the arguments passed to matcher functions.
+ */
+public final class FunctionArgs {
+    private FunctionArgs() {
+    }
+
+    /**
+     * checkCount checks the number of arguments.
+     *
+     * @param name     the function name, used in the error message.
+     * @param args     the arguments.
+     * @param expected the expected number of arguments.
+     */
+    public static void checkCount(String name, Object[] args, int expected) {
+        if (args.length != expected) {
+            throw new ExpressionException("wrong number of arguments (" + 
args.length + ") passed to " + name + ", expected " + expected);
+        }
+    }
+
+    /**
+     * getString returns the argument at index as a string.
+     *
+     * @param args  the arguments.
+     * @param index the index of the argument.
+     * @return the string value.
+     */
+    public static String getString(Object[] args, int index) {
+        Object value = args[index];
+        if (value instanceof String) {
+            return (String) value;
+        }
+        if (value instanceof Character) {
+            return value.toString();
+        }
+        throw new ExpressionException("argument " + (index + 1) + " must be a 
string, got "
+            + (value == null ? "null" : value + " (" + 
value.getClass().getSimpleName() + ")"));
+    }
+}
diff --git a/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
index 1dcbfbd..57e798e 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/GlobMatchFunc.java
@@ -17,30 +17,25 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * GlobMatchFunc is the wrapper for globMatch.
  *
  * @author shy
  * @since 2021/1/12
  */
-public class GlobMatchFunc extends AbstractFunction {
+public class GlobMatchFunc implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("globMatch", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.globMatch(key1, key2));
+        return BuiltInFunctions.globMatch(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "globMatch";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
index 5d32c6d..ca6d583 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/IPMatchFunc.java
@@ -17,27 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * IPMatchFunc is the wrapper for ipMatch.
  */
-public class IPMatchFunc extends AbstractFunction {
+public class IPMatchFunc implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String ip1 = FunctionUtils.getStringValue(arg1, env);
-        String ip2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("ipMatch", args, 2);
+        String ip1 = FunctionArgs.getString(args, 0);
+        String ip2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.ipMatch(ip1, ip2));
+        return BuiltInFunctions.ipMatch(ip1, ip2);
     }
 
-    @Override
     public String getName() {
         return "ipMatch";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
index 78549fb..cf33e8f 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet2Func.java
@@ -17,31 +17,26 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyGet2Func is the wrapper for keyGet2.
  *
  * @author shy
  * @since 2021/02/09
  */
-public class KeyGet2Func extends AbstractFunction {
+public class KeyGet2Func implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2, AviatorObject arg3) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
-        String pathVar = FunctionUtils.getStringValue(arg3, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyGet2", args, 3);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
+        String pathVar = FunctionArgs.getString(args, 2);
 
-        return new AviatorString(BuiltInFunctions.keyGet2Func(key1, key2, 
pathVar));
+        return BuiltInFunctions.keyGet2Func(key1, key2, pathVar);
     }
 
-    @Override
     public String getName() {
         return "keyGet2";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
index 293b93c..c3ecc97 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGet3Func.java
@@ -17,31 +17,26 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyGet3Func is the wrapper for keyGet3.
  *
  * @author yanglif
  * @since 2021/02/09
  */
-public class KeyGet3Func extends AbstractFunction {
+public class KeyGet3Func implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2, AviatorObject arg3) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
-        String pathVar = FunctionUtils.getStringValue(arg3, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyGet3", args, 3);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
+        String pathVar = FunctionArgs.getString(args, 2);
 
-        return new AviatorString(BuiltInFunctions.keyGet3Func(key1, key2, 
pathVar));
+        return BuiltInFunctions.keyGet3Func(key1, key2, pathVar);
     }
 
-    @Override
     public String getName() {
         return "keyGet3";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
index 5a73906..b6bb183 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyGetFunc.java
@@ -17,30 +17,25 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorObject;
-import com.googlecode.aviator.runtime.type.AviatorString;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyGetFunc is the wrapper for keyGet.
  *
  * @author shy
  * @since 2021/02/09
  */
-public class KeyGetFunc extends AbstractFunction {
+public class KeyGetFunc implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyGet", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return new AviatorString(BuiltInFunctions.keyGetFunc(key1, key2));
+        return BuiltInFunctions.keyGetFunc(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyGet";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
index 2c62105..e9624eb 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch2Func.java
@@ -17,27 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyMatch2Func is the wrapper for keyMatch2.
  */
-public class KeyMatch2Func extends AbstractFunction {
+public class KeyMatch2Func implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyMatch2", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch2(key1, key2));
+        return BuiltInFunctions.keyMatch2(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyMatch2";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
index ac33e11..917cdd2 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch3Func.java
@@ -17,27 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyMatch3Func is the wrapper for keyMatch3.
  */
-public class KeyMatch3Func extends AbstractFunction {
+public class KeyMatch3Func implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyMatch3", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch3(key1, key2));
+        return BuiltInFunctions.keyMatch3(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyMatch3";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
index 432e13b..11f63d6 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch4Func.java
@@ -17,27 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyMatch4Func is the wrapper for keyMatch4.
  */
-public class KeyMatch4Func extends AbstractFunction {
+public class KeyMatch4Func implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyMatch4", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch4(key1, key2));
+        return BuiltInFunctions.keyMatch4(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyMatch4";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
index 99c440c..5fe6fc1 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatch5Func.java
@@ -16,30 +16,25 @@
 // under the License.
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyMatch5Func is the wrapper for KeyMatch5.
  * @author Yixiang Zhao (@seriouszyx)
  * 2021-11-13 20:32
  **/
-public class KeyMatch5Func extends AbstractFunction {
+public class KeyMatch5Func implements ExpressionFunction {
 
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyMatch5", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch5(key1, key2));
+        return BuiltInFunctions.keyMatch5(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyMatch5";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
index 3acae6e..d18521d 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/KeyMatchFunc.java
@@ -17,27 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * KeyMatchFunc is the wrapper for keyMatch.
  */
-public class KeyMatchFunc extends AbstractFunction {
+public class KeyMatchFunc implements ExpressionFunction {
     @Override
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("keyMatch", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.keyMatch(key1, key2));
+        return BuiltInFunctions.keyMatch(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "keyMatch";
     }
diff --git a/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java 
b/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
index cdd8f65..4a25426 100644
--- a/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
+++ b/src/main/java/org/casbin/jcasbin/util/function/RegexMatchFunc.java
@@ -17,26 +17,22 @@
 
 package org.casbin.jcasbin.util.function;
 
-import com.googlecode.aviator.runtime.function.AbstractFunction;
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 
-import java.util.Map;
-
 /**
  * RegexMatchFunc is the wrapper for regexMatch.
  */
-public class RegexMatchFunc extends AbstractFunction {
-    public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-        String key1 = FunctionUtils.getStringValue(arg1, env);
-        String key2 = FunctionUtils.getStringValue(arg2, env);
+public class RegexMatchFunc implements ExpressionFunction {
+    @Override
+    public Object call(Object... args) {
+        FunctionArgs.checkCount("regexMatch", args, 2);
+        String key1 = FunctionArgs.getString(args, 0);
+        String key2 = FunctionArgs.getString(args, 1);
 
-        return AviatorBoolean.valueOf(BuiltInFunctions.regexMatch(key1, key2));
+        return BuiltInFunctions.regexMatch(key1, key2);
     }
 
-    @Override
     public String getName() {
         return "regexMatch";
     }
diff --git 
a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
index a341b60..8e200b6 100644
--- a/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/BuiltInFunctionsUnitTest.java
@@ -17,8 +17,8 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.model.FunctionMap;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 import org.casbin.jcasbin.util.Util;
 import org.testng.annotations.Test;
@@ -291,11 +291,11 @@ public class BuiltInFunctionsUnitTest {
         testEval("r_sub.name == 'alice'", env, null, true);
         testEval("r_sub.name == 'bob'", env, null, false);
 
-        AviatorEvaluatorInstance aviatorEval = AviatorEvaluator.newInstance();
-        aviatorEval.addFunction(new FunctionTest.CustomFunc());
+        Map<String, ExpressionFunction> functions = 
FunctionMap.loadFunctionMap().fm;
+        functions.put("custom", new FunctionTest.CustomFunc());
         env.put("r_obj", "/test/url1/url2");
 
-        testEval("r_sub.age >= 18 && custom(r_obj)", env, aviatorEval, true);
+        testEval("r_sub.age >= 18 && custom(r_obj)", env, functions, true);
     }
 
     @Test
@@ -344,13 +344,12 @@ public class BuiltInFunctionsUnitTest {
     @Test
     public void should_logged_when_eval_given_errorExpression() {
         // given
-        AviatorEvaluatorInstance instance = AviatorEvaluator.getInstance();
         Map<String, Object> env = new HashMap<>();
 
         try (MockedStatic<Util> utilMocked = 
BDDMockito.mockStatic(Util.class)) {
             utilMocked.when(() -> Util.logPrintfWarn(anyString(), 
anyString())).thenCallRealMethod();
             // when
-            BuiltInFunctions.eval("error", env, instance);
+            BuiltInFunctions.eval("error(", env, null);
             // then
             utilMocked.verify(() -> Util.logPrintfWarn(eq("Execute 'eval' 
function error, nested exception is: {}"), any()));
         }
diff --git a/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java 
b/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java
new file mode 100644
index 0000000..82c1858
--- /dev/null
+++ b/src/test/java/org/casbin/jcasbin/main/ExpressionTest.java
@@ -0,0 +1,228 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.casbin.jcasbin.main;
+
+import org.casbin.jcasbin.expression.Expression;
+import org.casbin.jcasbin.expression.ExpressionException;
+import org.casbin.jcasbin.expression.ExpressionFunction;
+import org.casbin.jcasbin.util.BuiltInFunctions;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.testng.Assert.*;
+
+public class ExpressionTest {
+    public static class Subject {
+        public String Department = "dev";
+        private final String name;
+        private final int age;
+        private final boolean admin;
+
+        Subject(String name, int age, boolean admin) {
+            this.name = name;
+            this.age = age;
+            this.admin = admin;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public int getAge() {
+            return age;
+        }
+
+        public boolean isAdmin() {
+            return admin;
+        }
+
+        public void delete() {
+            throw new IllegalStateException("must not be called");
+        }
+    }
+
+    private static Object eval(String expression, Map<String, Object> 
variables) {
+        return Expression.compile(expression).evaluate(variables, 
Collections.<String, ExpressionFunction>emptyMap());
+    }
+
+    private static Object eval(String expression) {
+        return eval(expression, Collections.emptyMap());
+    }
+
+    private static Map<String, Object> vars(Object... pairs) {
+        Map<String, Object> m = new HashMap<>();
+        for (int i = 0; i < pairs.length; i += 2) {
+            m.put((String) pairs[i], pairs[i + 1]);
+        }
+        return m;
+    }
+
+    @Test
+    public void testLiterals() {
+        assertEquals(eval("'a\\'b'"), "a'b");
+        assertEquals(eval("\"x\\ty\""), "x\ty");
+        assertEquals(eval("'^/foo\\d+$'"), "^/foo\\d+$");
+        assertEquals(eval("42"), 42L);
+        assertEquals(eval("1.5"), 1.5);
+        assertEquals(eval("true"), true);
+        assertNull(eval("nil"));
+        assertEquals(eval("('a', 'b')"), Arrays.asList("a", "b"));
+        assertEquals(eval("['a']"), Collections.singletonList("a"));
+        assertEquals(eval("()"), Collections.emptyList());
+    }
+
+    @Test
+    public void testArithmetic() {
+        assertEquals(eval("1 + 2 * 3"), 7L);
+        assertEquals(eval("(1 + 2) * 3"), 9L);
+        assertEquals(eval("10 - 4 - 3"), 3L);
+        assertEquals(eval("10 / 4"), 2.5);
+        assertEquals(eval("10 / 5"), 2.0);
+        assertEquals(eval("10 / 4.0"), 2.5);
+        assertEquals(eval("7 % 3"), 1L);
+        // Same as Go: ** is left-associative and binds looser than unary 
minus.
+        assertEquals(eval("2 ** 3 ** 2"), 64L);
+        assertEquals(eval("-2 ** 2"), 4L);
+        assertEquals(eval("'a' + 1"), "a1");
+        assertEquals(eval("1 / 0"), Double.POSITIVE_INFINITY);
+        assertThrows(ExpressionException.class, () -> eval("'a' - 1"));
+    }
+
+    @Test
+    public void testPrecedence() {
+        // Same as Go: & before ^ before |, and all of them before comparisons.
+        assertEquals(eval("r_bits | 2 & 4", vars("r_bits", 1L)), 1L);
+        assertEquals(eval("1 ^ 3 & 1"), 0L);
+        assertEquals(eval("5 & 4 == 4"), true);
+        assertEquals(eval("true || false && false"), true);
+        assertEquals(eval("1 + 1 == 2 && 'a' < 'b'"), true);
+        // 'in' binds like ==, so the guard before it still applies.
+        assertEquals(eval("r_sub == 'bob' && r_public in (false)", 
vars("r_sub", "alice", "r_public", false)), false);
+        assertEquals(eval("r_age > 18 ? 'adult' : 'minor'", vars("r_age", 
20)), "adult");
+        assertEquals(eval("r_name ?? 'anonymous'"), "anonymous");
+    }
+
+    @Test
+    public void testComparison() {
+        assertEquals(eval("r_age >= 18", vars("r_age", 18)), true);
+        assertEquals(eval("r_age == 18", vars("r_age", 18)), true);
+        assertEquals(eval("r_age == 18.0", vars("r_age", 18)), true);
+        assertEquals(eval("r_score < 1.5", vars("r_score", 1.25f)), true);
+        assertEquals(eval("'1' == 1"), false);
+        assertEquals(eval("r_x == nil"), true);
+        assertEquals(eval("r_x != 'a'"), true);
+        assertEquals(eval("'abc' < 'abd'"), true);
+        assertThrows(ExpressionException.class, () -> eval("'a' < 1"));
+        assertThrows(ExpressionException.class, () -> eval("r_x < 1"));
+    }
+
+    @Test
+    public void testIn() {
+        assertEquals(eval("r_obj in ('data2', 'data3')", vars("r_obj", 
"data3")), true);
+        assertEquals(eval("r_obj in ['data2', 'data3']", vars("r_obj", 
"data1")), false);
+        assertEquals(eval("'bob' in r_admins", vars("r_admins", 
Arrays.asList("alice", "bob"))), true);
+        assertEquals(eval("2 in r_ids", vars("r_ids", new int[]{1, 2})), true);
+        assertEquals(eval("'x' in r_ids", vars("r_ids", new String[]{"y"})), 
false);
+        assertEquals(eval("!('a' in ('b'))"), true);
+        assertThrows(ExpressionException.class, () -> eval("'a' in 'abc'"));
+    }
+
+    @Test
+    public void testRegex() {
+        assertEquals(eval("r_path =~ '^/api/v[0-9]+/'", vars("r_path", 
"/api/v2/users")), true);
+        assertEquals(eval("r_path !~ 'admin'", vars("r_path", 
"/api/v2/users")), true);
+        assertThrows(ExpressionException.class, () -> eval("'a' =~ '('"));
+    }
+
+    @Test
+    public void testProperties() {
+        Subject alice = new Subject("alice", 20, true);
+        Map<String, Object> v = vars("r_sub", alice, "r_obj", vars("Owner", 
"alice", "Meta", vars("level", 3)));
+        assertEquals(eval("r_sub.name", v), "alice");
+        assertEquals(eval("r_sub.Name", v), "alice");
+        assertEquals(eval("r_sub.age > 18", v), true);
+        assertEquals(eval("r_sub.admin", v), true);
+        assertEquals(eval("r_sub.Department", v), "dev");
+        assertEquals(eval("r_sub.name == r_obj.Owner", v), true);
+        assertEquals(eval("r_obj.Meta.level >= 3", v), true);
+        assertNull(eval("r_obj.Missing", v));
+        assertNull(eval("r_none.name", v));
+        assertThrows(ExpressionException.class, () -> eval("r_sub.class", v));
+        assertThrows(ExpressionException.class, () -> eval("r_sub.delete", v));
+        assertThrows(ExpressionException.class, () -> eval("r_sub.missing", 
v));
+    }
+
+    @Test
+    public void testFunctions() {
+        Map<String, ExpressionFunction> functions = new HashMap<>();
+        functions.put("concat", args -> "" + args[0] + args[1]);
+        Expression expression = Expression.compile("concat(r_sub, 'x') == 'ax' 
&& concat('(', ')') == '()'");
+        assertEquals(expression.evaluate(vars("r_sub", "a"), functions), true);
+        assertThrows(ExpressionException.class, () -> eval("unknown(1)"));
+    }
+
+    @Test
+    public void testShortCircuit() {
+        Map<String, ExpressionFunction> functions = new HashMap<>();
+        functions.put("fail", args -> {
+            throw new IllegalStateException("must not be called");
+        });
+        assertEquals(Expression.compile("false && fail()").evaluate(vars(), 
functions), false);
+        assertEquals(Expression.compile("true || fail()").evaluate(vars(), 
functions), true);
+        assertThrows(ExpressionException.class, () -> eval("1 && true"));
+    }
+
+    @Test
+    public void testSyntaxErrors() {
+        String[] invalid = {
+            "", "r_sub ==", "(r_sub", "r_sub)", "'unterminated", "r_sub = 
'a'", "r_sub in", "1 2",
+            // AviatorScript syntax that is not part of Casbin.
+            "seq.list('A', 'B')",
+            "seq.map(r_items, lambda(x) -> x * 2)",
+            "string.startsWith(r_path, '/admin')",
+            "math.sqrt(r_value) > 10",
+            "lambda(x) -> x * 2",
+            "(x) => x * 2",
+            "let x = 1",
+            "for x in r_items { x * 2 }",
+            "while x < 10 { x = x + 1 }",
+            "if r_age > 18 then 'adult' else 'minor'",
+            "use java.util.*",
+            "r_sub.getName()",
+        };
+        for (String expression : invalid) {
+            try {
+                Expression.compile(expression);
+                fail("expected a syntax error: " + expression);
+            } catch (ExpressionException expected) {
+            }
+        }
+    }
+
+    @Test
+    public void testEvalRejectsInvalidRules() {
+        Map<String, Object> env = vars("r_path", "/admin", "r_value", 200);
+        assertFalse(BuiltInFunctions.eval("string.startsWith(r_path, 
'/admin')", env, null));
+        assertFalse(BuiltInFunctions.eval("math.sqrt(r_value) > 10", env, 
null));
+        assertTrue(BuiltInFunctions.eval("keyMatch(r_path, '/admin*') && 
r_value > 10", env, null));
+    }
+}
diff --git a/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java 
b/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java
deleted file mode 100644
index 182c9bb..0000000
--- a/src/test/java/org/casbin/jcasbin/main/ExpressionValidatorTest.java
+++ /dev/null
@@ -1,234 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package org.casbin.jcasbin.main;
-
-import org.casbin.jcasbin.util.ExpressionValidator;
-import org.testng.annotations.Test;
-
-import static org.testng.Assert.*;
-
-public class ExpressionValidatorTest {
-
-    @Test
-    public void testValidStandardCasbinExpressions() {
-        // Standard operators and comparisons should be allowed
-        ExpressionValidator.validateExpression("r_sub == p_sub");
-        ExpressionValidator.validateExpression("r_sub == p_sub && r_obj == 
p_obj");
-        ExpressionValidator.validateExpression("r_sub == p_sub || r_obj == 
p_obj");
-        ExpressionValidator.validateExpression("r_age > 18");
-        ExpressionValidator.validateExpression("r_age >= 18 && r_age < 65");
-        ExpressionValidator.validateExpression("r_status != 'banned'");
-        
-        // Arithmetic should be allowed
-        ExpressionValidator.validateExpression("r_price * 1.1 > p_threshold");
-        ExpressionValidator.validateExpression("r_count + p_offset < 100");
-        ExpressionValidator.validateExpression("r_value - p_discount >= 0");
-        ExpressionValidator.validateExpression("r_total / r_count > 50");
-        
-        // Negation should be allowed
-        ExpressionValidator.validateExpression("!r_disabled");
-        ExpressionValidator.validateExpression("!(r_sub == p_sub)");
-    }
-
-    @Test
-    public void testValidCasbinBuiltInFunctions() {
-        // All standard Casbin functions should be allowed
-        ExpressionValidator.validateExpression("g(r_sub, p_sub)");
-        ExpressionValidator.validateExpression("g2(r_sub, p_sub, r_domain)");
-        ExpressionValidator.validateExpression("keyMatch(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyMatch2(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyMatch3(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyMatch4(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyMatch5(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyGet(r_path, p_path)");
-        ExpressionValidator.validateExpression("keyGet2(r_path, p_path, 
'id')");
-        ExpressionValidator.validateExpression("regexMatch(r_path, 
p_pattern)");
-        ExpressionValidator.validateExpression("ipMatch(r_ip, p_cidr)");
-        ExpressionValidator.validateExpression("globMatch(r_path, p_glob)");
-        ExpressionValidator.validateExpression("allMatch(r_key, p_key)");
-        ExpressionValidator.validateExpression("timeMatch(r_time, p_time)");
-        ExpressionValidator.validateExpression("eval(p_rule)");
-        
-        // Include and tuple are used for "in" operator conversion
-        ExpressionValidator.validateExpression("include(r_obj, r_sub)");
-        ExpressionValidator.validateExpression("include(tuple('admin', 
'editor'), r_role)");
-        
-        // Custom functions should be allowed (users can register them)
-        ExpressionValidator.validateExpression("customFunc(r_sub, p_sub)");
-        ExpressionValidator.validateExpression("myFunction(r_value)");
-    }
-
-    @Test
-    public void testValidComplexExpressions() {
-        // Complex combinations should be allowed
-        ExpressionValidator.validateExpression("g(r_sub, p_sub) && r_obj == 
p_obj && r_act == p_act");
-        ExpressionValidator.validateExpression("g(r_sub, p_sub) && 
keyMatch(r_path, p_path)");
-        ExpressionValidator.validateExpression("eval(p_sub_rule) && r_obj == 
p_obj");
-        ExpressionValidator.validateExpression("r_age > 18 && 
include(tuple('read', 'write'), r_act)");
-        ExpressionValidator.validateExpression("r_sub.age >= 18 && 
custom(r_obj)");
-    }
-
-    @Test
-    public void testDisallowedAviatorScriptSequenceMethods() {
-        // seq.list() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("seq.list('A', 'B')");
-            fail("Should have thrown IllegalArgumentException for seq.list()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("seq."));
-            assertTrue(e.getMessage().contains("AviatorScript-specific"));
-        }
-        
-        // seq.map() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("seq.map(r_items, lambda(x) 
-> x * 2)");
-            fail("Should have thrown IllegalArgumentException for seq.map()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("seq."));
-        }
-    }
-
-    @Test
-    public void testDisallowedAviatorScriptStringMethods() {
-        // string.startsWith() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("string.startsWith(r_path, 
'/admin')");
-            fail("Should have thrown IllegalArgumentException for 
string.startsWith()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("string."));
-            assertTrue(e.getMessage().contains("AviatorScript-specific"));
-        }
-        
-        // string.endsWith() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("string.endsWith(r_path, 
'.pdf')");
-            fail("Should have thrown IllegalArgumentException for 
string.endsWith()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("string."));
-        }
-        
-        // string.substring() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("string.substring(r_path, 
0, 5)");
-            fail("Should have thrown IllegalArgumentException for 
string.substring()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("string."));
-        }
-    }
-
-    @Test
-    public void testDisallowedAviatorScriptMathMethods() {
-        // math.sqrt() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("math.sqrt(r_value) > 10");
-            fail("Should have thrown IllegalArgumentException for 
math.sqrt()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("math."));
-            assertTrue(e.getMessage().contains("AviatorScript-specific"));
-        }
-        
-        // math.pow() should be disallowed
-        try {
-            ExpressionValidator.validateExpression("math.pow(r_base, 2)");
-            fail("Should have thrown IllegalArgumentException for math.pow()");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("math."));
-        }
-    }
-
-    @Test
-    public void testDisallowedLambdaExpressions() {
-        // Lambda with arrow should be disallowed
-        try {
-            ExpressionValidator.validateExpression("lambda(x) -> x * 2");
-            fail("Should have thrown IllegalArgumentException for lambda");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("lambda") || 
e.getMessage().contains("->"));
-        }
-        
-        // Alternative lambda syntax should be disallowed
-        try {
-            ExpressionValidator.validateExpression("(x) => x * 2");
-            fail("Should have thrown IllegalArgumentException for lambda 
arrow");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("=>"));
-        }
-    }
-
-    @Test
-    public void testDisallowedControlStructures() {
-        // for loops should be disallowed
-        try {
-            ExpressionValidator.validateExpression("for x in r_items { x * 2 
}");
-            fail("Should have thrown IllegalArgumentException for 'for'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("for"));
-        }
-        
-        // while loops should be disallowed
-        try {
-            ExpressionValidator.validateExpression("while x < 10 { x = x + 1 
}");
-            fail("Should have thrown IllegalArgumentException for 'while'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("while"));
-        }
-        
-        // if-then-else (Aviator style) should be disallowed
-        try {
-            ExpressionValidator.validateExpression("if r_age > 18 then 'adult' 
else 'minor'");
-            fail("Should have thrown IllegalArgumentException for 
'if-then-else'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("if") || 
e.getMessage().contains("then") || e.getMessage().contains("else"));
-        }
-    }
-
-    @Test
-    public void testDisallowedVariableBindingAndFunctions() {
-        // let variable binding should be disallowed
-        try {
-            ExpressionValidator.validateExpression("let x = 10; x * 2");
-            fail("Should have thrown IllegalArgumentException for 'let'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("let"));
-        }
-        
-        // function definitions should be disallowed
-        try {
-            ExpressionValidator.validateExpression("fn add(a, b) { a + b }");
-            fail("Should have thrown IllegalArgumentException for 'fn'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("fn"));
-        }
-        
-        // return statements should be disallowed
-        try {
-            ExpressionValidator.validateExpression("return r_value * 2");
-            fail("Should have thrown IllegalArgumentException for 'return'");
-        } catch (IllegalArgumentException e) {
-            assertTrue(e.getMessage().contains("return"));
-        }
-    }
-
-    @Test
-    public void testNullAndEmptyExpressions() {
-        // Null and empty expressions should be allowed (no validation needed)
-        ExpressionValidator.validateExpression(null);
-        ExpressionValidator.validateExpression("");
-        ExpressionValidator.validateExpression("   ");
-    }
-}
diff --git a/src/test/java/org/casbin/jcasbin/main/FunctionTest.java 
b/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
index add09e5..47372fb 100644
--- a/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/FunctionTest.java
@@ -17,13 +17,10 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
 import org.casbin.jcasbin.util.function.CustomFunction;
+import org.casbin.jcasbin.util.function.FunctionArgs;
 import org.testng.annotations.Test;
 
-import java.util.Map;
 import java.util.regex.Pattern;
 
 import static org.casbin.jcasbin.main.TestUtil.testEnforce;
@@ -45,10 +42,9 @@ public class FunctionTest {
 
     public static class CustomFunc extends CustomFunction {
         @Override
-        public AviatorObject call(Map<String, Object> env, AviatorObject arg1) 
{
-            String obj = FunctionUtils.getStringValue(arg1, env);
-            boolean res = Pattern.compile("/*/url1/url2/*", 
Pattern.CASE_INSENSITIVE).matcher(obj).find();
-            return AviatorBoolean.valueOf(res);
+        public Object call(Object... args) {
+            String obj = FunctionArgs.getString(args, 0);
+            return Pattern.compile("/*/url1/url2/*", 
Pattern.CASE_INSENSITIVE).matcher(obj).find();
         }
 
         @Override
diff --git a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
index bf7164c..6428c49 100644
--- a/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ManagementAPIUnitTest.java
@@ -17,8 +17,6 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.AviatorEvaluator;
-import com.googlecode.aviator.AviatorEvaluatorInstance;
 import org.casbin.jcasbin.util.Util;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -281,27 +279,6 @@ public class ManagementAPIUnitTest {
         testGetRoles(e, "eve", asList("data3_admin"));
     }
 
-    @Test
-    public void 
should_throwsNullPointException_when_setAviatorEvaluator_given_nullInstance() {
-        // given
-        AviatorEvaluatorInstance instance = null;
-        Enforcer enforcer = new Enforcer();
-        // when
-        Assert.assertThrows(NullPointerException.class,
-            () -> enforcer.setAviatorEvaluator(instance));
-    }
-
-    @Test
-    public void should_true_when_setAviatorEvaluator_given_customInstance() {
-        // given
-        AviatorEvaluatorInstance instance = AviatorEvaluator.newInstance();
-        Enforcer enforcer = new Enforcer();
-        // when
-        enforcer.setAviatorEvaluator(instance);
-        // then
-        assertEquals(enforcer.getAviatorEval(), instance);
-    }
-
     @Test
     public void testGetAllUsers() {
         // Basic RBAC: alice and bob are users, data2_admin is a role
diff --git a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
index 387a3b1..b8b7188 100644
--- a/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/ModelUnitTest.java
@@ -17,15 +17,13 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.runtime.function.FunctionUtils;
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
 import org.casbin.jcasbin.model.Model;
 import org.casbin.jcasbin.persist.file_adapter.AdapterMock;
 import org.casbin.jcasbin.rbac.RoleManager;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 import org.casbin.jcasbin.util.Util;
 import org.casbin.jcasbin.util.function.CustomFunction;
+import org.casbin.jcasbin.util.function.FunctionArgs;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
@@ -869,11 +867,11 @@ public class ModelUnitTest {
 
     public class customFunctionWrapper extends CustomFunction {
         @Override
-        public AviatorObject call(Map<String, Object> env, AviatorObject arg1, 
AviatorObject arg2) {
-            String key1 = FunctionUtils.getStringValue(arg1, env);
-            String key2 = FunctionUtils.getStringValue(arg2, env);
+        public Object call(Object... args) {
+            String key1 = FunctionArgs.getString(args, 0);
+            String key2 = FunctionArgs.getString(args, 1);
 
-            return AviatorBoolean.valueOf(customFunction(key1, key2));
+            return customFunction(key1, key2);
         }
 
         @Override
diff --git a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java 
b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
index 05dc00c..02af6c2 100644
--- a/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/SyncedEnforcerUnitTest.java
@@ -17,8 +17,6 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.runtime.type.AviatorBoolean;
-import com.googlecode.aviator.runtime.type.AviatorObject;
 import org.casbin.jcasbin.model.Model;
 import org.casbin.jcasbin.persist.Adapter;
 import org.casbin.jcasbin.persist.file_adapter.FileAdapter;
@@ -55,7 +53,7 @@ public class SyncedEnforcerUnitTest {
         volatile boolean block;
 
         @Override
-        public AviatorObject call(Map<String, Object> env, AviatorObject arg) {
+        public Object call(Object... args) {
             if (block) {
                 entered.countDown();
                 try {
@@ -64,7 +62,7 @@ public class SyncedEnforcerUnitTest {
                     Thread.currentThread().interrupt();
                 }
             }
-            return AviatorBoolean.TRUE;
+            return true;
         }
 
         @Override
diff --git a/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java 
b/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java
deleted file mode 100644
index d39c5b1..0000000
--- a/src/test/java/org/casbin/jcasbin/main/TestAviatorValidation.java
+++ /dev/null
@@ -1,64 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements. See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership. The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License. You may obtain a copy of the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-import org.casbin.jcasbin.util.ExpressionValidator;
-
-public class TestAviatorValidation {
-    public static void main(String[] args) {
-        System.out.println("Testing Expression Validator:\n");
-        
-        // Test 1: Valid Casbin expression
-        try {
-            ExpressionValidator.validateExpression("r.age > 18 && r.role == 
'admin'");
-            System.out.println("✅ PASS: Standard Casbin expression allowed");
-        } catch (Exception e) {
-            System.out.println("❌ FAIL: " + e.getMessage());
-        }
-        
-        // Test 2: Invalid - seq.list()
-        try {
-            ExpressionValidator.validateExpression("seq.list('A', 'B', 'C')");
-            System.out.println("❌ FAIL: seq.list() should be blocked");
-        } catch (IllegalArgumentException e) {
-            System.out.println("✅ PASS: seq.list() blocked - " + 
e.getMessage());
-        }
-        
-        // Test 3: Invalid - string.startsWith()
-        try {
-            ExpressionValidator.validateExpression("string.startsWith(r.path, 
'/admin')");
-            System.out.println("❌ FAIL: string.startsWith() should be 
blocked");
-        } catch (IllegalArgumentException e) {
-            System.out.println("✅ PASS: string.startsWith() blocked - " + 
e.getMessage());
-        }
-        
-        // Test 4: Valid - custom function
-        try {
-            ExpressionValidator.validateExpression("myCustomFunc(r.value)");
-            System.out.println("✅ PASS: Custom functions allowed");
-        } catch (Exception e) {
-            System.out.println("❌ FAIL: " + e.getMessage());
-        }
-        
-        // Test 5: Invalid - lambda
-        try {
-            ExpressionValidator.validateExpression("lambda(x) -> x * 2");
-            System.out.println("❌ FAIL: lambda should be blocked");
-        } catch (IllegalArgumentException e) {
-            System.out.println("✅ PASS: lambda blocked - " + e.getMessage());
-        }
-    }
-}
diff --git a/src/test/java/org/casbin/jcasbin/main/TestUtil.java 
b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
index 2e1155d..f1d2516 100644
--- a/src/test/java/org/casbin/jcasbin/main/TestUtil.java
+++ b/src/test/java/org/casbin/jcasbin/main/TestUtil.java
@@ -17,8 +17,8 @@
 
 package org.casbin.jcasbin.main;
 
-import com.googlecode.aviator.AviatorEvaluatorInstance;
 import org.casbin.jcasbin.rbac.RoleManager;
+import org.casbin.jcasbin.expression.ExpressionFunction;
 import org.casbin.jcasbin.util.BuiltInFunctions;
 import org.casbin.jcasbin.util.EnforceContext;
 import org.casbin.jcasbin.util.SyncedLRUCache;
@@ -280,8 +280,8 @@ public class TestUtil {
         assertEquals(res, BuiltInFunctions.keyGet3Func(key1, key2, pathVar));
     }
 
-    static void testEval(String eval, Map<String, Object> env, 
AviatorEvaluatorInstance aviatorEval, boolean res) {
-        assertEquals(res, BuiltInFunctions.eval(eval, env, aviatorEval));
+    static void testEval(String eval, Map<String, Object> env, Map<String, 
ExpressionFunction> functions, boolean res) {
+        assertEquals(res, BuiltInFunctions.eval(eval, env, functions));
     }
 
     static void testKeyMatch(String key1, String key2, boolean res) {
diff --git a/src/test/java/org/casbin/jcasbin/main/UtilTest.java 
b/src/test/java/org/casbin/jcasbin/main/UtilTest.java
index f2f159d..91f0b17 100644
--- a/src/test/java/org/casbin/jcasbin/main/UtilTest.java
+++ b/src/test/java/org/casbin/jcasbin/main/UtilTest.java
@@ -59,32 +59,6 @@ public class UtilTest {
       assertEquals("r_obj == \"a\\\"p.b\" || p_obj == 'r.c'", 
Util.escapeAssertion("r.obj == \"a\\\"p.b\" || p.obj == 'r.c'"));
   }
 
-    @Test
-    public void testConvertInSyntax(){
-        // Basic cases
-        assertEquals("include(r_obj, r_sub)", Util.convertInSyntax("r_sub in 
r_obj"));
-        assertEquals("include(r_obj, r_sub.name)", 
Util.convertInSyntax("r_sub.name in r_obj"));
-        assertEquals("include(r_obj.name, r_sub.name)", 
Util.convertInSyntax("r_sub.name in r_obj.name"));
-        assertEquals("include(r_obj, r_sub) && r.obj == p.obj", 
Util.convertInSyntax("r_sub in r_obj && r.obj == p.obj"));
-        
-        // Tuple/array with single quotes - should convert to tuple() function
-        assertEquals("include(tuple('data2', 'data3'), r.obj)", 
Util.convertInSyntax("r.obj in ('data2', 'data3')"));
-        
-        // Tuple/array with double quotes - should convert to tuple() function
-        assertEquals("include(tuple(\"data2\", \"data3\"), r.obj)", 
Util.convertInSyntax("r.obj in (\"data2\", \"data3\")"));
-        
-        // Tuple/array without quotes - should convert to tuple() function
-        assertEquals("include(tuple(data2, data3), r.obj)", 
Util.convertInSyntax("r.obj in (data2, data3)"));
-        
-        // Complex expression with 'in' operator and tuple (the original issue 
scenario)
-        assertEquals("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || 
include(tuple('data2', 'data3'), r.obj)", 
-                     Util.convertInSyntax("g(r.sub, p.sub) && r.obj == p.obj 
&& r.act == p.act || r.obj in ('data2', 'data3')"));
-        
-        // Multiple 'in' operators
-        assertEquals("include(r.obj.admins, r.sub.name) && 
include(r.obj.admins, (\"bob\"))", 
-                     Util.convertInSyntax("r.sub.name in r.obj.admins && 
(\"bob\") in r.obj.admins"));
-    }
-
   @Test
   public void testRemoveComments(){
     assertEquals("r.act == p.act", Util.removeComments("r.act == p.act # 
comments"));


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to