stdlib: add SplitChar / SplitLines / JoinList to StrUtils
StrUtils had JoinStr (open-array join) but no splitter at all — a gap any text-processing code hits. Add SplitChar (split on a delimiter byte), SplitLines (split on LF, tolerating CRLF), and JoinList (the list-valued inverse of SplitChar), all returning/taking TList<String>. This pulls Generics.Collections into StrUtils' interface. Lifted from the Luhmann web server. Adds 10 tests (57 stdlib tests total).
This commit is contained in:
parent
c45fcbca35
commit
88a11034b9
|
|
@ -24,6 +24,9 @@ unit StrUtils;
|
|||
|
||||
interface
|
||||
|
||||
uses
|
||||
Generics.Collections; { TList<String> for the Split/Join helpers }
|
||||
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Containment }
|
||||
|
|
@ -131,6 +134,18 @@ function IsEmptyOrWhitespace(const S: string): Boolean;
|
|||
{ Joins Parts with Sep between each element. }
|
||||
function JoinStr(const Sep: string; const Parts: array of string): string;
|
||||
|
||||
{ Split S on a single delimiter byte, returning the pieces (one more piece
|
||||
than there are delimiters; empty pieces are kept). Caller owns the list. }
|
||||
function SplitChar(const S: string; ADelim: Byte): TList<String>;
|
||||
|
||||
{ Split S into lines on LF, tolerating CRLF (a trailing CR is stripped from
|
||||
each line). Caller owns the list. }
|
||||
function SplitLines(const S: string): TList<String>;
|
||||
|
||||
{ Join the items of AList with ASep between them (the list-valued inverse of
|
||||
SplitChar; cf. JoinStr for an open array). nil yields ''. }
|
||||
function JoinList(AList: TList<String>; const ASep: string): string;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ UTF-8 Codepoint operations }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
@ -666,6 +681,74 @@ begin
|
|||
SB.Free();
|
||||
end;
|
||||
|
||||
function SplitChar(const S: string; ADelim: Byte): TList<String>;
|
||||
var
|
||||
I, N, Start: Integer;
|
||||
begin
|
||||
Result := TList<String>.Create();
|
||||
N := Length(S);
|
||||
Start := 0;
|
||||
I := 0;
|
||||
while I < N do
|
||||
begin
|
||||
if Byte(S[I]) = ADelim then
|
||||
begin
|
||||
Result.Add(Copy(S, Start, I - Start));
|
||||
Start := I + 1;
|
||||
end;
|
||||
I := I + 1;
|
||||
end;
|
||||
Result.Add(Copy(S, Start, N - Start));
|
||||
end;
|
||||
|
||||
{ Copy S[AStart..AEnd) as a line, dropping one trailing CR so a CRLF line
|
||||
yields the bare text. }
|
||||
function LineSlice(const S: string; AStart, AEnd: Integer): string;
|
||||
begin
|
||||
if (AEnd > AStart) and (Byte(S[AEnd - 1]) = 13) then { trailing CR }
|
||||
Result := Copy(S, AStart, AEnd - AStart - 1)
|
||||
else
|
||||
Result := Copy(S, AStart, AEnd - AStart);
|
||||
end;
|
||||
|
||||
function SplitLines(const S: string): TList<String>;
|
||||
var
|
||||
I, N, Start: Integer;
|
||||
begin
|
||||
Result := TList<String>.Create();
|
||||
N := Length(S);
|
||||
Start := 0;
|
||||
I := 0;
|
||||
while I < N do
|
||||
begin
|
||||
if Byte(S[I]) = 10 then { LF }
|
||||
begin
|
||||
Result.Add(LineSlice(S, Start, I));
|
||||
Start := I + 1;
|
||||
end;
|
||||
I := I + 1;
|
||||
end;
|
||||
Result.Add(LineSlice(S, Start, N));
|
||||
end;
|
||||
|
||||
function JoinList(AList: TList<String>; const ASep: string): string;
|
||||
var
|
||||
SB: TStringBuilder;
|
||||
I: Integer;
|
||||
begin
|
||||
if AList = nil then
|
||||
Exit('');
|
||||
SB := TStringBuilder.Create();
|
||||
for I := 0 to AList.Count - 1 do
|
||||
begin
|
||||
if I > 0 then
|
||||
SB.Append(ASep);
|
||||
SB.Append(AList.Get(I));
|
||||
end;
|
||||
Result := SB.ToString();
|
||||
SB.Free();
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ TStringBuilder }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
134
stdlib/src/test/pascal/strutils.tests.pas
Normal file
134
stdlib/src/test/pascal/strutils.tests.pas
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
{ Tests for the StrUtils split/join helpers (SplitChar / SplitLines /
|
||||
JoinList). Self-registers via the initialization section. }
|
||||
|
||||
unit StrUtils.Tests;
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
blaise.testing, StrUtils, Generics.Collections;
|
||||
|
||||
type
|
||||
TStrUtilsTests = class(TTestCase)
|
||||
published
|
||||
procedure TestSplitChar_Basic;
|
||||
procedure TestSplitChar_EmptyPieces;
|
||||
procedure TestSplitChar_NoDelim;
|
||||
procedure TestSplitChar_Empty;
|
||||
procedure TestSplitLines_LF;
|
||||
procedure TestSplitLines_CRLF;
|
||||
procedure TestSplitLines_TrailingNewline;
|
||||
procedure TestJoinList_Basic;
|
||||
procedure TestJoinList_Empty;
|
||||
procedure TestRoundTrip;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
const
|
||||
CH_COMMA = 44;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitChar_Basic;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := SplitChar('a,b,c', CH_COMMA);
|
||||
AssertEquals('count', 3, Integer(L.Count));
|
||||
AssertEquals('0', 'a', L.Get(0));
|
||||
AssertEquals('1', 'b', L.Get(1));
|
||||
AssertEquals('2', 'c', L.Get(2));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitChar_EmptyPieces;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
{ leading, trailing and adjacent delimiters keep empty pieces }
|
||||
L := SplitChar(',a,,b,', CH_COMMA);
|
||||
AssertEquals('count', 5, Integer(L.Count));
|
||||
AssertEquals('0', '', L.Get(0));
|
||||
AssertEquals('1', 'a', L.Get(1));
|
||||
AssertEquals('2', '', L.Get(2));
|
||||
AssertEquals('3', 'b', L.Get(3));
|
||||
AssertEquals('4', '', L.Get(4));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitChar_NoDelim;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := SplitChar('abc', CH_COMMA);
|
||||
AssertEquals('count', 1, Integer(L.Count));
|
||||
AssertEquals('0', 'abc', L.Get(0));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitChar_Empty;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := SplitChar('', CH_COMMA);
|
||||
AssertEquals('count', 1, Integer(L.Count));
|
||||
AssertEquals('0', '', L.Get(0));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitLines_LF;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := SplitLines('one'#10'two'#10'three');
|
||||
AssertEquals('count', 3, Integer(L.Count));
|
||||
AssertEquals('0', 'one', L.Get(0));
|
||||
AssertEquals('2', 'three', L.Get(2));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitLines_CRLF;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
{ CRLF lines: the trailing CR must be stripped }
|
||||
L := SplitLines('one'#13#10'two'#13#10'three');
|
||||
AssertEquals('count', 3, Integer(L.Count));
|
||||
AssertEquals('0', 'one', L.Get(0));
|
||||
AssertEquals('1', 'two', L.Get(1));
|
||||
AssertEquals('2', 'three', L.Get(2));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSplitLines_TrailingNewline;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
{ a trailing LF yields a final empty line }
|
||||
L := SplitLines('a'#10'b'#10);
|
||||
AssertEquals('count', 3, Integer(L.Count));
|
||||
AssertEquals('last', '', L.Get(2));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestJoinList_Basic;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := TList<String>.Create();
|
||||
L.Add('a'); L.Add('b'); L.Add('c');
|
||||
AssertEquals('joined', 'a-b-c', JoinList(L, '-'));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestJoinList_Empty;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
L := TList<String>.Create();
|
||||
AssertEquals('empty list', '', JoinList(L, '-'));
|
||||
AssertEquals('nil list', '', JoinList(nil, '-'));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestRoundTrip;
|
||||
var L: TList<String>;
|
||||
begin
|
||||
{ JoinList is the inverse of SplitChar on the same delimiter }
|
||||
L := SplitChar('x,y,z', CH_COMMA);
|
||||
AssertEquals('roundtrip', 'x,y,z', JoinList(L, ','));
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TStrUtilsTests);
|
||||
|
||||
end.
|
||||
|
|
@ -27,7 +27,8 @@ uses
|
|||
Sockets.Tests,
|
||||
WebSockets.Tests,
|
||||
HttpServer.Tests,
|
||||
Guid.Tests;
|
||||
Guid.Tests,
|
||||
StrUtils.Tests;
|
||||
|
||||
implementation
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue