Przeglądaj źródła

Remake AdjustLineBreaks.

This version is correct and supposedly better in other ways (except for a bit of clarity maybe).
Rika Ichinose 2 lat temu
rodzic
commit
1c4151d82e

+ 53 - 62
rtl/objpas/sysutils/sysstr.inc

@@ -783,69 +783,60 @@ end;
 
 function AdjustLineBreaks(const S: string; Style: TTextLineBreakStyle): string;
 var
-  Source,Dest: PAnsiChar;
-  DestLen: Integer;
-  I,J,L: Longint;
-
+  Sp,Se,SLiteralStart,SLiteralEnd,Rp: PChar;
 begin
-  Source:=Pointer(S);
-  L:=Length(S);
-  DestLen:=L;
-  I:=1;
-  while (I<=L) do
-    begin
-    case S[i] of
-      #10: if (Style=tlbsCRLF) then
-               Inc(DestLen);
-      #13: if (Style=tlbsCRLF) then
-             if (I<L) and (S[i+1]=#10) then
-               Inc(I)
-             else
-               Inc(DestLen)
-             else if (I<L) and (S[I+1]=#10) then
-               Dec(DestLen);
-    end;
-    Inc(I);
-    end;
-  if (DestLen=L) then
-    Result:=S
-  else
-    begin
-    SetLength(Result, DestLen);
-    FillChar(Result[1],DestLen,0);
-    Dest := Pointer(Result);
-    J:=0;
-    I:=0;
-    While I<L do
-      case Source[I] of
-        #10: begin
-             if Style=tlbsCRLF then
-               begin
-               Dest[j]:=#13;
-               Inc(J);
-              end;
-             Dest[J] := #10;
-             Inc(J);
-             Inc(I);
-             end;
-        #13: begin
-             if Style=tlbsCRLF then
-               begin
-               Dest[j] := #13;
-               Inc(J);
-               end;
-             Dest[j]:=#10;
-             Inc(J);
-             Inc(I);
-             if Source[I]=#10 then
-               Inc(I);
-             end;
-      else
-        Dest[j]:=Source[i];
-        Inc(J);
-        Inc(I);
-      end;
-    end;
+  Result:='';
+  repeat { Does two iterations, first is prepass, second fills the result with data and is distinguished by Assigned(Pointer(Result)). }
+    Rp:=Pointer(Result);
+    Sp:=PChar(S); { Readable #0 for empty string. }
+    Se:=Sp+Length(S);
+    SLiteralStart:=Sp;
+    repeat
+      while (Sp<Se) and not (Sp^ in [#13,#10]) do
+        Inc(Sp);
+      SLiteralEnd:=Sp; { Save position before consuming line ending. }
+      if Sp^=#10 then { These accesses rely on terminating #0. }
+        begin
+          Inc(Sp);
+          if Style=tlbsLF then
+            continue;
+        end
+      else if Sp^=#13 then
+        if Sp[1]=#10 then
+          begin
+            Inc(Sp,2);
+            if Style=tlbsCRLF then
+              continue;
+          end
+        else
+          begin
+            Inc(Sp);
+            if Style=tlbsCR then
+              continue;
+          end;
+      if Assigned(Pointer(Result)) then
+        Move(SLiteralStart^,Rp^,Pointer(SLiteralEnd)-Pointer(SLiteralStart)); { Byte difference to avoid signed div 2 on char = widechar. }
+      Inc(Pointer(Rp),Pointer(SLiteralEnd)-Pointer(SLiteralStart)); { Again, byte difference. }
+      if SLiteralEnd=Sp then
+        break;
+      SLiteralStart:=Sp;
+      Inc(Rp,1+ord(Style=tlbsCRLF));
+      if Assigned(Pointer(Result)) then
+        begin
+          if Style=tlbsCRLF then
+            Rp[-2]:=#13;
+          if Style=tlbsCR then
+            Rp[-1]:=#13
+          else
+            Rp[-1]:=#10;
+        end;
+    until false;
+    if Assigned(Pointer(Result)) then { Second pass finished. }
+      break;
+    if SLiteralStart=PChar(S) then { String is unchanged. }
+      Exit(S);
+    SetLength(Result,SizeUint(Pointer(Rp)-Pointer(Result)) div SizeOf(Char)); { Prepare second pass. }
+  until false;
 end;
 
 

+ 72 - 0
tests/test/units/sysutils/tadjustlinebreaks.pp

@@ -0,0 +1,72 @@
+{$mode objfpc} {$longstrings on} {$coperators on}
+uses
+	SysUtils;
+
+var
+	somethingFailed: boolean = false;
+
+function Repr(const s: string): string;
+var
+	i: SizeInt;
+begin
+	result := '';
+	for i := 1 to length(s) do
+		if (s[i] >= #32) and (s[i] <= #127) then
+			result += s[i]
+		else
+			result += '#' + IntToStr(ord(s[i])) +
+				specialize IfThen<string>((i < length(s)) and ((s[i] = #10) or (s[i] = #13) and (pChar(pointer(s))[i] <> #10)), LineEnding, '');
+end;
+
+procedure TestAdjustLineBreaks(const src: string; style: TTextLineBreakStyle; expect: string);
+var
+	got, styleName: string;
+begin
+	got := AdjustLineBreaks(src, style);
+	if got <> expect then
+	begin
+		WriteStr(styleName, style);
+		writeln('AdjustLineBreaks(' + LineEnding +
+			LineEnding +
+			Repr(src) + ',' + LineEnding +
+			LineEnding +
+			styleName + ')' + LineEnding +
+			LineEnding +
+			'=' + LineEnding +
+			LineEnding +
+			Repr(got) + LineEnding +
+			LineEnding +
+			'expected' + LineEnding +
+			LineEnding +
+			Repr(expect) + LineEnding);
+		somethingFailed := true;
+	end;
+end;
+
+const
+	D1 = 'Drinking the soup in the Dining Room will poison Viola and cause her to lose HP with each step.';
+	D2 = 'The Chef will chop Viola''s hands off if she chooses to lend the chef a hand in the Kitchen.';
+	D3 = 'Upon entering the Spider Room, If Viola takes the Butterfly without placing the Butterfly Model in the web, trying to exit the room will make a spider decapitate her.';
+	D4 = 'Reading the Book of Death will cause Viola to violently and uncontrollably bleed to death.';
+	D5 = 'Entering the Snake Room without feeding the Frog to the Snake will cause the Snake to eat Viola.';
+	D6 = 'If Viola visits the Frog Room after the Frog was killed, and Viola reads the note, a black hand will emerge from the black pit and grab her.';
+	LEs: array[TTextLineBreakStyle] of string = (#10, #13#10, #13);
+
+var
+	style: TTextLineBreakStyle;
+
+begin
+	for style in TTextLineBreakStyle do
+	begin
+		TestAdjustLineBreaks(
+			#10#13 + D1 + #13#10 + D2 + #10 + D3 + #13 + D4 + #13#10#10, style,
+			LEs[style] + LEs[style] + D1 + LEs[style] + D2 + LEs[style] + D3 + LEs[style] + D4 + LEs[style] + LEs[style]);
+
+		TestAdjustLineBreaks(
+			D5 + #13 + D6, style,
+			D5 + LEs[style] + D6);
+	end;
+
+	if somethingFailed then halt(1);
+	writeln('ok');
+end.