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 e57381850a5d9610515f286b54421d4356c7fc53
Author: Wu Sheng <[email protected]>
AuthorDate: Sun Mar 1 17:31:06 2026 +0800

    Fix LAL compiler gaps: tag(), safe nav, nested blocks, else-if chains, 
interpolated sampler IDs
    
    Address five critical gaps in the LAL v2 compiler that broke shipped 
production rules:
    
    1. tag("LOG_KIND") in conditions now emits tagValue() helper instead of null
    2. Safe navigation (?.) for method calls emits safeCall() helper to prevent 
NPE
    3. Metrics, slowSql, sampledTrace, sampler/rateLimit blocks generate proper
       sub-consumer classes with BindingAware wiring
    4. else-if chains build nested IfBlock AST nodes instead of dropping
       intermediate branches
    5. GString interpolation in rateLimit IDs (e.g. 
"${log.service}:${parsed.code}")
       parsed into InterpolationPart segments and emitted as string 
concatenation
    
    Also fixes ProcessRegistry static calls to pass arguments through, and adds
    comprehensive tests (55 total: 35 generator + 20 parser) covering all gaps
    including production-like envoy-als, nginx, and k8s-service rule patterns.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../log/analyzer/compiler/LALClassGenerator.java   | 636 ++++++++++++++++++++-
 .../oap/log/analyzer/compiler/LALScriptModel.java  |  77 ++-
 .../oap/log/analyzer/compiler/LALScriptParser.java | 193 ++++++-
 .../analyzer/compiler/LALClassGeneratorTest.java   | 458 +++++++++++++++
 .../log/analyzer/compiler/LALScriptParserTest.java | 337 +++++++++++
 5 files changed, 1653 insertions(+), 48 deletions(-)

diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGenerator.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGenerator.java
index a5cdcab939..be5e78d9ef 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGenerator.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGenerator.java
@@ -52,6 +52,22 @@ public final class LALClassGenerator {
         "org.apache.skywalking.oap.log.analyzer.dsl.Binding";
     private static final String BINDING_PARSED =
         "org.apache.skywalking.oap.log.analyzer.dsl.Binding.Parsed";
+    private static final String EXTRACTOR_SPEC =
+        
"org.apache.skywalking.oap.log.analyzer.dsl.spec.extractor.ExtractorSpec";
+    private static final String SLOW_SQL_SPEC =
+        
"org.apache.skywalking.oap.log.analyzer.dsl.spec.extractor.slowsql.SlowSqlSpec";
+    private static final String SAMPLED_TRACE_SPEC =
+        
"org.apache.skywalking.oap.log.analyzer.dsl.spec.extractor.sampledtrace.SampledTraceSpec";
+    private static final String SINK_SPEC =
+        "org.apache.skywalking.oap.log.analyzer.dsl.spec.sink.SinkSpec";
+    private static final String SAMPLER_SPEC =
+        "org.apache.skywalking.oap.log.analyzer.dsl.spec.sink.SamplerSpec";
+    private static final String RATE_LIMITING_SAMPLER =
+        
"org.apache.skywalking.oap.log.analyzer.dsl.spec.sink.sampler.RateLimitingSampler";
+    private static final String SAMPLE_BUILDER =
+        EXTRACTOR_SPEC + "$SampleBuilder";
+    private static final String PROCESS_REGISTRY =
+        
"org.apache.skywalking.oap.meter.analyzer.dsl.registry.ProcessRegistry";
 
     private final ClassPool classPool;
 
@@ -82,7 +98,7 @@ public final class LALClassGenerator {
         final List<ConsumerInfo> consumers = new ArrayList<>();
         collectConsumers(model.getStatements(), consumers);
 
-        // Phase 2: Compile consumer classes
+        // Phase 2: Compile consumer classes (recursively handles 
sub-consumers)
         final List<Object> consumerInstances = new ArrayList<>();
         for (int i = 0; i < consumers.size(); i++) {
             final String consumerName = className + "_C" + i;
@@ -131,7 +147,7 @@ public final class LALClassGenerator {
 
     // ==================== Consumer info ====================
 
-    private static final class ConsumerInfo {
+    private static class ConsumerInfo {
         final String body;
         final String castType;
         final List<ConsumerInfo> subConsumers;
@@ -141,6 +157,13 @@ public final class LALClassGenerator {
             this.castType = castType;
             this.subConsumers = new ArrayList<>();
         }
+
+        ConsumerInfo(final String body, final String castType,
+                     final List<ConsumerInfo> subConsumers) {
+            this.body = body;
+            this.castType = castType;
+            this.subConsumers = new ArrayList<>(subConsumers);
+        }
     }
 
     // ==================== Phase 1: Collect consumers ====================
@@ -184,19 +207,29 @@ public final class LALClassGenerator {
         } else if (stmt instanceof LALScriptModel.ExtractorBlock) {
             final LALScriptModel.ExtractorBlock block =
                 (LALScriptModel.ExtractorBlock) stmt;
+            final ConsumerInfo info = new ConsumerInfo("", EXTRACTOR_SPEC);
             final StringBuilder sb = new StringBuilder();
-            generateExtractorStatementsFlat(sb, block.getStatements());
-            consumers.add(new ConsumerInfo(sb.toString(),
-                "org.apache.skywalking.oap.log.analyzer.dsl"
-                + ".spec.extractor.ExtractorSpec"));
+            final int[] subCounter = {0};
+            final List<LALScriptModel.FilterStatement> extractorStmts = new 
ArrayList<>();
+            for (final LALScriptModel.ExtractorStatement es : 
block.getStatements()) {
+                extractorStmts.add((LALScriptModel.FilterStatement) es);
+            }
+            generateExtractorBody(sb, extractorStmts, info, subCounter);
+            consumers.add(new ConsumerInfo(sb.toString(), EXTRACTOR_SPEC,
+                info.subConsumers));
         } else if (stmt instanceof LALScriptModel.SinkBlock) {
             final LALScriptModel.SinkBlock sink = (LALScriptModel.SinkBlock) 
stmt;
             if (!sink.getStatements().isEmpty()) {
+                final ConsumerInfo info = new ConsumerInfo("", SINK_SPEC);
                 final StringBuilder sb = new StringBuilder();
-                generateSinkStatementsFlat(sb, sink.getStatements());
-                consumers.add(new ConsumerInfo(sb.toString(),
-                    "org.apache.skywalking.oap.log.analyzer.dsl"
-                    + ".spec.sink.SinkSpec"));
+                final int[] subCounter = {0};
+                final List<LALScriptModel.FilterStatement> sinkStmts = new 
ArrayList<>();
+                for (final LALScriptModel.SinkStatement ss : 
sink.getStatements()) {
+                    sinkStmts.add((LALScriptModel.FilterStatement) ss);
+                }
+                generateSinkBody(sb, sinkStmts, info, subCounter);
+                consumers.add(new ConsumerInfo(sb.toString(), SINK_SPEC,
+                    info.subConsumers));
             }
         } else if (stmt instanceof LALScriptModel.IfBlock) {
             final LALScriptModel.IfBlock ifBlock = (LALScriptModel.IfBlock) 
stmt;
@@ -207,12 +240,14 @@ public final class LALClassGenerator {
         }
     }
 
-    // ==================== Flat code for consumer bodies ====================
+    // ==================== Extractor body generation ====================
 
-    private void generateExtractorStatementsFlat(
+    private void generateExtractorBody(
             final StringBuilder sb,
-            final List<LALScriptModel.ExtractorStatement> stmts) {
-        for (final LALScriptModel.ExtractorStatement stmt : stmts) {
+            final List<? extends LALScriptModel.FilterStatement> stmts,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.FilterStatement stmt : stmts) {
             if (stmt instanceof LALScriptModel.FieldAssignment) {
                 final LALScriptModel.FieldAssignment field =
                     (LALScriptModel.FieldAssignment) stmt;
@@ -227,13 +262,423 @@ public final class LALClassGenerator {
                 }
                 sb.append(");\n");
             } else if (stmt instanceof LALScriptModel.TagAssignment) {
-                final LALScriptModel.TagAssignment tag =
-                    (LALScriptModel.TagAssignment) stmt;
-                generateTagAssignment(sb, tag);
+                generateTagAssignment(sb, (LALScriptModel.TagAssignment) stmt);
+            } else if (stmt instanceof LALScriptModel.IfBlock) {
+                generateIfBlockInBody(sb, (LALScriptModel.IfBlock) stmt,
+                    parentInfo, subCounter, true);
+            } else if (stmt instanceof LALScriptModel.MetricsBlock) {
+                generateMetricsSubConsumer(sb, (LALScriptModel.MetricsBlock) 
stmt,
+                    parentInfo, subCounter);
+            } else if (stmt instanceof LALScriptModel.SlowSqlBlock) {
+                generateSlowSqlSubConsumer(sb, (LALScriptModel.SlowSqlBlock) 
stmt,
+                    parentInfo, subCounter);
+            } else if (stmt instanceof LALScriptModel.SampledTraceBlock) {
+                generateSampledTraceSubConsumer(sb,
+                    (LALScriptModel.SampledTraceBlock) stmt,
+                    parentInfo, subCounter);
+            }
+        }
+    }
+
+    private void generateIfBlockInBody(
+            final StringBuilder sb,
+            final LALScriptModel.IfBlock ifBlock,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter,
+            final boolean isExtractorContext) {
+        sb.append("  if (");
+        generateCondition(sb, ifBlock.getCondition());
+        sb.append(") {\n");
+        if (isExtractorContext) {
+            generateExtractorBody(sb, ifBlock.getThenBranch(), parentInfo, 
subCounter);
+        } else {
+            generateSinkBody(sb, ifBlock.getThenBranch(), parentInfo, 
subCounter);
+        }
+        sb.append("  }\n");
+        if (!ifBlock.getElseBranch().isEmpty()) {
+            sb.append("  else {\n");
+            if (isExtractorContext) {
+                generateExtractorBody(sb, ifBlock.getElseBranch(), parentInfo, 
subCounter);
+            } else {
+                generateSinkBody(sb, ifBlock.getElseBranch(), parentInfo, 
subCounter);
+            }
+            sb.append("  }\n");
+        }
+    }
+
+    // ==================== Metrics sub-consumer ====================
+
+    private void generateMetricsSubConsumer(
+            final StringBuilder sb,
+            final LALScriptModel.MetricsBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        final int idx = subCounter[0]++;
+        final StringBuilder body = new StringBuilder();
+        if (block.getName() != null) {
+            body.append("  _t.name(\"")
+                .append(escapeJava(block.getName())).append("\");\n");
+        }
+        if (block.getTimestampValue() != null) {
+            body.append("  _t.timestamp(");
+            generateCastedValueAccess(body, block.getTimestampValue(),
+                block.getTimestampCast());
+            body.append(");\n");
+        }
+        if (!block.getLabels().isEmpty()) {
+            body.append("  { java.util.Map _labels = new 
java.util.LinkedHashMap();\n");
+            for (final Map.Entry<String, LALScriptModel.TagValue> entry
+                    : block.getLabels().entrySet()) {
+                body.append("    _labels.put(\"")
+                    .append(escapeJava(entry.getKey())).append("\", ");
+                generateCastedValueAccess(body, entry.getValue().getValue(),
+                    entry.getValue().getCastType());
+                body.append(");\n");
+            }
+            body.append("    _t.labels(_labels); }\n");
+        }
+        if (block.getValue() != null) {
+            body.append("  _t.value(");
+            if ("Long".equals(block.getValueCast())) {
+                body.append("(double) toLong(");
+                generateValueAccess(body, block.getValue());
+                body.append(")");
+            } else if ("Integer".equals(block.getValueCast())) {
+                body.append("(double) toInt(");
+                generateValueAccess(body, block.getValue());
+                body.append(")");
+            } else {
+                // Number literal or untyped value — cast to double for 
Sample.value(double)
+                if (block.getValue().isNumberLiteral()) {
+                    body.append("(double) 
").append(block.getValue().getSegments().get(0));
+                } else {
+                    body.append("((Number) ");
+                    generateValueAccess(body, block.getValue());
+                    body.append(").doubleValue()");
+                }
+            }
+            body.append(");\n");
+        }
+
+        final ConsumerInfo sub = new ConsumerInfo(body.toString(), 
SAMPLE_BUILDER);
+        parentInfo.subConsumers.add(sub);
+        sb.append("  ((").append(PACKAGE_PREFIX)
+          .append("BindingAware) this._sub").append(idx)
+          .append(").setBinding(this.binding);\n");
+        sb.append("  _t.metrics(this._sub").append(idx).append(");\n");
+    }
+
+    // ==================== SlowSql sub-consumer ====================
+
+    private void generateSlowSqlSubConsumer(
+            final StringBuilder sb,
+            final LALScriptModel.SlowSqlBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        final int idx = subCounter[0]++;
+        final StringBuilder body = new StringBuilder();
+        if (block.getId() != null) {
+            body.append("  _t.id(");
+            generateCastedValueAccess(body, block.getId(), block.getIdCast());
+            body.append(");\n");
+        }
+        if (block.getStatement() != null) {
+            body.append("  _t.statement(");
+            generateCastedValueAccess(body, block.getStatement(),
+                block.getStatementCast());
+            body.append(");\n");
+        }
+        if (block.getLatency() != null) {
+            body.append("  _t.latency(Long.valueOf(toLong(");
+            generateValueAccess(body, block.getLatency());
+            body.append(")));\n");
+        }
+
+        final ConsumerInfo sub = new ConsumerInfo(body.toString(), 
SLOW_SQL_SPEC);
+        parentInfo.subConsumers.add(sub);
+        sb.append("  ((").append(PACKAGE_PREFIX)
+          .append("BindingAware) this._sub").append(idx)
+          .append(").setBinding(this.binding);\n");
+        sb.append("  _t.slowSql(this._sub").append(idx).append(");\n");
+    }
+
+    // ==================== SampledTrace sub-consumer ====================
+
+    private void generateSampledTraceSubConsumer(
+            final StringBuilder sb,
+            final LALScriptModel.SampledTraceBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        final int idx = subCounter[0]++;
+        final StringBuilder body = new StringBuilder();
+        final ConsumerInfo sub = new ConsumerInfo("", SAMPLED_TRACE_SPEC);
+        final int[] innerSubCounter = {0};
+        generateSampledTraceBody(body, block.getStatements(), sub, 
innerSubCounter);
+
+        // Propagate any sub-sub-consumers
+        parentInfo.subConsumers.add(new ConsumerInfo(body.toString(),
+            SAMPLED_TRACE_SPEC, sub.subConsumers));
+        sb.append("  ((").append(PACKAGE_PREFIX)
+          .append("BindingAware) this._sub").append(idx)
+          .append(").setBinding(this.binding);\n");
+        sb.append("  _t.sampledTrace(this._sub").append(idx).append(");\n");
+    }
+
+    private void generateSampledTraceBody(
+            final StringBuilder sb,
+            final List<LALScriptModel.SampledTraceStatement> stmts,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.SampledTraceStatement stmt : stmts) {
+            if (stmt instanceof LALScriptModel.SampledTraceField) {
+                generateSampledTraceField(sb, 
(LALScriptModel.SampledTraceField) stmt);
+            } else if (stmt instanceof LALScriptModel.IfBlock) {
+                generateSampledTraceIfBlock(sb, (LALScriptModel.IfBlock) stmt,
+                    parentInfo, subCounter);
+            }
+        }
+    }
+
+    private void generateSampledTraceField(
+            final StringBuilder sb,
+            final LALScriptModel.SampledTraceField field) {
+        final String methodName;
+        switch (field.getFieldType()) {
+            case LATENCY:
+                methodName = "latency";
+                sb.append("  _t.latency(Long.valueOf(toLong(");
+                generateValueAccess(sb, field.getValue());
+                sb.append(")));\n");
+                return;
+            case COMPONENT_ID:
+                methodName = "componentId";
+                sb.append("  _t.componentId(toInt(");
+                generateValueAccess(sb, field.getValue());
+                sb.append("));\n");
+                return;
+            case URI:
+                methodName = "uri";
+                break;
+            case REASON:
+                methodName = "reason";
+                break;
+            case PROCESS_ID:
+                methodName = "processId";
+                break;
+            case DEST_PROCESS_ID:
+                methodName = "destProcessId";
+                break;
+            case DETECT_POINT:
+                methodName = "detectPoint";
+                break;
+            case REPORT_SERVICE:
+                methodName = "reportService";
+                break;
+            default:
+                return;
+        }
+        sb.append("  _t.").append(methodName).append("(");
+        generateCastedValueAccess(sb, field.getValue(), field.getCastType());
+        sb.append(");\n");
+    }
+
+    private void generateSampledTraceIfBlock(
+            final StringBuilder sb,
+            final LALScriptModel.IfBlock ifBlock,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        sb.append("  if (");
+        generateCondition(sb, ifBlock.getCondition());
+        sb.append(") {\n");
+        generateSampledTraceBodyFromFilterStmts(sb, ifBlock.getThenBranch(),
+            parentInfo, subCounter);
+        sb.append("  }\n");
+        if (!ifBlock.getElseBranch().isEmpty()) {
+            sb.append("  else {\n");
+            generateSampledTraceBodyFromFilterStmts(sb, 
ifBlock.getElseBranch(),
+                parentInfo, subCounter);
+            sb.append("  }\n");
+        }
+    }
+
+    private void generateSampledTraceBodyFromFilterStmts(
+            final StringBuilder sb,
+            final List<? extends LALScriptModel.FilterStatement> stmts,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.FilterStatement stmt : stmts) {
+            if (stmt instanceof LALScriptModel.FieldAssignment) {
+                // SampledTrace fields (processId, latency, etc.) are parsed 
as FieldAssignment
+                generateSampledTraceFieldFromAssignment(sb,
+                    (LALScriptModel.FieldAssignment) stmt);
+            } else if (stmt instanceof LALScriptModel.IfBlock) {
+                generateSampledTraceIfBlock(sb, (LALScriptModel.IfBlock) stmt,
+                    parentInfo, subCounter);
+            }
+        }
+    }
+
+    private void generateSampledTraceFieldFromAssignment(
+            final StringBuilder sb,
+            final LALScriptModel.FieldAssignment fa) {
+        // Map FieldType to SampledTraceSpec methods
+        switch (fa.getFieldType()) {
+            case TIMESTAMP:
+                sb.append("  _t.latency(Long.valueOf(toLong(");
+                generateValueAccess(sb, fa.getValue());
+                sb.append(")));\n");
+                break;
+            default:
+                sb.append("  
_t.").append(fa.getFieldType().name().toLowerCase())
+                  .append("(");
+                generateCastedValueAccess(sb, fa.getValue(), fa.getCastType());
+                sb.append(");\n");
+                break;
+        }
+    }
+
+    // ==================== Sink body generation ====================
+
+    private void generateSinkBody(
+            final StringBuilder sb,
+            final List<? extends LALScriptModel.FilterStatement> stmts,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.FilterStatement stmt : stmts) {
+            if (stmt instanceof LALScriptModel.EnforcerStatement) {
+                sb.append("  _t.enforcer();\n");
+            } else if (stmt instanceof LALScriptModel.DropperStatement) {
+                sb.append("  _t.dropper();\n");
+            } else if (stmt instanceof LALScriptModel.SamplerBlock) {
+                generateSamplerSubConsumer(sb, (LALScriptModel.SamplerBlock) 
stmt,
+                    parentInfo, subCounter);
+            } else if (stmt instanceof LALScriptModel.IfBlock) {
+                generateIfBlockInBody(sb, (LALScriptModel.IfBlock) stmt,
+                    parentInfo, subCounter, false);
+            }
+        }
+    }
+
+    // ==================== Sampler sub-consumer ====================
+
+    private void generateSamplerSubConsumer(
+            final StringBuilder sb,
+            final LALScriptModel.SamplerBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        final int idx = subCounter[0]++;
+        final StringBuilder body = new StringBuilder();
+        final ConsumerInfo sub = new ConsumerInfo("", SAMPLER_SPEC);
+        final int[] innerSubCounter = {0};
+        generateSamplerBody(body, block.getContents(), sub, innerSubCounter);
+
+        parentInfo.subConsumers.add(new ConsumerInfo(body.toString(),
+            SAMPLER_SPEC, sub.subConsumers));
+        sb.append("  ((").append(PACKAGE_PREFIX)
+          .append("BindingAware) this._sub").append(idx)
+          .append(").setBinding(this.binding);\n");
+        sb.append("  _t.sampler(this._sub").append(idx).append(");\n");
+    }
+
+    private void generateSamplerBody(
+            final StringBuilder sb,
+            final List<LALScriptModel.SamplerContent> contents,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.SamplerContent content : contents) {
+            if (content instanceof LALScriptModel.RateLimitBlock) {
+                generateRateLimitSubConsumer(sb, 
(LALScriptModel.RateLimitBlock) content,
+                    parentInfo, subCounter);
+            } else if (content instanceof LALScriptModel.IfBlock) {
+                generateSamplerIfBlock(sb, (LALScriptModel.IfBlock) content,
+                    parentInfo, subCounter);
+            }
+        }
+    }
+
+    private void generateSamplerIfBlock(
+            final StringBuilder sb,
+            final LALScriptModel.IfBlock ifBlock,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        sb.append("  if (");
+        generateCondition(sb, ifBlock.getCondition());
+        sb.append(") {\n");
+        generateSamplerBodyFromFilterStmts(sb, ifBlock.getThenBranch(),
+            parentInfo, subCounter);
+        sb.append("  }\n");
+        if (!ifBlock.getElseBranch().isEmpty()) {
+            sb.append("  else {\n");
+            generateSamplerBodyFromFilterStmts(sb, ifBlock.getElseBranch(),
+                parentInfo, subCounter);
+            sb.append("  }\n");
+        }
+    }
+
+    private void generateSamplerBodyFromFilterStmts(
+            final StringBuilder sb,
+            final List<? extends LALScriptModel.FilterStatement> stmts,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        for (final LALScriptModel.FilterStatement stmt : stmts) {
+            if (stmt instanceof LALScriptModel.SamplerBlock) {
+                // SamplerBlock appears in if-branches inside a sampler
+                generateSamplerSubConsumerInline(sb,
+                    (LALScriptModel.SamplerBlock) stmt,
+                    parentInfo, subCounter);
+            } else if (stmt instanceof LALScriptModel.IfBlock) {
+                generateSamplerIfBlock(sb, (LALScriptModel.IfBlock) stmt,
+                    parentInfo, subCounter);
             }
         }
     }
 
+    private void generateSamplerSubConsumerInline(
+            final StringBuilder sb,
+            final LALScriptModel.SamplerBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        // When a sampler block appears inside an if branch of a sampler,
+        // generate its contents inline
+        generateSamplerBody(sb, block.getContents(), parentInfo, subCounter);
+    }
+
+    private void generateRateLimitSubConsumer(
+            final StringBuilder sb,
+            final LALScriptModel.RateLimitBlock block,
+            final ConsumerInfo parentInfo,
+            final int[] subCounter) {
+        final int idx = subCounter[0]++;
+        final String body = "  _t.rpm(" + block.getRpm() + ");\n";
+        final ConsumerInfo sub = new ConsumerInfo(body, RATE_LIMITING_SAMPLER);
+        parentInfo.subConsumers.add(sub);
+        sb.append("  ((").append(PACKAGE_PREFIX)
+          .append("BindingAware) this._sub").append(idx)
+          .append(").setBinding(this.binding);\n");
+
+        if (block.isIdInterpolated()) {
+            // Emit string concatenation for interpolated IDs
+            // e.g. "${log.service}:${parsed?.field}" →
+            //   "" + String.valueOf(binding.log().getService()) + ":" + 
String.valueOf(...)
+            sb.append("  _t.rateLimit(\"\"");
+            for (final LALScriptModel.InterpolationPart part : 
block.getIdParts()) {
+                sb.append(" + ");
+                if (part.isLiteral()) {
+                    
sb.append("\"").append(escapeJava(part.getLiteral())).append("\"");
+                } else {
+                    sb.append("String.valueOf(");
+                    generateValueAccess(sb, part.getExpression());
+                    sb.append(")");
+                }
+            }
+            sb.append(", this._sub").append(idx).append(");\n");
+        } else {
+            sb.append("  _t.rateLimit(\"")
+              .append(escapeJava(block.getId())).append("\", this._sub")
+              .append(idx).append(");\n");
+        }
+    }
+
     private void generateTagAssignment(final StringBuilder sb,
                                          final LALScriptModel.TagAssignment 
tag) {
         final Map<String, LALScriptModel.TagValue> tags = tag.getTags();
@@ -262,18 +707,6 @@ public final class LALClassGenerator {
         }
     }
 
-    private void generateSinkStatementsFlat(
-            final StringBuilder sb,
-            final List<LALScriptModel.SinkStatement> stmts) {
-        for (final LALScriptModel.SinkStatement stmt : stmts) {
-            if (stmt instanceof LALScriptModel.EnforcerStatement) {
-                sb.append("  _t.enforcer();\n");
-            } else if (stmt instanceof LALScriptModel.DropperStatement) {
-                sb.append("  _t.dropper();\n");
-            }
-        }
-    }
-
     // ==================== Phase 2: Compile consumer classes 
====================
 
     private Object compileConsumerClass(final String className,
@@ -294,17 +727,39 @@ public final class LALClassGenerator {
             "public " + BINDING + " getBinding() {"
             + " return this.binding; }", ctClass));
 
+        // Add sub-consumer fields
+        for (int i = 0; i < info.subConsumers.size(); i++) {
+            ctClass.addField(CtField.make(
+                "public java.util.function.Consumer _sub" + i + ";",
+                ctClass));
+        }
+
         addHelperMethods(ctClass);
 
         final String method = "public void accept(Object arg) {\n"
             + "  " + info.castType + " _t = (" + info.castType + ") arg;\n"
             + info.body
             + "}\n";
+
+        if (log.isDebugEnabled()) {
+            log.debug("LAL compile consumer {} body:\n{}", className, method);
+        }
+
         ctClass.addMethod(CtNewMethod.make(method, ctClass));
 
         final Class<?> clazz = 
ctClass.toClass(LalExpressionPackageHolder.class);
         ctClass.detach();
-        return clazz.getDeclaredConstructor().newInstance();
+        final Object instance = clazz.getDeclaredConstructor().newInstance();
+
+        // Compile and wire sub-consumers
+        for (int i = 0; i < info.subConsumers.size(); i++) {
+            final String subName = className + "_S" + i;
+            final Object subInstance = compileConsumerClass(
+                subName, info.subConsumers.get(i));
+            clazz.getField("_sub" + i).set(instance, subInstance);
+        }
+
+        return instance;
     }
 
     // ==================== Phase 4: Generate execute method 
====================
@@ -442,6 +897,32 @@ public final class LALClassGenerator {
             + " return ((Number) obj).doubleValue() != 0;"
             + "  return true;"
             + "}", ctClass));
+
+        // tag() value lookup using Binding
+        ctClass.addMethod(CtNewMethod.make(
+            "private static String tagValue("
+            + BINDING + " b, String key) {"
+            + "  java.util.List dl = b.log().getTags().getDataList();"
+            + "  for (int i = 0; i < dl.size(); i++) {"
+            + "    org.apache.skywalking.apm.network.common.v3"
+            + ".KeyStringValuePair kv = "
+            + "(org.apache.skywalking.apm.network.common.v3"
+            + ".KeyStringValuePair) dl.get(i);"
+            + "    if (key.equals(kv.getKey())) return kv.getValue();"
+            + "  }"
+            + "  return \"\";"
+            + "}", ctClass));
+
+        // Safe method call helper
+        ctClass.addMethod(CtNewMethod.make(
+            "private static Object safeCall(Object obj, String method) {"
+            + "  if (obj == null) return null;"
+            + "  if (\"toString\".equals(method)) return obj.toString();"
+            + "  if (\"trim\".equals(method)) return obj.toString().trim();"
+            + "  if (\"isEmpty\".equals(method))"
+            + " return Boolean.valueOf(obj.toString().isEmpty());"
+            + "  return obj.toString();"
+            + "}", ctClass));
     }
 
     // ==================== Conditions ====================
@@ -597,6 +1078,45 @@ public final class LALClassGenerator {
 
     private void generateValueAccess(final StringBuilder sb,
                                       final LALScriptModel.ValueAccess value) {
+        // Handle function call primaries (e.g., tag("LOG_KIND"))
+        if (value.getFunctionCallName() != null) {
+            if ("tag".equals(value.getFunctionCallName())
+                    && !value.getFunctionCallArgs().isEmpty()) {
+                // tag("KEY") → tagValue(binding, "KEY")
+                sb.append("tagValue(binding, \"");
+                final String key = value.getFunctionCallArgs().get(0)
+                    .getValue().getSegments().get(0);
+                sb.append(escapeJava(key)).append("\")");
+            } else {
+                // Unknown function call — emit null for safety
+                sb.append("null");
+            }
+            return;
+        }
+
+        // Handle string/number literals
+        if (value.isStringLiteral() && value.getChain().isEmpty()) {
+            sb.append("\"").append(escapeJava(value.getSegments().get(0)))
+              .append("\"");
+            return;
+        }
+        if (value.isNumberLiteral() && value.getChain().isEmpty()) {
+            final String num = value.getSegments().get(0);
+            // Box number literals so Javassist resolves Object-param methods
+            if (num.contains(".")) {
+                sb.append("Double.valueOf(").append(num).append(")");
+            } else {
+                sb.append("Integer.valueOf(").append(num).append(")");
+            }
+            return;
+        }
+
+        // Handle ProcessRegistry static calls
+        if (value.isProcessRegistryRef()) {
+            generateProcessRegistryCall(sb, value);
+            return;
+        }
+
         String current;
         if (value.isParsedRef()) {
             current = "binding.parsed()";
@@ -622,17 +1142,71 @@ public final class LALClassGenerator {
             if (seg instanceof LALScriptModel.FieldSegment) {
                 final String name =
                     ((LALScriptModel.FieldSegment) seg).getName();
+                // getAt() already handles null → null, so safe nav is free
                 current = "getAt(" + current + ", \""
                     + escapeJava(name) + "\")";
             } else if (seg instanceof LALScriptModel.MethodSegment) {
                 final LALScriptModel.MethodSegment ms =
                     (LALScriptModel.MethodSegment) seg;
-                current = current + "." + ms.getName() + "()";
+                if (ms.isSafeNav()) {
+                    // Safe navigation: null-safe method call
+                    current = "safeCall(" + current + ", \""
+                        + escapeJava(ms.getName()) + "\")";
+                } else {
+                    if (ms.getArguments().isEmpty()) {
+                        current = current + "." + ms.getName() + "()";
+                    } else {
+                        current = current + "." + ms.getName() + "("
+                            + generateMethodArgs(ms.getArguments()) + ")";
+                    }
+                }
             }
         }
         sb.append(current);
     }
 
+    private void generateProcessRegistryCall(
+            final StringBuilder sb,
+            final LALScriptModel.ValueAccess value) {
+        final List<LALScriptModel.ValueAccessSegment> chain = value.getChain();
+        if (chain.isEmpty()) {
+            sb.append("null");
+            return;
+        }
+        // Expect exactly one method segment: ProcessRegistry.methodName(args)
+        final LALScriptModel.ValueAccessSegment seg = chain.get(0);
+        if (seg instanceof LALScriptModel.MethodSegment) {
+            final LALScriptModel.MethodSegment ms =
+                (LALScriptModel.MethodSegment) seg;
+            sb.append(PROCESS_REGISTRY).append(".")
+              .append(ms.getName()).append("(");
+            final List<LALScriptModel.FunctionArg> args = ms.getArguments();
+            for (int i = 0; i < args.size(); i++) {
+                if (i > 0) {
+                    sb.append(", ");
+                }
+                generateCastedValueAccess(sb,
+                    args.get(i).getValue(), args.get(i).getCastType());
+            }
+            sb.append(")");
+        } else {
+            sb.append("null");
+        }
+    }
+
+    private String generateMethodArgs(
+            final List<LALScriptModel.FunctionArg> args) {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < args.size(); i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            generateCastedValueAccess(sb,
+                args.get(i).getValue(), args.get(i).getCastType());
+        }
+        return sb.toString();
+    }
+
     // ==================== Utilities ====================
 
     private static String escapeJava(final String s) {
diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptModel.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptModel.java
index cc3d1d3df0..8609deb66d 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptModel.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptModel.java
@@ -245,12 +245,44 @@ public final class LALScriptModel {
     @Getter
     public static final class RateLimitBlock implements SamplerContent {
         private final String id;
+        private final List<InterpolationPart> idParts;
         private final long rpm;
 
-        public RateLimitBlock(final String id, final long rpm) {
+        public RateLimitBlock(final String id,
+                              final List<InterpolationPart> idParts,
+                              final long rpm) {
             this.id = id;
+            this.idParts = idParts != null
+                ? Collections.unmodifiableList(idParts) : 
Collections.emptyList();
             this.rpm = rpm;
         }
+
+        public boolean isIdInterpolated() {
+            return !idParts.isEmpty();
+        }
+    }
+
+    @Getter
+    public static final class InterpolationPart {
+        private final String literal;
+        private final ValueAccess expression;
+
+        private InterpolationPart(final String literal, final ValueAccess 
expression) {
+            this.literal = literal;
+            this.expression = expression;
+        }
+
+        public static InterpolationPart ofLiteral(final String text) {
+            return new InterpolationPart(text, null);
+        }
+
+        public static InterpolationPart ofExpression(final ValueAccess expr) {
+            return new InterpolationPart(null, expr);
+        }
+
+        public boolean isLiteral() {
+            return literal != null;
+        }
     }
 
     public static final class EnforcerStatement implements SinkStatement, 
FilterStatement {
@@ -341,17 +373,41 @@ public final class LALScriptModel {
         private final List<String> segments;
         private final boolean parsedRef;
         private final boolean logRef;
+        private final boolean processRegistryRef;
+        private final boolean stringLiteral;
+        private final boolean numberLiteral;
         private final List<ValueAccessSegment> chain;
+        private final String functionCallName;
+        private final List<FunctionArg> functionCallArgs;
 
         public ValueAccess(final List<String> segments,
                            final boolean parsedRef,
                            final boolean logRef,
                            final List<ValueAccessSegment> chain) {
+            this(segments, parsedRef, logRef, false, false, false,
+                chain, null, Collections.emptyList());
+        }
+
+        public ValueAccess(final List<String> segments,
+                           final boolean parsedRef,
+                           final boolean logRef,
+                           final boolean processRegistryRef,
+                           final boolean stringLiteral,
+                           final boolean numberLiteral,
+                           final List<ValueAccessSegment> chain,
+                           final String functionCallName,
+                           final List<FunctionArg> functionCallArgs) {
             this.segments = Collections.unmodifiableList(segments);
             this.parsedRef = parsedRef;
             this.logRef = logRef;
+            this.processRegistryRef = processRegistryRef;
+            this.stringLiteral = stringLiteral;
+            this.numberLiteral = numberLiteral;
             this.chain = chain != null
                 ? Collections.unmodifiableList(chain) : 
Collections.emptyList();
+            this.functionCallName = functionCallName;
+            this.functionCallArgs = functionCallArgs != null
+                ? Collections.unmodifiableList(functionCallArgs) : 
Collections.emptyList();
         }
 
         public String toPathString() {
@@ -359,6 +415,17 @@ public final class LALScriptModel {
         }
     }
 
+    @Getter
+    public static final class FunctionArg {
+        private final ValueAccess value;
+        private final String castType;
+
+        public FunctionArg(final ValueAccess value, final String castType) {
+            this.value = value;
+            this.castType = castType;
+        }
+    }
+
     public interface ValueAccessSegment {
     }
 
@@ -376,12 +443,14 @@ public final class LALScriptModel {
     @Getter
     public static final class MethodSegment implements ValueAccessSegment {
         private final String name;
-        private final List<String> arguments;
+        private final List<FunctionArg> arguments;
         private final boolean safeNav;
 
-        public MethodSegment(final String name, final List<String> arguments, 
final boolean safeNav) {
+        public MethodSegment(final String name, final List<FunctionArg> 
arguments,
+                             final boolean safeNav) {
             this.name = name;
-            this.arguments = Collections.unmodifiableList(arguments);
+            this.arguments = arguments != null
+                ? Collections.unmodifiableList(arguments) : 
Collections.emptyList();
             this.safeNav = safeNav;
         }
     }
diff --git 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParser.java
 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParser.java
index 9bd9d241c1..2793ed4dde 100644
--- 
a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParser.java
+++ 
b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParser.java
@@ -29,6 +29,7 @@ import org.antlr.v4.runtime.Recognizer;
 import org.apache.skywalking.lal.rt.grammar.LALLexer;
 import org.apache.skywalking.lal.rt.grammar.LALParser;
 import 
org.apache.skywalking.oap.log.analyzer.compiler.LALScriptModel.AbortStatement;
+import 
org.apache.skywalking.oap.log.analyzer.compiler.LALScriptModel.InterpolationPart;
 import 
org.apache.skywalking.oap.log.analyzer.compiler.LALScriptModel.CompareOp;
 import 
org.apache.skywalking.oap.log.analyzer.compiler.LALScriptModel.ComparisonCondition;
 import 
org.apache.skywalking.oap.log.analyzer.compiler.LALScriptModel.Condition;
@@ -428,7 +429,8 @@ public final class LALScriptParser {
         for (final LALParser.RateLimitBlockContext rlc : 
ctx.samplerContent().rateLimitBlock()) {
             final String id = stripQuotes(rlc.rateLimitId().getText());
             final long rpm = 
Long.parseLong(rlc.rateLimitContent().NUMBER().getText());
-            contents.add(new RateLimitBlock(id, rpm));
+            final List<InterpolationPart> idParts = parseInterpolation(id);
+            contents.add(new RateLimitBlock(id, idParts, rpm));
         }
         for (final LALParser.IfStatementContext isc : 
ctx.samplerContent().ifStatement()) {
             contents.add((SamplerContent) visitIfStatement(isc));
@@ -439,17 +441,45 @@ public final class LALScriptParser {
     // ==================== If statement ====================
 
     private static IfBlock visitIfStatement(final LALParser.IfStatementContext 
ctx) {
-        final Condition condition = visitCondition(ctx.condition(0));
-        final List<FilterStatement> thenBranch = visitIfBody(ctx.ifBody(0));
-
-        List<FilterStatement> elseBranch = null;
-        // Handle else-if and else branches
+        final int condCount = ctx.condition().size();
         final int bodyCount = ctx.ifBody().size();
-        if (bodyCount > 1) {
-            elseBranch = visitIfBody(ctx.ifBody(bodyCount - 1));
+        // Whether there is a trailing else (no condition) block
+        final boolean hasElse = bodyCount > condCount;
+
+        // Build the chain from the last else-if backwards.
+        // For: if(A){b0} else if(B){b1} else if(C){b2} else{b3}
+        //   condCount=3, bodyCount=4, hasElse=true
+        //   Result: IfBlock(A, b0, IfBlock(B, b1, IfBlock(C, b2, b3)))
+
+        // Start from the innermost else-if (last condition)
+        List<FilterStatement> trailingElse = hasElse
+            ? visitIfBody(ctx.ifBody(bodyCount - 1)) : null;
+
+        // Build from the last condition backwards to index 1
+        IfBlock nested = null;
+        for (int i = condCount - 1; i >= 1; i--) {
+            final Condition cond = visitCondition(ctx.condition(i));
+            final List<FilterStatement> body = visitIfBody(ctx.ifBody(i));
+            final List<FilterStatement> elsePart;
+            if (nested != null) {
+                elsePart = List.of(nested);
+            } else {
+                elsePart = trailingElse;
+            }
+            nested = new IfBlock(cond, body, elsePart);
+        }
+
+        // Build the outermost if block (index 0)
+        final Condition topCond = visitCondition(ctx.condition(0));
+        final List<FilterStatement> topBody = visitIfBody(ctx.ifBody(0));
+        final List<FilterStatement> topElse;
+        if (nested != null) {
+            topElse = List.of(nested);
+        } else {
+            topElse = trailingElse;
         }
 
-        return new IfBlock(condition, thenBranch, elseBranch);
+        return new IfBlock(topCond, topBody, topElse);
     }
 
     private static List<FilterStatement> visitIfBody(final 
LALParser.IfBodyContext ctx) {
@@ -541,8 +571,11 @@ public final class LALScriptParser {
         if (leftCtx instanceof LALParser.CondFunctionCallContext) {
             final LALParser.FunctionInvocationContext fi =
                 ((LALParser.CondFunctionCallContext) 
leftCtx).functionInvocation();
+            final String funcName = fi.functionName().getText();
+            final List<LALScriptModel.FunctionArg> funcArgs = 
visitFunctionArgs(fi);
             final ValueAccess left = new ValueAccess(
-                List.of(fi.getText()), false, false, List.of());
+                List.of(fi.getText()), false, false, false, false, false,
+                List.of(), funcName, funcArgs);
             return new ComparisonCondition(left, null, op,
                 visitConditionExprAsValue(rightCtx));
         }
@@ -582,6 +615,16 @@ public final class LALScriptParser {
             final String cast = va.typeCast() != null ? 
extractCastType(va.typeCast()) : null;
             return new ExprCondition(visitValueAccess(va.valueAccess()), cast);
         }
+        if (ctx instanceof LALParser.CondFunctionCallContext) {
+            final LALParser.FunctionInvocationContext fi =
+                ((LALParser.CondFunctionCallContext) ctx).functionInvocation();
+            final String funcName = fi.functionName().getText();
+            final List<LALScriptModel.FunctionArg> funcArgs = 
visitFunctionArgs(fi);
+            final ValueAccess va = new ValueAccess(
+                List.of(fi.getText()), false, false, false, false, false,
+                List.of(), funcName, funcArgs);
+            return new ExprCondition(va, null);
+        }
         return new ExprCondition(
             new ValueAccess(List.of(ctx.getText()), false, false, List.of()), 
null);
     }
@@ -592,6 +635,11 @@ public final class LALScriptParser {
         final List<String> segments = new ArrayList<>();
         boolean parsedRef = false;
         boolean logRef = false;
+        boolean processRegistryRef = false;
+        boolean stringLiteral = false;
+        boolean numberLiteral = false;
+        String functionCallName = null;
+        List<LALScriptModel.FunctionArg> functionCallArgs = null;
 
         final LALParser.ValueAccessPrimaryContext primary = 
ctx.valueAccessPrimary();
         if (primary instanceof LALParser.ValueParsedContext) {
@@ -601,17 +649,22 @@ public final class LALScriptParser {
             logRef = true;
             segments.add("log");
         } else if (primary instanceof LALParser.ValueProcessRegistryContext) {
+            processRegistryRef = true;
             segments.add("ProcessRegistry");
         } else if (primary instanceof LALParser.ValueIdentifierContext) {
             segments.add(((LALParser.ValueIdentifierContext) 
primary).IDENTIFIER().getText());
         } else if (primary instanceof LALParser.ValueStringContext) {
+            stringLiteral = true;
             segments.add(stripQuotes(
                 ((LALParser.ValueStringContext) primary).STRING().getText()));
         } else if (primary instanceof LALParser.ValueNumberContext) {
+            numberLiteral = true;
             segments.add(((LALParser.ValueNumberContext) 
primary).NUMBER().getText());
         } else if (primary instanceof LALParser.ValueFunctionCallContext) {
             final LALParser.FunctionInvocationContext fi =
                 ((LALParser.ValueFunctionCallContext) 
primary).functionInvocation();
+            functionCallName = fi.functionName().getText();
+            functionCallArgs = visitFunctionArgs(fi);
             segments.add(fi.getText());
         } else {
             segments.add(primary.getText());
@@ -634,17 +687,57 @@ public final class LALScriptParser {
                     ((LALParser.SegmentMethodContext) 
seg).functionInvocation();
                 segments.add(fi.functionName().getText() + "()");
                 chain.add(new LALScriptModel.MethodSegment(
-                    fi.functionName().getText(), List.of(), false));
+                    fi.functionName().getText(), visitFunctionArgs(fi), 
false));
             } else if (seg instanceof LALParser.SegmentSafeMethodContext) {
                 final LALParser.FunctionInvocationContext fi =
                     ((LALParser.SegmentSafeMethodContext) 
seg).functionInvocation();
                 segments.add(fi.functionName().getText() + "()");
                 chain.add(new LALScriptModel.MethodSegment(
-                    fi.functionName().getText(), List.of(), true));
+                    fi.functionName().getText(), visitFunctionArgs(fi), true));
             }
         }
 
-        return new ValueAccess(segments, parsedRef, logRef, chain);
+        return new ValueAccess(segments, parsedRef, logRef,
+            processRegistryRef, stringLiteral, numberLiteral,
+            chain, functionCallName, functionCallArgs);
+    }
+
+    private static List<LALScriptModel.FunctionArg> visitFunctionArgs(
+            final LALParser.FunctionInvocationContext fi) {
+        if (fi.functionArgList() == null) {
+            return List.of();
+        }
+        final List<LALScriptModel.FunctionArg> args = new ArrayList<>();
+        for (final LALParser.FunctionArgContext fac : 
fi.functionArgList().functionArg()) {
+            if (fac.valueAccess() != null) {
+                final ValueAccess va = visitValueAccess(fac.valueAccess());
+                final String cast = fac.typeCast() != null
+                    ? extractCastType(fac.typeCast()) : null;
+                args.add(new LALScriptModel.FunctionArg(va, cast));
+            } else if (fac.STRING() != null) {
+                final String val = stripQuotes(fac.STRING().getText());
+                final ValueAccess va = new ValueAccess(
+                    List.of(val), false, false, true, true, false,
+                    List.of(), null, null);
+                args.add(new LALScriptModel.FunctionArg(va, null));
+            } else if (fac.NUMBER() != null) {
+                final ValueAccess va = new ValueAccess(
+                    List.of(fac.NUMBER().getText()), false, false,
+                    false, false, true, List.of(), null, null);
+                args.add(new LALScriptModel.FunctionArg(va, null));
+            } else if (fac.boolValue() != null) {
+                final ValueAccess va = new ValueAccess(
+                    List.of(fac.boolValue().getText()), false, false,
+                    false, false, false, List.of(), null, null);
+                args.add(new LALScriptModel.FunctionArg(va, null));
+            } else {
+                // NULL
+                final ValueAccess va = new ValueAccess(
+                    List.of("null"), false, false, List.of());
+                args.add(new LALScriptModel.FunctionArg(va, null));
+            }
+        }
+        return args;
     }
 
     private static String resolveValueAsString(final 
LALParser.ValueAccessContext ctx) {
@@ -694,4 +787,78 @@ public final class LALScriptParser {
         }
         return s.substring(0, maxLen) + "...";
     }
+
+    // ==================== GString interpolation ====================
+
+    /**
+     * Parses Groovy-style GString interpolation in a string.
+     * E.g. {@code "${log.service}:${parsed?.field}"} produces
+     * [expr(log.service), literal(":"), expr(parsed?.field)].
+     *
+     * @return list of parts, or {@code null} if no interpolation found
+     */
+    static List<InterpolationPart> parseInterpolation(final String s) {
+        if (!s.contains("${")) {
+            return null;
+        }
+        final List<InterpolationPart> parts = new ArrayList<>();
+        int pos = 0;
+        while (pos < s.length()) {
+            final int start = s.indexOf("${", pos);
+            if (start < 0) {
+                // Remaining literal text
+                if (pos < s.length()) {
+                    parts.add(InterpolationPart.ofLiteral(s.substring(pos)));
+                }
+                break;
+            }
+            // Literal text before ${
+            if (start > pos) {
+                parts.add(InterpolationPart.ofLiteral(s.substring(pos, 
start)));
+            }
+            // Find matching closing brace, respecting nesting
+            int depth = 1;
+            int i = start + 2;
+            while (i < s.length() && depth > 0) {
+                final char c = s.charAt(i);
+                if (c == '{') {
+                    depth++;
+                } else if (c == '}') {
+                    depth--;
+                }
+                i++;
+            }
+            if (depth != 0) {
+                throw new IllegalArgumentException(
+                    "Unclosed interpolation in: " + s);
+            }
+            final String expr = s.substring(start + 2, i - 1);
+            // Parse the expression as a valueAccess through ANTLR
+            
parts.add(InterpolationPart.ofExpression(parseValueAccessExpr(expr)));
+            pos = i;
+        }
+        return parts;
+    }
+
+    /**
+     * Parses a standalone valueAccess expression string by wrapping it in
+     * a minimal LAL script and extracting the parsed ValueAccess.
+     */
+    private static ValueAccess parseValueAccessExpr(final String expr) {
+        // Wrap in: filter { if (EXPR) { sink {} } }
+        // The expression becomes a condition, parsed as ExprCondition
+        // whose ValueAccess is what we want.
+        final String wrapper = "filter { if (" + expr + ") { sink {} } }";
+        final LALScriptModel model = parse(wrapper);
+        final IfBlock ifBlock = (IfBlock) model.getStatements().get(0);
+        final LALScriptModel.Condition cond = ifBlock.getCondition();
+        if (cond instanceof ExprCondition) {
+            return ((ExprCondition) cond).getExpr();
+        }
+        if (cond instanceof ComparisonCondition) {
+            return ((ComparisonCondition) cond).getLeft();
+        }
+        throw new IllegalArgumentException(
+            "Cannot parse interpolation expression: " + expr);
+    }
 }
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGeneratorTest.java
 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGeneratorTest.java
index 11cee26a83..a1a10209e5 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGeneratorTest.java
+++ 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALClassGeneratorTest.java
@@ -22,8 +22,11 @@ import 
org.apache.skywalking.oap.log.analyzer.dsl.LalExpression;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 class LALClassGeneratorTest {
 
@@ -127,4 +130,459 @@ class LALClassGeneratorTest {
         assertThrows(Exception.class,
             () -> generator.compile("filter { invalid {} }"));
     }
+
+    // ==================== tag() function in conditions ====================
+
+    @Test
+    void compileTagFunctionInCondition() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  if (tag(\"LOG_KIND\") == \"SLOW_SQL\") {\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void generateSourceTagFunctionEmitsTagValue() {
+        final String source = generator.generateSource(
+            "filter {\n"
+            + "  if (tag(\"LOG_KIND\") == \"SLOW_SQL\") {\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        // Should use tagValue helper, not emit null
+        assertTrue(source.contains("tagValue(binding, \"LOG_KIND\")"),
+            "Expected tagValue call but got: " + source);
+        assertTrue(source.contains("SLOW_SQL"));
+    }
+
+    @Test
+    void compileTagFunctionNestedInExtractor() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    if (tag(\"LOG_KIND\") == \"NET_PROFILING_SAMPLED_TRACE\") 
{\n"
+            + "      service parsed.service as String\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== Safe navigation ====================
+
+    @Test
+    void compileSafeNavigationFieldAccess() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    service parsed?.response?.service as String\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileSafeNavigationMethodCalls() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    service parsed?.flags?.toString()?.trim() as String\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void generateSourceSafeNavMethodEmitsSafeCall() {
+        final String source = generator.generateSource(
+            "filter {\n"
+            + "  if (parsed?.flags?.toString()) {\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        // Safe method calls should use safeCall helper
+        assertTrue(source.contains("safeCall("),
+            "Expected safeCall for safe nav method but got: " + source);
+    }
+
+    // ==================== ProcessRegistry static calls ====================
+
+    @Test
+    void compileProcessRegistryCall() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    service ProcessRegistry.generateVirtualLocalProcess("
+            + "parsed.service as String, parsed.serviceInstance as String"
+            + ") as String\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileProcessRegistryWithThreeArgs() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    service ProcessRegistry.generateVirtualRemoteProcess("
+            + "parsed.service as String, parsed.serviceInstance as String, "
+            + "parsed.address as String) as String\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== Metrics block ====================
+
+    @Test
+    void compileMetricsBlock() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    metrics {\n"
+            + "      timestamp log.timestamp as Long\n"
+            + "      labels level: parsed.level, service: log.service\n"
+            + "      name \"nginx_error_log_count\"\n"
+            + "      value 1\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== SlowSql block ====================
+
+    @Test
+    void compileSlowSqlBlock() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    slowSql {\n"
+            + "      id parsed.id as String\n"
+            + "      statement parsed.statement as String\n"
+            + "      latency parsed.query_time as Long\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== SampledTrace block ====================
+
+    @Test
+    void compileSampledTraceBlock() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    sampledTrace {\n"
+            + "      latency parsed.latency as Long\n"
+            + "      uri parsed.uri as String\n"
+            + "      reason parsed.reason as String\n"
+            + "      detectPoint parsed.detect_point as String\n"
+            + "      componentId 49\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileSampledTraceWithIfBlocks() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    sampledTrace {\n"
+            + "      latency parsed.latency as Long\n"
+            + "      if (parsed.client_process.process_id as String != \"\") 
{\n"
+            + "        processId parsed.client_process.process_id as String\n"
+            + "      } else {\n"
+            + "        processId parsed.fallback as String\n"
+            + "      }\n"
+            + "      detectPoint parsed.detect_point as String\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== Sampler / rateLimit ====================
+
+    @Test
+    void compileSamplerWithRateLimit() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  sink {\n"
+            + "    sampler {\n"
+            + "      rateLimit('service:error') {\n"
+            + "        rpm 6000\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileSamplerWithInterpolatedId() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  sink {\n"
+            + "    sampler {\n"
+            + "      rateLimit(\"${log.service}:${parsed.code}\") {\n"
+            + "        rpm 6000\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void parseInterpolatedIdParts() {
+        // Verify the parser correctly splits interpolated strings
+        final java.util.List<LALScriptModel.InterpolationPart> parts =
+            LALScriptParser.parseInterpolation(
+                "${log.service}:${parsed.code}");
+        assertNotNull(parts);
+        // expr, literal ":", expr
+        assertEquals(3, parts.size());
+        assertFalse(parts.get(0).isLiteral());
+        assertTrue(parts.get(0).getExpression().isLogRef());
+        assertTrue(parts.get(1).isLiteral());
+        assertEquals(":", parts.get(1).getLiteral());
+        assertFalse(parts.get(2).isLiteral());
+        assertTrue(parts.get(2).getExpression().isParsedRef());
+    }
+
+    @Test
+    void compileSamplerWithSafeNavInterpolatedId() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  sink {\n"
+            + "    sampler {\n"
+            + "      
rateLimit(\"${log.service}:${parsed?.commonProperties?.responseFlags?.toString()}\")
 {\n"
+            + "        rpm 6000\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileSamplerWithIfAndRateLimit() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  sink {\n"
+            + "    sampler {\n"
+            + "      if (parsed?.error) {\n"
+            + "        rateLimit('svc:err') {\n"
+            + "          rpm 6000\n"
+            + "        }\n"
+            + "      } else {\n"
+            + "        rateLimit('svc:ok') {\n"
+            + "          rpm 3000\n"
+            + "        }\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== If blocks in extractor/sink ====================
+
+    @Test
+    void compileIfInsideExtractor() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    if (parsed?.status) {\n"
+            + "      tag 'http.status_code': parsed.status\n"
+            + "    }\n"
+            + "    tag 'response.flag': parsed.flags\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileIfInsideExtractorWithTagCondition() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    if (tag(\"LOG_KIND\") == \"NET_PROFILING\") {\n"
+            + "      service parsed.service as String\n"
+            + "      layer parsed.layer as String\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== Complex production-like rules ====================
+
+    @Test
+    void compileNginxAccessLogRule() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  if (tag(\"LOG_KIND\") == \"NGINX_ACCESS_LOG\") {\n"
+            + "    text {\n"
+            + "      regexp '.+\"(?<request>.+)\"(?<status>\\\\d{3}).+'\n"
+            + "    }\n"
+            + "    extractor {\n"
+            + "      if (parsed.status) {\n"
+            + "        tag 'http.status_code': parsed.status\n"
+            + "      }\n"
+            + "    }\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileSlowSqlProductionRule() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    if (tag(\"LOG_KIND\") == \"SLOW_SQL\") {\n"
+            + "      layer parsed.layer as String\n"
+            + "      service parsed.service as String\n"
+            + "      timestamp parsed.time as String\n"
+            + "      slowSql {\n"
+            + "        id parsed.id as String\n"
+            + "        statement parsed.statement as String\n"
+            + "        latency parsed.query_time as Long\n"
+            + "      }\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileEnvoyAlsAbortRule() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  if (parsed?.response?.responseCode?.value as Integer < 400"
+            + " && 
!parsed?.commonProperties?.responseFlags?.toString()?.trim()) {\n"
+            + "    abort {}\n"
+            + "  }\n"
+            + "  extractor {\n"
+            + "    if (parsed?.response?.responseCode) {\n"
+            + "      tag 'status.code': 
parsed?.response?.responseCode?.value\n"
+            + "    }\n"
+            + "    tag 'response.flag': 
parsed?.commonProperties?.responseFlags\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    // ==================== Else-if chain ====================
+
+    @Test
+    void compileElseIfChain() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  if (parsed.a) {\n"
+            + "    sink {}\n"
+            + "  } else if (parsed.b) {\n"
+            + "    sink {}\n"
+            + "  } else if (parsed.c) {\n"
+            + "    sink {}\n"
+            + "  } else {\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void compileElseIfInSampledTrace() throws Exception {
+        final LalExpression expr = generator.compile(
+            "filter {\n"
+            + "  json {}\n"
+            + "  extractor {\n"
+            + "    sampledTrace {\n"
+            + "      latency parsed.latency as Long\n"
+            + "      if (parsed.client_process.process_id as String != \"\") 
{\n"
+            + "        processId parsed.client_process.process_id as String\n"
+            + "      } else if (parsed.client_process.local as Boolean) {\n"
+            + "        processId ProcessRegistry.generateVirtualLocalProcess("
+            + "parsed.service as String, parsed.serviceInstance as String) as 
String\n"
+            + "      } else {\n"
+            + "        processId ProcessRegistry.generateVirtualRemoteProcess("
+            + "parsed.service as String, parsed.serviceInstance as String, "
+            + "parsed.client_process.address as String) as String\n"
+            + "      }\n"
+            + "      detectPoint parsed.detect_point as String\n"
+            + "    }\n"
+            + "  }\n"
+            + "  sink {}\n"
+            + "}");
+        assertNotNull(expr);
+    }
+
+    @Test
+    void generateSourceElseIfEmitsNestedBranches() {
+        final String source = generator.generateSource(
+            "filter {\n"
+            + "  if (parsed.a) {\n"
+            + "    sink {}\n"
+            + "  } else if (parsed.b) {\n"
+            + "    sink {}\n"
+            + "  } else {\n"
+            + "    sink {}\n"
+            + "  }\n"
+            + "}");
+        // The else-if should produce a nested if inside else
+        assertTrue(source.contains("else"),
+            "Expected else branch but got: " + source);
+        // Both condition branches should appear
+        int ifCount = 0;
+        for (int i = 0; i < source.length() - 2; i++) {
+            if (source.substring(i, i + 3).equals("if ")) {
+                ifCount++;
+            }
+        }
+        assertTrue(ifCount >= 2,
+            "Expected at least 2 if-conditions for else-if chain but got "
+            + ifCount + " in: " + source);
+    }
 }
diff --git 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParserTest.java
 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParserTest.java
index 5c29397f8c..8760ca4f6c 100644
--- 
a/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParserTest.java
+++ 
b/oap-server/analyzer/log-analyzer/src/test/java/org/apache/skywalking/oap/log/analyzer/compiler/LALScriptParserTest.java
@@ -20,6 +20,7 @@ package org.apache.skywalking.oap.log.analyzer.compiler;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -165,6 +166,65 @@ class LALScriptParserTest {
         assertEquals(6000, rateLimit.getRpm());
     }
 
+    @Test
+    void parseInterpolatedRateLimitId() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  sink {\n"
+                + "    sampler {\n"
+                + "      rateLimit(\"${log.service}:${parsed.code}\") {\n"
+                + "        rpm 3000\n"
+                + "      }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.SinkBlock sink =
+            (LALScriptModel.SinkBlock) model.getStatements().get(0);
+        final LALScriptModel.SamplerBlock sampler =
+            (LALScriptModel.SamplerBlock) sink.getStatements().get(0);
+        final LALScriptModel.RateLimitBlock rl =
+            (LALScriptModel.RateLimitBlock) sampler.getContents().get(0);
+
+        assertTrue(rl.isIdInterpolated());
+        assertEquals(3, rl.getIdParts().size());
+
+        // Part 0: expression ${log.service}
+        assertFalse(rl.getIdParts().get(0).isLiteral());
+        assertTrue(rl.getIdParts().get(0).getExpression().isLogRef());
+
+        // Part 1: literal ":"
+        assertTrue(rl.getIdParts().get(1).isLiteral());
+        assertEquals(":", rl.getIdParts().get(1).getLiteral());
+
+        // Part 2: expression ${parsed.code}
+        assertFalse(rl.getIdParts().get(2).isLiteral());
+        assertTrue(rl.getIdParts().get(2).getExpression().isParsedRef());
+    }
+
+    @Test
+    void parsePlainRateLimitIdNotInterpolated() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  sink {\n"
+                + "    sampler {\n"
+                + "      rateLimit('service:error') {\n"
+                + "        rpm 6000\n"
+                + "      }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.SinkBlock sink =
+            (LALScriptModel.SinkBlock) model.getStatements().get(0);
+        final LALScriptModel.SamplerBlock sampler =
+            (LALScriptModel.SamplerBlock) sink.getStatements().get(0);
+        final LALScriptModel.RateLimitBlock rl =
+            (LALScriptModel.RateLimitBlock) sampler.getContents().get(0);
+
+        assertFalse(rl.isIdInterpolated());
+    }
+
     @Test
     void parseIfCondition() {
         final LALScriptModel model = LALScriptParser.parse(
@@ -184,9 +244,286 @@ class LALScriptParserTest {
         assertEquals(2, ifBlock.getThenBranch().size());
     }
 
+    @Test
+    void parseElseIfChain() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  if (parsed.a) {\n"
+                + "    sink {}\n"
+                + "  } else if (parsed.b) {\n"
+                + "    sink {}\n"
+                + "  } else if (parsed.c) {\n"
+                + "    sink {}\n"
+                + "  } else {\n"
+                + "    sink {}\n"
+                + "  }\n"
+                + "}");
+
+        assertEquals(1, model.getStatements().size());
+        final LALScriptModel.IfBlock top =
+            (LALScriptModel.IfBlock) model.getStatements().get(0);
+        assertNotNull(top.getCondition());
+        assertEquals(1, top.getThenBranch().size());
+
+        // else branch contains a nested IfBlock for "else if (parsed.b)"
+        assertEquals(1, top.getElseBranch().size());
+        final LALScriptModel.IfBlock elseIf1 =
+            (LALScriptModel.IfBlock) top.getElseBranch().get(0);
+        assertNotNull(elseIf1.getCondition());
+        assertEquals(1, elseIf1.getThenBranch().size());
+
+        // nested else branch contains another IfBlock for "else if (parsed.c)"
+        assertEquals(1, elseIf1.getElseBranch().size());
+        final LALScriptModel.IfBlock elseIf2 =
+            (LALScriptModel.IfBlock) elseIf1.getElseBranch().get(0);
+        assertNotNull(elseIf2.getCondition());
+        assertEquals(1, elseIf2.getThenBranch().size());
+
+        // innermost else branch is the final else body
+        assertEquals(1, elseIf2.getElseBranch().size());
+        assertInstanceOf(LALScriptModel.SinkBlock.class, 
elseIf2.getElseBranch().get(0));
+    }
+
+    @Test
+    void parseElseIfWithoutFinalElse() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  if (parsed.a) {\n"
+                + "    sink {}\n"
+                + "  } else if (parsed.b) {\n"
+                + "    sink {}\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.IfBlock top =
+            (LALScriptModel.IfBlock) model.getStatements().get(0);
+        assertEquals(1, top.getElseBranch().size());
+        final LALScriptModel.IfBlock elseIf =
+            (LALScriptModel.IfBlock) top.getElseBranch().get(0);
+        assertNotNull(elseIf.getCondition());
+        assertTrue(elseIf.getElseBranch().isEmpty());
+    }
+
     @Test
     void parseSyntaxErrorThrows() {
         assertThrows(IllegalArgumentException.class,
             () -> LALScriptParser.parse("filter {"));
     }
+
+    // ==================== Function call parsing ====================
+
+    @Test
+    void parseTagFunctionCallInCondition() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  if (tag(\"LOG_KIND\") == \"SLOW_SQL\") {\n"
+                + "    sink {}\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.IfBlock ifBlock =
+            (LALScriptModel.IfBlock) model.getStatements().get(0);
+        final LALScriptModel.ComparisonCondition cond =
+            (LALScriptModel.ComparisonCondition) ifBlock.getCondition();
+
+        // Left side should be a function call
+        final LALScriptModel.ValueAccess left = cond.getLeft();
+        assertEquals("tag", left.getFunctionCallName());
+        assertEquals(1, left.getFunctionCallArgs().size());
+        assertEquals("LOG_KIND",
+            left.getFunctionCallArgs().get(0).getValue().getSegments().get(0));
+
+        // Right side should be a string value (parsed as ValueAccess with 
stringLiteral flag)
+        assertInstanceOf(LALScriptModel.ValueAccessConditionValue.class, 
cond.getRight());
+        final LALScriptModel.ValueAccessConditionValue rightVal =
+            (LALScriptModel.ValueAccessConditionValue) cond.getRight();
+        assertTrue(rightVal.getValue().isStringLiteral());
+        assertEquals("SLOW_SQL", rightVal.getValue().getSegments().get(0));
+    }
+
+    @Test
+    void parseTagFunctionCallAsSingleCondition() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  if (tag(\"LOG_KIND\")) {\n"
+                + "    sink {}\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.IfBlock ifBlock =
+            (LALScriptModel.IfBlock) model.getStatements().get(0);
+        final LALScriptModel.ExprCondition cond =
+            (LALScriptModel.ExprCondition) ifBlock.getCondition();
+        assertEquals("tag", cond.getExpr().getFunctionCallName());
+        assertEquals(1, cond.getExpr().getFunctionCallArgs().size());
+    }
+
+    // ==================== Safe navigation parsing ====================
+
+    @Test
+    void parseSafeNavigationFields() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  extractor {\n"
+                + "    service parsed?.response?.service as String\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(0);
+        final LALScriptModel.FieldAssignment field =
+            (LALScriptModel.FieldAssignment) extractor.getStatements().get(0);
+
+        assertTrue(field.getValue().isParsedRef());
+        assertEquals(2, field.getValue().getChain().size());
+        assertTrue(((LALScriptModel.FieldSegment) 
field.getValue().getChain().get(0))
+            .isSafeNav());
+        assertTrue(((LALScriptModel.FieldSegment) 
field.getValue().getChain().get(1))
+            .isSafeNav());
+    }
+
+    @Test
+    void parseSafeNavigationMethods() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  extractor {\n"
+                + "    service parsed?.flags?.toString()?.trim() as String\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(0);
+        final LALScriptModel.FieldAssignment field =
+            (LALScriptModel.FieldAssignment) extractor.getStatements().get(0);
+
+        assertEquals(3, field.getValue().getChain().size());
+        // flags is a safe field
+        assertInstanceOf(LALScriptModel.FieldSegment.class,
+            field.getValue().getChain().get(0));
+        assertTrue(((LALScriptModel.FieldSegment) 
field.getValue().getChain().get(0))
+            .isSafeNav());
+        // toString() is a safe method
+        assertInstanceOf(LALScriptModel.MethodSegment.class,
+            field.getValue().getChain().get(1));
+        assertTrue(((LALScriptModel.MethodSegment) 
field.getValue().getChain().get(1))
+            .isSafeNav());
+        assertEquals("toString",
+            ((LALScriptModel.MethodSegment) 
field.getValue().getChain().get(1)).getName());
+        // trim() is a safe method
+        assertTrue(((LALScriptModel.MethodSegment) 
field.getValue().getChain().get(2))
+            .isSafeNav());
+        assertEquals("trim",
+            ((LALScriptModel.MethodSegment) 
field.getValue().getChain().get(2)).getName());
+    }
+
+    // ==================== Method argument parsing ====================
+
+    @Test
+    void parseMethodWithArguments() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    service ProcessRegistry.generateVirtualLocalProcess("
+                + "parsed.service as String, parsed.instance as String) as 
String\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
+        final LALScriptModel.FieldAssignment field =
+            (LALScriptModel.FieldAssignment) extractor.getStatements().get(0);
+
+        assertTrue(field.getValue().isProcessRegistryRef());
+        assertEquals(1, field.getValue().getChain().size());
+
+        final LALScriptModel.MethodSegment method =
+            (LALScriptModel.MethodSegment) field.getValue().getChain().get(0);
+        assertEquals("generateVirtualLocalProcess", method.getName());
+        assertEquals(2, method.getArguments().size());
+        assertTrue(method.getArguments().get(0).getValue().isParsedRef());
+        assertEquals("String", method.getArguments().get(0).getCastType());
+        assertTrue(method.getArguments().get(1).getValue().isParsedRef());
+        assertEquals("String", method.getArguments().get(1).getCastType());
+    }
+
+    // ==================== Sampled trace parsing ====================
+
+    @Test
+    void parseSampledTrace() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    sampledTrace {\n"
+                + "      latency parsed.latency as Long\n"
+                + "      uri parsed.uri as String\n"
+                + "      reason parsed.reason as String\n"
+                + "      detectPoint parsed.detect_point as String\n"
+                + "      componentId 49\n"
+                + "    }\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
+        final LALScriptModel.SampledTraceBlock st =
+            (LALScriptModel.SampledTraceBlock) 
extractor.getStatements().get(0);
+        assertEquals(5, st.getStatements().size());
+    }
+
+    // ==================== If in extractor/sink parsing ====================
+
+    @Test
+    void parseIfInsideExtractor() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  json {}\n"
+                + "  extractor {\n"
+                + "    if (parsed.status) {\n"
+                + "      tag 'http.status_code': parsed.status\n"
+                + "    }\n"
+                + "    tag 'response.flag': parsed.flags\n"
+                + "  }\n"
+                + "  sink {}\n"
+                + "}");
+
+        final LALScriptModel.ExtractorBlock extractor =
+            (LALScriptModel.ExtractorBlock) model.getStatements().get(1);
+        assertEquals(2, extractor.getStatements().size());
+        assertInstanceOf(LALScriptModel.IfBlock.class, 
extractor.getStatements().get(0));
+        assertInstanceOf(LALScriptModel.TagAssignment.class, 
extractor.getStatements().get(1));
+    }
+
+    @Test
+    void parseIfInsideSink() {
+        final LALScriptModel model = LALScriptParser.parse(
+            "filter {\n"
+                + "  sink {\n"
+                + "    sampler {\n"
+                + "      if (parsed.error) {\n"
+                + "        rateLimit('svc:err') {\n"
+                + "          rpm 6000\n"
+                + "        }\n"
+                + "      } else {\n"
+                + "        rateLimit('svc:ok') {\n"
+                + "          rpm 3000\n"
+                + "        }\n"
+                + "      }\n"
+                + "    }\n"
+                + "  }\n"
+                + "}");
+
+        final LALScriptModel.SinkBlock sink =
+            (LALScriptModel.SinkBlock) model.getStatements().get(0);
+        final LALScriptModel.SamplerBlock sampler =
+            (LALScriptModel.SamplerBlock) sink.getStatements().get(0);
+        // The sampler has one if-block as content
+        assertEquals(1, sampler.getContents().size());
+        assertInstanceOf(LALScriptModel.IfBlock.class, 
sampler.getContents().get(0));
+    }
 }


Reply via email to