@@ -584,7 +584,7 @@ internal static object SplitOperator(ExecutionContext context, IScriptExtent err |
584 | 584 | return SplitOperatorImpl(context, errorPosition, lval, rval, SplitImplOptions.None, ignoreCase); |
585 | 585 | } |
586 | 586 |
|
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) |
588 | 588 | { |
589 | 589 | IEnumerable<string> content = enumerateContent(context, errorPosition, implOptions, lval); |
590 | 590 |
|
@@ -634,84 +634,166 @@ private static object SplitOperatorImpl(ExecutionContext context, IScriptExtent |
634 | 634 | options |= SplitOptions.IgnoreCase; |
635 | 635 | } |
636 | 636 |
|
637 | | - if (predicate != null) |
| 637 | + if (predicate == null) |
| 638 | + { |
| 639 | + return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options); |
| 640 | + } |
| 641 | + else if (limit >= 0) |
638 | 642 | { |
639 | 643 | return SplitWithPredicate(context, errorPosition, content, predicate, limit); |
640 | 644 | } |
641 | 645 | else |
642 | 646 | { |
643 | | - return SplitWithPattern(context, errorPosition, content, separatorPattern, limit, options); |
| 647 | + return NegativeSplitWithPredicate(context, errorPosition, content, predicate, limit); |
644 | 648 | } |
645 | 649 | } |
646 | 650 |
|
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) |
648 | 652 | { |
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 | + |
650 | 662 | foreach (string item in content) |
651 | 663 | { |
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; |
653 | 691 |
|
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) |
655 | 701 | { |
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)); |
660 | 707 | } |
| 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>(); |
661 | 739 |
|
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++) |
664 | 747 | { |
665 | | - object isDelimChar = predicate.DoInvokeReturnAsIs( |
| 748 | + // Evaluate the predicate using the character at cursor. |
| 749 | + object predicateResult = predicate.DoInvokeReturnAsIs( |
666 | 750 | useLocalScope: true, |
667 | 751 | errorHandlingBehavior: ScriptBlock.ErrorHandlingBehavior.WriteToExternalErrorPipe, |
668 | | - dollarUnder: CharToString(item[strIndex]), |
| 752 | + dollarUnder: CharToString(item[cursor]), |
669 | 753 | input: AutomationNull.Value, |
670 | 754 | 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 }); |
676 | 756 |
|
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++; |
692 | 762 |
|
693 | | - break; |
694 | | - } |
| 763 | + cursor += 1; |
695 | 764 |
|
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; |
703 | 766 | } |
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)) |
705 | 777 | { |
706 | | - buf.Append(item[strIndex]); |
| 778 | + break; |
707 | 779 | } |
708 | 780 | } |
709 | 781 |
|
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) |
713 | 783 | { |
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)); |
715 | 797 | } |
716 | 798 |
|
717 | 799 | results.AddRange(split); |
@@ -720,7 +802,7 @@ private static object SplitWithPredicate(ExecutionContext context, IScriptExtent |
720 | 802 | return results.ToArray(); |
721 | 803 | } |
722 | 804 |
|
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) |
724 | 806 | { |
725 | 807 | // Default to Regex matching if no match specified. |
726 | 808 | if ((options & SplitOptions.SimpleMatch) == 0 && |
@@ -743,20 +825,24 @@ private static object SplitWithPattern(ExecutionContext context, IScriptExtent e |
743 | 825 | separatorPattern = Regex.Escape(separatorPattern); |
744 | 826 | } |
745 | 827 |
|
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) |
747 | 834 | { |
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; |
751 | 837 | } |
752 | 838 |
|
753 | | - RegexOptions regexOptions = parseRegexOptions(options); |
754 | 839 | Regex regex = NewRegex(separatorPattern, regexOptions); |
755 | 840 |
|
756 | | - List<string> results = new List<string>(); |
| 841 | + var results = new List<string>(); |
| 842 | + |
757 | 843 | foreach (string item in content) |
758 | 844 | { |
759 | | - string[] split = regex.Split(item, limit, 0); |
| 845 | + string[] split = regex.Split(item, calculatedLimit); |
760 | 846 | results.AddRange(split); |
761 | 847 | } |
762 | 848 |
|
|