Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 79 additions & 24 deletions src/System.Management.Automation/engine/lang/parserutils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -632,22 +632,54 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent

private static object SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)
{
List<string> results = new List<string>();
foreach (string item in content)
// If user supplied a negative Max-substrings argument,
// we employ Right-to-Left splitting instead
bool rightToLeft = limit < 0;

limit = System.Math.Abs(limit);

if (limit == 1)
{
List<string> split = new List<String>();
// Don't bother with looking for any delimiters,
// just return the original string(s).
return new List<String>(content);
}

List<string> results = new List<string>();

if (limit == 1)
List<char> buf = new List<char>();

foreach (string item in content)
{
List<string> split;
if(limit == 0)
{
// Don't bother with looking for any delimiters,
// just return the original string.
results.Add(item);
continue;
// No limit specified
// set capacity to item.Length + 1
// this covers worst common case (predicate == {$true})
split = new List<String>(item.Length + 1);
}
else
{
// Limit was specified by the user
// instantiate list with maximum needed capacity
split = new List<String>(limit);
}

// Clear char buffer
buf.Clear();

StringBuilder buf = new StringBuilder();
for (int strIndex = 0; strIndex < item.Length; strIndex++)
int strIndex = 0;
for (int cursor = 0; cursor < item.Length; cursor++)
{
if(rightToLeft)
{
strIndex = item.Length - 1 - cursor;
}
else
{
strIndex = cursor;
}
object isDelimChar = predicate.DoInvokeReturnAsIs(
useLocalScope: true,
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,
Expand All @@ -657,8 +689,12 @@ private static object SplitWithPredicate(ExecutionContext context, IScriptExtent
args: new object[] { item, strIndex });
if (LanguagePrimitives.IsTrue(isDelimChar))
{
split.Add(buf.ToString());
buf = new StringBuilder();
if(rightToLeft)
{
buf.Reverse();
}
split.Add(string.Concat(buf));
buf.Clear();

if (limit > 0 && split.Count >= (limit - 1))
{
Expand All @@ -667,9 +703,16 @@ private static object SplitWithPredicate(ExecutionContext context, IScriptExtent
// and add it as the last item, otherwise
// add an empty string if there was
// a delimiter at the end.
if ((strIndex + 1) < item.Length)
if ((cursor + 1) < item.Length)
{
split.Add(item.Substring(strIndex + 1));
if(rightToLeft)
{
split.Add(item.Substring(0, strIndex));
}
else
{
split.Add(item.Substring(strIndex + 1));
}
}
else
{
Expand All @@ -681,24 +724,34 @@ private static object SplitWithPredicate(ExecutionContext context, IScriptExtent
// If this delimiter is at the end of the string,
// add an empty string to denote the item "after"
// it.
if (strIndex == (item.Length - 1))
if (cursor == (item.Length - 1))
{
split.Add("");
}
}
else
{
buf.Append(item[strIndex]);
buf.Add(item[strIndex]);
}
}

// Add any remainder, if we're under the limit.
if (buf.Length > 0 &&
(limit <= 0 || split.Count < limit))
if (buf.Count > 0 &&
(limit == 0 || split.Count < limit))
{
split.Add(buf.ToString());
if(rightToLeft)
{
buf.Reverse();
}
split.Add(string.Concat(buf));
}

if(rightToLeft)
{
// We want to preserve the order from the
// original string (always output chunks left-to-right)
split.Reverse();
}
ExtendList(results, split);
}

Expand Down Expand Up @@ -728,20 +781,22 @@ private static object SplitWithPattern(ExecutionContext context, IScriptExtent e
separatorPattern = Regex.Escape(separatorPattern);
}

RegexOptions regexOptions = parseRegexOptions(options);

if (limit < 0)
{
// Regex only allows 0 to signify "no limit", whereas
// we allow any integer <= 0.
limit = 0;
// If user-suppplied limit is negative we
// interpret it as "split Right-to-Left".
regexOptions |= RegexOptions.RightToLeft;
limit = System.Math.Abs(limit);
}

RegexOptions regexOptions = parseRegexOptions(options);
Regex regex = NewRegex(separatorPattern, regexOptions);

List<string> results = new List<string>();
foreach (string item in content)
{
string[] split = regex.Split(item, limit, 0);
string[] split = regex.Split(item, limit);
ExtendList(results, split);
}

Expand Down
46 changes: 41 additions & 5 deletions test/powershell/Language/Operators/SplitOperator.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@ Describe "Split Operator" -Tags CI {
$res[1] | Should Be "b"
$res[2] | Should Be "c"
$res[3] | Should Be "d"
}

It "Binary split operator works with negative substring limit" {
$res = "a b c d" -split " ", -3
$res.count | Should Be 3
$res[0] | Should Be "a b"
$res[1] | Should Be "c"
$res[2] | Should Be "d"

$res = "a b c d" -split " ", -1
$res.count | Should Be 4
$res[0] | Should Be "a"
$res[1] | Should Be "b"
$res[2] | Should Be "c"
$res[3] | Should Be "d"
$res.count | Should Be 1
$res[0] | Should Be "a b c d"
}

It "Binary split operator can works with freeform delimiter" {
Expand Down Expand Up @@ -103,6 +108,37 @@ Describe "Split Operator" -Tags CI {
$res[2] | Should Be "::d"
}

It "Binary split operator works with script block and substring limit" {
$res = "a::b::c::d" -split {$_ -eq "b" -or $_ -eq "C"}, 2
$res.count | Should Be 2
$res[0] | Should Be "a::"
$res[1] | Should Be "::c::d"
}

It "Binary split operator works with script block and substring limit on arrays" {
$res = "a::b::c::d","e::f::g::h" -split {$_ -eq "b" -or $_ -eq "c" -or $_ -eq "f" -or $_ -eq "g"}, 2
$res.count | Should Be 4
$res[0] | Should Be "a::"
$res[1] | Should Be "::c::d"
$res[2] | Should Be "e::"
$res[3] | Should Be "::g::h"
}

It "Binary split operator works with script block and negative substring limit" {
$res = "a::b::c::d" -split {$_ -eq "b" -or $_ -eq "C"}, -2
$res.count | Should Be 2
$res[0] | Should Be "a::b::"
$res[1] | Should Be "::d"
}

It "Binary split operator works with script block and negative substring limit on arrays" {
$res = "a::b::c::d","e::f::g::h" -split {$_ -eq "b" -or $_ -eq "c" -or $_ -eq "f" -or $_ -eq "g"}, -2
$res.count | Should Be 4
$res[0] | Should Be "a::b::"
$res[1] | Should Be "::d"
$res[2] | Should Be "e::f::"
$res[3] | Should Be "::h"
}
}

Context "Binary split operator options" {
Expand Down