pas2js/tools/releasecreator/findwriteln.pas
2024-01-10 12:10:36 +01:00

275 lines
6.6 KiB
ObjectPascal

unit FindWriteln;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
TFindWritelnLog = procedure(EventType : TEventType; const Msg: string) of object;
function FindWritelnInDirectory(Dir: string; Recurse: boolean; const Log: TFindWritelnLog): integer;
implementation
function ReadNextToken(const Src: string; var SrcP: PChar; var Line: integer): string;
var
p, TokenStart: PChar;
begin
p:=SrcP;
while p^ in [' ',#9] do inc(p);
repeat
case p^ of
#0:
if p-PChar(Src)=length(Src) then begin
SrcP:=p;
exit('');
end
else
inc(p);
#10,#13:
begin
inc(Line);
if (p[1] in [#10,#13]) and (p^<>p[1]) then
inc(p,2)
else
inc(p);
end;
' ',#9:
inc(p);
else
break;
end;
until false;
TokenStart:=p;
case p^ of
'a'..'z','A'..'Z','_':
while p^ in ['a'..'z','A'..'Z','_','0'..'9'] do inc(p);
'0'..'9':
while p^ in ['0'..'9'] do inc(p);
'''':
begin
inc(p);
repeat
case p^ of
#0,#10,#13: break;
'''':
begin
inc(p);
break;
end;
end;
inc(p);
until false;
end;
'/':
if p[1]='/' then begin
inc(p,2);
while not (p^ in [#0,#10,#13]) do inc(p);
end else
inc(p);
'{':
begin
inc(p);
repeat
case p^ of
#0:
if p-PChar(Src)=length(Src) then begin
SrcP:=p;
exit('');
end;
#10,#13:
begin
inc(Line);
if (p[1] in [#10,#13]) and (p^<>p[1]) then
inc(p);
end;
'}': break;
end;
inc(p);
until false;
inc(p);
end;
'(':
if p[1]='*' then begin
inc(p,2);
repeat
case p^ of
#0:
if p-PChar(Src)=length(Src) then begin
SrcP:=p;
exit('');
end;
#10,#13:
begin
inc(Line);
if (p[1] in [#10,#13]) and (p^<>p[1]) then
inc(p);
end;
'*':
if p[1]=')' then break;
end;
inc(p);
until false;
inc(p,2);
end else
inc(p);
else
inc(p);
end;
SetLength(Result,p-TokenStart);
Move(TokenStart^,Result[1],length(Result));
SrcP:=P;
end;
procedure GetLineStartEndAtPosition(const Source:string; Position:integer;
out LineStart,LineEnd:integer);
begin
if Position<1 then begin
LineStart:=0;
LineEnd:=0;
exit;
end;
if Position>length(Source)+1 then begin
LineStart:=length(Source)+1;
LineEnd:=LineStart;
exit;
end;
LineStart:=Position;
while (LineStart>1) and (not (Source[LineStart-1] in [#10,#13])) do
dec(LineStart);
LineEnd:=Position;
while (LineEnd<=length(Source)) and (not (Source[LineEnd] in [#10,#13])) do
inc(LineEnd);
end;
function GetLineInSrc(const Source: string; Position: integer): string;
var
LineStart, LineEnd: integer;
begin
GetLineStartEndAtPosition(Source,Position,LineStart,LineEnd);
Result:=copy(Source,LineStart,LineEnd-LineStart);
end;
function CheckFile(Filename: string; const Log: TFindWritelnLog): integer;
var
Token, LastToken, Src: String;
ms: TMemoryStream;
p: PChar;
Line, LastIFDEF, AllowWriteln: Integer;
Lvl, VerboseLvl: integer;
begin
Result:=0;
ms:=TMemoryStream.Create;
try
ms.LoadFromFile(Filename);
if ms.Size=0 then exit;
Src:='';
SetLength(Src,ms.Size);
Move(ms.Memory^,Src[1],length(Src));
p:=PChar(Src);
AllowWriteln:=0;
Line:=1;
LastIFDEF:=-1;
Token:='';
Lvl:=0;
VerboseLvl:=-1;
repeat
LastToken:=Token;
Token:=ReadNextToken(Src,p,Line);
if Token='' then break;
if Token[1]='{' then begin
Token:=lowercase(Token);
if Token='{allowwriteln}' then begin
if AllowWriteln>0 then begin
inc(Result);
Log(etError,Filename+'('+IntToStr(Line)+'): writeln already allowed at '+IntToStr(AllowWriteln)+': '+GetLineInSrc(Src,p-PChar(Src)+1));
end;
AllowWriteln:=Line;
end
else if Token='{allowwriteln-}' then begin
if AllowWriteln<1 then begin
inc(Result);
Log(etError,Filename+'('+IntToStr(Line)+'): writeln was not allowed: '+GetLineInSrc(Src,p-PChar(Src)+1));
end;
AllowWriteln:=0;
end
else if SameText(LeftStr(Token,4),'{$if') then begin
inc(Lvl);
LastIFDEF:=Line;
if SameText(LeftStr(Token,15),'{$ifdef Verbose') then begin
if VerboseLvl<0 then VerboseLvl:=Lvl;
end;
end else if SameText(LeftStr(Token,6),'{$else') then begin
if Lvl=VerboseLvl then
VerboseLvl:=-1;
LastIFDEF:=Line;
end else if SameText(LeftStr(Token,7),'{$endif') then begin
if Lvl=VerboseLvl then begin
VerboseLvl:=-1;
end;
dec(Lvl);
end;
end
else begin
if (CompareText(Token,'str')=0) and (LastToken<>'.') then begin
if byte(Line-LastIFDEF) in [0,1] then begin
// ignore writeln just behind IFDEF
LastIFDEF:=Line;
end;
end;
if (CompareText(Token,'writeln')=0)
and (LastToken<>'.')
and (LastToken<>':=')
and (LastToken<>'=')
and (LastToken<>'+')
and not SameText(LastToken,'function')
and not SameText(LastToken,'procedure') then begin
if Lvl=VerboseLvl then begin
// ignore writeln inside $IFDEF VerboseX
end else if byte(Line-LastIFDEF) in [0,1] then begin
// ignore writeln just behind IFDEF
LastIFDEF:=Line;
end else if AllowWriteln<1 then begin
inc(Result);
Log(etError,Filename+'('+IntToStr(Line)+'): '+GetLineInSrc(Src,p-PChar(Src)+1));
end;
end;
end;
until false;
finally
ms.Free;
end;
end;
function FindWritelnInDirectory(Dir: string; Recurse: boolean; const Log: TFindWritelnLog): integer;
var
Info: TRawByteSearchRec;
Ext: String;
begin
Result:=0;
Dir:=IncludeTrailingPathDelimiter(Dir);
if FindFirst(Dir+AllFilesMask,faAnyFile,Info)=0 then begin
repeat
if (Info.Name='') or (Info.Name='.') or (Info.Name='..') then continue;
if (Info.Attr and faDirectory)>0 then begin
if Recurse then
Result+=FindWritelnInDirectory(Dir+Info.Name,true,Log);
end
else begin
Ext:=lowercase(ExtractFileExt(Info.Name));
case Ext of
'.p','.pp','.pas','.inc': Result+=CheckFile(Dir+Info.Name,Log);
end;
end;
until FindNext(Info)<>0;
FindClose(Info);
end;
end;
end.