diff --git a/composer.json b/composer.json
index de721383618f89c74cfa877df2c2a9492901ff16..6c214b9a19b8b257170eab8639d975a750be8ce2 100644
--- a/composer.json
+++ b/composer.json
@@ -291,7 +291,8 @@
 			"TYPO3\\CMS\\Recycler\\Tests\\": "typo3/sysext/recycler/Tests/",
 			"TYPO3\\CMS\\T3editor\\Tests\\": "typo3/sysext/t3editor/Tests/",
 			"TYPO3\\CMS\\Tstemplate\\Tests\\": "typo3/sysext/tstemplate/Tests/",
-			"TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/"
+			"TYPO3Tests\\TestLogger\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_logger/Classes/",
+			"TYPO3Tests\\TestTyposcriptAstFunctionEvent\\": "typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/"
 		},
 		"classmap": [
 			"typo3/sysext/core/Tests/Unit/Core/Fixtures/test_extension/",
diff --git a/typo3/sysext/core/Classes/TypoScript/AST/AstBuilder.php b/typo3/sysext/core/Classes/TypoScript/AST/AstBuilder.php
index 66ca8e88fe034690e55d45f039ea458ff96e97b5..56b380d07359d2d40e387220ea7ad7467293eba1 100644
--- a/typo3/sysext/core/Classes/TypoScript/AST/AstBuilder.php
+++ b/typo3/sysext/core/Classes/TypoScript/AST/AstBuilder.php
@@ -17,8 +17,10 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\TypoScript\AST;
 
+use Psr\EventDispatcher\EventDispatcherInterface;
 use TYPO3\CMS\Core\TypoScript\AST\CurrentObjectPath\CurrentObjectPath;
 use TYPO3\CMS\Core\TypoScript\AST\CurrentObjectPath\CurrentObjectPathStack;
+use TYPO3\CMS\Core\TypoScript\AST\Event\EvaluateModifierFunctionEvent;
 use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNode;
 use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNodeInterface;
 use TYPO3\CMS\Core\TypoScript\AST\Node\NodeInterface;
@@ -54,6 +56,11 @@ final class AstBuilder
      */
     private array $flatConstants = [];
 
+    public function __construct(
+        private readonly EventDispatcherInterface $eventDispatcher,
+    ) {
+    }
+
     /**
      * @param array<string, string> $flatConstants
      */
@@ -237,42 +244,43 @@ final class AstBuilder
      * Evaluate operator functions, example TypoScript:
      * "page.10.value := appendString(foo)"
      */
-    private function evaluateValueModifier(Token $functionNameToken, ?Token $functionValueToken, ?string $currentValue): ?string
+    private function evaluateValueModifier(Token $functionNameToken, ?Token $functionArgumentToken, ?string $originalValue): ?string
     {
-        $functionValue = '';
-        if ($functionValueToken) {
-            $functionValue = $functionValueToken->getValue();
+        $functionName = $functionNameToken->getValue();
+        $functionArgument = null;
+        if ($functionArgumentToken) {
+            $functionArgument = $functionArgumentToken->getValue();
         }
-        switch ($functionNameToken->getValue()) {
+        switch ($functionName) {
             case 'prependString':
-                return $functionValue . $currentValue;
+                return $functionArgument . $originalValue;
             case 'appendString':
-                return $currentValue . $functionValue;
+                return $originalValue . $functionArgument;
             case 'removeString':
-                return str_replace($functionValue, '', $currentValue);
+                return str_replace((string)$functionArgument, '', $originalValue);
             case 'replaceString':
-                $functionValueArray = explode('|', $functionValue, 2);
+                $functionValueArray = explode('|', (string)$functionArgument, 2);
                 $fromStr = $functionValueArray[0] ?? '';
                 $toStr = $functionValueArray[1] ?? '';
-                return str_replace($fromStr, $toStr, $currentValue);
+                return str_replace($fromStr, $toStr, $originalValue);
             case 'addToList':
-                return ($currentValue !== null ? $currentValue . ',' : '') . $functionValue;
+                return ($originalValue !== null ? $originalValue . ',' : '') . $functionArgument;
             case 'removeFromList':
-                $existingElements = GeneralUtility::trimExplode(',', $currentValue);
-                $removeElements = GeneralUtility::trimExplode(',', $functionValue);
+                $existingElements = GeneralUtility::trimExplode(',', $originalValue);
+                $removeElements = GeneralUtility::trimExplode(',', (string)$functionArgument);
                 if (!empty($removeElements)) {
                     return implode(',', array_diff($existingElements, $removeElements));
                 }
-                return $currentValue;
+                return $originalValue;
             case 'uniqueList':
-                $elements = GeneralUtility::trimExplode(',', $currentValue);
+                $elements = GeneralUtility::trimExplode(',', $originalValue);
                 return implode(',', array_unique($elements));
             case 'reverseList':
-                $elements = GeneralUtility::trimExplode(',', $currentValue);
+                $elements = GeneralUtility::trimExplode(',', $originalValue);
                 return implode(',', array_reverse($elements));
             case 'sortList':
-                $elements = GeneralUtility::trimExplode(',', $currentValue);
-                $arguments = GeneralUtility::trimExplode(',', $functionValue);
+                $elements = GeneralUtility::trimExplode(',', $originalValue);
+                $arguments = GeneralUtility::trimExplode(',', (string)$functionArgument);
                 $arguments = array_map('strtolower', $arguments);
                 $sortFlags = SORT_REGULAR;
                 if (in_array('numeric', $arguments)) {
@@ -284,7 +292,7 @@ final class AstBuilder
                     foreach ($elements as $element) {
                         if (!is_numeric($element)) {
                             throw new \InvalidArgumentException(
-                                'The list "' . $currentValue . '" should be sorted numerically but contains a non-numeric value',
+                                'The list "' . $originalValue . '" should be sorted numerically but contains a non-numeric value',
                                 1650893781
                             );
                         }
@@ -296,26 +304,13 @@ final class AstBuilder
                 }
                 return implode(',', $elements);
             case 'getEnv':
-                $environmentValue = getenv(trim($functionValue));
+                $environmentValue = getenv(trim((string)$functionArgument));
                 if ($environmentValue !== false) {
                     return $environmentValue;
                 }
-                return $currentValue;
+                return $originalValue;
             default:
-                return $currentValue;
-                // @todo: Implement (and test) hook again or switch to event along the way
-                /*
-                if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName])) {
-                    $hookMethod = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc'][$modifierName];
-                    $params = ['currentValue' => $currentValue, 'functionArgument' => $modifierArgument];
-                    $fakeThis = null;
-                    $newValue = GeneralUtility::callUserFunction($hookMethod, $params, $fakeThis);
-                } else {
-                    self::getLogger()->warning('Missing function definition for {modifier_name} on TypoScript', [
-                        'modifier_name' => $modifierName,
-                    ]);
-                }
-                */
+                return $this->eventDispatcher->dispatch(new EvaluateModifierFunctionEvent($functionName, $functionArgument, $originalValue))->getValue() ?? $originalValue;
         }
     }
 }
diff --git a/typo3/sysext/core/Classes/TypoScript/AST/Event/EvaluateModifierFunctionEvent.php b/typo3/sysext/core/Classes/TypoScript/AST/Event/EvaluateModifierFunctionEvent.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d68d7973b88c3f25522c20408aa2f8b261f4807
--- /dev/null
+++ b/typo3/sysext/core/Classes/TypoScript/AST/Event/EvaluateModifierFunctionEvent.php
@@ -0,0 +1,85 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\TypoScript\AST\Event;
+
+/**
+ * Listeners to this event are able to implement own ":=" TypoScript modifier functions, example:
+ *
+ * foo = myOriginalValue
+ * foo := myNewFunction(myFunctionArgument)
+ *
+ * Listeners should take care function names can not overlap with function names
+ * from other extensions and should thus namespace, example naming: "extNewsSortFunction()"
+ */
+final class EvaluateModifierFunctionEvent
+{
+    private ?string $value = null;
+
+    public function __construct(
+        private readonly string $functionName,
+        private readonly ?string $functionArgument,
+        private readonly ?string $originalValue,
+    ) {
+    }
+
+    /**
+     * The function name, for example "extNewsSortFunction" when using "foo := extNewsSortFunction()"
+     */
+    public function getFunctionName(): string
+    {
+        return $this->functionName;
+    }
+
+    /**
+     * Optional function argument, for example "myArgument" when using "foo := extNewsSortFunction(myArgument)"
+     */
+    public function getFunctionArgument(): ?string
+    {
+        return $this->functionArgument;
+    }
+
+    /**
+     * Original / current value, for example "fooValue" when using:
+     * foo = fooValue
+     * foo := extNewsSortFunction(myArgument)
+     */
+    public function getOriginalValue(): ?string
+    {
+        return $this->originalValue;
+    }
+
+    /**
+     * Set the updated value calculated by a listener.
+     * Note you can not set to null to "unset", since getValue() falls back to
+     * originalValue in this case. Set to empty string instead for this edge case.
+     */
+    public function setValue(string $value): void
+    {
+        $this->value = $value;
+    }
+
+    /**
+     * Used by AstBuilder to fetch the updated value, falls back to given original value.
+     * Can be used by Listeners to see if a previous listener changed the value already
+     * by comparing with getOriginalValue().
+     */
+    public function getValue(): ?string
+    {
+        return $this->value;
+    }
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98016-RemovedTypoScriptFunctionHook.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98016-RemovedTypoScriptFunctionHook.rst
new file mode 100644
index 0000000000000000000000000000000000000000..b447f1fb5c3df0517222d77bbf04080c2f3301b5
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Breaking-98016-RemovedTypoScriptFunctionHook.rst
@@ -0,0 +1,49 @@
+.. include:: /Includes.rst.txt
+
+.. _breaking-98016-1658731955:
+
+================================================
+Breaking: #98016 - RemovedTypoScriptFunctionHook
+================================================
+
+See :issue:`98016`
+
+Description
+===========
+
+With the transition to the :ref:`new TypoScript parser <feature-97816-1656350667>`,
+the hook :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc']`
+is no longer called.
+
+This hook has been used to implement own functions for the TypoScript "function" operator :typoscript:`:=`.
+
+Additional functions can now be implemented using the :php:`\TYPO3\CMS\Core\TypoScript\AST\Event\EvaluateModifierFunctionEvent`
+as described in :ref:`this Changelog <feature-98016-1658732423>`
+
+Impact
+======
+
+With the continued implementation of the new TypoScript parser in TYPO3 v12,
+registered hook implementations are not executed anymore. The extension scanner
+will report possible usages.
+
+
+Affected installations
+======================
+
+Extensions registering own TypoScript function implementations like this:
+
+.. code-block:: typoscript
+
+    myValue := myCustomFunction(modifierArgument)
+
+
+Migration
+=========
+
+Implement the :ref:`new event <feature-98016-1658732423>`. Extensions that want to keep
+compatibility with both TYPO3 v11 and v12 can keep the old hook implementation without
+further deprecations.
+
+
+.. index:: PHP-API, TSConfig, TypoScript, FullyScanned, ext:core
diff --git a/typo3/sysext/core/Documentation/Changelog/12.0/Feature-98016-PSR-14EvaluateModifierFunctionEvent.rst b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-98016-PSR-14EvaluateModifierFunctionEvent.rst
new file mode 100644
index 0000000000000000000000000000000000000000..ef594d2d86efdfa04863253f2786d995db3aac7e
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/12.0/Feature-98016-PSR-14EvaluateModifierFunctionEvent.rst
@@ -0,0 +1,65 @@
+.. include:: /Includes.rst.txt
+
+.. _feature-98016-1658732423:
+
+======================================================
+Feature: #98016 - PSR-14 EvaluateModifierFunctionEvent
+======================================================
+
+See :issue:`98016`
+
+Description
+===========
+
+A new PSR-14 Event :php:`\TYPO3\CMS\Core\TypoScript\AST\Event\EvaluateModifierFunctionEvent`
+has been introduced which allows own TypoScript functions using the :typoscript:`:=` operator.
+
+This is a substitution of the old :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsparser.php']['preParseFunc']`
+hook as described in :ref:`this Changelog <breaking-98016-1658731955>`.
+
+
+Impact
+======
+
+The TYPO3 core tests come with test extension
+:file:`EXT:core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event` to functional
+test the new event. The extension implements an example listener that can be used as boilerplate.
+
+A simple TypoScript example looks like this:
+
+.. code-block:: typoscript
+
+    someIdentifier = originalValue
+    someIdentifier := myModifierFunction(myFunctionArgument)
+
+To implement :typoscript:`myModifierFunction`, an extension needs to register an event listener
+in file :file:`Configuration/Services.yaml`:
+
+.. code-block:: yaml
+
+    MyVendor\MyPackage\EventListener\MyTypoScriptModifierFunction:
+    tags:
+      - name: event.listener
+        identifier: 'my-package/typoscript/evaluate-modifier-function'
+
+The corresponding event listener class could look like this:
+
+.. code-block:: php
+
+    use TYPO3\CMS\Core\TypoScript\AST\Event\EvaluateModifierFunctionEvent;
+
+    final class MyTypoScriptModifierFunction
+    {
+        public function __invoke(EvaluateModifierFunctionEvent $event): void
+        {
+            if ($event->getFunctionName() === 'myModifierFunction') {
+                $originalValue = $event->getOriginalValue();
+                $functionArgument = $event->getFunctionArgument();
+                // Manipulate values and set new value
+                $event->setValue($originalValue . ' example ' . $functionArgument);
+            }
+        }
+    }
+
+
+.. index:: PHP-API, TSConfig, TypoScript, ext:core
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/EventListener/TyposcriptTestFunction.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/EventListener/TyposcriptTestFunction.php
new file mode 100644
index 0000000000000000000000000000000000000000..1a909310ac8a5ceef083634c54175b58d3af027b
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Classes/EventListener/TyposcriptTestFunction.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3Tests\TestTyposcriptAstFunctionEvent\EventListener;
+
+use TYPO3\CMS\Core\TypoScript\AST\Event\EvaluateModifierFunctionEvent;
+
+final class TyposcriptTestFunction
+{
+    public function __invoke(EvaluateModifierFunctionEvent $event): void
+    {
+        if ($event->getFunctionName() === 'testFunction') {
+            $event->setValue(($event->getOriginalValue() ?? '') . ' ' . ($event->getFunctionArgument() ?? ''));
+        }
+    }
+}
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Configuration/Services.yaml b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Configuration/Services.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ab2da15bda8ceac6f1e95decc7d01d7c6573676a
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Configuration/Services.yaml
@@ -0,0 +1,10 @@
+services:
+  _defaults:
+    autowire: true
+    autoconfigure: true
+    public: false
+
+  TYPO3Tests\TestTyposcriptAstFunctionEvent\EventListener\TyposcriptTestFunction:
+    tags:
+      - name: event.listener
+        identifier: 'typo3tests-test-typoscript-ast-function-event/typoscript-test-function'
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Resources/Public/Icons/Extension.svg b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Resources/Public/Icons/Extension.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bca7a17509c5f3e42481725c025ec1b8f597fa56
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/Resources/Public/Icons/Extension.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#FF8700" d="M0 0h64v64H0z"/><path fill="#FFF" d="M42.8 32.8c-3.6 0-8.1-10.1-8.1-15.1 0-2.3.9-2.7 3.2-2.7 5.5 0 11 .9 11 4-.1 6.2-4 13.8-6.1 13.8zM28.5 18.5c0 5 6.4 20.2 10.7 20.2.5 0 .9-.1 1.4-.2-3.8 6.1-8.4 10.6-11.2 10.6-5.9 0-14.3-17.9-14.3-25.7 0-1.2.3-2.2.7-2.8 2-2.5 8.4-4.4 13.7-5-.6.4-1 1-1 2.9z"/></svg>
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/ext_emconf.php b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/ext_emconf.php
new file mode 100644
index 0000000000000000000000000000000000000000..8fc6f6e0b913f61ad5dfa4df2706ae2fa8059a19
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event/ext_emconf.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+$EM_CONF[$_EXTKEY] = [
+    'title' => 'TypoScript AST function evaluation test',
+    'description' => 'TypoScript AST function evaluation test',
+    'category' => 'example',
+    'version' => '12.0.0',
+    'state' => 'beta',
+    'author' => 'Christian Kuhn',
+    'author_email' => 'lolli@schwarzbu.ch',
+    'author_company' => '',
+    'constraints' => [
+        'depends' => [
+            'typo3' => '12.0.0',
+            'workspaces' => '12.0.0',
+        ],
+        'conflicts' => [],
+        'suggests' => [],
+    ],
+];
diff --git a/typo3/sysext/core/Tests/Functional/TypoScript/AST/AstBuilderTest.php b/typo3/sysext/core/Tests/Functional/TypoScript/AST/AstBuilderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d1507d642500db84fbf56a1878aa72280effe304
--- /dev/null
+++ b/typo3/sysext/core/Tests/Functional/TypoScript/AST/AstBuilderTest.php
@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Functional\TypoScript\AST;
+
+use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
+use TYPO3\CMS\Core\TypoScript\AST\Node\RootNode;
+use TYPO3\CMS\Core\TypoScript\Tokenizer\LosslessTokenizer;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class AstBuilderTest extends FunctionalTestCase
+{
+    protected array $testExtensionsToLoad = [
+        'typo3/sysext/core/Tests/Functional/Fixtures/Extensions/test_typoscript_ast_function_event',
+    ];
+
+    /**
+     * @test
+     */
+    public function notModifiedValueKeepsNullValue(): void
+    {
+        $tokens = (new LosslessTokenizer())->tokenize('foo := doesNotExistFunction()');
+        /** @var AstBuilder $astBuilder */
+        $astBuilder = $this->get(AstBuilder::class);
+        $ast = $astBuilder->build($tokens, new RootNode());
+        self::assertNull($ast->getChildByName('foo')->getValue());
+    }
+
+    /**
+     * @test
+     */
+    public function notModifiedValueKeepsOriginalValue(): void
+    {
+        $tokens = (new LosslessTokenizer())->tokenize(
+            "foo = originalValue\n" .
+            'foo := doesNotExistFunction()'
+        );
+        /** @var AstBuilder $astBuilder */
+        $astBuilder = $this->get(AstBuilder::class);
+        $ast = $astBuilder->build($tokens, new RootNode());
+        self::assertSame('originalValue', $ast->getChildByName('foo')->getValue());
+    }
+
+    /**
+     * @test
+     */
+    public function modifiedValueUpdatesOriginalValue(): void
+    {
+        $tokens = (new LosslessTokenizer())->tokenize(
+            "foo = originalValue\n" .
+            'foo := testFunction(modifierArgument)'
+        );
+        /** @var AstBuilder $astBuilder */
+        $astBuilder = $this->get(AstBuilder::class);
+        $ast = $astBuilder->build($tokens, new RootNode());
+        self::assertSame('originalValue modifierArgument', $ast->getChildByName('foo')->getValue());
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Fixtures/EventDispatcher/NoopEventDispatcher.php b/typo3/sysext/core/Tests/Unit/Fixtures/EventDispatcher/NoopEventDispatcher.php
new file mode 100644
index 0000000000000000000000000000000000000000..7f4d2477d89f8ea6bf3fb07818ffcae3e15ad251
--- /dev/null
+++ b/typo3/sysext/core/Tests/Unit/Fixtures/EventDispatcher/NoopEventDispatcher.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Tests\Unit\Fixtures\EventDispatcher;
+
+use Psr\EventDispatcher\EventDispatcherInterface;
+
+final class NoopEventDispatcher implements EventDispatcherInterface
+{
+    public function dispatch(object $event)
+    {
+        return $event;
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/TypoScript/AST/AstBuilderTest.php b/typo3/sysext/core/Tests/Unit/TypoScript/AST/AstBuilderTest.php
index af35c454027d69aa13b624cec425a6d89ea1b208..972e8504fe03d79bf3bd65db64527d66c5774f00 100644
--- a/typo3/sysext/core/Tests/Unit/TypoScript/AST/AstBuilderTest.php
+++ b/typo3/sysext/core/Tests/Unit/TypoScript/AST/AstBuilderTest.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Core\Tests\Unit\TypoScript\AST;
 
+use TYPO3\CMS\Core\Tests\Unit\Fixtures\EventDispatcher\NoopEventDispatcher;
 use TYPO3\CMS\Core\TypoScript\AST\AstBuilder;
 use TYPO3\CMS\Core\TypoScript\AST\Node\ChildNode;
 use TYPO3\CMS\Core\TypoScript\AST\Node\ReferenceChildNode;
@@ -1329,8 +1330,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function build(string $source, RootNode $expectedAst): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode());
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
         self::assertEquals($expectedAst, $ast);
     }
 
@@ -1340,8 +1342,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function buildCompatArray(string $source, RootNode $_, array $expectedArray): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode());
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
         self::assertEquals($expectedArray, $ast->toArray());
     }
 
@@ -1490,8 +1493,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function buildReference(string $source, RootNode $expectedAst): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode());
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
         self::assertEquals($expectedAst, $ast);
     }
 
@@ -1501,8 +1505,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function buildReferenceArray(string $source, RootNode $_, array $expectedArray): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode());
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
         self::assertEquals($expectedArray, $ast->toArray());
     }
 
@@ -1633,8 +1638,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function buildConstant(string $source, array $constants, RootNode $expectedAst): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode(), $constants);
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode(), $constants);
         self::assertEquals($expectedAst, $ast);
     }
 
@@ -1644,8 +1650,9 @@ class AstBuilderTest extends UnitTestCase
      */
     public function buildConstantCompatArray(string $source, array $constants, RootNode $_, array $expectedArray): void
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode(), $constants);
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode(), $constants);
         self::assertEquals($expectedArray, $ast->toArray());
     }
 
@@ -1672,8 +1679,9 @@ class AstBuilderTest extends UnitTestCase
             'bar' => 'bar1',
         ];
 
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize('bar = bar1');
-        $resultAst = (new AstBuilder())->build($tokens, $inputAst, []);
+        $resultAst = (new AstBuilder($noopEventDispatcher))->build($tokens, $inputAst, []);
         self::assertEquals($expectedAst, $resultAst);
         self::assertEquals($expectedArray, $resultAst->toArray());
     }
@@ -1699,8 +1707,9 @@ class AstBuilderTest extends UnitTestCase
     {
         $this->expectException(\InvalidArgumentException::class);
         $this->expectExceptionCode(1650893781);
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        (new AstBuilder())->build($tokens, new RootNode());
+        (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
     }
 
     public function functionGetEnvDataProvider(): \Generator
@@ -1784,8 +1793,9 @@ class AstBuilderTest extends UnitTestCase
         if ($envVarName) {
             putenv($envVarName . '=' . $envVarValue);
         }
+        $noopEventDispatcher = new NoopEventDispatcher();
         $tokens = (new LosslessTokenizer())->tokenize($source);
-        $ast = (new AstBuilder())->build($tokens, new RootNode());
+        $ast = (new AstBuilder($noopEventDispatcher))->build($tokens, new RootNode());
         self::assertEquals($expectedAst, $ast);
         if ($envVarName) {
             putenv($envVarName);
@@ -1794,6 +1804,8 @@ class AstBuilderTest extends UnitTestCase
 
     public function flattenDataProvider(): \Generator
     {
+        $noopEventDispatcher = new NoopEventDispatcher();
+
         yield 'empty' => [
             new RootNode(),
             [],
@@ -1807,7 +1819,7 @@ class AstBuilderTest extends UnitTestCase
             'second' => 'secondValue',
         ];
         yield 'one level' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1817,7 +1829,7 @@ class AstBuilderTest extends UnitTestCase
             'first' => '',
         ];
         yield 'empty string as value is kept' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1827,7 +1839,7 @@ class AstBuilderTest extends UnitTestCase
             'first' => '0',
         ];
         yield 'zero as value is kept' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1839,7 +1851,7 @@ class AstBuilderTest extends UnitTestCase
             'second.secondSub' => 'secondSubValue',
         ];
         yield 'two levels' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1855,7 +1867,7 @@ class AstBuilderTest extends UnitTestCase
             'second.secondSub' => 'secondSubValue',
         ];
         yield 'two levels with values on first level' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1871,7 +1883,7 @@ class AstBuilderTest extends UnitTestCase
             'second.secondSub.secondSubSub' => 'secondSubSubValue',
         ];
         yield 'three levels, partially with values' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
 
@@ -1881,7 +1893,7 @@ class AstBuilderTest extends UnitTestCase
             'first.firstSub\.firstSubSub' => 'firstSubSubValue',
         ];
         yield 'two levels with quoted dote' => [
-            (new AstBuilder())->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
+            (new AstBuilder($noopEventDispatcher))->build((new LosslessTokenizer())->tokenize($typoscript), new RootNode()),
             $expected,
         ];
     }
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
index 8cdbc7fa8698094692a7331aea7b5899b22e35e3..1c9535b3f081092e5577de4bdebc6d7eb11ebbd1 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
@@ -820,4 +820,10 @@ return [
             'Feature-97945-PSR14AfterPageTreeItemsPreparedEvent.rst',
         ],
     ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'SC_OPTIONS\'][\'t3lib/class.t3lib_tsparser.php\'][\'preParseFunc\']' => [
+        'restFiles' => [
+            'Breaking-98016-RemovedTypoScriptFunctionHook.rst',
+            'Feature-98016-PSR-14EvaluateModifierFunctionEvent.rst',
+        ],
+    ],
 ];