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

wusheng pushed a commit to branch groovy-replace
in repository https://gitbox.apache.org/repos/asf/skywalking.git

commit 0170c109bf64997eb662336f734795bd81613588
Author: Wu Sheng <[email protected]>
AuthorDate: Sat Feb 28 14:47:20 2026 +0800

    Add MAL transpiler module for build-time Groovy-to-Java conversion (Phase 2)
    
    Ports MalToJavaTranspiler from skywalking-graalvm-distro into a new
    mal-transpiler analyzer submodule. The transpiler parses Groovy MAL
    expressions/filters via AST at CONVERSION phase and emits equivalent
    Java classes implementing MalExpression/MalFilter interfaces from Phase 1.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 oap-server/analyzer/{ => mal-transpiler}/pom.xml   |   24 +-
 .../server/transpiler/mal/MalToJavaTranspiler.java | 1099 ++++++++++++++++++++
 .../transpiler/mal/MalToJavaTranspilerTest.java    |  904 ++++++++++++++++
 oap-server/analyzer/pom.xml                        |    1 +
 4 files changed, 2009 insertions(+), 19 deletions(-)

diff --git a/oap-server/analyzer/pom.xml 
b/oap-server/analyzer/mal-transpiler/pom.xml
similarity index 67%
copy from oap-server/analyzer/pom.xml
copy to oap-server/analyzer/mal-transpiler/pom.xml
index 9dca94257f..4f45b67acb 100644
--- a/oap-server/analyzer/pom.xml
+++ b/oap-server/analyzer/mal-transpiler/pom.xml
@@ -19,37 +19,23 @@
 
 <project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
     <parent>
-        <artifactId>oap-server</artifactId>
+        <artifactId>analyzer</artifactId>
         <groupId>org.apache.skywalking</groupId>
         <version>${revision}</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 
-    <artifactId>analyzer</artifactId>
-    <packaging>pom</packaging>
-
-    <modules>
-        <module>agent-analyzer</module>
-        <module>log-analyzer</module>
-        <module>meter-analyzer</module>
-        <module>event-analyzer</module>
-    </modules>
+    <artifactId>mal-transpiler</artifactId>
 
     <dependencies>
         <dependency>
             <groupId>org.apache.skywalking</groupId>
-            <artifactId>apm-network</artifactId>
+            <artifactId>meter-analyzer</artifactId>
             <version>${project.version}</version>
         </dependency>
         <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>library-module</artifactId>
-            <version>${project.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.skywalking</groupId>
-            <artifactId>library-util</artifactId>
-            <version>${project.version}</version>
+            <groupId>org.apache.groovy</groupId>
+            <artifactId>groovy</artifactId>
         </dependency>
     </dependencies>
 </project>
diff --git 
a/oap-server/analyzer/mal-transpiler/src/main/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspiler.java
 
b/oap-server/analyzer/mal-transpiler/src/main/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspiler.java
new file mode 100644
index 0000000000..c256bb589c
--- /dev/null
+++ 
b/oap-server/analyzer/mal-transpiler/src/main/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspiler.java
@@ -0,0 +1,1099 @@
+/*
+ * 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
+ *
+ *     http://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.apache.skywalking.oap.server.transpiler.mal;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.ToolProvider;
+import lombok.extern.slf4j.Slf4j;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.ElvisOperatorExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TernaryExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.syntax.Types;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.NotExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.codehaus.groovy.control.Phases;
+
+/**
+ * Transpiles Groovy MAL expressions to Java source code at build time.
+ * Parses expression strings into Groovy AST (via CompilationUnit at 
CONVERSION phase),
+ * walks AST nodes, and produces equivalent Java classes implementing 
MalExpression or MalFilter.
+ *
+ * <p>Supported AST patterns:
+ * <ul>
+ *   <li>Variable references: sample family lookups, DownsamplingType 
constants, KNOWN_TYPES</li>
+ *   <li>Method chains: .sum(), .service(), .tagEqual(), .rate(), 
.histogram(), etc.</li>
+ *   <li>Binary arithmetic with operand-swap logic per upstream 
ExpandoMetaClass
+ *       (N-SF: sf.minus(N).negative(), N/SF: sf.newValue(v-&gt;N/v))</li>
+ *   <li>tag() closures: TagFunction lambda (assignment, remove, string 
concat, if/else)</li>
+ *   <li>Filter closures: MalFilter class (==, !=, in, truthiness, negation, 
&amp;&amp;, ||)</li>
+ *   <li>forEach() closures: ForEachFunction lambda (var decls, if/else-if, 
early return)</li>
+ *   <li>instance() with PropertiesExtractor closure: Map.of() from map 
literals</li>
+ *   <li>Elvis (?:), safe navigation (?.), ternary (? :)</li>
+ *   <li>Batch compilation via javax.tools.JavaCompiler + manifest writing</li>
+ * </ul>
+ */
+@Slf4j
+public class MalToJavaTranspiler {
+
+    static final String GENERATED_PACKAGE =
+        "org.apache.skywalking.oap.server.core.source.oal.rt.mal";
+
+    private static final Set<String> DOWNSAMPLING_CONSTANTS = Set.of(
+        "AVG", "SUM", "LATEST", "SUM_PER_MIN", "MAX", "MIN"
+    );
+
+    private static final Set<String> KNOWN_TYPES = Set.of(
+        "Layer", "DetectPoint", "K8sRetagType", "ProcessRegistry", "TimeUnit"
+    );
+
+    // ---- Batch state tracking ----
+
+    private final Map<String, String> expressionSources = new 
LinkedHashMap<>();
+
+    private final Map<String, String> filterSources = new LinkedHashMap<>();
+
+    private final Map<String, String> filterLiteralToClass = new 
LinkedHashMap<>();
+
+    /**
+     * Transpile a MAL expression to a Java class source implementing 
MalExpression.
+     *
+     * @param className  simple class name (e.g. "MalExpr_meter_jvm_heap")
+     * @param expression the Groovy expression string
+     * @return generated Java source code
+     */
+    public String transpileExpression(final String className, final String 
expression) {
+        final ModuleNode ast = parseToAST(expression);
+        final Statement body = extractBody(ast);
+
+        final Set<String> sampleNames = new LinkedHashSet<>();
+        collectSampleNames(body, sampleNames);
+
+        final String javaBody = visitStatement(body);
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("package ").append(GENERATED_PACKAGE).append(";\n\n");
+        sb.append("import java.util.*;\n");
+        sb.append("import org.apache.skywalking.oap.meter.analyzer.dsl.*;\n");
+        sb.append("import 
org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.*;\n");
+        sb.append("import 
org.apache.skywalking.oap.server.core.analysis.Layer;\n");
+        sb.append("import 
org.apache.skywalking.oap.server.core.source.DetectPoint;\n");
+        sb.append("import 
org.apache.skywalking.oap.meter.analyzer.dsl.tagOpt.K8sRetagType;\n");
+        sb.append("import 
org.apache.skywalking.oap.meter.analyzer.dsl.registry.ProcessRegistry;\n\n");
+
+        sb.append("public class ").append(className).append(" implements 
MalExpression {\n");
+        sb.append("    @Override\n");
+        sb.append("    public SampleFamily run(Map<String, SampleFamily> 
samples) {\n");
+
+        if (!sampleNames.isEmpty()) {
+            sb.append("        ExpressionParsingContext.get().ifPresent(ctx -> 
{\n");
+            for (String name : sampleNames) {
+                sb.append("            
ctx.getSamples().add(\"").append(escapeJava(name)).append("\");\n");
+            }
+            sb.append("        });\n");
+        }
+
+        sb.append("        return ").append(javaBody).append(";\n");
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    /**
+     * Transpile a MAL filter literal to a Java class source implementing 
MalFilter.
+     * Filter literals are closures like: { tags -> tags.job_name == 
'vm-monitoring' }
+     *
+     * @param className     simple class name (e.g. "MalFilter_0")
+     * @param filterLiteral the Groovy closure literal string
+     * @return generated Java source code
+     */
+    public String transpileFilter(final String className, final String 
filterLiteral) {
+        final ModuleNode ast = parseToAST(filterLiteral);
+        final Statement body = extractBody(ast);
+
+        // The filter literal is a closure expression at the top level
+        final ClosureExpression closure = extractClosure(body);
+        final Parameter[] params = closure.getParameters();
+        final String tagsVar = (params != null && params.length > 0) ? 
params[0].getName() : "tags";
+
+        // Get the body expression — may need to unwrap inner block/closure
+        final Expression bodyExpr = extractFilterBodyExpr(closure.getCode(), 
tagsVar);
+
+        // Generate the boolean condition
+        final String condition = visitFilterCondition(bodyExpr, tagsVar);
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("package ").append(GENERATED_PACKAGE).append(";\n\n");
+        sb.append("import java.util.*;\n");
+        sb.append("import 
org.apache.skywalking.oap.meter.analyzer.dsl.*;\n\n");
+
+        sb.append("public class ").append(className).append(" implements 
MalFilter {\n");
+        sb.append("    @Override\n");
+        sb.append("    public boolean test(Map<String, String> tags) {\n");
+        sb.append("        return ").append(condition).append(";\n");
+        sb.append("    }\n");
+        sb.append("}\n");
+
+        return sb.toString();
+    }
+
+    private ClosureExpression extractClosure(final Statement body) {
+        final List<Statement> stmts = getStatements(body);
+        if (stmts.size() == 1 && stmts.get(0) instanceof ExpressionStatement) {
+            final Expression expr = ((ExpressionStatement) 
stmts.get(0)).getExpression();
+            if (expr instanceof ClosureExpression) {
+                return (ClosureExpression) expr;
+            }
+        }
+        throw new IllegalStateException(
+            "Filter literal must be a single closure expression, got: "
+                + (stmts.isEmpty() ? "empty" : 
stmts.get(0).getClass().getSimpleName()));
+    }
+
+    private Expression extractFilterBodyExpr(final Statement code, final 
String tagsVar) {
+        final List<Statement> stmts = getStatements(code);
+        if (stmts.isEmpty()) {
+            throw new IllegalStateException("Empty filter closure body");
+        }
+
+        final Statement last = stmts.get(stmts.size() - 1);
+        Expression expr;
+        if (last instanceof ExpressionStatement) {
+            expr = ((ExpressionStatement) last).getExpression();
+        } else if (last instanceof ReturnStatement) {
+            expr = ((ReturnStatement) last).getExpression();
+        } else if (last instanceof BlockStatement) {
+            return extractFilterBodyExpr(last, tagsVar);
+        } else {
+            throw new UnsupportedOperationException(
+                "Unsupported filter body statement: " + 
last.getClass().getSimpleName());
+        }
+
+        if (expr instanceof ClosureExpression) {
+            final ClosureExpression inner = (ClosureExpression) expr;
+            return extractFilterBodyExpr(inner.getCode(), tagsVar);
+        }
+
+        return expr;
+    }
+
+    // ---- AST Parsing ----
+
+    ModuleNode parseToAST(final String expression) {
+        final CompilerConfiguration cc = new CompilerConfiguration();
+        final CompilationUnit cu = new CompilationUnit(cc);
+        cu.addSource("Script", expression);
+        cu.compile(Phases.CONVERSION);
+        final List<ModuleNode> modules = cu.getAST().getModules();
+        if (modules.isEmpty()) {
+            throw new IllegalStateException("No AST modules produced for: " + 
expression);
+        }
+        return modules.get(0);
+    }
+
+    Statement extractBody(final ModuleNode module) {
+        final BlockStatement block = module.getStatementBlock();
+        if (block != null && !block.getStatements().isEmpty()) {
+            return block;
+        }
+        final List<ClassNode> classes = module.getClasses();
+        if (!classes.isEmpty()) {
+            return module.getStatementBlock();
+        }
+        throw new IllegalStateException("Empty AST body");
+    }
+
+    // ---- Sample Name Collection ----
+
+    private void collectSampleNames(final Statement stmt, final Set<String> 
names) {
+        if (stmt instanceof BlockStatement) {
+            for (Statement s : ((BlockStatement) stmt).getStatements()) {
+                collectSampleNames(s, names);
+            }
+        } else if (stmt instanceof ExpressionStatement) {
+            collectSampleNamesFromExpr(((ExpressionStatement) 
stmt).getExpression(), names);
+        } else if (stmt instanceof ReturnStatement) {
+            collectSampleNamesFromExpr(((ReturnStatement) 
stmt).getExpression(), names);
+        }
+    }
+
+    void collectSampleNamesFromExpr(final Expression expr, final Set<String> 
names) {
+        if (expr instanceof VariableExpression) {
+            final String name = ((VariableExpression) expr).getName();
+            if (!DOWNSAMPLING_CONSTANTS.contains(name)
+                && !KNOWN_TYPES.contains(name)
+                && !name.equals("this") && !name.equals("time")) {
+                names.add(name);
+            }
+        } else if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            collectSampleNamesFromExpr(bin.getLeftExpression(), names);
+            collectSampleNamesFromExpr(bin.getRightExpression(), names);
+        } else if (expr instanceof PropertyExpression) {
+            final PropertyExpression pe = (PropertyExpression) expr;
+            collectSampleNamesFromExpr(pe.getObjectExpression(), names);
+        } else if (expr instanceof MethodCallExpression) {
+            final MethodCallExpression mce = (MethodCallExpression) expr;
+            collectSampleNamesFromExpr(mce.getObjectExpression(), names);
+            collectSampleNamesFromExpr(mce.getArguments(), names);
+        } else if (expr instanceof ArgumentListExpression) {
+            for (Expression e : ((ArgumentListExpression) 
expr).getExpressions()) {
+                collectSampleNamesFromExpr(e, names);
+            }
+        } else if (expr instanceof TupleExpression) {
+            for (Expression e : ((TupleExpression) expr).getExpressions()) {
+                collectSampleNamesFromExpr(e, names);
+            }
+        }
+    }
+
+    // ---- Statement Visiting ----
+
+    String visitStatement(final Statement stmt) {
+        if (stmt instanceof BlockStatement) {
+            final List<Statement> stmts = ((BlockStatement) 
stmt).getStatements();
+            if (stmts.size() == 1) {
+                return visitStatement(stmts.get(0));
+            }
+            // Multi-statement: last one is the return value
+            return visitStatement(stmts.get(stmts.size() - 1));
+        } else if (stmt instanceof ExpressionStatement) {
+            return visitExpression(((ExpressionStatement) 
stmt).getExpression());
+        } else if (stmt instanceof ReturnStatement) {
+            return visitExpression(((ReturnStatement) stmt).getExpression());
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported statement: " + stmt.getClass().getSimpleName());
+    }
+
+    // ---- Expression Visiting ----
+
+    String visitExpression(final Expression expr) {
+        if (expr instanceof VariableExpression) {
+            return visitVariable((VariableExpression) expr);
+        } else if (expr instanceof ConstantExpression) {
+            return visitConstant((ConstantExpression) expr);
+        } else if (expr instanceof MethodCallExpression) {
+            return visitMethodCall((MethodCallExpression) expr);
+        } else if (expr instanceof PropertyExpression) {
+            return visitProperty((PropertyExpression) expr);
+        } else if (expr instanceof ListExpression) {
+            return visitList((ListExpression) expr);
+        } else if (expr instanceof BinaryExpression) {
+            return visitBinary((BinaryExpression) expr);
+        } else if (expr instanceof ClosureExpression) {
+            throw new UnsupportedOperationException(
+                "Bare ClosureExpression outside method call context: " + 
expr.getText());
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported expression (not yet implemented): "
+                + expr.getClass().getSimpleName() + " = " + expr.getText());
+    }
+
+    private String visitVariable(final VariableExpression expr) {
+        final String name = expr.getName();
+        if (DOWNSAMPLING_CONSTANTS.contains(name)) {
+            return "DownsamplingType." + name;
+        }
+        if (KNOWN_TYPES.contains(name)) {
+            return name;
+        }
+        if (name.equals("this")) {
+            return "this";
+        }
+        // Sample family lookup
+        return "samples.getOrDefault(\"" + escapeJava(name) + "\", 
SampleFamily.EMPTY)";
+    }
+
+    private String visitConstant(final ConstantExpression expr) {
+        final Object value = expr.getValue();
+        if (value == null) {
+            return "null";
+        }
+        if (value instanceof String) {
+            return "\"" + escapeJava((String) value) + "\"";
+        }
+        if (value instanceof Integer) {
+            return value.toString();
+        }
+        if (value instanceof Long) {
+            return value + "L";
+        }
+        if (value instanceof Double) {
+            return value.toString();
+        }
+        if (value instanceof Float) {
+            return value + "f";
+        }
+        if (value instanceof Boolean) {
+            return value.toString();
+        }
+        return value.toString();
+    }
+
+    // ---- MethodCall, Property, List ----
+
+    private String visitMethodCall(final MethodCallExpression expr) {
+        final String methodName = expr.getMethodAsString();
+        final Expression objExpr = expr.getObjectExpression();
+        final ArgumentListExpression args = toArgList(expr.getArguments());
+
+        // tag(closure) -> TagFunction lambda
+        if ("tag".equals(methodName) && args.getExpressions().size() == 1
+            && args.getExpression(0) instanceof ClosureExpression) {
+            final String obj = visitExpression(objExpr);
+            final String lambda = visitTagClosure((ClosureExpression) 
args.getExpression(0));
+            return obj + ".tag((TagFunction) " + lambda + ")";
+        }
+
+        // forEach(list, closure) -> ForEachFunction lambda
+        if ("forEach".equals(methodName) && args.getExpressions().size() == 2
+            && args.getExpression(1) instanceof ClosureExpression) {
+            final String obj = visitExpression(objExpr);
+            final String list = visitExpression(args.getExpression(0));
+            final String lambda = visitForEachClosure((ClosureExpression) 
args.getExpression(1));
+            return obj + ".forEach(" + list + ", (ForEachFunction) " + lambda 
+ ")";
+        }
+
+        // instance(..., closure) -> last arg is PropertiesExtractor lambda
+        if ("instance".equals(methodName) && !args.getExpressions().isEmpty()) 
{
+            final Expression lastArg = 
args.getExpression(args.getExpressions().size() - 1);
+            if (lastArg instanceof ClosureExpression) {
+                final String obj = visitExpression(objExpr);
+                final List<String> argStrs = new ArrayList<>();
+                for (int i = 0; i < args.getExpressions().size() - 1; i++) {
+                    argStrs.add(visitExpression(args.getExpression(i)));
+                }
+                final String lambda = 
visitPropertiesExtractorClosure((ClosureExpression) lastArg);
+                argStrs.add("(PropertiesExtractor) " + lambda);
+                return obj + ".instance(" + String.join(", ", argStrs) + ")";
+            }
+        }
+
+        final String obj = visitExpression(objExpr);
+
+        // Static method calls: ClassExpression.method(...)
+        if (objExpr instanceof ClassExpression) {
+            final String typeName = objExpr.getType().getNameWithoutPackage();
+            final List<String> argStrs = visitArgList(args);
+            return typeName + "." + methodName + "(" + String.join(", ", 
argStrs) + ")";
+        }
+
+        // Regular instance method call: obj.method(args)
+        final List<String> argStrs = visitArgList(args);
+        return obj + "." + methodName + "(" + String.join(", ", argStrs) + ")";
+    }
+
+    private String visitProperty(final PropertyExpression expr) {
+        final Expression obj = expr.getObjectExpression();
+        final String prop = expr.getPropertyAsString();
+
+        if (obj instanceof ClassExpression) {
+            return obj.getType().getNameWithoutPackage() + "." + prop;
+        }
+        if (obj instanceof VariableExpression) {
+            final String varName = ((VariableExpression) obj).getName();
+            if (KNOWN_TYPES.contains(varName)) {
+                return varName + "." + prop;
+            }
+        }
+
+        return visitExpression(obj) + "." + prop;
+    }
+
+    private String visitList(final ListExpression expr) {
+        final List<String> elements = new ArrayList<>();
+        for (Expression e : expr.getExpressions()) {
+            elements.add(visitExpression(e));
+        }
+        return "List.of(" + String.join(", ", elements) + ")";
+    }
+
+    // ---- Binary Arithmetic ----
+
+    private String visitBinary(final BinaryExpression expr) {
+        final int opType = expr.getOperation().getType();
+
+        if (isArithmetic(opType)) {
+            return visitArithmetic(expr.getLeftExpression(), 
expr.getRightExpression(), opType);
+        }
+
+        throw new UnsupportedOperationException(
+            "Unsupported binary operator (not yet implemented): "
+                + expr.getOperation().getText() + " in " + expr.getText());
+    }
+
+    /**
+     * Arithmetic with operand-swap logic per upstream ExpandoMetaClass:
+     * <pre>
+     *   SF + SF  -> left.plus(right)
+     *   SF - SF  -> left.minus(right)
+     *   SF * SF  -> left.multiply(right)
+     *   SF / SF  -> left.div(right)
+     *   SF op N  -> sf.op(N)
+     *   N + SF   -> sf.plus(N)          (swap)
+     *   N - SF   -> sf.minus(N).negative()
+     *   N * SF   -> sf.multiply(N)      (swap)
+     *   N / SF   -> sf.newValue(v -> N / v)
+     *   N op N   -> plain arithmetic
+     * </pre>
+     */
+    private String visitArithmetic(final Expression left, final Expression 
right, final int opType) {
+        final boolean leftNum = isNumberLiteral(left);
+        final boolean rightNum = isNumberLiteral(right);
+        final String leftStr = visitExpression(left);
+        final String rightStr = visitExpression(right);
+
+        if (leftNum && rightNum) {
+            return "(" + leftStr + " " + opSymbol(opType) + " " + rightStr + 
")";
+        }
+
+        if (!leftNum && rightNum) {
+            return leftStr + "." + opMethod(opType) + "(" + rightStr + ")";
+        }
+
+        if (leftNum && !rightNum) {
+            switch (opType) {
+                case Types.PLUS:
+                    return rightStr + ".plus(" + leftStr + ")";
+                case Types.MINUS:
+                    return rightStr + ".minus(" + leftStr + ").negative()";
+                case Types.MULTIPLY:
+                    return rightStr + ".multiply(" + leftStr + ")";
+                case Types.DIVIDE:
+                    return rightStr + ".newValue(v -> " + leftStr + " / v)";
+                default:
+                    break;
+            }
+        }
+
+        // SF op SF
+        return leftStr + "." + opMethod(opType) + "(" + rightStr + ")";
+    }
+
+    private boolean isNumberLiteral(final Expression expr) {
+        if (expr instanceof ConstantExpression) {
+            return ((ConstantExpression) expr).getValue() instanceof Number;
+        }
+        return false;
+    }
+
+    private boolean isArithmetic(final int opType) {
+        return opType == Types.PLUS || opType == Types.MINUS
+            || opType == Types.MULTIPLY || opType == Types.DIVIDE;
+    }
+
+    private String opMethod(final int opType) {
+        switch (opType) {
+            case Types.PLUS: return "plus";
+            case Types.MINUS: return "minus";
+            case Types.MULTIPLY: return "multiply";
+            case Types.DIVIDE: return "div";
+            default: return "???";
+        }
+    }
+
+    private String opSymbol(final int opType) {
+        switch (opType) {
+            case Types.PLUS: return "+";
+            case Types.MINUS: return "-";
+            case Types.MULTIPLY: return "*";
+            case Types.DIVIDE: return "/";
+            default: return "?";
+        }
+    }
+
+    // ---- tag() Closure ----
+
+    private String visitTagClosure(final ClosureExpression closure) {
+        final Parameter[] params = closure.getParameters();
+        final String tagsVar = (params != null && params.length > 0) ? 
params[0].getName() : "tags";
+        final List<Statement> stmts = getStatements(closure.getCode());
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("(").append(tagsVar).append(" -> {\n");
+        for (Statement s : stmts) {
+            sb.append("            ").append(visitTagStatement(s, 
tagsVar)).append("\n");
+        }
+        sb.append("            return ").append(tagsVar).append(";\n");
+        sb.append("        })");
+        return sb.toString();
+    }
+
+    private String visitTagStatement(final Statement stmt, final String 
tagsVar) {
+        if (stmt instanceof ExpressionStatement) {
+            return visitTagExpr(((ExpressionStatement) stmt).getExpression(), 
tagsVar) + ";";
+        }
+        if (stmt instanceof ReturnStatement) {
+            return "return " + tagsVar + ";";
+        }
+        if (stmt instanceof IfStatement) {
+            return visitTagIf((IfStatement) stmt, tagsVar);
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported tag closure statement: " + 
stmt.getClass().getSimpleName());
+    }
+
+    // ---- If/Else + Compound Conditions in tag() ----
+
+    private String visitTagIf(final IfStatement ifStmt, final String tagsVar) {
+        final String condition = 
visitTagCondition(ifStmt.getBooleanExpression().getExpression(), tagsVar);
+        final List<Statement> ifBody = getStatements(ifStmt.getIfBlock());
+        final Statement elseBlock = ifStmt.getElseBlock();
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("if (").append(condition).append(") {\n");
+        for (Statement s : ifBody) {
+            sb.append("                ").append(visitTagStatement(s, 
tagsVar)).append("\n");
+        }
+        sb.append("            }");
+
+        if (elseBlock != null && !(elseBlock instanceof EmptyStatement)) {
+            sb.append(" else {\n");
+            final List<Statement> elseBody = getStatements(elseBlock);
+            for (Statement s : elseBody) {
+                sb.append("                ").append(visitTagStatement(s, 
tagsVar)).append("\n");
+            }
+            sb.append("            }");
+        }
+
+        return sb.toString();
+    }
+
+    private String visitTagCondition(final Expression expr, final String 
tagsVar) {
+        if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            final int opType = bin.getOperation().getType();
+
+            if (opType == Types.COMPARE_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), 
bin.getRightExpression(), tagsVar, false);
+            }
+            if (opType == Types.COMPARE_NOT_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), 
bin.getRightExpression(), tagsVar, true);
+            }
+            if (opType == Types.LOGICAL_OR) {
+                return visitTagCondition(bin.getLeftExpression(), tagsVar)
+                    + " || " + visitTagCondition(bin.getRightExpression(), 
tagsVar);
+            }
+            if (opType == Types.LOGICAL_AND) {
+                return visitTagCondition(bin.getLeftExpression(), tagsVar)
+                    + " && " + visitTagCondition(bin.getRightExpression(), 
tagsVar);
+            }
+        }
+        if (expr instanceof BooleanExpression) {
+            return visitTagCondition(((BooleanExpression) 
expr).getExpression(), tagsVar);
+        }
+        return visitTagValue(expr, tagsVar);
+    }
+
+    private String visitTagEquals(final Expression left, final Expression 
right,
+                                  final String tagsVar, final boolean negate) {
+        if (isNullConstant(right)) {
+            final String leftStr = visitTagValue(left, tagsVar);
+            return negate ? leftStr + " != null" : leftStr + " == null";
+        }
+        if (isNullConstant(left)) {
+            final String rightStr = visitTagValue(right, tagsVar);
+            return negate ? rightStr + " != null" : rightStr + " == null";
+        }
+
+        final String leftStr = visitTagValue(left, tagsVar);
+        final String rightStr = visitTagValue(right, tagsVar);
+
+        if (right instanceof ConstantExpression && ((ConstantExpression) 
right).getValue() instanceof String) {
+            final String result = rightStr + ".equals(" + leftStr + ")";
+            return negate ? "!" + result : result;
+        }
+        if (left instanceof ConstantExpression && ((ConstantExpression) 
left).getValue() instanceof String) {
+            final String result = leftStr + ".equals(" + rightStr + ")";
+            return negate ? "!" + result : result;
+        }
+        final String result = "Objects.equals(" + leftStr + ", " + rightStr + 
")";
+        return negate ? "!" + result : result;
+    }
+
+    // ---- Filter Conditions ----
+
+    private String visitFilterCondition(final Expression expr, final String 
tagsVar) {
+        if (expr instanceof NotExpression) {
+            final Expression inner = ((NotExpression) expr).getExpression();
+            final String val = visitTagValue(inner, tagsVar);
+            return "(" + val + " == null || " + val + ".isEmpty())";
+        }
+
+        if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            final int opType = bin.getOperation().getType();
+
+            if (opType == Types.COMPARE_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), 
bin.getRightExpression(), tagsVar, false);
+            }
+            if (opType == Types.COMPARE_NOT_EQUAL) {
+                return visitTagEquals(bin.getLeftExpression(), 
bin.getRightExpression(), tagsVar, true);
+            }
+            if (opType == Types.LOGICAL_OR) {
+                return visitFilterCondition(bin.getLeftExpression(), tagsVar)
+                    + " || " + visitFilterCondition(bin.getRightExpression(), 
tagsVar);
+            }
+            if (opType == Types.LOGICAL_AND) {
+                return visitFilterCondition(bin.getLeftExpression(), tagsVar)
+                    + " && " + visitFilterCondition(bin.getRightExpression(), 
tagsVar);
+            }
+            if (opType == Types.KEYWORD_IN) {
+                final String val = visitTagValue(bin.getLeftExpression(), 
tagsVar);
+                final String list = visitTagValue(bin.getRightExpression(), 
tagsVar);
+                return list + ".contains(" + val + ")";
+            }
+        }
+
+        if (expr instanceof BooleanExpression) {
+            return visitFilterCondition(((BooleanExpression) 
expr).getExpression(), tagsVar);
+        }
+
+        final String val = visitTagValue(expr, tagsVar);
+        return "(" + val + " != null && !" + val + ".isEmpty())";
+    }
+
+    private String visitTagExpr(final Expression expr, final String tagsVar) {
+        if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.ASSIGN) {
+                return visitTagAssignment(bin.getLeftExpression(), 
bin.getRightExpression(), tagsVar);
+            }
+        }
+        if (expr instanceof MethodCallExpression) {
+            final MethodCallExpression mce = (MethodCallExpression) expr;
+            if ("remove".equals(mce.getMethodAsString()) && 
isTagsVar(mce.getObjectExpression(), tagsVar)) {
+                final ArgumentListExpression args = 
toArgList(mce.getArguments());
+                return tagsVar + ".remove(" + 
visitTagValue(args.getExpression(0), tagsVar) + ")";
+            }
+        }
+        return visitTagValue(expr, tagsVar);
+    }
+
+    private String visitTagAssignment(final Expression left, final Expression 
right, final String tagsVar) {
+        final String val = visitTagValue(right, tagsVar);
+
+        if (left instanceof PropertyExpression) {
+            final PropertyExpression prop = (PropertyExpression) left;
+            if (isTagsVar(prop.getObjectExpression(), tagsVar)) {
+                return tagsVar + ".put(\"" + 
escapeJava(prop.getPropertyAsString()) + "\", " + val + ")";
+            }
+        }
+        if (left instanceof BinaryExpression) {
+            final BinaryExpression sub = (BinaryExpression) left;
+            if (sub.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(sub.getLeftExpression(), tagsVar)) {
+                final String key = visitTagValue(sub.getRightExpression(), 
tagsVar);
+                return tagsVar + ".put(" + key + ", " + val + ")";
+            }
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported tag assignment target: " + 
left.getClass().getSimpleName() + " = " + left.getText());
+    }
+
+    String visitTagValue(final Expression expr, final String tagsVar) {
+        if (expr instanceof PropertyExpression) {
+            final PropertyExpression prop = (PropertyExpression) expr;
+            if (isTagsVar(prop.getObjectExpression(), tagsVar)) {
+                return tagsVar + ".get(\"" + 
escapeJava(prop.getPropertyAsString()) + "\")";
+            }
+            return visitProperty(prop);
+        }
+        if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(bin.getLeftExpression(), tagsVar)) {
+                return tagsVar + ".get(" + 
visitTagValue(bin.getRightExpression(), tagsVar) + ")";
+            }
+            if (bin.getOperation().getType() == Types.PLUS) {
+                return visitTagValue(bin.getLeftExpression(), tagsVar)
+                    + " + " + visitTagValue(bin.getRightExpression(), tagsVar);
+            }
+        }
+        // Elvis operator — must check BEFORE TernaryExpression since it 
extends it
+        if (expr instanceof ElvisOperatorExpression) {
+            final ElvisOperatorExpression elvis = (ElvisOperatorExpression) 
expr;
+            final String val = visitTagValue(elvis.getTrueExpression(), 
tagsVar);
+            final String defaultVal = 
visitTagValue(elvis.getFalseExpression(), tagsVar);
+            return "(" + val + " != null ? " + val + " : " + defaultVal + ")";
+        }
+        if (expr instanceof TernaryExpression) {
+            final TernaryExpression tern = (TernaryExpression) expr;
+            final String cond = 
visitFilterCondition(tern.getBooleanExpression().getExpression(), tagsVar);
+            final String trueVal = visitTagValue(tern.getTrueExpression(), 
tagsVar);
+            final String falseVal = visitTagValue(tern.getFalseExpression(), 
tagsVar);
+            return "(" + cond + " ? " + trueVal + " : " + falseVal + ")";
+        }
+        if (expr instanceof MethodCallExpression) {
+            final MethodCallExpression mce = (MethodCallExpression) expr;
+            final String obj = visitTagValue(mce.getObjectExpression(), 
tagsVar);
+            final ArgumentListExpression args = toArgList(mce.getArguments());
+            final List<String> argStrs = new ArrayList<>();
+            for (Expression a : args.getExpressions()) {
+                argStrs.add(visitTagValue(a, tagsVar));
+            }
+            final String call = obj + "." + mce.getMethodAsString() + "(" + 
String.join(", ", argStrs) + ")";
+            if (mce.isSafe()) {
+                return "(" + obj + " != null ? " + call + " : null)";
+            }
+            return call;
+        }
+        if (expr instanceof VariableExpression) {
+            final String name = ((VariableExpression) expr).getName();
+            if (name.equals(tagsVar)) {
+                return tagsVar;
+            }
+            return name;
+        }
+        if (expr instanceof ConstantExpression) {
+            return visitConstant((ConstantExpression) expr);
+        }
+        if (expr instanceof ListExpression) {
+            return visitList((ListExpression) expr);
+        }
+        if (expr instanceof MapExpression) {
+            final MapExpression map = (MapExpression) expr;
+            final List<String> entries = new ArrayList<>();
+            for (MapEntryExpression entry : map.getMapEntryExpressions()) {
+                entries.add(visitTagValue(entry.getKeyExpression(), tagsVar));
+                entries.add(visitTagValue(entry.getValueExpression(), 
tagsVar));
+            }
+            return "Map.of(" + String.join(", ", entries) + ")";
+        }
+        return visitExpression(expr);
+    }
+
+    private boolean isTagsVar(final Expression expr, final String tagsVar) {
+        return expr instanceof VariableExpression
+            && ((VariableExpression) expr).getName().equals(tagsVar);
+    }
+
+    private List<Statement> getStatements(final Statement stmt) {
+        if (stmt instanceof BlockStatement) {
+            return ((BlockStatement) stmt).getStatements();
+        }
+        return List.of(stmt);
+    }
+
+    // ---- forEach() Closure ----
+
+    private String visitForEachClosure(final ClosureExpression closure) {
+        final Parameter[] params = closure.getParameters();
+        final String prefixVar = (params != null && params.length > 0) ? 
params[0].getName() : "prefix";
+        final String tagsVar = (params != null && params.length > 1) ? 
params[1].getName() : "tags";
+
+        final List<Statement> stmts = getStatements(closure.getCode());
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("(").append(prefixVar).append(", 
").append(tagsVar).append(") -> {\n");
+        for (Statement s : stmts) {
+            sb.append("            ").append(visitForEachStatement(s, 
tagsVar)).append("\n");
+        }
+        sb.append("        }");
+        return sb.toString();
+    }
+
+    private String visitForEachStatement(final Statement stmt, final String 
tagsVar) {
+        if (stmt instanceof ExpressionStatement) {
+            return visitForEachExpr(((ExpressionStatement) 
stmt).getExpression(), tagsVar) + ";";
+        }
+        if (stmt instanceof ReturnStatement) {
+            return "return;";
+        }
+        if (stmt instanceof IfStatement) {
+            return visitForEachIf((IfStatement) stmt, tagsVar);
+        }
+        throw new UnsupportedOperationException(
+            "Unsupported forEach closure statement: " + 
stmt.getClass().getSimpleName());
+    }
+
+    private String visitForEachExpr(final Expression expr, final String 
tagsVar) {
+        if (expr instanceof DeclarationExpression) {
+            final DeclarationExpression decl = (DeclarationExpression) expr;
+            final String typeName = 
decl.getVariableExpression().getType().getNameWithoutPackage();
+            final String varName = decl.getVariableExpression().getName();
+            final String init = visitTagValue(decl.getRightExpression(), 
tagsVar);
+            return typeName + " " + varName + " = " + init;
+        }
+        if (expr instanceof BinaryExpression) {
+            final BinaryExpression bin = (BinaryExpression) expr;
+            if (bin.getOperation().getType() == Types.ASSIGN) {
+                final Expression left = bin.getLeftExpression();
+                if (isTagWrite(left, tagsVar)) {
+                    return visitTagAssignment(left, bin.getRightExpression(), 
tagsVar);
+                }
+                if (left instanceof VariableExpression) {
+                    return ((VariableExpression) left).getName()
+                        + " = " + visitTagValue(bin.getRightExpression(), 
tagsVar);
+                }
+            }
+        }
+        return visitTagExpr(expr, tagsVar);
+    }
+
+    private String visitForEachIf(final IfStatement ifStmt, final String 
tagsVar) {
+        final String condition = 
visitTagCondition(ifStmt.getBooleanExpression().getExpression(), tagsVar);
+        final List<Statement> ifBody = getStatements(ifStmt.getIfBlock());
+        final Statement elseBlock = ifStmt.getElseBlock();
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append("if (").append(condition).append(") {\n");
+        for (Statement s : ifBody) {
+            sb.append("                ").append(visitForEachStatement(s, 
tagsVar)).append("\n");
+        }
+        sb.append("            }");
+
+        if (elseBlock instanceof IfStatement) {
+            sb.append(" else ").append(visitForEachIf((IfStatement) elseBlock, 
tagsVar));
+        } else if (elseBlock != null && !(elseBlock instanceof 
EmptyStatement)) {
+            sb.append(" else {\n");
+            for (Statement s : getStatements(elseBlock)) {
+                sb.append("                ").append(visitForEachStatement(s, 
tagsVar)).append("\n");
+            }
+            sb.append("            }");
+        }
+
+        return sb.toString();
+    }
+
+    private boolean isTagWrite(final Expression left, final String tagsVar) {
+        if (left instanceof PropertyExpression) {
+            return isTagsVar(((PropertyExpression) 
left).getObjectExpression(), tagsVar);
+        }
+        if (left instanceof BinaryExpression) {
+            final BinaryExpression sub = (BinaryExpression) left;
+            return sub.getOperation().getType() == Types.LEFT_SQUARE_BRACKET
+                && isTagsVar(sub.getLeftExpression(), tagsVar);
+        }
+        return false;
+    }
+
+    private boolean isNullConstant(final Expression expr) {
+        return expr instanceof ConstantExpression && ((ConstantExpression) 
expr).getValue() == null;
+    }
+
+    // ---- PropertiesExtractor Closure ----
+
+    private String visitPropertiesExtractorClosure(final ClosureExpression 
closure) {
+        final Parameter[] params = closure.getParameters();
+        final String tagsVar = (params != null && params.length > 0) ? 
params[0].getName() : "tags";
+        final List<Statement> stmts = getStatements(closure.getCode());
+
+        final Statement last = stmts.get(stmts.size() - 1);
+        Expression bodyExpr;
+        if (last instanceof ExpressionStatement) {
+            bodyExpr = ((ExpressionStatement) last).getExpression();
+        } else if (last instanceof ReturnStatement) {
+            bodyExpr = ((ReturnStatement) last).getExpression();
+        } else {
+            throw new UnsupportedOperationException(
+                "Unsupported PropertiesExtractor closure body: " + 
last.getClass().getSimpleName());
+        }
+        return "(" + tagsVar + " -> " + visitTagValue(bodyExpr, tagsVar) + ")";
+    }
+
+    // ---- Batch Registration, Compilation, and Manifest Writing ----
+
+    public void registerExpression(final String className, final String 
source) {
+        expressionSources.put(className, source);
+    }
+
+    public void registerFilter(final String className, final String 
filterLiteral, final String source) {
+        filterSources.put(className, source);
+        filterLiteralToClass.put(filterLiteral, GENERATED_PACKAGE + "." + 
className);
+    }
+
+    /**
+     * Compile all registered sources using javax.tools.JavaCompiler.
+     *
+     * @param sourceDir  directory to write .java source files (package dirs 
created automatically)
+     * @param outputDir  directory for compiled .class files
+     * @param classpath  classpath for javac (semicolon/colon-separated JAR 
paths)
+     * @throws IOException if file I/O fails
+     */
+    public void compileAll(final File sourceDir, final File outputDir,
+                           final String classpath) throws IOException {
+        final Map<String, String> allSources = new LinkedHashMap<>();
+        allSources.putAll(expressionSources);
+        allSources.putAll(filterSources);
+
+        if (allSources.isEmpty()) {
+            log.info("No MAL sources to compile.");
+            return;
+        }
+
+        final String packageDir = GENERATED_PACKAGE.replace('.', 
File.separatorChar);
+        final File srcPkgDir = new File(sourceDir, packageDir);
+        if (!srcPkgDir.exists() && !srcPkgDir.mkdirs()) {
+            throw new IOException("Failed to create source dir: " + srcPkgDir);
+        }
+        if (!outputDir.exists() && !outputDir.mkdirs()) {
+            throw new IOException("Failed to create output dir: " + outputDir);
+        }
+
+        final List<File> javaFiles = new ArrayList<>();
+        for (Map.Entry<String, String> entry : allSources.entrySet()) {
+            final File javaFile = new File(srcPkgDir, entry.getKey() + 
".java");
+            Files.writeString(javaFile.toPath(), entry.getValue());
+            javaFiles.add(javaFile);
+        }
+
+        final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        if (compiler == null) {
+            throw new IllegalStateException("No Java compiler available — 
requires JDK");
+        }
+
+        final StringWriter errorWriter = new StringWriter();
+
+        try (StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(null, null, null)) {
+            final Iterable<? extends JavaFileObject> compilationUnits =
+                fileManager.getJavaFileObjectsFromFiles(javaFiles);
+
+            final List<String> options = Arrays.asList(
+                "-d", outputDir.getAbsolutePath(),
+                "-classpath", classpath
+            );
+
+            final JavaCompiler.CompilationTask task = compiler.getTask(
+                errorWriter, fileManager, null, options, null, 
compilationUnits);
+
+            final boolean success = task.call();
+            if (!success) {
+                throw new RuntimeException(
+                    "Java compilation failed for " + javaFiles.size() + " MAL 
sources:\n"
+                        + errorWriter);
+            }
+        }
+
+        log.info("Compiled {} MAL sources to {}", allSources.size(), 
outputDir);
+    }
+
+    /**
+     * Write mal-expressions.txt manifest: one FQCN per line.
+     */
+    public void writeExpressionManifest(final File outputDir) throws 
IOException {
+        final File manifestDir = new File(outputDir, "META-INF");
+        if (!manifestDir.exists() && !manifestDir.mkdirs()) {
+            throw new IOException("Failed to create META-INF dir: " + 
manifestDir);
+        }
+
+        final List<String> lines = expressionSources.keySet().stream()
+            .map(name -> GENERATED_PACKAGE + "." + name)
+            .collect(Collectors.toList());
+        Files.write(new File(manifestDir, "mal-expressions.txt").toPath(), 
lines);
+        log.info("Wrote mal-expressions.txt with {} entries", lines.size());
+    }
+
+    /**
+     * Write mal-filter-expressions.properties manifest: literal=FQCN.
+     */
+    public void writeFilterManifest(final File outputDir) throws IOException {
+        final File manifestDir = new File(outputDir, "META-INF");
+        if (!manifestDir.exists() && !manifestDir.mkdirs()) {
+            throw new IOException("Failed to create META-INF dir: " + 
manifestDir);
+        }
+
+        final List<String> lines = filterLiteralToClass.entrySet().stream()
+            .map(e -> escapeProperties(e.getKey()) + "=" + e.getValue())
+            .collect(Collectors.toList());
+        Files.write(new File(manifestDir, 
"mal-filter-expressions.properties").toPath(), lines);
+        log.info("Wrote mal-filter-expressions.properties with {} entries", 
lines.size());
+    }
+
+    private static String escapeProperties(final String s) {
+        return s.replace("\\", "\\\\")
+                .replace("=", "\\=")
+                .replace(":", "\\:")
+                .replace(" ", "\\ ");
+    }
+
+    // ---- Argument Utilities ----
+
+    private ArgumentListExpression toArgList(final Expression args) {
+        if (args instanceof ArgumentListExpression) {
+            return (ArgumentListExpression) args;
+        }
+        if (args instanceof TupleExpression) {
+            final ArgumentListExpression ale = new ArgumentListExpression();
+            for (Expression e : ((TupleExpression) args).getExpressions()) {
+                ale.addExpression(e);
+            }
+            return ale;
+        }
+        final ArgumentListExpression ale = new ArgumentListExpression();
+        ale.addExpression(args);
+        return ale;
+    }
+
+    private List<String> visitArgList(final ArgumentListExpression args) {
+        final List<String> result = new ArrayList<>();
+        for (Expression arg : args.getExpressions()) {
+            result.add(visitExpression(arg));
+        }
+        return result;
+    }
+
+    // ---- Utility ----
+
+    static String escapeJava(final String s) {
+        return s.replace("\\", "\\\\")
+                .replace("\"", "\\\"")
+                .replace("\n", "\\n")
+                .replace("\r", "\\r")
+                .replace("\t", "\\t");
+    }
+}
diff --git 
a/oap-server/analyzer/mal-transpiler/src/test/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspilerTest.java
 
b/oap-server/analyzer/mal-transpiler/src/test/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspilerTest.java
new file mode 100644
index 0000000000..0207d8df32
--- /dev/null
+++ 
b/oap-server/analyzer/mal-transpiler/src/test/java/org/apache/skywalking/oap/server/transpiler/mal/MalToJavaTranspilerTest.java
@@ -0,0 +1,904 @@
+/*
+ * 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
+ *
+ *     http://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.apache.skywalking.oap.server.transpiler.mal;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class MalToJavaTranspilerTest {
+
+    private MalToJavaTranspiler transpiler;
+
+    @BeforeEach
+    void setUp() {
+        transpiler = new MalToJavaTranspiler();
+    }
+
+    // ---- AST Parsing + Simple Variable References ----
+
+    @Test
+    void simpleVariableReference() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"metric_name");
+        assertNotNull(java);
+
+        assertTrue(java.contains("package " + 
MalToJavaTranspiler.GENERATED_PACKAGE),
+            "Should have correct package");
+        assertTrue(java.contains("public class MalExpr_test implements 
MalExpression"),
+            "Should implement MalExpression");
+        assertTrue(java.contains("public SampleFamily run(Map<String, 
SampleFamily> samples)"),
+            "Should have run method");
+
+        assertTrue(java.contains("ctx.getSamples().add(\"metric_name\")"),
+            "Should track sample name in parsing context");
+
+        assertTrue(java.contains("samples.getOrDefault(\"metric_name\", 
SampleFamily.EMPTY)"),
+            "Should look up sample family from map");
+    }
+
+    @Test
+    void downsamplingConstantNotTrackedAsSample() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"SUM");
+        assertNotNull(java);
+
+        assertTrue(!java.contains("ctx.getSamples().add(\"SUM\")"),
+            "Should not track DownsamplingType constant as sample");
+
+        assertTrue(java.contains("DownsamplingType.SUM"),
+            "Should resolve to DownsamplingType.SUM");
+    }
+
+    @Test
+    void parseToAST_producesModuleNode() {
+        final var ast = transpiler.parseToAST("some_metric");
+        assertNotNull(ast, "Should produce a ModuleNode");
+        assertNotNull(ast.getStatementBlock(), "Should have a statement 
block");
+    }
+
+    @Test
+    void constantString() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"'hello'");
+        assertNotNull(java);
+        assertTrue(java.contains("\"hello\""),
+            "Should convert Groovy string to Java string");
+    }
+
+    @Test
+    void constantNumber() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"42");
+        assertNotNull(java);
+        assertTrue(java.contains("42"),
+            "Should preserve number literal");
+    }
+
+    // ---- Method Chains + List Literals + Enum Properties ----
+
+    @Test
+    void simpleMethodChain() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric_name.sum(['a', 'b']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".sum(List.of(\"a\", \"b\"))"),
+            "Should translate ['a','b'] to List.of(\"a\", \"b\")");
+        assertTrue(java.contains(".service(List.of(\"svc\"), Layer.GENERAL)"),
+            "Should translate Layer.GENERAL as enum");
+    }
+
+    @Test
+    void tagEqualChain() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "cpu_seconds.tagNotEqual('mode', 
'idle').sum(['host']).rate('PT1M')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tagNotEqual(\"mode\", \"idle\")"),
+            "Should translate tagNotEqual with string args");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should translate single-element list");
+        assertTrue(java.contains(".rate(\"PT1M\")"),
+            "Should translate rate with string arg");
+    }
+
+    @Test
+    void downsamplingMethod() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.downsampling(SUM)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".downsampling(DownsamplingType.SUM)"),
+            "Should resolve SUM to DownsamplingType.SUM");
+    }
+
+    @Test
+    void retagByK8sMeta() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.retagByK8sMeta('service', K8sRetagType.Pod2Service, 'pod', 
'namespace')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".retagByK8sMeta(\"service\", 
K8sRetagType.Pod2Service, \"pod\", \"namespace\")"),
+            "Should translate K8sRetagType enum and string args");
+    }
+
+    @Test
+    void histogramPercentile() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.sum(['le', 'svc']).histogram().histogram_percentile([50, 
75, 90])");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".histogram()"),
+            "Should translate no-arg histogram()");
+        assertTrue(java.contains(".histogram_percentile(List.of(50, 75, 90))"),
+            "Should translate integer list");
+    }
+
+    @Test
+    void sampleNameCollectionThroughChain() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "my_metric.sum(['a']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("ctx.getSamples().add(\"my_metric\")"),
+            "Should collect sample name from root of method chain");
+        assertTrue(!java.contains("ctx.getSamples().add(\"a\")"),
+            "Should NOT collect 'a' (it's a string constant arg, not a 
sample)");
+    }
+
+    @Test
+    void detectPointEnum() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.serviceRelation(DetectPoint.CLIENT, ['src'], ['dst'], 
Layer.MESH_DP)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("DetectPoint.CLIENT"),
+            "Should translate DetectPoint enum");
+        assertTrue(java.contains("Layer.MESH_DP"),
+            "Should translate Layer enum");
+    }
+
+    @Test
+    void enumImportsPresent() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains("import 
org.apache.skywalking.oap.server.core.analysis.Layer;"),
+            "Should import Layer");
+        assertTrue(java.contains("import 
org.apache.skywalking.oap.server.core.source.DetectPoint;"),
+            "Should import DetectPoint");
+        assertTrue(java.contains("import 
org.apache.skywalking.oap.meter.analyzer.dsl.tagOpt.K8sRetagType;"),
+            "Should import K8sRetagType");
+    }
+
+    // ---- Binary Arithmetic with Operand-Swap ----
+
+    @Test
+    void sfTimesNumber() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"metric * 100");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "SF * N should call .multiply(N)");
+    }
+
+    @Test
+    void sfDivNumber() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"metric / 1024");
+        assertNotNull(java);
+        assertTrue(java.contains(".div(1024)"),
+            "SF / N should call .div(N)");
+    }
+
+    @Test
+    void numberMinusSf() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"100 - metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".minus(100).negative()"),
+            "N - SF should produce sf.minus(N).negative()");
+    }
+
+    @Test
+    void numberDivSf() {
+        final String java = transpiler.transpileExpression("MalExpr_test", "1 
/ metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".newValue(v -> 1 / v)"),
+            "N / SF should produce sf.newValue(v -> N / v)");
+    }
+
+    @Test
+    void numberPlusSf() {
+        final String java = transpiler.transpileExpression("MalExpr_test", "10 
+ metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".plus(10)"),
+            "N + SF should swap to sf.plus(N)");
+    }
+
+    @Test
+    void numberTimesSf() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"100 * metric");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "N * SF should swap to sf.multiply(N)");
+    }
+
+    @Test
+    void sfMinusSf() {
+        final String java = transpiler.transpileExpression("MalExpr_test", 
"mem_total - mem_avail");
+        assertNotNull(java);
+        assertTrue(java.contains("ctx.getSamples().add(\"mem_total\")"),
+            "Should collect both sample names");
+        assertTrue(java.contains("ctx.getSamples().add(\"mem_avail\")"),
+            "Should collect both sample names");
+        assertTrue(java.contains(".minus("),
+            "SF - SF should call .minus()");
+    }
+
+    @Test
+    void sfDivSfTimesNumber() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "used_bytes / max_bytes * 100");
+        assertNotNull(java);
+        assertTrue(java.contains(".div("),
+            "Should have .div() for SF / SF");
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have .multiply(100) for result * 100");
+    }
+
+    @Test
+    void nestedParenArithmetic() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "100 - ((mem_free * 100) / mem_total)");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have inner multiply");
+        assertTrue(java.contains(".negative()"),
+            "100 - SF should produce .negative()");
+    }
+
+    @Test
+    void parenthesizedWithMethodChain() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "(metric * 100).tagNotEqual('mode', 
'idle').sum(['host']).rate('PT1M')");
+        assertNotNull(java);
+        assertTrue(java.contains(".multiply(100)"),
+            "Should have multiply inside parens");
+        assertTrue(java.contains(".tagNotEqual(\"mode\", \"idle\")"),
+            "Should chain tagNotEqual after parens");
+        assertTrue(java.contains(".rate(\"PT1M\")"),
+            "Should chain rate at the end");
+    }
+
+    // ---- tag() Closure — Simple Cases ----
+
+    @Test
+    void tagAssignmentWithStringConcat() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.route = 'route/' + tags['route']})");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should cast closure to TagFunction");
+        assertTrue(java.contains("tags.put(\"route\", \"route/\" + 
tags.get(\"route\"))"),
+            "Should translate assignment with string concat and subscript 
read");
+        assertTrue(java.contains("return tags;"),
+            "Should return tags at end of lambda");
+    }
+
+    @Test
+    void tagRemove() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.remove('condition')})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.remove(\"condition\")"),
+            "Should translate remove call");
+    }
+
+    @Test
+    void tagPropertyToProperty() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.rs_nm = tags.set})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"rs_nm\", tags.get(\"set\"))"),
+            "Should translate property read on RHS to tags.get()");
+    }
+
+    @Test
+    void tagStringConcatWithPropertyRead() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.cluster = 'es::' + tags.cluster})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"cluster\", \"es::\" + 
tags.get(\"cluster\"))"),
+            "Should translate string concat with property read");
+    }
+
+    @Test
+    void tagClosureLambdaStructure() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.x = 'y'})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags -> {"),
+            "Should have lambda opening");
+        assertTrue(java.contains("return tags;"),
+            "Should return tags variable");
+    }
+
+    @Test
+    void tagWithSubscriptWrite() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags['service_name'] = tags['svc']})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.put(\"service_name\", 
tags.get(\"svc\"))"),
+            "Should translate subscript write and read");
+    }
+
+    @Test
+    void tagChainAfterTag() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.x = 'y'}).sum(['host']).service(['svc'], 
Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should have tag call");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should chain sum after tag");
+        assertTrue(java.contains(".service(List.of(\"svc\"), Layer.GENERAL)"),
+            "Should chain service after sum");
+    }
+
+    // ---- tag() Closure — if/else + Compound Conditions ----
+
+    @Test
+    void ifOnlyWithChainedOr() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['gc'] == 'PS Scavenge' || tags['gc'] 
== 'Copy' || tags['gc'] == 'ParNew' || tags['gc'] == 'G1 Young Generation') 
{tags.gc = 'young_gc_count'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"PS 
Scavenge\".equals(tags.get(\"gc\"))"),
+            "Should translate first == with constant on left for null-safety");
+        assertTrue(java.contains("|| \"Copy\".equals(tags.get(\"gc\"))"),
+            "Should chain || for second comparison");
+        assertTrue(java.contains("|| \"ParNew\".equals(tags.get(\"gc\"))"),
+            "Should chain || for third comparison");
+        assertTrue(java.contains("|| \"G1 Young 
Generation\".equals(tags.get(\"gc\"))"),
+            "Should chain || for fourth comparison");
+        assertTrue(java.contains("tags.put(\"gc\", \"young_gc_count\")"),
+            "Should translate assignment in if body");
+    }
+
+    @Test
+    void ifElse() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['primary'] == 'true') {tags.primary 
= 'primary'} else {tags.primary = 'replica'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"true\".equals(tags.get(\"primary\"))"),
+            "Should translate == comparison in condition");
+        assertTrue(java.contains("tags.put(\"primary\", \"primary\")"),
+            "Should translate if-branch assignment");
+        assertTrue(java.contains("} else {"),
+            "Should have else clause");
+        assertTrue(java.contains("tags.put(\"primary\", \"replica\")"),
+            "Should translate else-branch assignment");
+    }
+
+    @Test
+    void ifOnlyNoElse() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['level'] == '1') {tags.level = 'L1 
aggregation'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("if (\"1\".equals(tags.get(\"level\"))"),
+            "Should translate condition");
+        assertTrue(java.contains("tags.put(\"level\", \"L1 aggregation\")"),
+            "Should translate if-body");
+        assertTrue(!java.contains("else"),
+            "Should NOT have else clause");
+    }
+
+    @Test
+    void notEqualComparison() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['status'] != 'ok') {tags.status = 
'error'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("!\"ok\".equals(tags.get(\"status\"))"),
+            "Should translate != with negated .equals()");
+    }
+
+    @Test
+    void logicalAndCondition() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['a'] == 'x' && tags['b'] == 'y') 
{tags.c = 'z'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"x\".equals(tags.get(\"a\")) && 
\"y\".equals(tags.get(\"b\"))"),
+            "Should translate && with .equals() on both sides");
+    }
+
+    @Test
+    void chainedTagClosuresWithIf() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['level'] == '1') {tags.level = 'L1 
aggregation'} })" +
+            ".tag({tags -> if (tags['level'] == '2') {tags.level = 'L2 
aggregation'} })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"1\".equals(tags.get(\"level\"))"),
+            "Should translate first tag closure condition");
+        assertTrue(java.contains("\"2\".equals(tags.get(\"level\"))"),
+            "Should translate second tag closure condition");
+    }
+
+    @Test
+    void ifWithMethodChainAfter() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> if (tags['gc'] == 'Copy') {tags.gc = 'young'} 
}).sum(['host']).service(['svc'], Layer.GENERAL)");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".tag((TagFunction)"),
+            "Should have TagFunction cast");
+        assertTrue(java.contains("\"Copy\".equals(tags.get(\"gc\"))"),
+            "Should have if condition");
+        assertTrue(java.contains(".sum(List.of(\"host\"))"),
+            "Should chain sum after tag");
+    }
+
+    // ---- Filter Closures ----
+
+    @Test
+    void simpleEqualityFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_0",
+            "{ tags -> tags.job_name == 'vm-monitoring' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("public class MalFilter_0 implements 
MalFilter"),
+            "Should implement MalFilter");
+        assertTrue(java.contains("public boolean test(Map<String, String> 
tags)"),
+            "Should have test method");
+        
assertTrue(java.contains("\"vm-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate == with constant on left");
+    }
+
+    @Test
+    void filterPackageAndImports() {
+        final String java = transpiler.transpileFilter("MalFilter_0",
+            "{ tags -> tags.job_name == 'x' }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("package " + 
MalToJavaTranspiler.GENERATED_PACKAGE),
+            "Should have correct package");
+        assertTrue(java.contains("import java.util.*;"),
+            "Should import java.util");
+        assertTrue(java.contains("import 
org.apache.skywalking.oap.meter.analyzer.dsl.*;"),
+            "Should import dsl package");
+    }
+
+    @Test
+    void orFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_1",
+            "{ tags -> tags.job_name == 'flink-jobManager-monitoring' || 
tags.job_name == 'flink-taskManager-monitoring' }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"flink-jobManager-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate first ==");
+        assertTrue(java.contains("|| 
\"flink-taskManager-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate || with second ==");
+    }
+
+    @Test
+    void inListFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_2",
+            "{ tags -> tags.job_name in ['kubernetes-cadvisor', 
'kube-state-metrics'] }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("List.of(\"kubernetes-cadvisor\", 
\"kube-state-metrics\").contains(tags.get(\"job_name\"))"),
+            "Should translate 'in' to List.of().contains()");
+    }
+
+    @Test
+    void compoundAndFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_3",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Namespace == 
'AWS/S3' }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate first ==");
+        assertTrue(java.contains("&& 
\"AWS/S3\".equals(tags.get(\"Namespace\"))"),
+            "Should translate && with second ==");
+    }
+
+    @Test
+    void truthinessFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_4",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Stage }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate ==");
+        assertTrue(java.contains("(tags.get(\"Stage\") != null && 
!tags.get(\"Stage\").isEmpty())"),
+            "Should translate bare tags.Stage as truthiness check");
+    }
+
+    @Test
+    void negatedTruthinessFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_5",
+            "{ tags -> tags.cloud_provider == 'aws' && !tags.Method }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"Method\") == null || 
tags.get(\"Method\").isEmpty())"),
+            "Should translate !tags.Method as negated truthiness");
+    }
+
+    @Test
+    void compoundWithTruthinessAndNegation() {
+        final String java = transpiler.transpileFilter("MalFilter_6",
+            "{ tags -> tags.cloud_provider == 'aws' && tags.Namespace == 
'AWS/ApiGateway' && tags.Stage && !tags.Method }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should translate first ==");
+        
assertTrue(java.contains("\"AWS/ApiGateway\".equals(tags.get(\"Namespace\"))"),
+            "Should translate second ==");
+        assertTrue(java.contains("(tags.get(\"Stage\") != null && 
!tags.get(\"Stage\").isEmpty())"),
+            "Should translate truthiness");
+        assertTrue(java.contains("(tags.get(\"Method\") == null || 
tags.get(\"Method\").isEmpty())"),
+            "Should translate negated truthiness");
+    }
+
+    @Test
+    void wrappedBlockFilter() {
+        final String java = transpiler.transpileFilter("MalFilter_7",
+            "{ tags -> {tags.cloud_provider == 'aws' && tags.Namespace == 
'AWS/S3'} }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"aws\".equals(tags.get(\"cloud_provider\"))"),
+            "Should unwrap inner block and translate ==");
+        assertTrue(java.contains("\"AWS/S3\".equals(tags.get(\"Namespace\"))"),
+            "Should translate second == after unwrapping");
+    }
+
+    @Test
+    void truthinessWithOrInParens() {
+        final String java = transpiler.transpileFilter("MalFilter_8",
+            "{ tags -> tags.cloud_provider == 'aws' && (tags.ApiId || 
tags.ApiName) }");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"ApiId\") != null && 
!tags.get(\"ApiId\").isEmpty())"),
+            "Should translate ApiId truthiness");
+        assertTrue(java.contains("(tags.get(\"ApiName\") != null && 
!tags.get(\"ApiName\").isEmpty())"),
+            "Should translate ApiName truthiness");
+    }
+
+    // ---- forEach() Closure ----
+
+    @Test
+    void forEachBasicStructure() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client', 'server'], { prefix, tags -> 
tags[prefix + '_id'] = 'test' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".forEach(List.of(\"client\", \"server\"), 
(ForEachFunction)"),
+            "Should cast closure to ForEachFunction");
+        assertTrue(java.contains("(prefix, tags) -> {"),
+            "Should have two-parameter lambda");
+        assertTrue(java.contains("tags.put(prefix + \"_id\", \"test\")"),
+            "Should translate dynamic subscript write");
+    }
+
+    @Test
+    void forEachNullCheckWithEarlyReturn() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> if (tags[prefix + 
'_process_id'] != null) { return } })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.get(prefix + \"_process_id\") != null"),
+            "Should translate null check");
+        assertTrue(java.contains("return;"),
+            "Should have void return for early exit");
+    }
+
+    @Test
+    void forEachWithProcessRegistry() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> " +
+            "tags[prefix + '_process_id'] = 
ProcessRegistry.generateVirtualLocalProcess(tags.service, tags.instance) })");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("ProcessRegistry.generateVirtualLocalProcess(tags.get(\"service\"),
 tags.get(\"instance\"))"),
+            "Should translate static method call with tag reads as args");
+        assertTrue(java.contains("tags.put(prefix + \"_process_id\","),
+            "Should translate dynamic subscript write");
+    }
+
+    @Test
+    void forEachVarDeclaration() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> String result = '' 
})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("String result = \"\""),
+            "Should translate variable declaration with empty string");
+    }
+
+    @Test
+    void forEachVarDeclWithTagRead() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> String protocol = 
tags['protocol'] })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("String protocol = tags.get(\"protocol\")"),
+            "Should translate var decl with tag read");
+    }
+
+    @Test
+    void forEachIfElseIfChain() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['component'], { key, tags -> " +
+            "String protocol = tags['protocol']\n" +
+            "String ssl = tags['is_ssl']\n" +
+            "String result = ''\n" +
+            "if (protocol == 'http' && ssl == 'true') { result = '129' } " +
+            "else if (protocol == 'http') { result = '49' } " +
+            "else if (ssl == 'true') { result = '130' } " +
+            "else { result = '110' }\n" +
+            "tags[key] = result })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("String protocol = tags.get(\"protocol\")"),
+            "Should declare protocol");
+        assertTrue(java.contains("String ssl = tags.get(\"is_ssl\")"),
+            "Should declare ssl");
+
+        assertTrue(java.contains("\"http\".equals(protocol)"),
+            "Should compare local var with .equals()");
+        assertTrue(java.contains("\"true\".equals(ssl)"),
+            "Should compare ssl with .equals()");
+
+        assertTrue(java.contains("} else if ("),
+            "Should produce else-if, not nested else { if }");
+
+        assertTrue(java.contains("} else {"),
+            "Should have final else");
+        assertTrue(java.contains("result = \"110\""),
+            "Should assign default value in else");
+
+        assertTrue(java.contains("tags.put(key, result)"),
+            "Should write result to tags[key]");
+    }
+
+    @Test
+    void forEachLocalVarAssignment() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['x'], { key, tags -> " +
+            "String r = ''\n" +
+            "r = 'abc'\n" +
+            "tags[key] = r })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("r = \"abc\""),
+            "Should translate local var reassignment");
+    }
+
+    @Test
+    void forEachEqualsOnStringComparison() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['client'], { prefix, tags -> " +
+            "if (tags[prefix + '_local'] == 'true') { tags[prefix + '_id'] = 
'local' } })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("\"true\".equals(tags.get(prefix + 
\"_local\"))"),
+            "Should translate dynamic subscript comparison with .equals()");
+        assertTrue(java.contains("tags.put(prefix + \"_id\", \"local\")"),
+            "Should translate dynamic subscript assignment");
+    }
+
+    @Test
+    void chainedForEach() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.forEach(['a'], { k1, tags -> tags[k1] = 'x' })" +
+            ".forEach(['b'], { k2, tags -> tags[k2] = 'y' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(ForEachFunction) (k1, tags)"),
+            "Should have first forEach with k1");
+        assertTrue(java.contains("(ForEachFunction) (k2, tags)"),
+            "Should have second forEach with k2");
+    }
+
+    // ---- Elvis (?:), Safe Navigation (?.), Ternary (? :) ----
+
+    @Test
+    void safeNavigation() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.svc = tags['skywalking_service']?.trim() 
})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"skywalking_service\") != null ? 
tags.get(\"skywalking_service\").trim() : null)"),
+            "Should translate ?.trim() to null-checked call");
+    }
+
+    @Test
+    void elvisOperator() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.svc = tags['name'] ?: 'unknown' })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("(tags.get(\"name\") != null ? 
tags.get(\"name\") : \"unknown\")"),
+            "Should translate ?: to null-check with default");
+    }
+
+    @Test
+    void safeNavPlusElvis() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.service_name = 
'APISIX::'+(tags['skywalking_service']?.trim()?:'APISIX') })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.get(\"skywalking_service\") != null ? 
tags.get(\"skywalking_service\").trim() : null"),
+            "Should have safe nav for trim");
+        assertTrue(java.contains("!= null ?") && java.contains(": \"APISIX\""),
+            "Should have elvis default to APISIX");
+        assertTrue(java.contains("\"APISIX::\" + "),
+            "Should have string prefix concatenation");
+    }
+
+    @Test
+    void ternaryOperator() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.tag({tags -> tags.service_name = tags.ApiId ? 
'gw::'+tags.ApiId : 'gw::'+tags.ApiName })");
+        assertNotNull(java);
+
+        assertTrue(java.contains("tags.get(\"ApiId\") != null && 
!tags.get(\"ApiId\").isEmpty()"),
+            "Should translate ternary condition as truthiness check");
+        assertTrue(java.contains("\"gw::\" + tags.get(\"ApiId\")"),
+            "Should have true branch expression");
+        assertTrue(java.contains("\"gw::\" + tags.get(\"ApiName\")"),
+            "Should have false branch expression");
+    }
+
+    @Test
+    void safeNavInFilterCondition() {
+        final String java = transpiler.transpileFilter("MalFilter_test",
+            "{ tags -> tags.job_name == 'eks-monitoring' && 
tags.Service?.trim() }");
+        assertNotNull(java);
+
+        
assertTrue(java.contains("\"eks-monitoring\".equals(tags.get(\"job_name\"))"),
+            "Should translate == comparison");
+        assertTrue(java.contains("tags.get(\"Service\") != null ? 
tags.get(\"Service\").trim() : null"),
+            "Should have safe nav for Service?.trim()");
+    }
+
+    // ---- instance() with PropertiesExtractor, MapExpression ----
+
+    @Test
+    void instanceWithPropertiesExtractor() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.instance(['cluster', 'service'], '::', ['pod'], '', 
Layer.K8S_SERVICE, " +
+            "{tags -> ['pod': tags.pod, 'namespace': tags.namespace]})");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".instance("),
+            "Should have instance call");
+        assertTrue(java.contains("(PropertiesExtractor)"),
+            "Should cast closure to PropertiesExtractor");
+        assertTrue(java.contains("Map.of(\"pod\", tags.get(\"pod\"), 
\"namespace\", tags.get(\"namespace\"))"),
+            "Should translate map literal to Map.of()");
+    }
+
+    @Test
+    void mapExpressionInTagValue() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.instance(['svc'], ['inst'], Layer.GENERAL, {tags -> 
['key': tags.val]})");
+        assertNotNull(java);
+
+        assertTrue(java.contains("Map.of(\"key\", tags.get(\"val\"))"),
+            "Should translate single-entry map");
+        assertTrue(java.contains("(PropertiesExtractor)"),
+            "Should have PropertiesExtractor cast");
+    }
+
+    @Test
+    void processRelationNoClosures() {
+        final String java = transpiler.transpileExpression("MalExpr_test",
+            "metric.processRelation('side', ['service'], ['instance'], " +
+            "'client_process_id', 'server_process_id', 'component')");
+        assertNotNull(java);
+
+        assertTrue(java.contains(".processRelation(\"side\", 
List.of(\"service\"), List.of(\"instance\"), " +
+            "\"client_process_id\", \"server_process_id\", \"component\")"),
+            "Should translate processRelation as regular method call");
+    }
+
+    // ---- Compilation + Manifests ----
+
+    @Test
+    void sourceWrittenForCompilation(@TempDir Path tempDir) throws Exception {
+        final String source = 
transpiler.transpileExpression("MalExpr_compile_test",
+            "metric.sum(['host']).service(['svc'], Layer.GENERAL)");
+        transpiler.registerExpression("MalExpr_compile_test", source);
+
+        final File sourceDir = tempDir.resolve("src").toFile();
+        final File outputDir = tempDir.resolve("classes").toFile();
+
+        try {
+            transpiler.compileAll(sourceDir, outputDir, 
System.getProperty("java.class.path"));
+
+            final String classPath = 
MalToJavaTranspiler.GENERATED_PACKAGE.replace('.', File.separatorChar)
+                + File.separator + "MalExpr_compile_test.class";
+            assertTrue(new File(outputDir, classPath).exists(),
+                "Compiled .class file should exist");
+        } catch (RuntimeException e) {
+            if (e.getMessage().contains("compilation failed")) {
+                final String pkgPath = 
MalToJavaTranspiler.GENERATED_PACKAGE.replace('.', File.separatorChar);
+                final File javaFile = new File(sourceDir, pkgPath + 
"/MalExpr_compile_test.java");
+                assertTrue(javaFile.exists(), "Source .java file should be 
written");
+                final String written = Files.readString(javaFile.toPath());
+                assertTrue(written.contains("implements MalExpression"),
+                    "Written source should implement MalExpression");
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    @Test
+    void expressionManifest(@TempDir Path tempDir) throws Exception {
+        transpiler.registerExpression("MalExpr_a",
+            transpiler.transpileExpression("MalExpr_a", "metric_a"));
+        transpiler.registerExpression("MalExpr_b",
+            transpiler.transpileExpression("MalExpr_b", "metric_b"));
+
+        final File outputDir = tempDir.toFile();
+        transpiler.writeExpressionManifest(outputDir);
+
+        final File manifest = new File(outputDir, 
"META-INF/mal-expressions.txt");
+        assertTrue(manifest.exists(), "Manifest file should exist");
+
+        final List<String> lines = Files.readAllLines(manifest.toPath());
+        assertTrue(lines.contains(MalToJavaTranspiler.GENERATED_PACKAGE + 
".MalExpr_a"),
+            "Should contain MalExpr_a FQCN");
+        assertTrue(lines.contains(MalToJavaTranspiler.GENERATED_PACKAGE + 
".MalExpr_b"),
+            "Should contain MalExpr_b FQCN");
+    }
+
+    @Test
+    void filterManifest(@TempDir Path tempDir) throws Exception {
+        final String literal = "{ tags -> tags.job == 'x' }";
+        transpiler.registerFilter("MalFilter_0", literal,
+            transpiler.transpileFilter("MalFilter_0", literal));
+
+        final File outputDir = tempDir.toFile();
+        transpiler.writeFilterManifest(outputDir);
+
+        final File manifest = new File(outputDir, 
"META-INF/mal-filter-expressions.properties");
+        assertTrue(manifest.exists(), "Filter manifest should exist");
+
+        final String content = Files.readString(manifest.toPath());
+        assertTrue(content.contains(MalToJavaTranspiler.GENERATED_PACKAGE + 
".MalFilter_0"),
+            "Should contain MalFilter_0 FQCN");
+    }
+}
diff --git a/oap-server/analyzer/pom.xml b/oap-server/analyzer/pom.xml
index 9dca94257f..4039928a50 100644
--- a/oap-server/analyzer/pom.xml
+++ b/oap-server/analyzer/pom.xml
@@ -33,6 +33,7 @@
         <module>log-analyzer</module>
         <module>meter-analyzer</module>
         <module>event-analyzer</module>
+        <module>mal-transpiler</module>
     </modules>
 
     <dependencies>


Reply via email to