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

davsclaus pushed a commit to branch fix/CAMEL-24962
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 3bd9ca7e4aadb5b53dc9396d9e58afe5bc82e10a
Author: Claus Ibsen <[email protected]>
AuthorDate: Wed Sep 23 18:59:58 2026 +0200

    CAMEL-24965: simple - fix init block comments, one-liners, CRLF, predicates 
and local functions
    
    - a // comment in the init block is ignored, so a commented out assignment 
no longer runs
    - several statements on one line work ($init{ $a := 1; $b := 2; }init$), 
and Windows line endings
    - a predicate whose init block only defines functions ($f ~:= ...) parses, 
and $f() works in predicates
    - a custom function from an init block is not shared through the cache with 
another expression
      that defines a function with the same name
    - $fn('x') in a chain keeps its argument
    - errors in the init block point at the right place
    - sync the catalog copies of the simple docs and metadata
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../apache/camel/catalog/docs/simple-advanced.adoc |   2 +-
 .../camel/catalog/docs/simple-functions.adoc       |   7 +-
 .../org/apache/camel/catalog/languages/simple.json |   2 +-
 .../modules/languages/pages/simple-advanced.adoc   |   2 +-
 .../language/simple/MiscExpressionBuilder.java     |   1 -
 .../language/simple/SimpleExpressionParser.java    |  26 +----
 .../language/simple/SimpleInitBlockParser.java     | 128 +++++++++++++++------
 .../language/simple/SimplePredicateParser.java     |  20 +---
 .../language/simple/StringExpressionBuilder.java   |   1 -
 .../camel/language/simple/ast/ChainExpression.java |   2 +-
 .../simple/ast/SimpleFunctionExpression.java       |   4 +-
 .../simple/SimpleInitBlockEdgeCasesTest.java       | 111 ++++++++++++++++++
 12 files changed, 220 insertions(+), 86 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
index daed623fcfef..1e22bcd72c3e 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-advanced.adoc
@@ -29,7 +29,7 @@ Notice how the block uses the `$init{` ... `}init$` markers 
to indicate the star
 Inside the init block, then you can assign local variables in the syntax `$key 
:= <statement>;` where you can then use simple language to compute
 the value of the variable.
 
-IMPORTANT: Each statement must end with semicolon and new-line (`;\n`). You 
can only have 1 statement per line.
+IMPORTANT: Each statement must end with a semicolon (`;`). Statements are 
usually one per line, but several statements can also be on one line when 
separated by a space, such as `$init{ $a := 1; $b := 2; }init$`.
 This makes the init block more similar to Java programming language,
 and it was also necessary to make this work for the internal simple parser 
used by Camel.
 
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
index 525d77ecd2f1..41d80e7bbcf8 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/simple-functions.adoc
@@ -520,7 +520,7 @@ The string functions is various functions to work with 
values that are String an
 |`lowercase(exp)` | `String` | Lowercases the expression
 |`normalizeWhitespace()` | `String` | Normalizes the whitespace in the message 
body by cleaning up excess whitespaces.
 |`normalizeWhitespace(exp)` | `String` | Normalizes the whitespace in the 
expression by cleaning up excess whitespaces.
-|`pad(exp,width,separator)` | `String` | Pads the expression with extra 
padding if necessary, according the total width The separator is by default a 
space. If the width is negative then padding to the right, otherwise to the 
left.
+|`pad(exp,width,separator)` | `String` | Pads the expression with extra 
padding if necessary, according to the total width. The separator is by default 
a space. A positive width pads to the right (after the value), and a negative 
width pads to the left (before the value).
 |`replace(from,to)` | `String` | Replace all the string values in the message 
body. To make it easier to replace single and double quotes, then you can use 
XML escaped values `\&quot;` as double quote, `\&apos;` as single quote, and 
`\&empty;` as empty value.
 |`replace(from,to,exp)` | `String` | Replace all the string values in the 
given expression. To make it easier to replace single and double quotes, then 
you can use XML escaped values `\&quot;` as double quote, `\&apos;` as single 
quote, and `\&empty;` as empty value.
 |`safeQuote()` | `String` | Returns the message body safely quoted if needed
@@ -636,13 +636,16 @@ If the value is currently in single quote, then this will 
be converted to double
 
 If the message body contains `Hello World` then `${quote()}` returns `"Hello 
World"`.
 And using `${quote('Hi from me')}` then `"Hi from me"` is returned.
+The `quote` function does not escape the value, so a value with double quotes 
or backslashes is not a valid JSON string;
+use `safeQuote` (or `escape(json)`) for JSON.
 
 The `unquote` function removes any surrounding single or double quotes from a 
value.
 If the body contains `'hello'` then `${unquote()}` returns `hello` (without 
the quotes).
 
 The `safeQuote` function is quoting the value depending on the value type
 (uses the same logic as `kindOfType` function). In essence values that are 
null, boolean or numbers are not quoted,
-while everything else is. The `safeQuote` function is useful when working with 
JSON data.
+while everything else is. When quoted, double quotes, backslashes and control 
characters in the value are escaped,
+so the result is a valid JSON string. The `safeQuote` function is useful when 
working with JSON data.
 
 For example when doing JSON to JSON mapping you can extract values form the 
source document,
 to be included in the output, but the values may or may not need to be quoted. 
Then you can use the `~>` chain operator
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
index a1df2fe174f9..dfc3d543b094 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json
@@ -131,7 +131,7 @@
     "not": { "index": 102, "kind": "function", "displayName": "Not", "group": 
"condition", "label": "condition", "required": false, "javaType": "boolean", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Evaluates the predicate and returns the 
opposite.", "ognl": false, "suffix": "}" },
     "null": { "index": 103, "kind": "function", "displayName": "Null", 
"group": "other", "label": "other", "required": false, "javaType": "Object", 
"prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, 
"secret": false, "description": "Returns a null value", "ognl": false, 
"suffix": "}", "examples": [ "${null} -> null" ] },
     "originalBody": { "index": 104, "kind": "function", "displayName": 
"Original Body", "group": "core", "label": "core", "required": false, 
"javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": 
"", "autowired": false, "secret": false, "description": "The original incoming 
body (only available if allowUseOriginalMessage=true).", "ognl": false, 
"suffix": "}" },
-    "pad(exp,width,separator)": { "index": 105, "kind": "function", 
"displayName": "Pad String", "group": "string", "label": "string", "required": 
false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according the the total 
width. The separator is by default a space. If the width is negative then 
padding to the right, otherwise to the left.", "ognl [...]
+    "pad(exp,width,separator)": { "index": 105, "kind": "function", 
"displayName": "Pad String", "group": "string", "label": "string", "required": 
false, "javaType": "String", "prefix": "${", "deprecated": false, 
"deprecationNote": "", "autowired": false, "secret": false, "description": 
"Pads the expression with extra padding if necessary, according to the total 
width. The separator is by default a space. A positive width pads to the right 
(after the value), and a negative width pads to  [...]
     "pretty(exp)": { "index": 106, "kind": "function", "displayName": "Pretty 
Print", "group": "xml", "label": "json,xml", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the expression to 
a String, and attempts to pretty print if JSon or XML, otherwise the expression 
is returned as the String value.", "ognl": false, "suffix": "}", "params": [ { 
"name": "exp", "javaType": "O [...]
     "prettyBody": { "index": 107, "kind": "function", "displayName": "Pretty 
Body", "group": "xml", "label": "json,xml", "required": false, "javaType": 
"String", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Converts the body to a 
String, and attempts to pretty print if JSon or XML; otherwise the body is 
returned as the String value.", "ognl": false, "suffix": "}" },
     "sort(exp,reverse)": { "index": 108, "kind": "function", "displayName": 
"Sort", "group": "collection", "label": "collection", "required": false, 
"javaType": "List", "prefix": "${", "deprecated": false, "deprecationNote": "", 
"autowired": false, "secret": false, "description": "Sorts the message body or 
expression in natural order", "ognl": false, "suffix": "}", "params": [ { 
"name": "exp", "javaType": "Object", "required": false, "defaultValue": "body", 
"description": "The expression [...]
diff --git 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
index daed623fcfef..1e22bcd72c3e 100644
--- 
a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
+++ 
b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-advanced.adoc
@@ -29,7 +29,7 @@ Notice how the block uses the `$init{` ... `}init$` markers 
to indicate the star
 Inside the init block, then you can assign local variables in the syntax `$key 
:= <statement>;` where you can then use simple language to compute
 the value of the variable.
 
-IMPORTANT: Each statement must end with semicolon and new-line (`;\n`). You 
can only have 1 statement per line.
+IMPORTANT: Each statement must end with a semicolon (`;`). Statements are 
usually one per line, but several statements can also be on one line when 
separated by a space, such as `$init{ $a := 1; $b := 2; }init$`.
 This makes the init block more similar to Java programming language,
 and it was also necessary to make this work for the internal simple parser 
used by Camel.
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
index 5adb2c781128..6e497e5dc05f 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/MiscExpressionBuilder.java
@@ -27,7 +27,6 @@ import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
-import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicReference;
 
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 eacaf07a7e8e..93809e9e5f08 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
@@ -17,7 +17,6 @@
 package org.apache.camel.language.simple;
 
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
@@ -81,31 +80,8 @@ public class SimpleExpressionParser extends BaseSimpleParser 
{
             if (SimpleInitBlockTokenizer.hasInitBlock(expression)) {
                 SimpleInitBlockParser initParser
                         = new SimpleInitBlockParser(camelContext, expression, 
allowEscape, skipFileFunctions, cacheExpression);
-                // the init block should be parsed in predicate mode as that 
is needed to fully parse with all the operators and functions
                 init = initParser.parseExpression();
-                String part = StringHelper.after(expression, 
SimpleInitBlockTokenizer.INIT_END);
-                if (part.startsWith("\n")) {
-                    // skip newline after ending init block
-                    part = part.substring(1);
-                }
-                this.expression = part;
-                // use $$key as local variable in the expression afterwards.
-                // Sort by descending length so a longer key (e.g. "$ab") is 
replaced before any
-                // shorter prefix (e.g. "$a"), preventing "$ab" from becoming 
"${variable.a}b".
-                List<String> sortedKeys = new 
ArrayList<>(initParser.getInitKeys());
-                
sortedKeys.sort(Comparator.comparingInt(String::length).reversed());
-                for (String key : sortedKeys) {
-                    this.expression = this.expression.replace("$" + key, 
"${variable." + key + "}");
-                }
-                // use $$key() as local function in the expression afterwards
-                List<String> sortedFunctions = new 
ArrayList<>(initParser.getInitFunctions());
-                
sortedFunctions.sort(Comparator.comparingInt(String::length).reversed());
-                for (String key : sortedFunctions) {
-                    // no-arg functions
-                    this.expression = this.expression.replace("$" + key + 
"()", "${function(" + key + ")}");
-                    // arg functions
-                    this.expression = this.expression.replace("${" + key + 
"(", "${function(" + key + ",");
-                }
+                this.expression = 
initParser.rewriteExpressionAfterInitBlock(expression);
             }
 
             // parse simple expression
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
index 8bbc06dfdeff..75a509ce4c19 100644
--- 
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
@@ -17,6 +17,7 @@
 package org.apache.camel.language.simple;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -30,6 +31,7 @@ import org.apache.camel.language.simple.ast.LiteralExpression;
 import org.apache.camel.language.simple.ast.LiteralNode;
 import org.apache.camel.language.simple.ast.SimpleNode;
 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.language.simple.types.TokenType;
 import org.apache.camel.util.StringHelper;
@@ -42,10 +44,93 @@ class SimpleInitBlockParser extends SimpleExpressionParser {
     public SimpleInitBlockParser(CamelContext camelContext, String expression, 
boolean allowEscape, boolean skipFileFunctions,
                                  Map<String, Expression> cacheExpression) {
         super(camelContext,
-              StringHelper.between(expression, 
SimpleInitBlockTokenizer.INIT_START, SimpleInitBlockTokenizer.INIT_END),
+              normalize(StringHelper.between(expression, 
SimpleInitBlockTokenizer.INIT_START,
+                      SimpleInitBlockTokenizer.INIT_END)),
               allowEscape, skipFileFunctions, cacheExpression, new 
SimpleInitBlockTokenizer());
     }
 
+    /**
+     * Prepares the text of the init block for the tokenizer, keeping every 
character at its index so errors point at
+     * the right place.
+     * <ul>
+     * <li>a // comment (outside quotes and functions) is blanked out, so a 
commented out assignment does not run</li>
+     * <li>Windows line endings are turned into unix line endings</li>
+     * <li>a ; followed by a space ends the statement as a new line does, so 
one-liners such as {@code $init{ $a := 1;
+     * $b := 2; }init$} work</li>
+     * </ul>
+     */
+    static String normalize(String text) {
+        if (text == null) {
+            return null;
+        }
+        char[] chars = text.toCharArray();
+        boolean single = false;
+        boolean dubble = false;
+        int depth = 0;
+        for (int i = 0; i < chars.length; i++) {
+            char c = chars[i];
+            char next = i + 1 < chars.length ? chars[i + 1] : 0;
+            if (c == '\r' && next == '\n') {
+                // keep the new line right after the statement end (;)
+                chars[i] = '\n';
+                chars[i + 1] = ' ';
+            } else if (c == '\'' && !dubble) {
+                single = !single;
+            } else if (c == '"' && !single) {
+                dubble = !dubble;
+            } else if (single || dubble) {
+                continue;
+            } else if (c == '$' && next == '{') {
+                depth++;
+            } else if (c == '}' && depth > 0) {
+                depth--;
+            } else if (depth == 0 && c == '/' && next == '/') {
+                // blank out the comment until the end of the line
+                while (i < chars.length && chars[i] != '\n') {
+                    chars[i++] = ' ';
+                }
+            } else if (depth == 0 && c == ';' && (next == ' ' || next == 
'\t')) {
+                chars[i + 1] = '\n';
+            }
+        }
+        String answer = new String(chars);
+        if (answer.stripTrailing().endsWith(";")) {
+            // the last statement ends without a new line
+            answer = answer.stripTrailing() + "\n";
+        }
+        return answer;
+    }
+
+    /**
+     * The expression after the init block, where the init variables ($key) 
and init functions ($key() and ${key(...)})
+     * are rewritten to ${variable.key} and ${function(key)}.
+     */
+    String rewriteExpressionAfterInitBlock(String expression) {
+        String answer = StringHelper.after(expression, 
SimpleInitBlockTokenizer.INIT_END);
+        if (answer.startsWith("\r\n")) {
+            // skip newline after ending init block
+            answer = answer.substring(2);
+        } else if (answer.startsWith("\n")) {
+            answer = answer.substring(1);
+        }
+        // Sort by descending length so a longer key (e.g. "$ab") is replaced 
before any
+        // shorter prefix (e.g. "$a"), preventing "$ab" from becoming 
"${variable.a}b".
+        List<String> sortedKeys = new ArrayList<>(initKeys);
+        sortedKeys.sort(Comparator.comparingInt(String::length).reversed());
+        for (String key : sortedKeys) {
+            answer = answer.replace("$" + key, "${variable." + key + "}");
+        }
+        List<String> sortedFunctions = new ArrayList<>(initFunctions);
+        
sortedFunctions.sort(Comparator.comparingInt(String::length).reversed());
+        for (String key : sortedFunctions) {
+            // no-arg functions
+            answer = answer.replace("$" + key + "()", "${function(" + key + 
")}");
+            // arg functions
+            answer = answer.replace("${" + key + "(", "${function(" + key + 
",");
+        }
+        return answer;
+    }
+
     public Set<String> getInitKeys() {
         return initKeys;
     }
@@ -60,9 +145,14 @@ class SimpleInitBlockParser extends SimpleExpressionParser {
 
     @Override
     public Expression parseExpression() {
-        // parse init block
-        parseInitTokens();
-        return doParseInitExpression();
+        try {
+            // parse init block
+            parseInitTokens();
+            return doParseInitExpression();
+        } catch (SimpleParserException e) {
+            // the index is within the init block, so make it point into the 
whole expression
+            throw new SimpleParserException(e.getMessage(), e.getIndex() + 
SimpleInitBlockTokenizer.INIT_START.length());
+        }
     }
 
     protected List<SimpleNode> parseInitTokens() {
@@ -201,34 +291,4 @@ class SimpleInitBlockParser extends SimpleExpressionParser 
{
         return text;
     }
 
-    @Deprecated
-    protected void prepareInitBlocks() {
-        List<SimpleNode> answer = new ArrayList<>();
-        for (int i = 1; i < nodes.size() - 1; i++) {
-            SimpleNode token = nodes.get(i);
-            if (token instanceof InitBlockExpression 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();
-                        if 
(ie.getOperator().equals(InitOperatorType.CHAIN_ASSIGNMENT)) {
-                            initFunctions.add(key);
-                        } else {
-                            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 6cea00cfba2b..c9bde8c99ae8 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
@@ -19,7 +19,6 @@ package org.apache.camel.language.simple;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.Deque;
 import java.util.Iterator;
 import java.util.List;
@@ -58,7 +57,6 @@ 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;
 
 /**
  * A parser to parse simple language as a Camel {@link Predicate}
@@ -91,22 +89,8 @@ public class SimplePredicateParser extends BaseSimpleParser {
                 SimpleInitBlockParser initParser
                         = new SimpleInitBlockParser(camelContext, expression, 
allowEscape, skipFileFunctions, cacheExpression);
                 init = initParser.parseExpression();
-                if (init != null) {
-                    String part = StringHelper.after(expression, 
SimpleInitBlockTokenizer.INIT_END);
-                    if (part.startsWith("\n")) {
-                        // skip newline after ending init block
-                        part = part.substring(1);
-                    }
-                    this.expression = part;
-                    // use $$key as local variable in the expression 
afterwards.
-                    // Sort by descending length so a longer key (e.g. "$ab") 
is replaced before any
-                    // shorter prefix (e.g. "$a"), preventing "$ab" from 
becoming "${variable.a}b".
-                    List<String> sortedKeys = new 
ArrayList<>(initParser.getInitKeys());
-                    
sortedKeys.sort(Comparator.comparingInt(String::length).reversed());
-                    for (String key : sortedKeys) {
-                        this.expression = this.expression.replace("$" + key, 
"${variable." + key + "}");
-                    }
-                }
+                // the init block may only define functions ($f ~:= ...) and 
then there is no init expression
+                this.expression = 
initParser.rewriteExpressionAfterInitBlock(expression);
             }
 
             parseTokens();
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java
index 2c66feb6d0eb..e6bbf4d0bed6 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/StringExpressionBuilder.java
@@ -31,7 +31,6 @@ import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.support.builder.PredicateBuilder;
 import org.apache.camel.util.EscapeHelper;
 import org.apache.camel.util.IOHelper;
-import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.util.StringQuoteHelper;
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
index b383d0772cd9..34847991d205 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/ChainExpression.java
@@ -134,7 +134,7 @@ public class ChainExpression extends BaseSimpleNode {
                 if 
(PluginHelper.getSimpleFunctionRegistry(camelContext).getCustomFunctionNames().contains(key))
 {
                     String changed = text.replace("$" + key + "()", 
"function(" + key + ")");
                     if (changed.equals(text)) {
-                        changed = text.replace("$" + key + "(", "function(" + 
key);
+                        changed = text.replace("$" + key + "(", "function(" + 
key + ",");
                     }
                     le.replaceText(changed);
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index 0b8f61958605..dc5204024177 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -65,7 +65,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             if (answer != null) {
                 answer.init(camelContext);
             }
-            if (cacheExpression != null && answer != null) {
+            // a custom function from an init block ($f ~:= ...) is bound to 
the definition of that block,
+            // so it is not shared with another expression that defines a 
function with the same name
+            if (cacheExpression != null && answer != null && 
!function.startsWith("function(")) {
                 cacheExpression.put(function, answer);
             }
         }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java
new file mode 100644
index 000000000000..31f7fe420f56
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockEdgeCasesTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * CAMEL-24965: edge cases of the init block.
+ */
+public class SimpleInitBlockEdgeCasesTest extends LanguageTestSupport {
+
+    @Override
+    protected String getLanguageName() {
+        return "simple";
+    }
+
+    @Test
+    public void testCommentedOutAssignmentDoesNotRun() {
+        assertExpression("""
+                $init{
+                  // $foo := 'x';
+                  $bar := 'y';
+                }init$
+                [${variable.foo}][$bar]""", "[][y]");
+    }
+
+    @Test
+    public void testCommentWithDollar() {
+        assertExpression("""
+                $init{
+                  // costs $5
+                  $bar := 'y'; // a trailing comment
+                }init$
+                [$bar]""", "[y]");
+    }
+
+    @Test
+    public void testCommentInsideQuotesIsText() {
+        assertExpression("""
+                $init{
+                  $url := 'http://camel.apache.org';
+                }init$
+                $url""", "http://camel.apache.org";);
+    }
+
+    @Test
+    public void testOneLiner() {
+        assertExpression("$init{ $minAge := 18; }init$[$minAge]", "[18]");
+        assertExpression("$init{ $a := 'x'; $b := 'y'; }init$[$a$b]", "[xy]");
+        // a ; inside a function or quotes is not the end of the statement
+        assertExpression("$init{ $a := 'x; y'; }init$[$a]", "[x; y]");
+    }
+
+    @Test
+    public void testWindowsLineEndings() {
+        assertExpression("$init{\r\n  $a := 'x';\r\n  $b := 
'y';\r\n}init$\r\n[$a$b]", "[xy]");
+    }
+
+    @Test
+    public void testPredicateWithOnlyFunctions() {
+        exchange.getMessage().setBody(" A ");
+        assertPredicate("""
+                $init{
+                  $clean ~:= ${trim()};
+                }init$
+                $clean() == 'A'""", true);
+    }
+
+    @Test
+    public void testCustomFunctionIsLocalToTheExpression() {
+        exchange.getMessage().setBody("  abc  ");
+        assertExpression("""
+                $init{
+                  $f ~:= ${trim()};
+                }init$
+                [$f()]""", "[abc]");
+        assertExpression("""
+                $init{
+                  $f ~:= ${uppercase()};
+                }init$
+                [$f()]""", "[  ABC  ]");
+    }
+
+    @Test
+    public void testErrorPointsIntoTheInitBlock() {
+        String text = "$init{\n  $a = 5;\n}init$";
+        SimpleIllegalSyntaxException e = 
assertThrows(SimpleIllegalSyntaxException.class,
+                () -> 
context.resolveLanguage("simple").createExpression(text));
+        // the error is at the = which is not an init operator
+        assertEquals(text.indexOf('='), e.getIndex());
+    }
+}

Reply via email to