123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- 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.
|