diff --git a/src/System.Management.Automation/engine/lang/parserutils.cs b/src/System.Management.Automation/engine/lang/parserutils.cs
index 217e348bfde..52c64400ccd 100644
--- a/src/System.Management.Automation/engine/lang/parserutils.cs
+++ b/src/System.Management.Automation/engine/lang/parserutils.cs
@@ -965,6 +965,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e
}
}
+ var replacer = ReplaceOperatorImpl.Create(context, rr, substitute);
IEnumerator list = LanguagePrimitives.GetEnumerator(lval);
if (list == null)
{
@@ -978,7 +979,7 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e
lvalString = lval?.ToString() ?? string.Empty;
}
- return ReplaceOperatorImpl(context, lvalString, rr, substitute);
+ return replacer.Replace(lvalString);
}
else
{
@@ -986,45 +987,55 @@ internal static object ReplaceOperator(ExecutionContext context, IScriptExtent e
while (ParserOps.MoveNext(context, errorPosition, list))
{
string lvalString = PSObject.ToStringParser(context, ParserOps.Current(errorPosition, list));
- resultList.Add(ReplaceOperatorImpl(context, lvalString, rr, substitute));
+ resultList.Add(replacer.Replace(lvalString));
}
return resultList.ToArray();
}
}
- ///
- /// ReplaceOperator implementation.
- /// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate
- /// and finally returns the result of the final Regex.Replace operation.
- ///
- /// The execution context in which to evaluate the expression.
- /// The input string.
- /// A Regex instance.
- /// The substitute value.
- /// The result of the regex.Replace operation.
- private static object ReplaceOperatorImpl(ExecutionContext context, string input, Regex regex, object substitute)
+ private struct ReplaceOperatorImpl
{
- switch (substitute)
+ public static ReplaceOperatorImpl Create(ExecutionContext context, Regex regex, object substitute)
+ {
+ return new ReplaceOperatorImpl(context, regex, substitute);
+ }
+
+ private readonly Regex _regex;
+ private readonly string _cachedReplacementString;
+ private readonly MatchEvaluator _cachedMatchEvaluator;
+
+ private ReplaceOperatorImpl(
+ ExecutionContext context,
+ Regex regex,
+ object substitute)
{
- case string replacementString:
- return regex.Replace(input, replacementString);
+ _regex = regex;
+ _cachedReplacementString = null;
+ _cachedMatchEvaluator = null;
+
+ switch (substitute)
+ {
+ case string replacement:
+ _cachedReplacementString = replacement;
+ break;
- case ScriptBlock sb:
- MatchEvaluator me = GetMatchEvaluator(context, sb);
- return regex.Replace(input, me);
+ case ScriptBlock sb:
+ _cachedMatchEvaluator = GetMatchEvaluator(context, sb);
+ break;
- case object val when LanguagePrimitives.TryConvertTo(val, out MatchEvaluator matchEvaluator):
- return regex.Replace(input, matchEvaluator);
+ case object val when LanguagePrimitives.TryConvertTo(val, out _cachedMatchEvaluator):
+ break;
- default:
- string replacement = PSObject.ToStringParser(context, substitute);
- return regex.Replace(input, replacement);
+ default:
+ _cachedReplacementString = PSObject.ToStringParser(context, substitute);
+ break;
+ }
}
// Local helper function to avoid creating an instance of the generated delegate helper class
// every time 'ReplaceOperatorImpl' is invoked.
- static MatchEvaluator GetMatchEvaluator(ExecutionContext context, ScriptBlock sb)
+ private static MatchEvaluator GetMatchEvaluator(ExecutionContext context, ScriptBlock sb)
{
return match =>
{
@@ -1039,6 +1050,22 @@ static MatchEvaluator GetMatchEvaluator(ExecutionContext context, ScriptBlock sb
return PSObject.ToStringParser(context, result);
};
}
+
+ ///
+ /// ReplaceOperator implementation.
+ /// Abstracts away conversion of the optional substitute parameter to either a string or a MatchEvaluator delegate
+ /// and finally returns the result of the final Regex.Replace operation.
+ ///
+ public object Replace(string input)
+ {
+ if (_cachedReplacementString is not null)
+ {
+ return _regex.Replace(input, _cachedReplacementString);
+ }
+
+ Dbg.Assert(_cachedMatchEvaluator is not null, "_cachedMatchEvaluator should be not null when code reach here.");
+ return _regex.Replace(input, _cachedMatchEvaluator);
+ }
}
///
diff --git a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1 b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1
index 59d7039898a..b72779f59c4 100644
--- a/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1
+++ b/test/powershell/Language/Operators/ReplaceOperator.Tests.ps1
@@ -11,6 +11,12 @@ Describe "Replace Operator" -Tags CI {
$res | Should -BeExactly "image.jpg"
}
+ It "Replace operator can convert an substitution object to string" {
+ $substitution = Get-Process -Id $pid
+ $res = "!3!" -replace "3",$substitution
+ $res | Should -BeExactly "!System.Diagnostics.Process (pwsh)!"
+ }
+
It "Replace operator can be case-insensitive and case-sensitive" {
$res = "book" -replace "B","C"
$res | Should -BeExactly "Cook"
@@ -29,6 +35,18 @@ Describe "Replace Operator" -Tags CI {
$res = "PowerPoint" -replace "Point"
$res | Should -BeExactly "Power"
}
+
+ It "Replace operator can take an enumerable as first argument, a mandatory pattern, and an optional substitution" {
+ $res = "PowerPoint1","PowerPoint2" -replace "Point","Shell"
+ $res.Count | Should -Be 2
+ $res[0] | Should -BeExactly "PowerShell1"
+ $res[1] | Should -BeExactly "PowerShell2"
+
+ $res = "PowerPoint1","PowerPoint2" -replace "Point"
+ $res.Count | Should -Be 2
+ $res[0] | Should -BeExactly "Power1"
+ $res[1] | Should -BeExactly "Power2"
+ }
}
Context "Replace operator substitutions" {