feat(strutils): clean literal replace API — Replace (first) + ReplaceAll
Replace the FPC/Delphi replace surface (ReplaceStr, ReplaceText, and the proposed StringReplace + TReplaceFlags set) with two self-explanatory functions: Replace(S, Old, New) — replaces the FIRST occurrence ReplaceAll(S, Old, New) — replaces EVERY occurrence Both are case-sensitive and literal (non-pattern). This sheds the legacy redundancy: ReplaceStr/ReplaceText were two names for one operation split by a 'String vs Text' distinction that is not obvious, and Delphi's StringReplace encodes the same two booleans (all-vs-first, sensitive-vs-insensitive) as an awkward flag-set. Modern languages (Go, Python, Java, Rust) use a plain first/all split with no case flag. Case-insensitive replace is expressed by lower-casing the inputs with the built-in LowerCase/UpperCase before calling Replace/ReplaceAll; a case-*preserving* insensitive replace and pattern/regex matching are deferred and recorded in docs/future-improvements.adoc. The internal worker keeps the existing TStringBuilder-based loop; it gains a FirstOnly flag and drops the now-unused case-fold path. No callers of the removed functions existed anywhere in the tree. IR/semantic and e2e tests updated to the new names; both fixpoints green (FIXPOINT_OK, NATIVE_FIXPOINT_OK, NATIVE_INTERNAL_OK); full suite OK (3346 tests).
This commit is contained in:
parent
712263abd4
commit
2f54707a63
|
|
@ -53,12 +53,13 @@ type
|
|||
procedure TestRun_IndexStr_NotFound;
|
||||
procedure TestRun_IndexText_CaseInsensitive;
|
||||
|
||||
{ ReplaceStr / ReplaceText }
|
||||
procedure TestRun_ReplaceStr_SingleOccurrence;
|
||||
procedure TestRun_ReplaceStr_MultipleOccurrences;
|
||||
procedure TestRun_ReplaceText_CaseInsensitive;
|
||||
procedure TestRun_ReplaceStr_EmptyOld;
|
||||
procedure TestRun_ReplaceStr_EmptyNew;
|
||||
{ Replace (first) / ReplaceAll }
|
||||
procedure TestRun_Replace_FirstOnly;
|
||||
procedure TestRun_Replace_NotFound;
|
||||
procedure TestRun_ReplaceAll_SingleOccurrence;
|
||||
procedure TestRun_ReplaceAll_MultipleOccurrences;
|
||||
procedure TestRun_ReplaceAll_EmptyOld;
|
||||
procedure TestRun_ReplaceAll_EmptyNew;
|
||||
|
||||
{ DupeString / ReverseString / StuffString }
|
||||
procedure TestRun_DupeString;
|
||||
|
|
@ -407,65 +408,77 @@ begin
|
|||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ ReplaceStr / ReplaceText }
|
||||
{ Replace (first) / ReplaceAll }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceStr_SingleOccurrence;
|
||||
procedure TE2EStrUtilsTests.TestRun_Replace_FirstOnly;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceStr('hello world', 'world', 'Blaise')) end.
|
||||
begin WriteLn(Replace('aabbaa', 'aa', 'X')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('only the first match replaced', 'Xbbaa', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_Replace_NotFound;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(Replace('hello', 'z', 'X')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('unchanged when not found', 'hello', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceAll_SingleOccurrence;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceAll('hello world', 'world', 'Blaise')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('hello Blaise', 'hello Blaise', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceStr_MultipleOccurrences;
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceAll_MultipleOccurrences;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceStr('aabbaa', 'aa', 'X')) end.
|
||||
begin WriteLn(ReplaceAll('aabbaa', 'aa', 'X')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('XbbX', 'XbbX', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceText_CaseInsensitive;
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceAll_EmptyOld;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceText('Hello World', 'WORLD', 'Blaise')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('Hello Blaise', 'Hello Blaise', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceStr_EmptyOld;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceStr('hello', '', 'X')) end.
|
||||
begin WriteLn(ReplaceAll('hello', '', 'X')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('empty old returns unchanged', 'hello', Trim(Output));
|
||||
end;
|
||||
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceStr_EmptyNew;
|
||||
procedure TE2EStrUtilsTests.TestRun_ReplaceAll_EmptyNew;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable() then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRunWithRTL(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
begin WriteLn(ReplaceStr('hello', 'l', '')) end.
|
||||
begin WriteLn(ReplaceAll('hello', 'l', '')) end.
|
||||
''', Output, RCode));
|
||||
AssertEquals('heo', 'heo', Trim(Output));
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@ type
|
|||
procedure TestSemantic_IndexText_OK;
|
||||
procedure TestSemantic_IndexStr_ReturnsInteger;
|
||||
|
||||
{ ReplaceStr / ReplaceText }
|
||||
procedure TestSemantic_ReplaceStr_OK;
|
||||
procedure TestSemantic_ReplaceText_OK;
|
||||
procedure TestSemantic_ReplaceStr_ReturnsString;
|
||||
{ Replace / ReplaceAll }
|
||||
procedure TestSemantic_Replace_OK;
|
||||
procedure TestSemantic_ReplaceAll_OK;
|
||||
procedure TestSemantic_ReplaceAll_ReturnsString;
|
||||
|
||||
{ DupeString / ReverseString / StuffString }
|
||||
procedure TestSemantic_DupeString_OK;
|
||||
|
|
@ -104,7 +104,7 @@ type
|
|||
|
||||
{ Codegen — unit-level function calls appear in IR }
|
||||
procedure TestCodegen_ContainsStr_InIR;
|
||||
procedure TestCodegen_ReplaceStr_InIR;
|
||||
procedure TestCodegen_ReplaceAll_InIR;
|
||||
procedure TestCodegen_TrimLeft_InIR;
|
||||
procedure TestCodegen_PosEx_InIR;
|
||||
procedure TestCodegen_LeftStr_InIR;
|
||||
|
|
@ -542,30 +542,30 @@ begin
|
|||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ ReplaceStr / ReplaceText }
|
||||
{ Replace / ReplaceAll }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
procedure TStrUtilsTests.TestSemantic_ReplaceStr_OK;
|
||||
procedure TStrUtilsTests.TestSemantic_Replace_OK;
|
||||
begin
|
||||
SemanticOK(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
var S, T: string;
|
||||
begin T := ReplaceStr(S, 'x', 'y') end.
|
||||
begin T := Replace(S, 'x', 'y') end.
|
||||
''');
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSemantic_ReplaceText_OK;
|
||||
procedure TStrUtilsTests.TestSemantic_ReplaceAll_OK;
|
||||
begin
|
||||
SemanticOK(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
var S, T: string;
|
||||
begin T := ReplaceText(S, 'x', 'y') end.
|
||||
begin T := ReplaceAll(S, 'x', 'y') end.
|
||||
''');
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestSemantic_ReplaceStr_ReturnsString;
|
||||
procedure TStrUtilsTests.TestSemantic_ReplaceAll_ReturnsString;
|
||||
var
|
||||
Lexer: TLexer;
|
||||
Parser: TParser;
|
||||
|
|
@ -580,7 +580,7 @@ begin
|
|||
Lexer := nil; Parser := nil; Prog := nil; Semantic := nil;
|
||||
Loader := nil; Units := nil; SearchPaths := nil;
|
||||
try
|
||||
Lexer := TLexer.Create('program P; uses StrUtils; var S, T: string; begin T := ReplaceStr(S, ''x'', ''y'') end.');
|
||||
Lexer := TLexer.Create('program P; uses StrUtils; var S, T: string; begin T := ReplaceAll(S, ''x'', ''y'') end.');
|
||||
Parser := TParser.Create(Lexer);
|
||||
Prog := Parser.Parse();
|
||||
Semantic := TSemanticAnalyser.Create();
|
||||
|
|
@ -593,7 +593,7 @@ begin
|
|||
Semantic.AnalyseUnitForExport(TUnit(Units.Items[I]));
|
||||
Semantic.Analyse(Prog);
|
||||
Assign := TAssignment(Prog.Block.Stmts[0]);
|
||||
AssertEquals('ReplaceStr returns string',
|
||||
AssertEquals('ReplaceAll returns string',
|
||||
Ord(tyString), Ord(Assign.Expr.ResolvedType.Kind));
|
||||
finally
|
||||
Semantic.Free();
|
||||
|
|
@ -1042,16 +1042,16 @@ begin
|
|||
AssertTrue('ContainsStr appears in IR', IRContains(IR, '$StrUtils_ContainsStr'));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestCodegen_ReplaceStr_InIR;
|
||||
procedure TStrUtilsTests.TestCodegen_ReplaceAll_InIR;
|
||||
var IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P; uses StrUtils;
|
||||
var S, T: string;
|
||||
begin T := ReplaceStr(S, 'x', 'y') end.
|
||||
begin T := ReplaceAll(S, 'x', 'y') end.
|
||||
''');
|
||||
AssertTrue('ReplaceStr appears in IR', IRContains(IR, '$StrUtils_ReplaceStr'));
|
||||
AssertTrue('ReplaceAll appears in IR', IRContains(IR, '$StrUtils_ReplaceAll'));
|
||||
end;
|
||||
|
||||
procedure TStrUtilsTests.TestCodegen_TrimLeft_InIR;
|
||||
|
|
|
|||
|
|
@ -246,6 +246,32 @@ replacements. `Extended` maps to `Double` with a precision-loss warning.
|
|||
*Effort.* Medium — primarily RTL/stdlib work once operator overloading is in place.
|
||||
No compiler changes needed.
|
||||
|
||||
== StdLib — String operations
|
||||
|
||||
=== Regular-expression search and replace
|
||||
|
||||
`StrUtils` provides literal (non-pattern) replacement only: `Replace` (first
|
||||
occurrence) and `ReplaceAll` (every occurrence), both case-sensitive. A
|
||||
pattern-based API — `regex` search, `ReplaceMatches`/`ReplaceFirstMatch`, and
|
||||
capture-group substitution — is deliberately deferred because it requires a
|
||||
regular-expression engine (parser + NFA/DFA matcher) that does not yet exist
|
||||
in the stdlib.
|
||||
|
||||
*Why deferred.* A correct, reasonably performant regex engine is a
|
||||
substantial component in its own right. The literal `Replace`/`ReplaceAll`
|
||||
functions cover the common cases without it.
|
||||
|
||||
*Current workarounds.*
|
||||
|
||||
- Case-insensitive replace: lower-case the inputs with the built-in
|
||||
`LowerCase` (or `UpperCase`) before calling `Replace`/`ReplaceAll`. Note
|
||||
that this also folds the case of the surrounding text in the result; a
|
||||
case-*preserving* insensitive replace is a regex-engine feature.
|
||||
- Fixed multi-pattern replacement: chain `ReplaceAll` calls.
|
||||
|
||||
*Effort.* Large — a regex engine is the bulk of the work; the replace API on
|
||||
top of it is small.
|
||||
|
||||
== macOS — Debugging and Code-Signing
|
||||
|
||||
=== Debugger entitlement requirements
|
||||
|
|
|
|||
|
|
@ -80,11 +80,15 @@ function IndexText(const Str: string; const Arr: array of string): Integer;
|
|||
{ Replacement }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
{ Replaces all occurrences of OldPattern with NewPattern (case-sensitive). }
|
||||
function ReplaceStr(const S, OldPattern, NewPattern: string): string;
|
||||
{ Replaces the FIRST occurrence of OldPattern with NewPattern. Returns S
|
||||
unchanged if OldPattern is empty or not found. Matching is case-sensitive;
|
||||
for a case-insensitive replace, lower-case the inputs as you pass them in. }
|
||||
function Replace(const S, OldPattern, NewPattern: string): string;
|
||||
|
||||
{ Replaces all occurrences of OldPattern with NewPattern (case-insensitive). }
|
||||
function ReplaceText(const S, OldPattern, NewPattern: string): string;
|
||||
{ Replaces ALL occurrences of OldPattern with NewPattern. Matching is
|
||||
case-sensitive; for a case-insensitive replace, lower-case the inputs as
|
||||
you pass them in. }
|
||||
function ReplaceAll(const S, OldPattern, NewPattern: string): string;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
{ Manipulation }
|
||||
|
|
@ -386,15 +390,15 @@ end;
|
|||
{ Replacement }
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
||||
{ Internal: replace all occurrences of OldP with NewP; Fold=True for case-insensitive. }
|
||||
function ReplaceAll(const S, OldP, NewP: string; Fold: Boolean): string;
|
||||
{ Internal worker for Replace/ReplaceAll. Matching is case-sensitive;
|
||||
FirstOnly=True stops after the first match (the rest is copied verbatim). }
|
||||
function DoReplace(const S, OldP, NewP: string; FirstOnly: Boolean): string;
|
||||
var
|
||||
SLen, OldLen, NewLen: Integer;
|
||||
SB: TStringBuilder;
|
||||
I, J: Integer;
|
||||
Match: Boolean;
|
||||
Match, Done: Boolean;
|
||||
SP, OP: PChar;
|
||||
C1, C2: Integer;
|
||||
begin
|
||||
SLen := Length(S);
|
||||
OldLen := Length(OldP);
|
||||
|
|
@ -406,29 +410,22 @@ begin
|
|||
SP := PChar(S);
|
||||
OP := PChar(OldP);
|
||||
SB := TStringBuilder.Create();
|
||||
I := 0;
|
||||
while I <= SLen - OldLen do
|
||||
I := 0;
|
||||
Done := False;
|
||||
while (not Done) and (I <= SLen - OldLen) do
|
||||
begin
|
||||
Match := True;
|
||||
for J := 0 to OldLen - 1 do
|
||||
begin
|
||||
if Fold then
|
||||
begin
|
||||
C1 := FoldLower(SP[I + J]);
|
||||
C2 := FoldLower(OP[J]);
|
||||
end
|
||||
else
|
||||
begin
|
||||
C1 := SP[I + J];
|
||||
C2 := OP[J];
|
||||
end;
|
||||
if C1 <> C2 then begin Match := False; Break; end;
|
||||
if SP[I + J] <> OP[J] then begin Match := False; Break; end;
|
||||
end;
|
||||
if Match then
|
||||
begin
|
||||
if NewLen > 0 then
|
||||
SB.Append(NewP);
|
||||
I := I + OldLen;
|
||||
if FirstOnly then
|
||||
Done := True;
|
||||
end
|
||||
else
|
||||
begin
|
||||
|
|
@ -446,14 +443,14 @@ begin
|
|||
SB.Free();
|
||||
end;
|
||||
|
||||
function ReplaceStr(const S, OldPattern, NewPattern: string): string;
|
||||
function Replace(const S, OldPattern, NewPattern: string): string;
|
||||
begin
|
||||
Result := ReplaceAll(S, OldPattern, NewPattern, False);
|
||||
Result := DoReplace(S, OldPattern, NewPattern, True);
|
||||
end;
|
||||
|
||||
function ReplaceText(const S, OldPattern, NewPattern: string): string;
|
||||
function ReplaceAll(const S, OldPattern, NewPattern: string): string;
|
||||
begin
|
||||
Result := ReplaceAll(S, OldPattern, NewPattern, True);
|
||||
Result := DoReplace(S, OldPattern, NewPattern, False);
|
||||
end;
|
||||
|
||||
{ ------------------------------------------------------------------ }
|
||||
|
|
|
|||
Loading…
Reference in a new issue