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 f4127038b610a02cd31a5d3b6deef3071b51ac21
Author: Wu Sheng <[email protected]>
AuthorDate: Sun Mar 1 10:24:42 2026 +0800

    Replace groovy-replacement-plan.md with dsl-compiler-design.md covering all 
four DSL compilers (OAL, MAL, LAL, Hierarchy). Remove Groovy references from 
docs: LAL code blocks, hierarchy matching rule labels, and stale MeterProcessor 
comment.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 docs/en/academy/dsl-compiler-design.md             |  157 +++
 docs/en/academy/groovy-replacement-plan.md         | 1025 --------------------
 docs/en/concepts-and-designs/lal.md                |   34 +-
 .../service-hierarchy-configuration.md             |    2 +-
 docs/en/concepts-and-designs/service-hierarchy.md  |   46 +-
 docs/menu.yml                                      |    2 +
 .../provider/meter/process/MeterProcessor.java     |    2 +-
 7 files changed, 200 insertions(+), 1068 deletions(-)

diff --git a/docs/en/academy/dsl-compiler-design.md 
b/docs/en/academy/dsl-compiler-design.md
new file mode 100644
index 0000000000..ce14082e20
--- /dev/null
+++ b/docs/en/academy/dsl-compiler-design.md
@@ -0,0 +1,157 @@
+# DSL Compiler Design: ANTLR4 + Javassist
+
+## Overview
+
+SkyWalking OAP server uses four domain-specific languages (DSLs) for telemetry 
analysis.
+All four share the same compilation tech stack: **ANTLR4** for grammar parsing 
and **Javassist** for
+runtime bytecode generation.
+
+| DSL | Purpose | Input | Generated Output |
+|-----|---------|-------|-----------------|
+| **OAL** (Observability Analysis Language) | Trace/mesh metrics aggregation | 
`.oal` script files | Metrics classes, builders, dispatchers |
+| **MAL** (Meter Analysis Language) | Meter/metrics expression evaluation | 
YAML config `exp` fields | `MalExpression` implementations |
+| **LAL** (Log Analysis Language) | Log processing pipelines | YAML config 
`filter` blocks | `LalExpression` implementations |
+| **Hierarchy Matching Rules** | Service hierarchy relationship matching | 
YAML config expressions | `BiFunction<Service, Service, Boolean>` 
implementations |
+
+## Compilation Pipeline
+
+All four DSLs follow the same three-phase compilation pipeline at OAP startup:
+
+```
+DSL string (from .oal script or YAML config)
+    |
+    v
+Phase 1: ANTLR4 Parsing
+    Lexer + Parser (generated from .g4 grammars at build time)
+    → Immutable AST model
+    |
+    v
+Phase 2: Java Source Generation
+    Walk AST model, emit Java source code as strings
+    |
+    v
+Phase 3: Javassist Bytecode Generation
+    ClassPool.makeClass() → CtClass → addMethod(source) → toClass()
+    → Ready-to-use class instance loaded into JVM
+```
+
+### What Each DSL Generates
+
+| DSL | Interface / Base Class | Key Method |
+|-----|----------------------|------------|
+| OAL | Extends metrics function class (e.g., `LongAvgMetrics`) | `id()`, 
`serialize()`, `deserialize()`, plus dispatcher `dispatch(source)` |
+| MAL metric | `MalExpression` | `SampleFamily run(Map<String, SampleFamily> 
samples)` |
+| MAL filter | `Predicate<Map<String, String>>` | `boolean test(Map<String, 
String> tags)` |
+| LAL | `LalExpression` | `void execute(Object filterSpec, Object binding)` |
+| Hierarchy | `BiFunction<Service, Service, Boolean>` | `Boolean apply(Service 
upper, Service lower)` |
+
+OAL is the most complex -- it generates **three classes per metric** (metrics 
class with storage annotations,
+metrics builder for serialization, and source dispatcher for routing), whereas 
MAL/LAL/Hierarchy each generate
+a single functional class per expression.
+
+## ANTLR4 Grammars
+
+Each DSL has its own ANTLR4 lexer and parser grammar. The Maven ANTLR4 plugin 
generates Java lexer/parser
+classes at build time; these are then used at runtime to parse DSL strings.
+
+| DSL | Grammar Location |
+|-----|-----------------|
+| OAL | `oap-server/oal-grammar/src/main/antlr4/.../OALLexer.g4`, 
`OALParser.g4` |
+| MAL | `oap-server/analyzer/meter-analyzer/src/main/antlr4/.../MALLexer.g4`, 
`MALParser.g4` |
+| LAL | `oap-server/analyzer/log-analyzer/src/main/antlr4/.../LALLexer.g4`, 
`LALParser.g4` |
+| Hierarchy | 
`oap-server/analyzer/hierarchy/src/main/antlr4/.../HierarchyRuleLexer.g4`, 
`HierarchyRuleParser.g4` |
+
+## Javassist Constraints
+
+Javassist compiles Java source strings into bytecode but has limitations that 
shape the code generation:
+
+- **No anonymous inner classes or lambdas** -- Callback-based APIs (e.g., 
LAL's `filterSpec.extractor(Consumer)`)
+  require pre-compiling each callback as a separate `CtClass` implementing the 
needed interface.
+- **No generics in method bodies** -- Generated source uses raw types with 
explicit casts.
+- **Class loading anchor** -- Each DSL uses a `PackageHolder` marker class so 
that
+  `ctClass.toClass(PackageHolder.class)` loads the generated class into the 
correct module/package
+  (required for JDK 9+ module system).
+
+OAL additionally uses **FreeMarker templates** to generate method bodies for 
metrics classes, builders, and
+dispatchers, since these classes are more complex and benefit from 
template-driven generation.
+
+## Module Structure
+
+```
+oap-server/
+  oal-grammar/            # OAL: ANTLR4 grammar
+  oal-rt/                 # OAL: compiler + runtime (Javassist + FreeMarker)
+  analyzer/
+    meter-analyzer/       # MAL: grammar + compiler + runtime
+    log-analyzer/         # LAL: grammar + compiler + runtime
+    hierarchy/            # Hierarchy: grammar + compiler + runtime
+    agent-analyzer/       # Calls MAL compiler for meter data
+```
+
+OAL keeps grammar and runtime in separate modules (`oal-grammar` and `oal-rt`) 
because `server-core`
+depends on the grammar while the runtime implementation depends on 
`server-core` (avoiding circular
+dependency). MAL, LAL, and Hierarchy are each self-contained in a single 
module.
+
+## Groovy Replacement (MAL, LAL, Hierarchy)
+
+Reference: [Discussion 
#13716](https://github.com/apache/skywalking/discussions/13716)
+
+MAL, LAL, and Hierarchy previously used **Groovy** as the runtime scripting 
engine. OAL has always used
+ANTLR4 + Javassist. The Groovy-based DSLs were replaced for the following 
reasons:
+
+1. **Startup cost** -- 1,250+ `GroovyShell.parse()` calls at OAP boot, each 
spinning up the full Groovy
+   compiler pipeline.
+
+2. **Runtime execution overhead** -- MAL expressions execute on every metrics 
ingestion cycle. Per-expression
+   overhead from dynamic Groovy compounds at scale: property resolution 
through 4+ layers of indirection,
+   `ExpandoMetaClass` closure allocation for simple arithmetic, and 
megamorphic call sites that defeat JIT
+   optimization.
+
+3. **Late error detection** -- MAL uses dynamic Groovy; typos in metric names 
or invalid method chains are
+   only discovered when that specific expression runs with real data.
+
+4. **Debugging complexity** -- Stack traces include Groovy MOP internals 
(`CallSite`, `MetaClassImpl`,
+   `ExpandoMetaClass`), obscuring the actual expression logic.
+
+5. **GraalVM incompatibility** -- `invokedynamic` bootstrapping and 
`ExpandoMetaClass` are fundamentally
+   incompatible with ahead-of-time (AOT) compilation, blocking the
+   [GraalVM native-image 
distribution](https://github.com/apache/skywalking-graalvm-distro).
+
+The DSL grammar for users remains **100% unchanged** -- the same expressions 
written in YAML config files
+work exactly as before. Only the internal compilation engine was replaced.
+
+### Verification: Groovy v1 Checker
+
+To ensure the new Java compilers produce identical results to the original 
Groovy implementation,
+a **dual-path comparison test suite** is maintained under 
`test/script-compiler/`:
+
+```
+test/script-compiler/
+  mal-groovy/               # MAL v1: original Groovy-based implementation
+  lal-groovy/               # LAL v1: original Groovy-based implementation
+  hierarchy-groovy/         # Hierarchy v1: original Groovy-based 
implementation
+  mal-lal-v1-v2-checker/    # Runs every MAL/LAL expression through BOTH v1 
and v2, compares results
+  hierarchy-v1-v2-checker/  # Runs every hierarchy rule through BOTH v1 and 
v2, compares results
+```
+
+The checker mechanism:
+
+1. Loads all production YAML config files (the same files used by OAP at 
runtime)
+2. For each DSL expression, compiles with **both** v1 (Groovy) and v2 (ANTLR4 
+ Javassist)
+3. Compares the compiled artifacts:
+   - **MAL**: Compare generated Java source code, extracted metadata (sample 
names, aggregation labels,
+     downsampling type, percentile config), and execution results with sample 
data
+   - **LAL**: Compare compiled expression execution against FilterSpec/Binding 
mocks
+   - **Hierarchy**: Compare `BiFunction` evaluation with test Service pairs
+
+This ensures 100% behavioral parity. The Groovy v1 modules are **test-only 
dependencies** -- they are not
+included in the OAP distribution.
+
+#### Current Checker Results
+
+| Checker | Expressions Tested | Status |
+|---------|-------------------|--------|
+| MAL metric expressions | 1,187 | All pass |
+| MAL filter expressions | 29 | All pass |
+| LAL scripts | 10 | All pass |
+| Hierarchy rules | 22 | All pass |
diff --git a/docs/en/academy/groovy-replacement-plan.md 
b/docs/en/academy/groovy-replacement-plan.md
deleted file mode 100644
index f656a7b94d..0000000000
--- a/docs/en/academy/groovy-replacement-plan.md
+++ /dev/null
@@ -1,1025 +0,0 @@
-# Groovy Replacement Plan: Build-Time Transpiler for MAL, LAL, and Hierarchy 
Scripts
-
-Reference: [Discussion 
#13716](https://github.com/apache/skywalking/discussions/13716)
-Reference Implementation: 
[skywalking-graalvm-distro](https://github.com/apache/skywalking-graalvm-distro)
-
-## 1. Background and Motivation
-
-SkyWalking OAP server currently uses Groovy as the runtime scripting engine 
for three subsystems:
-
-| Subsystem | YAML Files | Expressions | Groovy Pattern |
-|-----------|-----------|-------------|----------------|
-| MAL (Meter Analysis Language) | 71 (11 meter-analyzer-config, 55 otel-rules, 
2 log-mal-rules, 2 envoy-metrics-rules, 1 telegraf-rules) | 1,254 metric + 29 
filter | Dynamic Groovy: `propertyMissing()`, `ExpandoMetaClass` on `Number`, 
closures |
-| LAL (Log Analysis Language) | 8 | 10 rules | `@CompileStatic` Groovy: 
delegation-based closure DSL, safe navigation (`?.`), `as` casts |
-| Hierarchy Matching | 1 (hierarchy-definition.yml) | 4 rules | 
`GroovyShell.evaluate()` for `Closure<Boolean>` |
-
-### Problems with Groovy Runtime
-
-1. **Startup Cost**: 1,250+ `GroovyShell.parse()` calls at OAP boot, each 
spinning up the full Groovy compiler pipeline.
-2. **Runtime Errors Instead of Compile-Time Errors**: MAL uses dynamic Groovy 
-- typos in metric names or invalid method chains are only discovered when that 
specific expression runs with real data.
-3. **Debugging Complexity**: Stack traces include Groovy MOP internals 
(`CallSite`, `MetaClassImpl`, `ExpandoMetaClass`), obscuring the actual 
expression logic.
-4. **Runtime Execution Performance (Most Critical)**: MAL expressions execute 
on every metrics ingestion cycle. Per-expression overhead from dynamic Groovy 
compounds at scale:
-   - Property resolution: `CallSite` -> 
`MetaClassImpl.invokePropertyOrMissing()` -> 
`ExpressionDelegate.propertyMissing()` -> `ThreadLocal<Map>` lookup (4+ layers 
of indirection per metric name lookup)
-   - Method calls: Groovy `CallSite` dispatch with MetaClass lookup and MOP 
interception checks
-   - Arithmetic (`metric * 1000`): `ExpandoMetaClass` closure allocation + 
metaclass lookup + dynamic dispatch for what Java does as a single `imul`
-   - Per ingestion cycle: ~1,250 `propertyMissing()` calls, ~3,750 MOP method 
dispatches, ~29 metaclass arithmetic ops, ~200 closure allocations
-   - JIT cannot optimize Groovy's megamorphic call sites, defeating inlining 
and branch prediction
-5. **GraalVM Incompatibility**: `invokedynamic` bootstrapping and 
`ExpandoMetaClass` are fundamentally incompatible with AOT compilation.
-
-### Goal
-
-Eliminate Groovy from the OAP runtime entirely. Groovy becomes a 
**build-time-only** dependency used solely for AST parsing by the transpiler. 
Zero `GroovyShell`, zero `ExpandoMetaClass`, zero MOP at runtime.
-
----
-
-## 2. Solution Architecture
-
-### Build-Time Transpiler: Groovy DSL -> Pure Java Source Code
-
-```
-BUILD TIME (Maven compile phase):
-  MAL YAML files (71 files, 1,250+ expressions)
-  LAL YAML files (8 files, 10 scripts)
-          |
-          v
-  MalToJavaTranspiler / LalToJavaTranspiler
-  (Groovy CompilationUnit at CONVERSION phase -- AST parsing only, no 
execution)
-          |
-          v
-  ~1,254 MalExpr_*.java + ~6 LalExpr_*.java + MalFilter_*.java
-          |
-          v
-  javax.tools.JavaCompiler -> .class files on classpath
-  META-INF/mal-expressions.txt (manifest)
-  META-INF/mal-filter-expressions.properties (manifest)
-  META-INF/lal-expressions.txt (manifest)
-
-RUNTIME (OAP Server):
-  Class.forName(className) -> MalExpression / LalExpression instance
-  Zero Groovy. Zero GroovyShell. Zero ExpandoMetaClass.
-```
-
-The transpiler approach is already fully implemented and validated in the 
[skywalking-graalvm-distro](https://github.com/apache/skywalking-graalvm-distro)
 repository.
-
----
-
-## 3. Detailed Design
-
-### 3.1 New Functional Interfaces
-
-Three core interfaces replace Groovy's `DelegatingScript` and `Closure`:
-
-```java
-// MAL: replaces DelegatingScript + ExpandoMetaClass + 
ExpressionDelegate.propertyMissing()
-@FunctionalInterface
-public interface MalExpression {
-    SampleFamily run(Map<String, SampleFamily> samples);
-}
-
-// MAL: replaces Closure<Boolean> from GroovyShell.evaluate() for filter 
expressions
-@FunctionalInterface
-public interface MalFilter {
-    boolean test(Map<String, String> tags);
-}
-
-// LAL: replaces LALDelegatingScript + @CompileStatic closure DSL
-@FunctionalInterface
-public interface LalExpression {
-    void execute(FilterSpec filterSpec, Binding binding);
-}
-```
-
-### 3.2 SampleFamily: Closure -> Functional Interface
-
-Five `SampleFamily` methods currently accept `groovy.lang.Closure`. Each gets 
a new overload with a Java functional interface. During transition both 
overloads coexist; eventually the Closure overloads are removed.
-
-| Method | Current (Groovy) | New (Java) | Functional Interface |
-|--------|-----------------|------------|---------------------|
-| `tag()` | `Closure<?>` with `tags.key = val` | `TagFunction` | 
`Function<Map<String,String>, Map<String,String>>` |
-| `filter()` | `Closure<Boolean>` with `tags.x == 'y'` | `SampleFilter` | 
`Predicate<Map<String,String>>` |
-| `forEach()` | `Closure<Void>` with `(prefix, tags) -> ...` | 
`ForEachFunction` | `BiConsumer<String, Map<String,String>>` |
-| `decorate()` | `Closure<Void>` with `entity -> ...` | `DecorateFunction` | 
`Consumer<MeterEntity>` |
-| `instance(..., closure)` | `Closure<?>` with `tags -> Map.of(...)` | 
`PropertiesExtractor` | `Function<Map<String,String>, Map<String,String>>` |
-
-Source location in upstream: 
`oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java`
-
-### 3.3 MAL Transpiler: AST Mapping Rules
-
-The `MalToJavaTranspiler` (~1,230 lines) parses each MAL expression string 
into a Groovy AST at `Phases.CONVERSION` (no code execution), then walks the 
AST to emit equivalent Java code.
-
-#### Expression Mappings
-
-| Groovy Construct | Java Output | Notes |
-|-----------------|-------------|-------|
-| `metric_name` (bare property) | `samples.getOrDefault("metric_name", 
SampleFamily.EMPTY)` | Replaces `propertyMissing()` dispatch |
-| `.sum(['a','b'])` | `.sum(List.of("a", "b"))` | Direct method call |
-| `.tagEqual('resource', 'cpu')` | `.tagEqual("resource", "cpu")` | Direct 
method call |
-| `100 * metric` | `metric.multiply(100)` | Commutative: operands swapped |
-| `100 - metric` | `metric.minus(100).negative()` | Non-commutative: negate |
-| `100 / metric` | `metric.newValue(v -> 100 / v)` | Non-commutative: newValue 
|
-| `metricA / metricB` | `metricA.div(metricB)` | SampleFamily-SampleFamily op |
-| `.tag({tags -> tags.cluster = ...})` | `.tag(tags -> { tags.put("cluster", 
...); return tags; })` | Closure -> lambda |
-| `.filter({tags -> tags.job_name in [...]})` | `.filter(tags -> 
"...".equals(tags.get("job_name")))` | Closure -> predicate |
-| `.forEach(['a','b'], {p, tags -> ...})` | `.forEach(List.of("a","b"), (p, 
tags) -> { ... })` | Closure -> BiConsumer |
-| `.decorate({entity -> ...})` | `.decorate(entity -> { ... })` | Closure -> 
Consumer |
-| `.instance(..., {tags -> Map.of(...)})` | `.instance(..., tags -> 
Map.of(...))` | Closure -> Function |
-| `Layer.K8S` | `Layer.K8S` | Enum constant, passed through |
-| `time()` | `Instant.now().getEpochSecond()` | Direct Java API |
-| `AVG`, `SUM`, etc. | `DownsamplingType.AVG`, etc. | Enum constant reference |
-
-#### Closure Body Translation
-
-Inside closures, Groovy property-style access is mapped to explicit `Map` 
operations:
-
-| Groovy Closure Pattern | Java Lambda Output |
-|----------------------|-------------------|
-| `tags.key = "value"` | `tags.put("key", "value")` |
-| `tags.key` (read) | `tags.get("key")` |
-| `tags.remove("key")` | `tags.remove("key")` |
-| `tags.key == "value"` | `"value".equals(tags.get("key"))` |
-| `tags.key != "value"` | `!"value".equals(tags.get("key"))` |
-| `tags.key in ["a","b"]` | `List.of("a","b").contains(tags.get("key"))` |
-| `if/else` in closure | `if/else` in lambda |
-| `entity.serviceId = val` | `entity.setServiceId(val)` |
-
-#### Filter Expression Mappings
-
-Filter expressions (`filter: "{ tags -> ... }"`) generate `MalFilter` 
implementations:
-
-| Groovy Filter | Java Output |
-|--------------|-------------|
-| `tags.job_name == 'mysql'` | `"mysql".equals(tags.get("job_name"))` |
-| `tags.job_name != 'test'` | `!"test".equals(tags.get("job_name"))` |
-| `tags.job_name in ['a','b']` | 
`List.of("a","b").contains(tags.get("job_name"))` |
-| `cond1 && cond2` | `cond1 && cond2` |
-| `cond1 \|\| cond2` | `cond1 \|\| cond2` |
-| `!cond` | `!cond` |
-| `tags.job_name` (truthiness) | `tags.get("job_name") != null` |
-
-### 3.4 LAL Transpiler: AST Mapping Rules
-
-The `LalToJavaTranspiler` (~950 lines) handles LAL's `@CompileStatic` 
delegation-based DSL. LAL scripts have a fundamentally different structure from 
MAL -- they are statement-based builder patterns rather than expression-based 
computations.
-
-#### Statement Mappings
-
-| Groovy Construct | Java Output |
-|-----------------|-------------|
-| `filter { ... }` | Body unwrapped, emitted directly on `filterSpec` |
-| `json {}` | `filterSpec.json()` |
-| `json { abortOnFailure false }` | `filterSpec.json(jp -> { 
jp.abortOnFailure(false); })` |
-| `text { regexp /pattern/ }` | `filterSpec.text(tp -> { tp.regexp("pattern"); 
})` |
-| `yaml {}` | `filterSpec.yaml()` |
-| `extractor { ... }` | `filterSpec.extractor(ext -> { ... })` |
-| `sink { ... }` | `filterSpec.sink(s -> { ... })` |
-| `abort {}` | `filterSpec.abort()` |
-| `service parsed.service as String` | 
`ext.service(String.valueOf(getAt(binding.parsed(), "service")))` |
-| `layer parsed.layer as String` | 
`ext.layer(String.valueOf(getAt(binding.parsed(), "layer")))` |
-| `tag(key: val)` | `ext.tag(Map.of("key", val))` |
-| `timestamp parsed.time as String` | 
`ext.timestamp(String.valueOf(getAt(binding.parsed(), "time")))` |
-
-#### Property Access and Safe Navigation
-
-| Groovy Pattern | Java Output |
-|---------------|-------------|
-| `parsed.field` | `getAt(binding.parsed(), "field")` |
-| `parsed.field.nested` | `getAt(getAt(binding.parsed(), "field"), "nested")` |
-| `parsed?.field?.nested` | `((__v0 = binding.parsed()) == null ? null : 
((__v1 = getAt(__v0, "field")) == null ? null : getAt(__v1, "nested")))` |
-| `log.tags` | `binding.log().getTags()` |
-
-#### Cast and Type Handling
-
-| Groovy Pattern | Java Output |
-|---------------|-------------|
-| `expr as String` | `String.valueOf(expr)` |
-| `expr as Long` | `toLong(expr)` |
-| `expr as Integer` | `toInt(expr)` |
-| `expr as Boolean` | `toBoolean(expr)` |
-| `"${expr}"` (GString) | `"" + expr` |
-
-#### LAL Spec Consumer Overloads
-
-LAL spec classes (`FilterSpec`, `ExtractorSpec`, `SinkSpec`) get additional 
method overloads accepting `java.util.function.Consumer` alongside existing 
Groovy `Closure` parameters:
-
-```java
-// FilterSpec - existing
-public void extractor(Closure<?> cl) { ... }
-// FilterSpec - new overload
-public void extractor(Consumer<ExtractorSpec> consumer) { ... }
-
-// SinkSpec - existing
-public void sampler(Closure<?> cl) { ... }
-// SinkSpec - new overload
-public void sampler(Consumer<SamplerSpec> consumer) { ... }
-```
-
-Methods requiring Consumer overloads: `text()`, `json()`, `yaml()`, 
`extractor()`, `sink()`, `slowSql()`, `sampledTrace()`, `metrics()`, 
`sampler()`, `enforcer()`, `dropper()`.
-
-#### SHA-256 Deduplication
-
-LAL manifest is keyed by SHA-256 hash of the DSL content. Identical scripts 
across different YAML files share one compiled class. In practice, 10 LAL rules 
map to 6 unique classes.
-
-### 3.5 Hierarchy Script: v1/v2 Module Split
-
-The hierarchy matching rules in `hierarchy-definition.yml` use 
`GroovyShell.evaluate()` to compile 4 Groovy closures at runtime. Unlike 
MAL/LAL, hierarchy does not need a transpiler (only 4 rules, finite set), but 
it follows the same v1/v2/checker module pattern for consistency and to remove 
Groovy from `server-core`.
-
-#### Current State (server-core, Groovy-coupled)
-
-`HierarchyDefinitionService.java` lives in `server-core` and is registered as 
a `Service` in `CoreModule`. Its inner class `MatchingRule` holds a Groovy 
`Closure<Boolean>`:
-
-```java
-// server-core/...config/HierarchyDefinitionService.java (current)
-public static class MatchingRule {
-    private final String name;
-    private final String expression;
-    private final Closure<Boolean> closure;       // groovy.lang.Closure
-
-    public MatchingRule(final String name, final String expression) {
-        GroovyShell sh = new GroovyShell();
-        closure = (Closure<Boolean>) sh.evaluate(expression);  // Groovy at 
runtime
-    }
-}
-```
-
-This `MatchingRule` is referenced by three classes in `server-core`:
-- `HierarchyDefinitionService` -- builds the rule map from YAML
-- `HierarchyService` -- calls `matchingRule.getClosure().call(service, 
comparedService)` for auto-matching
-- `HierarchyQueryService` -- reads the hierarchy definition map
-
-#### Step 1: Make server-core Groovy-Free
-
-Refactor `MatchingRule` in `server-core` to use a Java functional interface 
instead of `Closure<Boolean>`:
-
-```java
-// server-core/...config/HierarchyDefinitionService.java (refactored)
-public static class MatchingRule {
-    private final String name;
-    private final String expression;
-    private final BiFunction<Service, Service, Boolean> matcher;  // pure Java
-
-    public MatchingRule(final String name, final String expression,
-                        final BiFunction<Service, Service, Boolean> matcher) {
-        this.name = name;
-        this.expression = expression;
-        this.matcher = matcher;
-    }
-
-    public boolean match(Service upper, Service lower) {
-        return matcher.apply(upper, lower);
-    }
-}
-```
-
-`HierarchyDefinitionService.init()` no longer compiles Groovy expressions 
itself. Instead, it receives a `Map<String, BiFunction<Service, Service, 
Boolean>>` (the rule registry) from outside -- injected by whichever 
implementation module (v1 or v2) is active.
-
-`HierarchyService` changes from `matchingRule.getClosure().call(u, l)` to 
`matchingRule.match(u, l)`.
-
-Remove all `groovy.lang.*` imports from `server-core`.
-
-#### Step 2: hierarchy-v1 (Groovy-based, for checker only)
-
-```java
-// analyzer/hierarchy-v1/.../GroovyHierarchyRuleProvider.java
-public class GroovyHierarchyRuleProvider {
-    public static Map<String, BiFunction<Service, Service, Boolean>> 
buildRules(
-            Map<String, String> ruleExpressions) {
-        Map<String, BiFunction<Service, Service, Boolean>> rules = new 
HashMap<>();
-        GroovyShell sh = new GroovyShell();
-        ruleExpressions.forEach((name, expression) -> {
-            Closure<Boolean> closure = (Closure<Boolean>) 
sh.evaluate(expression);
-            rules.put(name, (u, l) -> closure.call(u, l));
-        });
-        return rules;
-    }
-}
-```
-
-This module depends on Groovy and wraps the original `GroovyShell.evaluate()` 
logic. It is NOT included in the runtime classpath -- only used by the checker.
-
-#### Step 3: hierarchy-v2 (Pure Java, for runtime)
-
-```java
-// analyzer/hierarchy-v2/.../JavaHierarchyRuleProvider.java
-public class JavaHierarchyRuleProvider {
-    private static final Map<String, BiFunction<Service, Service, Boolean>> 
RULE_REGISTRY;
-    static {
-        RULE_REGISTRY = new HashMap<>();
-        RULE_REGISTRY.put("name",
-            (u, l) -> Objects.equals(u.getName(), l.getName()));
-        RULE_REGISTRY.put("short-name",
-            (u, l) -> Objects.equals(u.getShortName(), l.getShortName()));
-        RULE_REGISTRY.put("lower-short-name-remove-ns", (u, l) -> {
-            String sn = l.getShortName();
-            int dot = sn.lastIndexOf('.');
-            return dot > 0 && Objects.equals(u.getShortName(), sn.substring(0, 
dot));
-        });
-        RULE_REGISTRY.put("lower-short-name-with-fqdn", (u, l) -> {
-            String sn = u.getShortName();
-            int colon = sn.lastIndexOf(':');
-            return colon > 0 && Objects.equals(
-                sn.substring(0, colon),
-                l.getShortName() + ".svc.cluster.local");
-        });
-    }
-
-    public static Map<String, BiFunction<Service, Service, Boolean>> 
buildRules(
-            Map<String, String> ruleExpressions) {
-        Map<String, BiFunction<Service, Service, Boolean>> rules = new 
HashMap<>();
-        ruleExpressions.forEach((name, expression) -> {
-            BiFunction<Service, Service, Boolean> fn = RULE_REGISTRY.get(name);
-            if (fn == null) {
-                throw new IllegalArgumentException(
-                    "Unknown hierarchy matching rule: " + name
-                    + ". Known rules: " + RULE_REGISTRY.keySet());
-            }
-            rules.put(name, fn);
-        });
-        return rules;
-    }
-}
-```
-
-Unknown rule names fail fast at startup with `IllegalArgumentException`. The 
YAML file (`hierarchy-definition.yml`) continues to reference rule names 
(`name`, `short-name`, etc.) -- the Groovy expression strings in 
`auto-matching-rules` become documentation-only at runtime.
-
-#### Step 4: hierarchy-v1-v2-checker
-
-```java
-// analyzer/hierarchy-v1-v2-checker/.../HierarchyRuleComparisonTest.java
-class HierarchyRuleComparisonTest {
-    // Load rule expressions from hierarchy-definition.yml
-    // For each rule:
-    //   Path A: GroovyHierarchyRuleProvider.buildRules() (v1)
-    //   Path B: JavaHierarchyRuleProvider.buildRules() (v2)
-    //   Construct test Service pairs (matching and non-matching cases)
-    //   Assert v1.match(u, l) == v2.match(u, l) for all test pairs
-}
-```
-
-Test cases cover all 4 rules with realistic service name patterns:
-- `name`: exact match and mismatch
-- `short-name`: exact shortName match and mismatch
-- `lower-short-name-remove-ns`: `"svc" == "svc.namespace"` and edge cases (no 
dot, empty)
-- `lower-short-name-with-fqdn`: `"db:3306"` vs `"db.svc.cluster.local"` and 
edge cases (no colon, wrong suffix)
-
----
-
-## 4. Module Structure
-
-### 4.1 Upstream Module Layout
-
-```
-oap-server/
-  server-core/                       # MODIFIED: MatchingRule uses BiFunction 
(no Groovy imports)
-
-  analyzer/
-    meter-analyzer/                  # Modified: add MalExpression, functional 
interfaces
-    log-analyzer/                    # Modified: add LalExpression, Consumer 
overloads
-
-    mal-lal-v1/                      # NEW: Move existing Groovy-based code 
here
-      meter-analyzer-v1/             # Original MAL (GroovyShell + 
ExpandoMetaClass)
-      log-analyzer-v1/               # Original LAL (GroovyShell + 
@CompileStatic)
-
-    mal-lal-v2/                      # NEW: Pure Java transpiler-based 
implementations
-      meter-analyzer-v2/             # MalExpression loader + functional 
interface dispatch
-      log-analyzer-v2/               # LalExpression loader + Consumer dispatch
-      mal-transpiler/                # Build-time: Groovy AST -> Java source 
(MAL)
-      lal-transpiler/                # Build-time: Groovy AST -> Java source 
(LAL)
-
-    mal-lal-v1-v2-checker/           # NEW: Dual-path comparison tests (MAL + 
LAL)
-      73 MAL test classes (1,281 assertions)
-      5 LAL test classes (19 assertions)
-
-    hierarchy-v1/                    # NEW: Groovy-based hierarchy rule 
provider (checker only)
-    hierarchy-v2/                    # NEW: Pure Java hierarchy rule provider 
(runtime)
-    hierarchy-v1-v2-checker/         # NEW: Dual-path comparison tests 
(hierarchy)
-```
-
-### 4.2 Dependency Graph
-
-```
-mal-transpiler ──────────────> groovy (build-time only, for AST parsing)
-lal-transpiler ──────────────> groovy (build-time only, for AST parsing)
-hierarchy-v1 ────────────────> groovy (checker only, not runtime)
-
-meter-analyzer-v2 ──────────> meter-analyzer (interfaces + SampleFamily)
-log-analyzer-v2 ────────────> log-analyzer (interfaces + spec classes)
-hierarchy-v2 ───────────────> server-core (MatchingRule with BiFunction)
-
-mal-lal-v1-v2-checker ──────> mal-lal-v1 (Groovy path)
-mal-lal-v1-v2-checker ──────> mal-lal-v2 (Java path)
-hierarchy-v1-v2-checker ────> hierarchy-v1 (Groovy path)
-hierarchy-v1-v2-checker ────> hierarchy-v2 (Java path)
-
-server-starter ─────────────> meter-analyzer-v2 (runtime, no Groovy)
-server-starter ─────────────> log-analyzer-v2 (runtime, no Groovy)
-server-starter ─────────────> hierarchy-v2 (runtime, no Groovy)
-server-starter ────────────X─> mal-lal-v1 (NOT in runtime)
-server-starter ────────────X─> hierarchy-v1 (NOT in runtime)
-```
-
-### 4.3 Key Design Principle: No Coexistence
-
-v1 (Groovy) and v2 (Java) never coexist in the OAP runtime classpath. The 
`mal-lal-v1` and `hierarchy-v1` modules are only dependencies of their 
respective checker modules for CI validation. The runtime (`server-starter`) 
depends only on v2 modules.
-
----
-
-## 5. Implementation Steps
-
-### Phase 1: Interfaces and SampleFamily Modifications
-
-**Files to modify:**
-
-1. **Create `MalExpression.java`** in `meter-analyzer`
-   - Path: 
`oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java`
-
-2. **Create `MalFilter.java`** in `meter-analyzer`
-   - Path: 
`oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java`
-
-3. **Create functional interfaces** in `meter-analyzer`
-   - `TagFunction extends Function<Map<String,String>, Map<String,String>>`
-   - `SampleFilter extends Predicate<Map<String,String>>`
-   - `ForEachFunction extends BiConsumer<String, Map<String,String>>`
-   - `DecorateFunction extends Consumer<MeterEntity>`
-   - `PropertiesExtractor extends Function<Map<String,String>, 
Map<String,String>>`
-
-4. **Add overloads to `SampleFamily.java`**
-   - Add `tag(TagFunction)` alongside existing `tag(Closure<?>)`
-   - Add `filter(SampleFilter)` alongside existing `filter(Closure<Boolean>)`
-   - Add `forEach(List, ForEachFunction)` alongside existing `forEach(List, 
Closure)`
-   - Add `decorate(DecorateFunction)` alongside existing `decorate(Closure)`
-   - Add `instance(..., PropertiesExtractor)` alongside existing 
`instance(..., Closure)`
-
-5. **Create `LalExpression.java`** in `log-analyzer`
-   - Path: 
`oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/LalExpression.java`
-
-6. **Add Consumer overloads to LAL spec classes**
-   - `FilterSpec`: `text(Consumer)`, `json(Consumer)`, `yaml(Consumer)`, 
`extractor(Consumer)`, `sink(Consumer)`, `filter(Consumer)`
-   - `ExtractorSpec`: `slowSql(Consumer)`, `sampledTrace(Consumer)`, 
`metrics(Consumer)`
-   - `SinkSpec`: `sampler(Consumer)`, `enforcer(Consumer)`, `dropper(Consumer)`
-
-### Phase 2: MAL Transpiler
-
-**New module: `oap-server/analyzer/mal-lal-v2/mal-transpiler/`**
-
-1. **`MalToJavaTranspiler.java`** (~1,230 lines)
-   - Uses `org.codehaus.groovy.control.CompilationUnit` at `Phases.CONVERSION`
-   - Walks AST via recursive visitor pattern
-   - Core methods:
-     - `transpileExpression(String className, String expression)` -> generates 
Java source for `MalExpression`
-     - `transpileFilter(String className, String filterLiteral)` -> generates 
Java source for `MalFilter`
-     - `collectSampleNames(Expression expr)` -> extracts all metric name 
references
-     - `visitExpression(Expression node)` -> recursive Java code emitter
-     - `visitClosureExpression(ClosureExpression node, String contextType)` -> 
closure-to-lambda
-     - `compileAll()` -> batch `javac` compile + manifest generation
-
-2. **Maven integration**: `exec-maven-plugin` during `generate-sources` phase
-   - Reads all MAL YAML files from resources
-   - Generates Java source to `target/generated-sources/mal/`
-   - Compiles to `target/classes/`
-   - Writes `META-INF/mal-expressions.txt` and 
`META-INF/mal-filter-expressions.properties`
-
-### Phase 3: LAL Transpiler
-
-**New module: `oap-server/analyzer/mal-lal-v2/lal-transpiler/`**
-
-1. **`LalToJavaTranspiler.java`** (~950 lines)
-   - Same AST approach as MAL but statement-based emission
-   - Core methods:
-     - `transpile(String className, String dslText)` -> generates Java source 
for `LalExpression`
-     - `emitStatement(Statement node, String receiver, BindingContext ctx)` -> 
statement emitter
-     - `visitConditionExpr(Expression node)` -> boolean expression emitter
-     - `emitPropertyAccess(PropertyExpression node)` -> `getAt()` with null 
safety
-   - SHA-256 deduplication: identical DSL content shares one class
-   - Helper methods in generated class: `getAt()`, `toLong()`, `toInt()`, 
`toBoolean()`, `isTruthy()`, `isNonEmptyString()`
-
-2. **Maven integration**: same `exec-maven-plugin` approach
-   - Writes `META-INF/lal-expressions.txt` (SHA-256 hash -> FQCN)
-
-### Phase 4: Runtime Loading (v2 Modules)
-
-**New module: `oap-server/analyzer/mal-lal-v2/meter-analyzer-v2/`**
-
-1. **Modified `DSL.java`** (MAL runtime):
-   ```java
-   public static Expression parse(String metricName, String expression) {
-       Map<String, String> manifest = 
loadManifest("META-INF/mal-expressions.txt");
-       String className = manifest.get(metricName);
-       MalExpression malExpr = (MalExpression) Class.forName(className)
-           .getDeclaredConstructor().newInstance();
-       return new Expression(metricName, expression, malExpr);
-   }
-   ```
-
-2. **Modified `Expression.java`** (MAL runtime):
-   - Wraps `MalExpression` instead of `DelegatingScript`
-   - `run()` calls `malExpression.run(sampleFamilies)` directly
-   - No `ExpandoMetaClass`, no `ExpressionDelegate`, no `ThreadLocal<Map>`
-
-3. **Modified `FilterExpression.java`** (MAL runtime):
-   - Loads `MalFilter` from `META-INF/mal-filter-expressions.properties`
-   - `filter()` calls `malFilter::test` via `SampleFamily.filter(SampleFilter)`
-
-**New module: `oap-server/analyzer/mal-lal-v2/log-analyzer-v2/`**
-
-4. **Modified `DSL.java`** (LAL runtime):
-   - Computes SHA-256 of DSL text, loads class from 
`META-INF/lal-expressions.txt`
-   - `evaluate()` calls `lalExpression.execute(filterSpec, binding)` directly
-   - No `GroovyShell`, no `LALDelegatingScript`
-
-### Phase 5: Hierarchy v1/v2 Module Split
-
-**Step 5a: Refactor `server-core` to remove Groovy**
-
-**File to modify:** 
`oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/config/HierarchyDefinitionService.java`
-
-1. Change `MatchingRule.closure` from `Closure<Boolean>` to 
`BiFunction<Service, Service, Boolean> matcher`
-2. Add constructor that accepts the `BiFunction` matcher directly
-3. Replace `getClosure()` with `match(Service upper, Service lower)` method
-4. Change `init()` to accept a rule registry (`Map<String, BiFunction<Service, 
Service, Boolean>>`) from outside instead of calling `GroovyShell.evaluate()` 
internally
-5. Remove all `groovy.lang.*` imports
-
-**File to modify:** 
`oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/hierarchy/HierarchyService.java`
-
-6. Change `matchingRule.getClosure().call(service, comparedService)` (lines 
201-203, 220-222) to `matchingRule.match(service, comparedService)`
-
-**Step 5b: Create hierarchy-v1 module**
-
-**New module:** `oap-server/analyzer/hierarchy-v1/`
-
-1. `GroovyHierarchyRuleProvider.java`: wraps original `GroovyShell.evaluate()` 
logic
-2. Takes `Map<String, String>` (rule name -> Groovy expression) from YAML
-3. Returns `Map<String, BiFunction<Service, Service, Boolean>>` by evaluating 
closures
-4. Depends on Groovy -- NOT included in runtime, only used by checker
-
-**Step 5c: Create hierarchy-v2 module**
-
-**New module:** `oap-server/analyzer/hierarchy-v2/`
-
-1. `JavaHierarchyRuleProvider.java`: static `RULE_REGISTRY` with 4 Java lambdas
-2. Takes `Map<String, String>` (rule name -> Groovy expression) from YAML 
(expression ignored, only name used for registry lookup)
-3. Returns `Map<String, BiFunction<Service, Service, Boolean>>` from the 
registry
-4. Fails fast with `IllegalArgumentException` for unknown rule names
-5. Zero Groovy dependency
-
-### Phase 6: Comparison Test Suites
-
-**New module: `oap-server/analyzer/mal-lal-v1-v2-checker/`**
-
-1. **MAL comparison tests** (73 test classes):
-   - Base class `MALScriptComparisonBase` runs dual-path comparison:
-     - Path A: Fresh Groovy compilation with upstream `CompilerConfiguration`
-     - Path B: Load transpiled `MalExpression` from manifest
-   - Both receive identical `Map<String, SampleFamily>` input
-   - Compare: `ExpressionParsingContext` (scope, function, datatype, 
downsampling), `SampleFamily` result (values, labels, entity descriptions)
-   - JUnit 5 `@TestFactory` generates `DynamicTest` per metric rule
-   - Test data must be non-trivial to prevent vacuous agreement (both 
returning empty/null)
-
-2. **LAL comparison tests** (5 test classes):
-   - Base class `LALScriptComparisonBase` runs dual-path comparison:
-     - Path A: Groovy with `@CompileStatic` + `LALPrecompiledExtension`
-     - Path B: Load `LalExpression` from manifest via SHA-256
-   - Compare: `shouldAbort()`, `shouldSave()`, LogData.Builder state, metrics 
container, `databaseSlowStatement`, `sampledTraceBuilder`
-
-3. **Test statistics**: 1,281 MAL assertions + 19 LAL assertions = 1,300 total
-
-**New module: `oap-server/analyzer/hierarchy-v1-v2-checker/`**
-
-4. **Hierarchy comparison tests**:
-   - Load rule expressions from `hierarchy-definition.yml`
-   - For each of the 4 rules:
-     - Path A: `GroovyHierarchyRuleProvider.buildRules()` (v1, Groovy closures)
-     - Path B: `JavaHierarchyRuleProvider.buildRules()` (v2, Java lambdas)
-   - Construct test `Service` pairs covering matching and non-matching cases:
-     - `name`: exact match `("svc", "svc")` -> true, `("svc", "other")` -> 
false
-     - `short-name`: shortName match/mismatch
-     - `lower-short-name-remove-ns`: `"svc"` vs `"svc.namespace"` -> true, no 
dot -> false, empty -> false
-     - `lower-short-name-with-fqdn`: `"db:3306"` vs `"db.svc.cluster.local"` 
-> true, no colon -> false, wrong suffix -> false
-   - Assert `v1.match(u, l) == v2.match(u, l)` for all test pairs
-
-### Phase 7: Cleanup and Dependency Removal
-
-1. **Move v1 code to `mal-lal-v1/` and `hierarchy-v1/`** (or mark as 
`<scope>test</scope>`)
-2. **Remove Groovy from runtime classpath**: `groovy-5.0.3.jar` (~7 MB) 
becomes test-only
-3. **Remove from `server-starter` dependencies**: replace v1 with v2 module 
references for MAL, LAL, and hierarchy
-4. **Remove `NumberClosure.java`**: no longer needed without `ExpandoMetaClass`
-5. **Remove `ExpressionDelegate.propertyMissing()`**: replaced by 
`samples.getOrDefault()`
-6. **Remove Groovy closure overloads from `SampleFamily`** (after v1 is fully 
deprecated)
-7. **Remove `LALDelegatingScript.java`**: replaced by `LalExpression` interface
-8. **Verify `server-core` has zero Groovy imports**: 
`HierarchyDefinitionService` and `HierarchyService` now use `BiFunction` only
-
-### Phase 8: Replace v2 Manifest Loading with Real Compilers (ANTLR4 + 
Javassist)
-
-Phase 7 completed: v2 modules are standalone (zero Groovy), v1 depends on v2. 
Currently, v2 loads transpiled classes via manifest files 
(`META-INF/mal-expressions.txt`, `META-INF/lal-expressions.txt`) that were 
pre-compiled at build time. This prevents on-demand config changes since 
MAL/LAL/hierarchy configs are in the final package and users may want to modify 
them.
-
-The goal is to replace this manifest-based approach with **real compilers** 
following the OAL pattern: ANTLR4 grammar -> parser -> model -> Javassist class 
generation -> listener notification. This enables runtime compilation when 
configs change.
-
-#### Module Renaming: Drop `-v2` Suffix
-
-Since v1 modules move to `test/script-compiler/`, the v2 modules become the 
primary ones and lose the `-v2` suffix:
-- `meter-analyzer-v2` -> `meter-analyzer` (package stays 
`o.a.s.oap.meter.analyzer`)
-- `log-analyzer-v2` -> `log-analyzer` (package stays `o.a.s.oap.log.analyzer`)
-- `hierarchy-v2` -> `hierarchy` (no v1 name conflict since v1 moves out)
-- `.v2.` sub-packages (`dsl.v2.DSL`, `dsl.v2.Binding`, etc.) merge back into 
parent packages (`dsl.DSL`, `dsl.Binding`)
-
-#### Target Module Structure
-
-```
-oap-server/
-  mal-grammar/                   NEW — ANTLR4 grammar for MAL expressions
-  lal-grammar/                   NEW — ANTLR4 grammar for LAL scripts
-  hierarchy-rule-grammar/        NEW — ANTLR4 grammar for hierarchy matching 
rules
-
-oap-server/analyzer/
-  agent-analyzer/                (stays)
-  event-analyzer/                (stays)
-  meter-analyzer/                (renamed from meter-analyzer-v2, runtime MAL, 
calls mal-compiler)
-  log-analyzer/                  (renamed from log-analyzer-v2, runtime LAL, 
calls lal-compiler)
-  hierarchy/                     (renamed from hierarchy-v2, calls 
hierarchy-rule-compiler)
-  mal-compiler/                  NEW — MAL expression compiler engine
-  lal-compiler/                  NEW — LAL script compiler engine
-  hierarchy-rule-compiler/       NEW — hierarchy rule compiler engine
-
-test/script-compiler/            NEW — aggregator for v1/transpiler/checker 
(not in dist)
-  mal-groovy/                    <- meter-analyzer v1 (Groovy)
-  lal-groovy/                    <- log-analyzer v1 (Groovy)
-  hierarchy-groovy/              <- hierarchy-v1 (Groovy)
-  mal-transpiler/                <- mal-transpiler
-  lal-transpiler/                <- lal-transpiler
-  mal-lal-v1-v2-checker/         <- mal-lal-v1-v2-checker
-  hierarchy-v1-v2-checker/       <- hierarchy-v1-v2-checker
-```
-
-#### Generated Class Grouping by Config File Name
-
-MAL metrics come from YAML config files (e.g., `oap.yaml`, 
`spring-micrometer.yaml`). Each file contains multiple `metricsRules`. The 
compiler groups generated classes by source file name.
-
-- **MAL Compiler API**: `MALCompilerEngine.compile(configFileName, 
MetricRuleConfig)` -> grouped by file
-- **Generated class naming**: `rt.<configFile>.MalExpr_<metricName>`, e.g., 
`rt.oap.MalExpr_instance_jvm_cpu`
-- **LAL Compiler API**: `LALCompilerEngine.compile(configFileName, 
List<LALConfig>)` -> grouped by file
-- **Generated class naming**: `rt.<configFile>.LalExpr_<ruleName>`
-
-#### Eliminate `ExpressionParsingContext` ThreadLocal (MAL only)
-
-The current MAL `run()` method serves dual purposes controlled by a 
ThreadLocal:
-1. **Parse phase** (startup): `ExpressionParsingContext` ThreadLocal is set, 
`run()` is called with an empty map to discover which metric names the 
expression references
-2. **Runtime phase** (every ingestion cycle): ThreadLocal is not set, `run()` 
computes the actual result
-
-This is eliminated by extracting metadata statically. The `MalExpression` 
interface gains a `metadata()` method:
-
-```java
-public interface MalExpression {
-    /** Pure computation. No side effects. */
-    SampleFamily run(Map<String, SampleFamily> samples);
-
-    /** Compile-time metadata -- sample names, scope, downsampling, etc. */
-    ExpressionMetadata metadata();
-}
-```
-
-The ANTLR4 compiler extracts all metadata from the parse tree at compile time:
-
-| Metadata | Extracted from |
-|--|--|
-| `sampleNames` | Bare identifiers (metric references) |
-| `scopeType` | Terminal method: `.service()`, `.instance()`, `.endpoint()` |
-| `downsampling` | Aggregation arg: `AVG`, `SUM`, `MAX`, etc. |
-| `percentiles` | `.percentile()` call arguments |
-| `isHistogram` | Presence of `.histogram()` in chain |
-
-Generated class emits metadata as static fields:
-
-```java
-public class MalExpr_instance_jvm_cpu implements MalExpression {
-    private static final ExpressionMetadata METADATA = new ExpressionMetadata(
-        List.of("instance_jvm_cpu"),  // sampleNames
-        ScopeType.SERVICE_INSTANCE,   // from .service()/instance() call
-        DownsamplingType.AVG          // from aggregation
-    );
-
-    @Override
-    public ExpressionMetadata metadata() { return METADATA; }
-
-    @Override
-    public SampleFamily run(Map<String, SampleFamily> samples) {
-        return ((SampleFamily) samples.getOrDefault("instance_jvm_cpu", 
SampleFamily.EMPTY))
-            .sum(List.of("service", "instance"));
-    }
-}
-```
-
-Result: `run()` is pure computation, `metadata()` is static facts, 
`ExpressionParsingContext` and its ThreadLocal are deleted. No dry run with 
empty map at startup.
-
-LAL and hierarchy do **not** have this problem -- LAL passes `Binding` 
explicitly as a parameter, hierarchy rules are stateless lambdas.
-
-#### Implementation Sub-Phases
-
-- 8.1: Rename modules (drop -v2 suffix, flatten .v2. sub-packages)
-- 8.2: Grammar modules (ANTLR4 .g4 files)
-- 8.3: Compiler model + parser (no code gen)
-- 8.4: Javassist code generation (including static `ExpressionMetadata` on 
generated MAL classes)
-- 8.5: Engine integration (wire compilers into renamed modules, delete 
`ExpressionParsingContext`)
-- 8.6: Move v1/transpiler/checker to test/script-compiler/
-- 8.7: Cleanup (remove manifests, verify zero Groovy)
-
----
-
-## 6. What Gets Removed from Runtime
-
-| Component | Current | After |
-|-----------|---------|-------|
-| `GroovyShell.parse()` in MAL `DSL.java` | 1,250+ calls at boot | 
`Class.forName()` from manifest |
-| `GroovyShell.evaluate()` in MAL `FilterExpression.java` | 29 filter 
compilations | `Class.forName()` from manifest |
-| `GroovyShell.parse()` in LAL `DSL.java` | 10 script compilations | 
`Class.forName()` from manifest |
-| `GroovyShell.evaluate()` in `HierarchyDefinitionService` | 4 rule 
compilations | `hierarchy-v2` Java lambda registry |
-| `Closure<Boolean>` in `MatchingRule` | Groovy closure in `server-core` | 
`BiFunction<Service, Service, Boolean>` (Groovy-free `server-core`) |
-| `ExpandoMetaClass` registration in `Expression.empower()` | Runtime 
metaclass on `Number` | Direct `multiply()`/`div()` method calls |
-| `ExpressionDelegate.propertyMissing()` | Dynamic property dispatch | 
`samples.getOrDefault()` |
-| `groovy.lang.Closure` in `SampleFamily` | 5 method signatures | Java 
functional interfaces |
-| `groovy-5.0.3.jar` runtime dependency | ~7 MB on classpath | Removed 
(build-time only) |
-
----
-
-## 7. Transpiler Technical Details
-
-### 7.1 AST Parsing Strategy
-
-Both transpilers use Groovy's `CompilationUnit` at `Phases.CONVERSION`:
-
-```java
-CompilationUnit cu = new CompilationUnit();
-cu.addSource("expression", new StringReaderSource(
-    new StringReader(groovyCode), cu.getConfiguration()));
-cu.compile(Phases.CONVERSION);  // Parse + AST transform, no codegen
-ModuleNode ast = cu.getAST();
-```
-
-This extracts the complete syntax tree without:
-- Generating Groovy bytecode
-- Resolving classes on classpath
-- Activating MOP or MetaClass
-
-The Groovy dependency is therefore **build-time only**.
-
-### 7.2 MAL Arithmetic Operand Swap
-
-The transpiler must replicate the exact behavior of upstream's 
`ExpandoMetaClass` on `Number`. When a `Number` appears on the left side of an 
operator with a `SampleFamily` on the right, the operands must be handled 
carefully:
-
-```
-N + SF  ->  SF.plus(N)                    // commutative, swap operands
-N - SF  ->  SF.minus(N).negative()        // non-commutative: (N - SF) = -(SF 
- N)
-N * SF  ->  SF.multiply(N)               // commutative, swap operands
-N / SF  ->  SF.newValue(v -> N / v)      // non-commutative: per-sample (N / 
sample_value)
-```
-
-The transpiler detects `Number` vs `SampleFamily` operand types by tracking 
whether a sub-expression references sample names (metric properties) or is a 
numeric literal/constant.
-
-### 7.3 Batch Compilation
-
-Generated Java sources are compiled in a single `javac` invocation via 
`javax.tools.JavaCompiler`:
-
-```java
-JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
-List<JavaFileObject> sources = /* all generated .java files */;
-compiler.getTask(null, fileManager, diagnostics, options, null, 
sources).call();
-```
-
-This avoids 1,250+ individual `javac` invocations and provides full cross-file 
type checking.
-
-### 7.4 Manifest Format
-
-**`META-INF/mal-expressions.txt`**:
-```
-metric_name_1=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalExpr_metric_name_1
-metric_name_2=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalExpr_metric_name_2
-...
-```
-
-**`META-INF/mal-filter-expressions.properties`**:
-```
-{ tags -> tags.job_name == 'mysql-monitoring' 
}=org.apache.skywalking.oap.meter.analyzer.dsl.generated.MalFilter_0a1b2c3d
-...
-```
-
-**`META-INF/lal-expressions.txt`**:
-```
-sha256_hash_1=org.apache.skywalking.oap.log.analyzer.dsl.generated.LalExpr_sha256_hash_1
-sha256_hash_2=org.apache.skywalking.oap.log.analyzer.dsl.generated.LalExpr_sha256_hash_2
-...
-```
-
----
-
-## 8. Worked Examples
-
-### 8.1 MAL Expression: K8s Node CPU Capacity
-
-**Groovy (upstream):**
-```groovy
-kube_node_status_capacity.tagEqual('resource', 'cpu')
-    .sum(['node'])
-    .tag({tags -> tags.node_name = tags.node; tags.remove("node")})
-    .service(['node_name'], Layer.K8S)
-```
-
-**Transpiled Java:**
-```java
-public class MalExpr_k8s_node_cpu implements MalExpression {
-    @Override
-    public SampleFamily run(Map<String, SampleFamily> samples) {
-        return samples.getOrDefault("kube_node_status_capacity", 
SampleFamily.EMPTY)
-            .tagEqual("resource", "cpu")
-            .sum(List.of("node"))
-            .tag(tags -> {
-                tags.put("node_name", tags.get("node"));
-                tags.remove("node");
-                return tags;
-            })
-            .service(List.of("node_name"), Layer.K8S);
-    }
-}
-```
-
-### 8.2 MAL Expression: Arithmetic with Number on Left
-
-**Groovy (upstream):**
-```groovy
-100 - container_cpu_usage / container_resource_limit_cpu * 100
-```
-
-**Transpiled Java:**
-```java
-public class MalExpr_cpu_percent implements MalExpression {
-    @Override
-    public SampleFamily run(Map<String, SampleFamily> samples) {
-        return samples.getOrDefault("container_cpu_usage", SampleFamily.EMPTY)
-            .div(samples.getOrDefault("container_resource_limit_cpu", 
SampleFamily.EMPTY))
-            .multiply(100)
-            .minus(100)
-            .negative();
-    }
-}
-```
-
-### 8.3 MAL Filter Expression
-
-**Groovy (upstream):**
-```groovy
-{ tags -> tags.job_name == 'mysql-monitoring' }
-```
-
-**Transpiled Java:**
-```java
-public class MalFilter_mysql implements MalFilter {
-    @Override
-    public boolean test(Map<String, String> tags) {
-        return "mysql-monitoring".equals(tags.get("job_name"));
-    }
-}
-```
-
-### 8.4 LAL Expression: MySQL Slow SQL
-
-**Groovy (upstream):**
-```groovy
-filter {
-    json {}
-    extractor {
-        layer parsed.layer as String
-        service parsed.service as String
-        timestamp parsed.time as String
-        if (tag("LOG_KIND") == "SLOW_SQL") {
-            slowSql {
-                id parsed.id as String
-                statement parsed.statement as String
-                latency parsed.query_time as Long
-            }
-        }
-    }
-    sink {}
-}
-```
-
-**Transpiled Java:**
-```java
-public class LalExpr_mysql_slowsql implements LalExpression {
-
-    @Override
-    public void execute(FilterSpec filterSpec, Binding binding) {
-        filterSpec.json();
-        filterSpec.extractor(ext -> {
-            ext.layer(String.valueOf(getAt(binding.parsed(), "layer")));
-            ext.service(String.valueOf(getAt(binding.parsed(), "service")));
-            ext.timestamp(String.valueOf(getAt(binding.parsed(), "time")));
-            if ("SLOW_SQL".equals(ext.tag("LOG_KIND"))) {
-                ext.slowSql(ss -> {
-                    ss.id(String.valueOf(getAt(binding.parsed(), "id")));
-                    ss.statement(String.valueOf(getAt(binding.parsed(), 
"statement")));
-                    ss.latency(toLong(getAt(binding.parsed(), "query_time")));
-                });
-            }
-        });
-        filterSpec.sink(s -> {});
-    }
-
-    private static Object getAt(Object obj, String key) {
-        if (obj instanceof Binding.Parsed) return ((Binding.Parsed) 
obj).getAt(key);
-        if (obj instanceof Map) return ((Map<?, ?>) obj).get(key);
-        return null;
-    }
-
-    private static long toLong(Object val) {
-        if (val instanceof Number) return ((Number) val).longValue();
-        if (val instanceof String) return Long.parseLong((String) val);
-        return 0L;
-    }
-}
-```
-
-### 8.5 Hierarchy Rule: lower-short-name-remove-ns
-
-**Groovy (upstream, in hierarchy-definition.yml):**
-```groovy
-{ (u, l) -> {
-    if(l.shortName.lastIndexOf('.') > 0)
-        return u.shortName == l.shortName.substring(0, 
l.shortName.lastIndexOf('.'));
-    return false;
-} }
-```
-
-**Java replacement (in HierarchyDefinitionService.java):**
-```java
-RULE_REGISTRY.put("lower-short-name-remove-ns", (u, l) -> {
-    String sn = l.getShortName();
-    int dot = sn.lastIndexOf('.');
-    return dot > 0 && Objects.equals(u.getShortName(), sn.substring(0, dot));
-});
-```
-
----
-
-## 9. Verification Strategy
-
-### 9.1 Dual-Path Comparison Testing
-
-Every generated Java class is validated against the original Groovy behavior 
in CI:
-
-```
-For each MAL YAML file:
-  For each metric rule:
-    1. Compile expression with Groovy (v1 path)
-    2. Load transpiled MalExpression (v2 path)
-    3. Construct realistic sample data (non-trivial to prevent vacuous 
agreement)
-    4. Run both paths with identical input
-    5. Assert identical output:
-       - ExpressionParsingContext (scope, function, datatype, samples, 
downsampling)
-       - SampleFamily result (values, labels, entity descriptions)
-```
-
-### 9.2 Staleness Detection
-
-Properties files record SHA-256 hashes of upstream classes that have same-FQCN 
replacements. If upstream changes a class, the staleness test fails, forcing 
review of the replacement.
-
-### 9.3 Automatic Coverage
-
-New MAL/LAL YAML rules added to `server-starter/src/main/resources/` are 
automatically covered by the transpiler and comparison tests -- if the 
transpiler produces different results from Groovy, the build fails.
-
----
-
-## 10. Statistics
-
-| Metric | Count |
-|--------|-------|
-| MAL YAML files processed | 71 |
-| MAL metric expressions transpiled | 1,254 |
-| MAL filter expressions transpiled | 29 |
-| LAL YAML files processed | 8 |
-| LAL rules transpiled | 10 (6 unique after SHA-256 dedup) |
-| Hierarchy rules replaced | 4 |
-| Total generated Java classes | ~1,289 |
-| Comparison test assertions | 1,300+ (MAL: 1,281, LAL: 19, hierarchy: 4 rules 
x multiple service pairs) |
-| Lines of transpiler code (MAL) | ~1,230 |
-| Lines of transpiler code (LAL) | ~950 |
-| Runtime JAR removed | groovy-5.0.3.jar (~7 MB) |
-
----
-
-## 11. Risk Assessment
-
-| Risk | Mitigation |
-|------|-----------|
-| Transpiler misses an AST pattern | 1,300 dual-path comparison tests catch 
any divergence |
-| New MAL/LAL expression uses unsupported Groovy syntax | Transpiler throws 
clear error at build time; new pattern must be added |
-| Upstream SampleFamily/Spec changes break replacement | Staleness tests 
detect SHA-256 changes |
-| Performance regression | Eliminated dynamic dispatch should only improve 
performance; benchmark with `MetricConvert` pipeline |
-| Custom user MAL/LAL scripts | Users who extend default rules with custom 
Groovy scripts must follow the same syntax subset supported by the transpiler |
-
----
-
-## 12. Migration Timeline
-
-1. **Phase 1**: Add interfaces and functional interface overloads to existing 
`meter-analyzer` and `log-analyzer` (non-breaking, additive changes)
-2. **Phase 2-3**: Implement MAL and LAL transpilers in new `mal-lal-v2/` 
modules
-3. **Phase 4**: Implement v2 runtime loaders (modified `DSL.java`, 
`Expression.java`, `FilterExpression.java`)
-4. **Phase 5**: Hierarchy v1/v2 module split -- refactor `server-core` to 
remove Groovy, create `hierarchy-v1/` (Groovy, checker-only) and 
`hierarchy-v2/` (Java lambdas, runtime)
-5. **Phase 6**: Build comparison test suites -- `mal-lal-v1-v2-checker/` AND 
`hierarchy-v1-v2-checker/`
-6. **Phase 7**: Switch `server-starter` from v1 to v2 for all three subsystems 
(MAL, LAL, hierarchy), remove Groovy from runtime classpath
-7. **Phase 8**: Replace v2 manifest-based class loading with real ANTLR4 + 
Javassist compilers following the OAL pattern, enabling runtime compilation 
when configs change. Rename modules (drop `-v2` suffix), move 
v1/transpiler/checker to `test/script-compiler/`.
diff --git a/docs/en/concepts-and-designs/lal.md 
b/docs/en/concepts-and-designs/lal.md
index f843871cf8..4dff550d3d 100644
--- a/docs/en/concepts-and-designs/lal.md
+++ b/docs/en/concepts-and-designs/lal.md
@@ -31,7 +31,7 @@ are cases where you may want the filter chain to stop earlier 
when specified con
 the remaining filter chain from where it's declared, and all the remaining 
components won't be executed at all.
 `abort` function serves as a fast-fail mechanism in LAL.
 
-```groovy
+```
 filter {
     if (log.service == "TestingService") { // Don't waste resources on 
TestingServices
         abort {} // all remaining components won't be executed at all
@@ -67,7 +67,7 @@ We can add tags like following:
 ]
 ``` 
 And we can use this method to get the value of the tag key `TEST_KEY`.
-```groovy
+```
 filter {
     if (tag("TEST_KEY") == "TEST_VALUE") {
          ...   
@@ -95,7 +95,7 @@ See examples below.
 
 #### `json`
 
-```groovy
+```
 filter {
     json {
         abortOnFailure true // this is optional because it's default behaviour
@@ -105,7 +105,7 @@ filter {
 
 #### `yaml`
 
-```groovy
+```
 filter {
     yaml {
         abortOnFailure true // this is optional because it's default behaviour
@@ -123,7 +123,7 @@ For unstructured logs, there are some `text` parsers for 
use.
 all the captured groups can be used later in the extractors or sinks.
 `regexp` returns a `boolean` indicating whether the log matches the pattern or 
not.
 
-```groovy
+```
 filter {
     text {
         abortOnFailure true // this is optional because it's default behaviour
@@ -181,7 +181,7 @@ dropped) and is used to associate with traces / metrics.
 not dropped) and is used to associate with traces / metrics.
 
 The parameter of `timestamp` can be a millisecond:
-```groovy
+```
 filter {
     // ... parser
 
@@ -191,7 +191,7 @@ filter {
 }
 ```
 or a datetime string with a specified pattern:
-```groovy
+```
 filter {
     // ... parser
 
@@ -210,9 +210,7 @@ not dropped) and is used to associate with service.
 
 `tag` extracts the tags from the `parsed` result, and set them into the 
`LogData`. The form of this extractor should look something like this: `tag 
key1: value, key2: value2`. You may use the properties of `parsed` as both keys 
and values.
 
-```groovy
-import javax.swing.text.LayeredHighlighter
-
+```
 filter {
     // ... parser
 
@@ -242,7 +240,7 @@ log-analyzer:
 
 Examples are as follows:
 
-```groovy
+```
 filter {
     // ...
     extractor {
@@ -338,7 +336,7 @@ dropped) and is used to associate with 
TopNDatabaseStatement.
 
 An example of LAL to distinguish slow logs:
 
-```groovy
+```
 filter {
   json{
   }
@@ -386,7 +384,7 @@ An example of JSON sent to OAP is as following:
 ```
 Examples are as follows:
 
-```groovy
+```
 filter {
     json {
     }
@@ -447,7 +445,7 @@ final sampling result. See examples in 
[Enforcer](#enforcer).
 
 Examples 1, `rateLimit`:
 
-```groovy
+```
 filter {
     // ... parser
 
@@ -469,7 +467,7 @@ filter {
 
 Examples 2, `possibility`:
 
-```groovy
+```
 filter {
     // ... parser
 
@@ -492,7 +490,7 @@ filter {
 Dropper is a special sink, meaning that all logs are dropped without any 
exception. This is useful when you want to
 drop debugging logs.
 
-```groovy
+```
 filter {
     // ... parser
 
@@ -510,7 +508,7 @@ filter {
 
 Or if you have multiple filters, some of which are for extracting metrics, 
only one of them has to be persisted.
 
-```groovy
+```
 filter { // filter A: this is for persistence
     // ... parser
 
@@ -539,7 +537,7 @@ Enforcer is another special sink that forcibly samples the 
log. A typical use ca
 configured a sampler and want to save some logs forcibly, such as to save 
error logs even if the sampling mechanism
 has been configured.
 
-```groovy
+```
 filter {
     // ... parser
 
diff --git a/docs/en/concepts-and-designs/service-hierarchy-configuration.md 
b/docs/en/concepts-and-designs/service-hierarchy-configuration.md
index 6aa0d40d56..7aac0e99fa 100644
--- a/docs/en/concepts-and-designs/service-hierarchy-configuration.md
+++ b/docs/en/concepts-and-designs/service-hierarchy-configuration.md
@@ -69,7 +69,7 @@ layer-levels:
 
 ### Auto Matching Rules
 - The auto matching rules are defined in the `auto-matching-rules` section.
-- Use Groovy script to define the matching rules, the input parameters are the 
upper service(u) and the lower service(l) and the return value is a boolean, 
+- The matching rules are expressions where the input parameters are the upper 
service(u) and the lower service(l) and the return value is a boolean,
 which are used to match the relation between the upper service(u) and the 
lower service(l) on the different layers.
 - The default matching rules required the service name configured as 
SkyWalking default and follow the 
[Showcase](https://github.com/apache/skywalking-showcase).
 If you customized the service name in any layer, you should customize the 
related matching rules according your service name rules.
diff --git a/docs/en/concepts-and-designs/service-hierarchy.md 
b/docs/en/concepts-and-designs/service-hierarchy.md
index 8cd18bccda..5f3c5144fc 100644
--- a/docs/en/concepts-and-designs/service-hierarchy.md
+++ b/docs/en/concepts-and-designs/service-hierarchy.md
@@ -45,7 +45,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### GENERAL On K8S_SERVICE
 - Rule name: `lower-short-name-remove-ns`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName.substring(0, 
l.shortName.lastIndexOf('.')) }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName.substring(0, 
l.shortName.lastIndexOf('.')) }`
 - Description: GENERAL.service.shortName == K8S_SERVICE.service.shortName 
without namespace
 - Matched Example:
   - GENERAL.service.name: `agent::songs`
@@ -53,7 +53,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### GENERAL On APISIX
 - Rule name: `lower-short-name-remove-ns`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName.substring(0, 
l.shortName.lastIndexOf('.')) }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName.substring(0, 
l.shortName.lastIndexOf('.')) }`
 - Description: GENERAL.service.shortName == APISIX.service.shortName without 
namespace
 - Matched Example:
   - GENERAL.service.name: `agent::frontend`
@@ -62,7 +62,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_DATABASE On MYSQL
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_DATABASE.service.shortName remove port == 
MYSQL.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_DATABASE.service.name: 
`mysql.skywalking-showcase.svc.cluster.local:3306`
@@ -70,7 +70,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_DATABASE On POSTGRESQL
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_DATABASE.service.shortName remove port == 
POSTGRESQL.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_DATABASE.service.name: 
`psql.skywalking-showcase.svc.cluster.local:5432`
@@ -78,7 +78,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_DATABASE On CLICKHOUSE
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_DATABASE.service.shortName remove port == 
CLICKHOUSE.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_DATABASE.service.name: 
`clickhouse.skywalking-showcase.svc.cluster.local:8123`
@@ -87,7 +87,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_MQ On ROCKETMQ
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_MQ.service.shortName remove port == 
ROCKETMQ.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_MQ.service.name: 
`rocketmq.skywalking-showcase.svc.cluster.local:9876`
@@ -95,7 +95,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_MQ On RABBITMQ
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_MQ.service.shortName remove port == 
RABBITMQ.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_MQ.service.name: 
`rabbitmq.skywalking-showcase.svc.cluster.local:5672`
@@ -103,7 +103,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
   -
 #### VIRTUAL_MQ On KAFKA
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_MQ.service.shortName remove port == 
KAFKA.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_MQ.service.name: `kafka.skywalking-showcase.svc.cluster.local:9092`
@@ -111,7 +111,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### VIRTUAL_MQ On PULSAR
 - Rule name: `lower-short-name-with-fqdn`
-- Groovy script: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
+- Matching expression: `{ (u, l) -> u.shortName.substring(0, 
u.shortName.lastIndexOf(':')) == l.shortName.concat('.svc.cluster.local') }`
 - Description: VIRTUAL_MQ.service.shortName remove port == 
PULSAR.service.shortName with fqdn suffix
 - Matched Example:
   - VIRTUAL_MQ.service.name: 
`pulsar.skywalking-showcase.svc.cluster.local:6650`
@@ -119,7 +119,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### MESH On MESH_DP
 - Rule name: `name` 
-- Groovy script: `{ (u, l) -> u.name == l.name }`
+- Matching expression: `{ (u, l) -> u.name == l.name }`
 - Description: MESH.service.name == MESH_DP.service.name
 - Matched Example: 
     - MESH.service.name: `mesh-svr::songs.sample-services`
@@ -127,7 +127,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### MESH On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: MESH.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - MESH.service.name: `mesh-svr::songs.sample-services`
@@ -135,7 +135,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### MESH_DP On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: MESH_DP.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - MESH_DP.service.name: `mesh-svr::songs.sample-services`
@@ -143,7 +143,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### MYSQL On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: MYSQL.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - MYSQL.service.name: `mysql::mysql.skywalking-showcase`
@@ -151,7 +151,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### POSTGRESQL On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: POSTGRESQL.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - POSTGRESQL.service.name: `postgresql::psql.skywalking-showcase`
@@ -159,7 +159,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### CLICKHOUSE On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: CLICKHOUSE.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - CLICKHOUSE.service.name: `clickhouse::clickhouse.skywalking-showcase`
@@ -167,7 +167,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### NGINX On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: NGINX.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - NGINX.service.name: `nginx::nginx.skywalking-showcase`
@@ -175,7 +175,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### APISIX On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: APISIX.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example: 
     - APISIX.service.name: `APISIX::frontend.sample-services`
@@ -183,7 +183,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### ROCKETMQ On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: ROCKETMQ.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - ROCKETMQ.service.name: `rocketmq::rocketmq.skywalking-showcase`
@@ -191,7 +191,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### RABBITMQ On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: RABBITMQ.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - RABBITMQ.service.name: `rabbitmq::rabbitmq.skywalking-showcase`
@@ -199,7 +199,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### KAFKA On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: KAFKA.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - KAFKA.service.name: `kafka::kafka.skywalking-showcase`
@@ -207,7 +207,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### PULSAR On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: PULSAR.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - PULSAR.service.name: `pulsar::pulsar.skywalking-showcase`
@@ -215,7 +215,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### SO11Y_OAP On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: SO11Y_OAP.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - SO11Y_OAP.service.name: `demo-oap.skywalking-showcase`
@@ -223,7 +223,7 @@ If you want to customize it according to your own needs, 
please refer to [Servic
 
 #### KONG On K8S_SERVICE
 - Rule name: `short-name`
-- Groovy script: `{ (u, l) -> u.shortName == l.shortName }`
+- Matching expression: `{ (u, l) -> u.shortName == l.shortName }`
 - Description: KONG.service.shortName == K8S_SERVICE.service.shortName
 - Matched Example:
   - KONG.service.name: `kong::kong.skywalking-showcase`
diff --git a/docs/menu.yml b/docs/menu.yml
index adb2ee8936..72d08d1e32 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -392,6 +392,8 @@ catalog:
         path: "/en/concepts-and-designs/ebpf-cpu-profiling"
       - name: "Diagnose Service Mesh Network Performance with eBPF"
         path: "/en/academy/diagnose-service-mesh-network-performance-with-ebpf"
+      - name: "DSL Compiler Design"
+        path: "/en/academy/dsl-compiler-design"
   - name: "FAQs"
     path: "/en/faq/readme"
   - name: "Contributing Guides"
diff --git 
a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java
 
b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java
index d94afe0cdf..ea0900863a 100644
--- 
a/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java
+++ 
b/oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider/meter/process/MeterProcessor.java
@@ -53,7 +53,7 @@ public class MeterProcessor {
     private final MeterProcessService processService;
 
     /**
-     * All of meters has been read. Using it to process groovy script.
+     * All of meters has been read. Using it to process MAL expressions.
      */
     private final Map<String, List<SampleBuilder>> meters = new HashMap<>();
 

Reply via email to