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

davsclaus pushed a commit to branch ib
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 91bd1aff041a1f22032352aa589fe662786785c1
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Jan 21 17:07:45 2026 +0100

    CAMEL-22882: simple language - Add support for init block
---
 .../modules/languages/pages/simple-language.adoc   |  72 +++++++++-
 .../camel/language/simple/BaseSimpleParser.java    |   8 +-
 .../language/simple/SimpleExpressionParser.java    |  42 +++++-
 .../language/simple/SimpleInitBlockParser.java     | 154 +++++++++++++++++++++
 .../language/simple/SimplePredicateParser.java     |  20 ++-
 .../camel/language/simple/SimpleTokenizer.java     |  41 +++++-
 .../camel/language/simple/ast/InitExpression.java  | 145 +++++++++++++++++++
 .../{TokenType.java => InitOperatorType.java}      |  40 +++---
 .../language/simple/types/SimpleTokenType.java     |  14 ++
 .../camel/language/simple/types/TokenType.java     |   2 +
 .../camel/language/simple/SimpleInitBlockTest.java |  77 +++++++++++
 .../camel/support/builder/ExpressionBuilder.java   |  25 ++++
 .../camel/support/builder/PredicateBuilder.java    |  11 ++
 13 files changed, 620 insertions(+), 31 deletions(-)

diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index f0d9f7973d8d..e42c575a520c 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -122,8 +122,6 @@ The Simple language has many built-in functions which 
allows access to various p
 
 NOTE: Some functions take 1 or more arguments enclosed in parentheses, and 
arguments separated by comma (ie `${replace('custID','customerId')}`.
 
-TIP:
-
 === Camel Functions
 
 [width="100%",cols="10%,10%,80%",options="header",]
@@ -192,6 +190,7 @@ TIP:
 
 [width="100%",cols="10%,10%,80%",options="header",]
 |====
+|Function |Response Type |Description
 |`collate(size)` | `List` | The collate function iterates the message body and 
groups the data into sub lists of specified size. This can be used with the 
Splitter EIP to split a message body and group/batch the split sub message into 
a group of N sub lists. This method works similar to the collate method in 
Groovy.
 |`distinct(val1,val2,...)` | `Set` | Returns a set of all the values with 
duplicates removed
 |`empty(kind)` | `<T>` | *Deprecated* Use `createEmpty` instead.
@@ -207,6 +206,7 @@ TIP:
 
 [width="100%",cols="10%,10%,80%",options="header",]
 |====
+|Function |Response Type |Description
 |`iif(predicate,trueExp,falseExp`) | `Object` | The inlined if function 
evaluates the predicate expression and returns the value of _trueExp_ if the 
predicate is `true`, otherwise the value of `falseExp` is returned. This 
function is similar to the ternary operator in Java.
 |`isEmpty` | `boolean` | Whether the message body is `null` or empty (list/map 
types are tested if they have 0 elements).
 |`isEmpty(exp)` | `boolean` | Whether the expression is `null` or empty 
(list/map types are tested if they have 0 elements).
@@ -255,6 +255,7 @@ TIP:
 
 [width="100%",cols="10%,10%,80%",options="header",]
 |====
+|Function |Response Type |Description
 |`env.key` | `String` | Refers to the OS system environment variable with the 
given key. For example `env.HOME` to refer to the home directory.
 |`hash(exp,algorithm)` | `String` | Returns a hashed value (string in hex 
decimal) of the given expression. The algorithm can be `SHA-256` (default) or 
`SHA3-256`.
 |`hostname` | `String` | Returns the local hostname (may be `null` if not 
possible to resolve).
@@ -268,8 +269,9 @@ TIP:
 
 === String Functions
 
-0[width="100%",cols="10%,10%,80%",options="header",]
+[width="100%",cols="10%,10%,80%",options="header",]
 |====
+|Function |Response Type |Description
 |`capitalize()` | `String` | Capitalizes the message body as a String value 
(upper case every words)
 |`capitalize(exp)` | `String` | Capitalizes the expression as a String value 
(upper case every words)
 |`concat(exp,exp,separator)` | `String` | Performs a string concat using two 
expressions (message body as default) with optional separator (uses comma by 
default).
@@ -300,6 +302,7 @@ TIP:
 
 [width="100%",cols="10%,10%,80%",options="header",]
 |====
+|Function |Response Type |Description
 |`jq(exp)` | `Object` | When working with JSon data, then this allows using 
the JQ language, for example, to extract data from the message body (in JSon 
format). This requires having camel-jq JAR on the classpath.
 |`jq(input,exp)` | `Object` | Same as `jq(exp)` but to use the _input_ 
expression as the source of the JSon document.
 |`jsonpath(exp)` | `Object` | When working with JSon data, then this allows 
using the JsonPath language, for example, to extract data from the message body 
(in JSon format). This requires having camel-jsonpath JAR on the classpath.
@@ -617,6 +620,69 @@ And of course the `||` is also supported. The sample would 
be:
 simple("${header.title} contains 'Camel' || ${header.type'} == 'gold'")
 -----
 
+== Init Blocks
+
+Starting from *Camel 4.18* you can in the top of your Simple expressions 
declare an _initialization_ block that are used
+to define a set of local variables that are pre-computed, and can be used in 
the following Simple expression. This allows
+to reuse variables, and also avoid making the simple expression complicated 
when having inlined functions, and in general
+making using the simple expression easier to maintain and understand.
+
+The _init block_ is declared as follows:
+
+[source,text]
+----
+$init{
+  // init block
+  // goes here
+  // ...
+}init$
+// here is the regular simple expression
+----
+
+Notice how the block uses the `$init{` ... `}init$` markers to indicate the 
start and end of the block.
+
+Inside the init block, then you can assign local variables in the syntax 
`$$key := ...` where you can then use simple language to declare
+the value of the variable.
+
+In the example below the `foo` variable is assigned a constant value of `Hello 
foo`, while `bar` variable is a string value with the dynamic value
+of the message body.
+
+[source,text]
+----
+$init{
+  $$foo := 'Hello foo'
+  $$bar := 'Hi from ${body}'
+}init$
+----
+
+Yes you can use the full power of the simple language and all its functions, 
for example:
+
+[source,text]
+----
+$init{
+  $$foo := ${upper('Hello $body}'}
+  $$bar := ${iif(${header.code} > 999,'Gold','Silver')}
+}init$
+----
+
+The local variables can then easily be used in the Simple language either 
using the `${variable.foo}` syntax
+to the same shorthand syntax `$$foo`.
+
+For example
+
+[source,text]
+----
+$init{
+  $$greeting := ${upper('Hello $body}'}
+  $$level := ${iif(${header.code} > 999,'Gold','Silver')}
+}init$
+{
+  "message": "$$greeting",
+  "status": "$$level"
+}
+----
+
+
 == OGNL Expression Support
 
 The xref:simple-language.adoc[Simple] and xref:simple-language.adoc[Bean] 
languages support a _OGNL like_ notation for invoking methods (using 
reflection) in a fluent builder like style.
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
index 359f7a979ccb..52d4c98907ae 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
@@ -54,9 +54,12 @@ public abstract class BaseSimpleParser {
     public static final String CODE_END = "]]code@@\"";
 
     protected final CamelContext camelContext;
-    protected final String expression;
+    protected final String originalExpression;
+    // regular simple expression
     protected final List<SimpleToken> tokens = new ArrayList<>();
     protected final List<SimpleNode> nodes = new ArrayList<>();
+    // tokenizer state
+    protected String expression;
     protected SimpleToken token;
     protected int previousIndex;
     protected int index;
@@ -64,8 +67,9 @@ public abstract class BaseSimpleParser {
 
     protected BaseSimpleParser(CamelContext camelContext, String expression, 
boolean allowEscape) {
         this.camelContext = camelContext;
-        this.expression = expression;
+        this.originalExpression = expression;
         this.allowEscape = allowEscape;
+        this.expression = expression;
     }
 
     /**
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
index 7ef8b8271dc7..4e60bc85b640 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
@@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
+import org.apache.camel.language.simple.ast.InitExpression;
 import org.apache.camel.language.simple.ast.LiteralExpression;
 import org.apache.camel.language.simple.ast.LiteralNode;
 import org.apache.camel.language.simple.ast.OtherExpression;
@@ -64,8 +65,26 @@ public class SimpleExpressionParser extends BaseSimpleParser 
{
 
     public Expression parseExpression() {
         try {
+            // parse init block
+            SimpleInitBlockParser initParser
+                    = new SimpleInitBlockParser(camelContext, expression, 
allowEscape, cacheExpression);
+            Expression init = initParser.parseExpression();
+            if (init != null && 
SimpleTokenizer.hasInitBlock(originalExpression)) {
+                this.expression = StringHelper.after(originalExpression, 
SimpleTokenizer.INIT_END);
+                // use $$key as local variable
+                for (String key : initParser.getInitKeys()) {
+                    this.expression = this.expression.replace("$$" + key, 
"${variable." + key + "}");
+                }
+            }
+
+            // parse simple expression
             parseTokens();
-            return doParseExpression();
+            Expression exp = doParseExpression();
+            // include init block in expression
+            if (init != null) {
+                exp = ExpressionBuilder.concatExpression(List.of(init, exp));
+            }
+            return exp;
         } catch (SimpleParserException e) {
             // catch parser exception and turn that into a syntax exceptions
             throw new SimpleIllegalSyntaxException(expression, e.getIndex(), 
e.getMessage(), e);
@@ -142,20 +161,35 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         }
     }
 
+    /**
+     * Second step parsing into an expression
+     */
+    protected Expression doParseInitExpression() {
+        // create and return as a Camel expression
+        List<Expression> expressions = createExpressions();
+        if (expressions.isEmpty()) {
+            return null;
+        } else if (expressions.size() == 1) {
+            return expressions.get(0);
+        } else {
+            return ExpressionBuilder.eval(expressions);
+        }
+    }
+
     /**
      * Removes any ignorable whitespace tokens before and after other 
operators.
      * <p/>
      * During the initial parsing (input -> tokens), then there may be 
excessive whitespace tokens, which can safely be
      * removed, which makes the succeeding parsing easier.
      */
-    private void removeIgnorableWhiteSpaceTokens() {
+    protected void removeIgnorableWhiteSpaceTokens() {
         // white space should be removed before and after the other operator
         List<SimpleToken> toRemove = new ArrayList<>();
         for (int i = 1; i < tokens.size() - 1; i++) {
             SimpleToken prev = tokens.get(i - 1);
             SimpleToken cur = tokens.get(i);
             SimpleToken next = tokens.get(i + 1);
-            if (cur.getType().isOther()) {
+            if (cur.getType().isOther() || cur.getType().isInit()) {
                 if (prev.getType().isWhitespace()) {
                     toRemove.add(prev);
                 }
@@ -228,6 +262,8 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
             }
         } else if (token.getType().isOther()) {
             return new OtherExpression(token);
+        } else if (token.getType().isInit()) {
+            return new InitExpression(token);
         }
 
         // by returning null, we will let the parser determine what to do
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
new file mode 100644
index 000000000000..7e1a4c3ca55c
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.ast.InitExpression;
+import org.apache.camel.language.simple.ast.LiteralNode;
+import org.apache.camel.language.simple.ast.SimpleNode;
+import org.apache.camel.language.simple.types.TokenType;
+import org.apache.camel.util.StringHelper;
+
+class SimpleInitBlockParser extends SimpleExpressionParser {
+
+    private final Set<String> initKeys = new LinkedHashSet<>();
+
+    public SimpleInitBlockParser(CamelContext camelContext, String expression, 
boolean allowEscape,
+                                 Map<String, Expression> cacheExpression) {
+        super(camelContext, expression, allowEscape, cacheExpression);
+    }
+
+    public Set<String> getInitKeys() {
+        return initKeys;
+    }
+
+    @Override
+    public Expression parseExpression() {
+        // parse init block
+        try {
+            SimpleTokenizer.INIT_MODE.set(true);
+            parseInitTokens();
+            return doParseInitExpression();
+        } finally {
+            SimpleTokenizer.INIT_MODE.set(false);
+        }
+    }
+
+    protected List<SimpleNode> parseInitTokens() {
+        // prepare the expression to use for parsing
+        if (SimpleTokenizer.hasInitBlock(originalExpression)) {
+            this.expression = StringHelper.between(originalExpression, 
SimpleTokenizer.INIT_START, SimpleTokenizer.INIT_END);
+        } else {
+            this.expression = "";
+        }
+
+        clear();
+        initKeys.clear();
+
+        // run in init-mode
+        SimpleTokenizer.INIT_MODE.set(true);
+
+        // parse the expression using the following grammar
+        nextToken();
+        while (!token.getType().isEol()) {
+            // an expression supports just template (eg text), functions, 
unary, or other operator
+            initText();
+            templateText();
+            functionText();
+            unaryOperator();
+            otherOperator();
+            nextToken();
+        }
+
+        // now after parsing, we need a bit of work to do, to make it easier 
to turn the tokens
+        // into an ast, and then from the ast, to Camel expression(s).
+        // hence why there are a number of tasks going on below to accomplish 
this
+
+        // remove any ignorable white space tokens
+        removeIgnorableWhiteSpaceTokens();
+        // prepare for any local variables to use $$ syntax in simple 
expression
+
+        // turn the tokens into the ast model
+        parseAndCreateAstModel();
+        // compact and stack blocks (eg function start/end)
+        prepareBlocks();
+        // compact and stack init blocks
+        prepareInitBlocks();
+        // compact and stack unary operators
+        prepareUnaryExpressions();
+        // compact and stack other expressions
+        prepareOtherExpressions();
+
+        return nodes;
+    }
+
+    // special for init blocks that are only available in the top
+    // $$name := <function>
+    // $$name2 := <function>
+    protected boolean initText() {
+        while (!token.getType().isInitVariable() && !token.getType().isEol()) {
+            // skip until we find init variable
+            nextToken();
+        }
+        if (accept(TokenType.initVariable)) {
+            while (!token.getType().isWhitespace() && 
!token.getType().isEol()) {
+                nextToken();
+            }
+            expect(TokenType.whiteSpace);
+            nextToken();
+            expect(TokenType.initOperator);
+            nextToken();
+            expect(TokenType.whiteSpace);
+            return true;
+        }
+
+        return false;
+    }
+
+    protected void prepareInitBlocks() {
+        List<SimpleNode> answer = new ArrayList<>();
+        for (int i = 1; i < nodes.size() - 2; i++) {
+            SimpleNode token = nodes.get(i);
+            if (token instanceof InitExpression ie) {
+                SimpleNode prev = nodes.get(i - 1);
+                SimpleNode next = nodes.get(i + 1);
+                ie.acceptLeftNode(prev);
+                ie.acceptRightNode(next);
+                answer.add(ie);
+
+                // remember which init variables we have created
+                if (prev instanceof LiteralNode ln) {
+                    String key = StringHelper.after(ln.getText(), "$$");
+                    if (key != null) {
+                        key = key.trim();
+                        initKeys.add(key);
+                    }
+                }
+            }
+        }
+        nodes.clear();
+        nodes.addAll(answer);
+    }
+
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
index a0f674adaa26..0f2143e797d9 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
@@ -53,6 +53,7 @@ import org.apache.camel.language.simple.types.SimpleToken;
 import org.apache.camel.language.simple.types.TokenType;
 import org.apache.camel.support.ExpressionToPredicateAdapter;
 import org.apache.camel.support.builder.PredicateBuilder;
+import org.apache.camel.util.StringHelper;
 
 import static org.apache.camel.support.ObjectHelper.isFloatingNumber;
 import static org.apache.camel.support.ObjectHelper.isNumber;
@@ -82,8 +83,25 @@ public class SimplePredicateParser extends BaseSimpleParser {
 
     public Predicate parsePredicate() {
         try {
+            // parse init block
+            SimpleInitBlockParser initParser
+                    = new SimpleInitBlockParser(camelContext, expression, 
allowEscape, cacheExpression);
+            Expression init = initParser.parseExpression();
+            if (init != null && 
SimpleTokenizer.hasInitBlock(originalExpression)) {
+                this.expression = StringHelper.after(originalExpression, 
SimpleTokenizer.INIT_END);
+                // use $$key as local variable
+                for (String key : initParser.getInitKeys()) {
+                    this.expression = this.expression.replace("$$" + key, 
"${variable." + key + "}");
+                }
+            }
+
             parseTokens();
-            return doParsePredicate();
+            Predicate pre = doParsePredicate();
+            // include init block in expression
+            if (init != null) {
+                pre = PredicateBuilder.and(PredicateBuilder.alwaysTrue(init), 
pre);
+            }
+            return pre;
         } catch (SimpleParserException e) {
             // catch parser exception and turn that into a syntax exceptions
             throw new SimpleIllegalSyntaxException(expression, e.getIndex(), 
e.getMessage(), e);
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
index 0258ce18b8ff..5d6b875e7177 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.language.simple;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 import org.apache.camel.language.simple.types.SimpleToken;
 import org.apache.camel.language.simple.types.SimpleTokenType;
 import org.apache.camel.language.simple.types.TokenType;
@@ -27,15 +29,21 @@ import org.apache.camel.util.ObjectHelper;
 public final class SimpleTokenizer {
 
     // keep this number in sync with tokens list
-    private static final int NUMBER_OF_TOKENS = 50;
+    private static final int NUMBER_OF_TOKENS = 52;
 
     private static final SimpleTokenType[] KNOWN_TOKENS = new 
SimpleTokenType[NUMBER_OF_TOKENS];
 
-    // optimise to be able to quick check for start functions
+    // optimize to be able to quick check for start functions
     private static final String[] FUNCTION_START = new String[] { "${", 
"$simple{" };
-    // optimise to be able to quick check for end function
+    // optimize to be able to quick check for end function
     private static final String FUNCTION_END = "}";
 
+    // TODO: what symbols to use for init block?
+    // optimize to be able to quick check for init block
+    public static final String INIT_START = "$init{";
+    // optimize to be able to quick check for init block
+    public static final String INIT_END = "}init$";
+
     static {
         // add known tokens
         KNOWN_TOKENS[0] = new SimpleTokenType(TokenType.functionStart, 
FUNCTION_START[0]);
@@ -96,16 +104,35 @@ public final class SimpleTokenizer {
         KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, 
"&&");
         KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator, 
"||");
 
+        // init
+        KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.initOperator, ":=");
+        KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.initVariable, "$$");
+
         //binary operator
         // it is added as the last item because unary -- has the priority
         // if unary not found it is highly possible - operator is run into.
-        KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.minusValue, "-");
+        KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-");
     }
 
+    static final AtomicBoolean INIT_MODE = new AtomicBoolean();
+
     private SimpleTokenizer() {
         // static methods
     }
 
+    /**
+     * Does the expression include a simple init block.
+     *
+     * @param  expression the expression
+     * @return            <tt>true</tt> if init block exists
+     */
+    public static boolean hasInitBlock(String expression) {
+        if (expression != null) {
+            return expression.startsWith(INIT_START) && 
expression.contains(INIT_END);
+        }
+        return false;
+    }
+
     /**
      * Does the expression include a simple function.
      *
@@ -182,6 +209,12 @@ public final class SimpleTokenizer {
         String text = expression.substring(index);
         for (int i = 0; i < NUMBER_OF_TOKENS; i++) {
             SimpleTokenType token = KNOWN_TOKENS[i];
+
+            // init tokens is only valid if using init mode of tokenizer
+            boolean init = token.isInit() || token.isInitVariable();
+            if (init && !INIT_MODE.get()) {
+                break;
+            }
             if (acceptType(token.getType(), filters)
                     && acceptToken(token, text, expression, index)) {
                 return new SimpleToken(token, index);
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java
new file mode 100644
index 000000000000..24858d96a743
--- /dev/null
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple.ast;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.BaseSimpleParser;
+import org.apache.camel.language.simple.types.InitOperatorType;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.language.simple.types.SimpleToken;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
+/**
+ * Represents other init block expression in the AST.
+ */
+public class InitExpression extends BaseSimpleNode {
+
+    private final InitOperatorType operator;
+    private SimpleNode left;
+    private SimpleNode right;
+
+    public InitExpression(SimpleToken token) {
+        super(token);
+        operator = InitOperatorType.asOperator(token.getText());
+    }
+
+    @Override
+    public String toString() {
+        return left + " " + token.getText() + " " + right;
+    }
+
+    public boolean acceptLeftNode(SimpleNode lef) {
+        this.left = lef;
+        return true;
+    }
+
+    public boolean acceptRightNode(SimpleNode right) {
+        this.right = right;
+        return true;
+    }
+
+    public InitOperatorType getOperator() {
+        return operator;
+    }
+
+    public SimpleNode getLeft() {
+        return left;
+    }
+
+    public SimpleNode getRight() {
+        return right;
+    }
+
+    @Override
+    public Expression createExpression(CamelContext camelContext, String 
expression) {
+        ObjectHelper.notNull(left, "left node", this);
+        ObjectHelper.notNull(right, "right node", this);
+
+        // the expression parser does not parse literal text into 
single/double quote tokens
+        // so we need to manually remove leading quotes from the literal text 
when using the other operators
+        final Expression leftExp = left.createExpression(camelContext, 
expression);
+        if (right instanceof LiteralExpression le) {
+            String text = le.getText();
+            String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+            if (!changed.equals(text)) {
+                le.replaceText(changed);
+            }
+        }
+        final Expression rightExp = right.createExpression(camelContext, 
expression);
+
+        if (operator == InitOperatorType.ASSIGNMENT) {
+            return createAssignmentExpression(camelContext, leftExp, rightExp);
+        }
+
+        throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
+    }
+
+    private Expression createAssignmentExpression(
+            final CamelContext camelContext, final Expression leftExp, final 
Expression rightExp) {
+        return new Expression() {
+            @Override
+            public <T> T evaluate(Exchange exchange, Class<T> type) {
+                String name = leftExp.evaluate(exchange, String.class);
+                name = name.trim();
+                name = StringHelper.after(name, "$$", name);
+                Object value = rightExp.evaluate(exchange, Object.class);
+                exchange.setVariable(name, value);
+                return null;
+            }
+
+            @Override
+            public String toString() {
+                return left + " " + token.getText() + " " + right;
+            }
+        };
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
+    }
+
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        ObjectHelper.notNull(left, "left node", this);
+        ObjectHelper.notNull(right, "right node", this);
+
+        // the expression parser does not parse literal text into 
single/double quote tokens
+        // so we need to manually remove leading quotes from the literal text 
when using the other operators
+        final String leftExp = left.createCode(camelContext, expression);
+        if (right instanceof LiteralExpression le) {
+            String text = le.getText();
+            // must be in double quotes to be a String type
+            String changed = StringHelper.removeLeadingAndEndingQuotes(text);
+            changed = StringQuoteHelper.doubleQuote(changed);
+            if (!changed.equals(text)) {
+                le.replaceText(changed);
+            }
+        }
+        final String rightExp = right.createCode(camelContext, expression);
+
+        if (operator == InitOperatorType.ASSIGNMENT) {
+            return "initAssignment(exchange, " + leftExp + ", " + rightExp + 
")";
+        }
+
+        throw new SimpleParserException("Unknown other operator " + operator, 
token.getIndex());
+    }
+
+}
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
similarity index 60%
copy from 
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
copy to 
core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
index e737ab809b09..1ebccdd6a799 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java
@@ -17,25 +17,29 @@
 package org.apache.camel.language.simple.types;
 
 /**
- * Classifications of known token types.
+ * Types of init operators supported
  */
-public enum TokenType {
+public enum InitOperatorType {
 
-    whiteSpace,
-    character,
-    booleanValue,
-    numericValue,
-    nullValue,
-    singleQuote,
-    doubleQuote,
-    minusValue,
-    escape,
-    functionStart,
-    functionEnd,
-    binaryOperator,
-    otherOperator,
-    unaryOperator,
-    logicalOperator,
-    eol
+    ASSIGNMENT;
+
+    public static InitOperatorType asOperator(String text) {
+        if (":=".equals(text)) {
+            return ASSIGNMENT;
+        }
+        throw new IllegalArgumentException("Operator not supported: " + text);
+    }
+
+    public static String getOperatorText(InitOperatorType operator) {
+        if (operator == ASSIGNMENT) {
+            return ":=";
+        }
+        return "";
+    }
+
+    @Override
+    public String toString() {
+        return getOperatorText(this);
+    }
 
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
index 7663e1d21cd3..4573597f63e6 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java
@@ -124,6 +124,20 @@ public final class SimpleTokenType {
         return type == TokenType.logicalOperator;
     }
 
+    /**
+     * Whether the type is init operator
+     */
+    public boolean isInit() {
+        return type == TokenType.initOperator;
+    }
+
+    /**
+     * Whether the type is init variable
+     */
+    public boolean isInitVariable() {
+        return type == TokenType.initVariable;
+    }
+
     /**
      * Whether the type is a null value
      */
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
index e737ab809b09..46e16a57e687 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java
@@ -36,6 +36,8 @@ public enum TokenType {
     otherOperator,
     unaryOperator,
     logicalOperator,
+    initOperator,
+    initVariable,
     eol
 
 }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
new file mode 100644
index 000000000000..5867a0655656
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.language.simple;
+
+import org.apache.camel.LanguageTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class SimpleInitBlockTest extends LanguageTestSupport {
+
+    private static final String INIT = """
+            $init{
+              // this is a java like comment
+              $$sum := ${sum(${header.lines},100)}
+
+              $$sku := ${iif(${body} contains 'Camel',123,999)}
+              ## and here is also a yaml like comment
+            }init$
+            orderId=$$sku,total=$$sum
+            """;
+
+    private static final String INIT2 = """
+            $init{
+              // this is a java like comment
+              $$sum := ${sum(${header.lines},100)}
+
+              $$sku := ${iif(${body} contains 'Camel',123,999)}
+              ## and here is also a yaml like comment
+            }init$
+            $$sum > 200 && $$sku != 999
+            """;
+
+    @Test
+    public void testInitBlockExpression() throws Exception {
+        exchange.getMessage().setBody("Hello Camel");
+        exchange.getMessage().setHeader("lines", "75,33");
+
+        assertExpression(exchange, INIT, "\norderId=123,total=208\n");
+    }
+
+    @Test
+    public void testInitBlockPredicate() throws Exception {
+        exchange.getMessage().setBody("Hello Camel");
+        exchange.getMessage().setHeader("lines", "75,33");
+        assertPredicate(exchange, INIT2, true);
+
+        exchange.getMessage().setBody("Hello Camel");
+        exchange.getMessage().setHeader("lines", "3,5");
+        assertPredicate(exchange, INIT2, false);
+
+        exchange.getMessage().setBody("Hello World");
+        exchange.getMessage().setHeader("lines", "75,99");
+        assertPredicate(exchange, INIT2, false);
+
+        exchange.getMessage().setBody("Hello World");
+        exchange.getMessage().setHeader("lines", "3,5");
+        assertPredicate(exchange, INIT2, false);
+    }
+
+    @Override
+    protected String getLanguageName() {
+        return "simple";
+    }
+}
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index 669f05ab5a1f..84d9ba670f14 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -2278,6 +2278,31 @@ public class ExpressionBuilder {
         };
     }
 
+    public static Expression eval(List<Expression> expressions) {
+        return new ExpressionAdapter() {
+            @Override
+            public void init(CamelContext context) {
+                super.init(context);
+                for (Expression exp : expressions) {
+                    exp.init(context);
+                }
+            }
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                for (Expression exp : expressions) {
+                    exp.evaluate(exchange, Object.class);
+                }
+                return null;
+            }
+
+            @Override
+            public String toString() {
+                return "eval";
+            }
+        };
+    }
+
     /**
      * Returns an Expression for the inbound message id
      */
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
 
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
index d87ee46bdc10..213a91ab7d29 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java
@@ -37,6 +37,17 @@ import static org.apache.camel.util.ObjectHelper.notNull;
  * A helper class for working with predicates
  */
 public class PredicateBuilder {
+
+    /**
+     * Executes the expression and always return true
+     */
+    public static Predicate alwaysTrue(final Expression expression) {
+        return exchange -> {
+            expression.evaluate(exchange, Object.class);
+            return true;
+        };
+    }
+
     /**
      * Converts the given expression into an {@link Predicate}
      */


Reply via email to