GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Support negative numbers in `-split` operator (#8960) · PowerShell/PowerShell@f222a68

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
  • src/System.Management.Automation/engine/lang
    • parserutils.cs
  • test/powershell/Language/Operators
    • SplitOperator.Tests.ps1
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎src/System.Management.Automation/engine/lang/parserutils.cs‎

Copy file name to clipboardExpand all lines: src/System.Management.Automation/engine/lang/parserutils.cs
+144-58Lines changed: 144 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ internal static object SplitOperator(ExecutionContext context, IScriptExtent err
584584
return SplitOperatorImpl(context, errorPosition, lval, rval, SplitImplOptions.None, ignoreCase);
585585
}
586586

587-
private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, SplitImplOptions implOptions, bool ignoreCase)
587+
private static IReadOnlyList<string> SplitOperatorImpl(ExecutionContext context, IScriptExtent errorPosition, object lval, object rval, SplitImplOptions implOptions, bool ignoreCase)
588588
{
589589
IEnumerable<string> content = enumerateContent(context, errorPosition, implOptions, lval);
590590

@@ -634,84 +634,166 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent
634634
options |= SplitOptions.IgnoreCase;
635635
}
636636

637-
if (predicate != null)
637+
if (predicate == null)
638+
{
639+
return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options);
640+
}
641+
else if (limit >= 0)
638642
{
639643
return SplitWithPredicate(context, errorPosition, content, predicate, limit);
640644
}
641645
else
642646
{
643-
return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options);
647+
return NegativeSplitWithPredicate(context, errorPosition, content, predicate, limit);
644648
}
645649
}
646650

647-
private static object SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)
651+
private static IReadOnlyList<string> NegativeSplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)
648652
{
649-
List<string> results = new List<string>();
653+
var results = new List<string>();
654+
655+
if (limit == -1)
656+
{
657+
// If the user just wants 1 string
658+
// then just return the content
659+
return new List<string>(content);
660+
}
661+
650662
foreach (string item in content)
651663
{
652-
List<string> split = new List<string>();
664+
var split = new List<string>();
665+
666+
// Used to traverse through the item
667+
int cursor = item.Length - 1;
668+
669+
int subStringLength = 0;
670+
671+
for (int charCount = 0; charCount < item.Length; charCount++) {
672+
// Evaluate the predicate using the character at cursor.
673+
object predicateResult = predicate.DoInvokeReturnAsIs(
674+
useLocalScope: true,
675+
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,
676+
dollarUnder: CharToString(item[cursor]),
677+
input: AutomationNull.Value,
678+
scriptThis: AutomationNull.Value,
679+
args: new object[] { item, cursor });
680+
681+
if (!LanguagePrimitives.IsTrue(predicateResult))
682+
{
683+
subStringLength++;
684+
cursor -= 1;
685+
continue;
686+
}
687+
688+
split.Add(item.Substring(cursor + 1, subStringLength));
689+
690+
subStringLength = 0;
653691

654-
if (limit == 1)
692+
cursor -= 1;
693+
694+
if (System.Math.Abs(limit) == (split.Count + 1))
695+
{
696+
break;
697+
}
698+
}
699+
700+
if (cursor == -1)
655701
{
656-
// Don't bother with looking for any delimiters,
657-
// just return the original string.
658-
results.Add(item);
659-
continue;
702+
// Used when the limit is negative
703+
// and the cursor was allowed to go
704+
// all the way to the start of the
705+
// string.
706+
split.Add(item.Substring(0, subStringLength));
660707
}
708+
else
709+
{
710+
// Used to get the rest of the string
711+
// when using a negative limit and
712+
// the cursor doesn't reach the end
713+
// of the string.
714+
split.Add(item.Substring(0, cursor + 1));
715+
}
716+
717+
split.Reverse();
718+
719+
results.AddRange(split);
720+
}
721+
722+
return results.ToArray();
723+
}
724+
725+
private static IReadOnlyList<string> SplitWithPredicate(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, ScriptBlock predicate, int limit)
726+
{
727+
var results = new List<string>();
728+
729+
if (limit == 1)
730+
{
731+
// If the user just wants 1 string
732+
// then just return the content
733+
return new List<string>(content);
734+
}
735+
736+
foreach (string item in content)
737+
{
738+
var split = new List<string>();
661739

662-
StringBuilder buf = new StringBuilder();
663-
for (int strIndex = 0; strIndex < item.Length; strIndex++)
740+
// Used to traverse through the item
741+
int cursor = 0;
742+
743+
// This is used to calculate how much to split from item.
744+
int subStringLength = 0;
745+
746+
for (int charCount = 0; charCount < item.Length; charCount++)
664747
{
665-
object isDelimChar = predicate.DoInvokeReturnAsIs(
748+
// Evaluate the predicate using the character at cursor.
749+
object predicateResult = predicate.DoInvokeReturnAsIs(
666750
useLocalScope: true,
667751
errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe,
668-
dollarUnder: CharToString(item[strIndex]),
752+
dollarUnder: CharToString(item[cursor]),
669753
input: AutomationNull.Value,
670754
scriptThis: AutomationNull.Value,
671-
args: new object[] { item, strIndex });
672-
if (LanguagePrimitives.IsTrue(isDelimChar))
673-
{
674-
split.Add(buf.ToString());
675-
buf = new StringBuilder();
755+
args: new object[] { item, cursor });
676756

677-
if (limit > 0 && split.Count >= (limit - 1))
678-
{
679-
// We're one item below the limit. If
680-
// we have any string left, go ahead
681-
// and add it as the last item, otherwise
682-
// add an empty string if there was
683-
// a delimiter at the end.
684-
if ((strIndex + 1) < item.Length)
685-
{
686-
split.Add(item.Substring(strIndex + 1));
687-
}
688-
else
689-
{
690-
split.Add(string.Empty);
691-
}
757+
// If the current character is not a delimiter
758+
// then it must be included into a substring.
759+
if (!LanguagePrimitives.IsTrue(predicateResult))
760+
{
761+
subStringLength++;
692762

693-
break;
694-
}
763+
cursor += 1;
695764

696-
// If this delimiter is at the end of the string,
697-
// add an empty string to denote the item "after"
698-
// it.
699-
if (strIndex == (item.Length - 1))
700-
{
701-
split.Add(string.Empty);
702-
}
765+
continue;
703766
}
704-
else
767+
768+
// Else, if the character is a delimiter
769+
// then add a substring to the split list.
770+
split.Add(item.Substring(cursor - subStringLength, subStringLength));
771+
772+
subStringLength = 0;
773+
774+
cursor += 1;
775+
776+
if (limit == (split.Count + 1))
705777
{
706-
buf.Append(item[strIndex]);
778+
break;
707779
}
708780
}
709781

710-
// Add any remainder, if we're under the limit.
711-
if (buf.Length > 0 &&
712-
(limit <= 0 || split.Count < limit))
782+
if (cursor == item.Length)
713783
{
714-
split.Add(buf.ToString());
784+
// Used to get the rest of the string
785+
// when the limit is not negative and
786+
// the cursor is allowed to make it to
787+
// the end of the string.
788+
split.Add(item.Substring(cursor - subStringLength, subStringLength));
789+
}
790+
else
791+
{
792+
// Used to get the rest of the string
793+
// when the limit is not negative and
794+
// the cursor is not at the end of the
795+
// string.
796+
split.Add(item.Substring(cursor, item.Length - cursor));
715797
}
716798

717799
results.AddRange(split);
@@ -720,7 +802,7 @@ private static object SplitWithPredicate(ExecutionContext context, IScriptExtent
720802
return results.ToArray();
721803
}
722804

723-
private static object SplitWithPattern(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, string separatorPattern, int limit, SplitOptions options)
805+
private static IReadOnlyList<string> SplitWithPattern(ExecutionContext context, IScriptExtent errorPosition, IEnumerable<string> content, string separatorPattern, int limit, SplitOptions options)
724806
{
725807
// Default to Regex matching if no match specified.
726808
if ((options & SplitOptions.SimpleMatch) == 0 &&
@@ -743,20 +825,24 @@ private static object SplitWithPattern(ExecutionContext context, IScriptExtent e
743825
separatorPattern = Regex.Escape(separatorPattern);
744826
}
745827

746-
if (limit < 0)
828+
RegexOptions regexOptions = parseRegexOptions(options);
829+
830+
int calculatedLimit = limit;
831+
832+
// If the limit is negative then set Regex to read from right to left
833+
if (calculatedLimit < 0)
747834
{
748-
// Regex only allows 0 to signify "no limit", whereas
749-
// we allow any integer <= 0.
750-
limit = 0;
835+
regexOptions |= RegexOptions.RightToLeft;
836+
calculatedLimit *= -1;
751837
}
752838

753-
RegexOptions regexOptions = parseRegexOptions(options);
754839
Regex regex = NewRegex(separatorPattern, regexOptions);
755840

756-
List<string> results = new List<string>();
841+
var results = new List<string>();
842+
757843
foreach (string item in content)
758844
{
759-
string[] split = regex.Split(item, limit, 0);
845+
string[] split = regex.Split(item, calculatedLimit);
760846
results.AddRange(split);
761847
}
762848

Collapse file

‎test/powershell/Language/Operators/SplitOperator.Tests.ps1‎

Copy file name to clipboardExpand all lines: test/powershell/Language/Operators/SplitOperator.Tests.ps1
+66Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,78 @@ Describe "Split Operator" -Tags CI {
3333
$res[2] | Should -Be "c"
3434
$res[3] | Should -Be "d"
3535

36+
$res = "a b c d" -split " ", -2
37+
$res.count | Should -Be 2
38+
$res[0] | Should -Be "a b c"
39+
$res[1] | Should -Be "d"
40+
3641
$res = "a b c d" -split " ", -1
42+
$res.count | Should -Be 1
43+
$res[0] | Should -Be "a b c d"
44+
}
45+
46+
It "Binary split operator can work with different delimeter than split string" {
47+
$res = "a b c d" -split " ",8
3748
$res.count | Should -Be 4
3849
$res[0] | Should -Be "a"
3950
$res[1] | Should -Be "b"
4051
$res[2] | Should -Be "c"
4152
$res[3] | Should -Be "d"
53+
54+
$res = "a b c d" -split " ",-8
55+
$res.count | Should -Be 4
56+
$res[0] | Should -Be "a"
57+
$res[1] | Should -Be "b"
58+
$res[2] | Should -Be "c"
59+
$res[3] | Should -Be "d"
60+
61+
$res = " " -split " ",-2
62+
$res.count | Should -Be 2
63+
$res[0] | Should -Be ""
64+
$res[1] | Should -Be ""
65+
}
66+
67+
It "Binary split operator with predicate can work with negative numbers" {
68+
$res = "a b c d" -split {$_ -like ' '},-2
69+
$res.count | Should -Be 2
70+
$res[0] | Should -Be "a b c"
71+
$res[1] | Should -Be "d"
72+
73+
$res = "a b c d" -split {$_ -like ' '},-4
74+
$res.count | Should -Be 4
75+
$res[0] | Should -Be "a"
76+
$res[1] | Should -Be "b"
77+
$res[2] | Should -Be "c"
78+
$res[3] | Should -Be "d"
79+
80+
$res = "a b c d" -split {$_ -like ' '},-8
81+
$res.count | Should -Be 4
82+
$res[0] | Should -Be "a"
83+
$res[1] | Should -Be "b"
84+
$res[2] | Should -Be "c"
85+
$res[3] | Should -Be "d"
86+
87+
$res = " " -split {$_ -like ' '},-4
88+
$res.count | Should -Be 2
89+
$res[0] | Should -Be ""
90+
$res[1] | Should -Be ""
91+
92+
$res = "folder/path/to/file" -split {$_ -like '/'}, -2
93+
$res.count | Should -Be 2
94+
$res[0] | Should -Be "folder/path/to"
95+
$res[1] | Should -Be "file"
96+
}
97+
98+
It "Binary split operator can work with regex expression" {
99+
$res = "a2b3c4d" -split '\d+',2
100+
$res.count | Should -Be 2
101+
$res[0] | Should -Be "a"
102+
$res[1] | Should -Be "b3c4d"
103+
104+
$res = "a2b3c4d" -split '\d+',-2
105+
$res.count | Should -Be 2
106+
$res[0] | Should -Be "a2b3c"
107+
$res[1] | Should -Be "d"
42108
}
43109

44110
It "Binary split operator can works with freeform delimiter" {

0 commit comments

Comments
0 (0)
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.