1 {
2  ***************************************************************************
3  *                                                                         *
4  *   This source is free software; you can redistribute it and/or modify   *
5  *   it under the terms of the GNU General Public License as published by  *
6  *   the Free Software Foundation; either version 2 of the License, or     *
7  *   (at your option) any later version.                                   *
8  *                                                                         *
9  *   This code is distributed in the hope that it will be useful, but      *
10  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
12  *   General Public License for more details.                              *
13  *                                                                         *
14  *   A copy of the GNU General Public License is available on the World    *
15  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
16  *   obtain it by writing to the Free Software Foundation,                 *
17  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
18  *                                                                         *
19  ***************************************************************************
20 
21   Author: Mattias Gaertner
22 
23   Abstract:
24     Parser for Free Pascal Compiler output.
25 }
26 unit etFPCMsgParser;
27 
28 {$mode objfpc}{$H+}
29 
30 { $DEFINE VerboseFPCMsgUnitNotFound}
31 
32 interface
33 
34 uses
35   // RTL
36   Classes, SysUtils, strutils, math,
37   // CodeTools
38   KeywordFuncLists, CodeToolsFPCMsgs, CodeCache, FileProcs, CodeToolManager,
39   DirectoryCacher, BasicCodeTools, DefineTemplates, SourceLog, LinkScanner,
40   // LazUtils
41   LConvEncoding, LazUTF8, FileUtil, LazFileUtils, LazFileCache, LazUtilities,
42   AvgLvlTree,
43   // IDEIntf
44   IDEExternToolIntf, PackageIntf, LazIDEIntf, ProjectIntf, MacroIntf, IDEUtils,
45   // IDE
46   IDECmdLine, LazarusIDEStrConsts, EnvironmentOpts, LazConf, TransferMacros,
47   etMakeMsgParser;
48 
49 const
50   FPCMsgIDCompiling = 3104;
51   FPCMsgIDLogo = 11023;
52   FPCMsgIDCantFindUnitUsedBy = 10022;
53   FPCMsgIDLinking = 9015;
54   FPCMsgIDErrorWhileLinking = 9013;
55   FPCMsgIDErrorWhileCompilingResources = 9029;
56   FPCMsgIDCallingResourceCompiler = 9028;
57   FPCMsgIDThereWereErrorsCompiling = 10026;
58   FPCMsgIDMethodIdentifierExpected = 3047;
59   FPCMsgIDIdentifierNotFound = 5000;
60   FPCMsgIDChecksumChanged = 10028;
61   FPCMsgIDUnitNotUsed = 5023; // Unit "$1" not used in $2
62   FPCMsgIDCompilationAborted = 1018;
63   FPCMsgIDLinesCompiled = 1008;
64 
65   FPCMsgAttrWorkerDirectory = 'WD';
66   FPCMsgAttrMissingUnit = 'MissingUnit';
67   FPCMsgAttrUsedByUnit = 'UsedByUnit';
68 type
69   TFPCMsgFilePool = class;
70 
71   { TFPCMsgFilePoolItem }
72 
73   TFPCMsgFilePoolItem = class
74   private
75     FMsgFile: TFPCMsgFile;
76     FFilename: string;
77     FPool: TFPCMsgFilePool;
78     FLoadedFileAge: integer;
79     fUseCount: integer;
80   public
81     constructor Create(aPool: TFPCMsgFilePool; const aFilename: string);
82     destructor Destroy; override;
83     property Pool: TFPCMsgFilePool read FPool;
84     property Filename: string read FFilename;
85     property LoadedFileAge: integer read FLoadedFileAge;
GetMsgnull86     function GetMsg(ID: integer): TFPCMsgItem;
87     property MsgFile: TFPCMsgFile read FMsgFile;
88     property UseCount: integer read fUseCount;
89   end;
90 
91   TETLoadFileEvent = procedure(aFilename: string; out s: string) of object;
92 
93   { TFPCMsgFilePool }
94 
95   TFPCMsgFilePool = class(TComponent)
96   private
97     fCritSec: TRTLCriticalSection;
98     FDefaultEnglishFile: string;
99     FDefaultTranslationFile: string;
100     FFiles: TFPList; // list of TFPCMsgFilePoolItem sorted for loaded
101     FOnLoadFile: TETLoadFileEvent;
102     fPendingLog: TStrings;
103     fMsgFileStamp: integer;
104     fCurrentEnglishFile: string; // valid only if fMsgFileStamp=CompilerParseStamp
105     fCurrentTranslationFile: string; // valid only if fMsgFileStamp=CompilerParseStamp
106     procedure Log(Msg: string; AThread: TThread);
107     procedure LogSync;
108     procedure SetDefaultEnglishFile(AValue: string);
109     procedure SetDefaultTranslationFile(AValue: string);
110   public
111     constructor Create(AOwner: TComponent); override;
112     destructor Destroy; override;
LoadCurrentEnglishFilenull113     function LoadCurrentEnglishFile(UpdateFromDisk: boolean;
114       AThread: TThread): TFPCMsgFilePoolItem; virtual; // don't forget UnloadFile
115     function LoadFile(aFilename: string; UpdateFromDisk: boolean;
116       AThread: TThread): TFPCMsgFilePoolItem; // don't forget UnloadFile
117     procedure UnloadFile(var aFile: TFPCMsgFilePoolItem);
118     procedure EnterCriticalsection;
119     procedure LeaveCriticalSection;
120     procedure GetMsgFileNames(CompilerFilename, TargetOS, TargetCPU: string;
121       out anEnglishFile, aTranslationFile: string); virtual; // (main thread)
122     property DefaultEnglishFile: string read FDefaultEnglishFile write SetDefaultEnglishFile;
123     property DefaulTranslationFile: string read FDefaultTranslationFile write SetDefaultTranslationFile;
124     property OnLoadFile: TETLoadFileEvent read FOnLoadFile write FOnLoadFile; // (main or workerthread)
125   end;
126 
127   { TPatternToMsgID }
128 
129   TPatternToMsgID = class
130   public
131     Pattern: string;
132     MsgID: integer;
133     PatternLine: integer; // line index in a multi line pattern, starting at 0
134   end;
135   PPatternToMsgID = ^TPatternToMsgID;
136 
137   { TPatternToMsgIDs }
138 
139   TPatternToMsgIDs = class
140   private
141     fItems: array of TPatternToMsgID;
IndexOfnull142     function IndexOf(Pattern: PChar; Insert: boolean): integer;
143   public
144     constructor Create;
145     destructor Destroy; override;
146     procedure Clear;
147     procedure Add(Pattern: string; MsgID: integer; PatternLine: integer = 0);
148     procedure AddLines(const Lines: string; MsgID: integer);
LineToMsgIDnull149     function LineToMsgID(p: PChar): integer; inline; // 0 = not found
LineToPatternnull150     function LineToPattern(p: PChar): PPatternToMsgID;
151     procedure WriteDebugReport;
152     procedure ConsistencyCheck;
153   end;
154 
155   { TIDEFPCParser }
156 
157   TIDEFPCParser = class(TFPCParser)
158   private
159     fCurSource: TCodeBuffer;
160     fFileExists: TFilenameToPointerTree;
161     fIncludePath: string; // only valid if fIncludePathValidForWorkerDir=Tool.WorkerDirectory
162     fIncludePathValidForWorkerDir: string;
163     fUnitPath: string; // only valid if fUnitPathValidForWorkerDir=Tool.WorkerDirectory
164     fUnitPathValidForWorkerDir: string;
165     fLastWorkerImprovedMessage: array[TExtToolParserSyncPhase] of integer;
166     fLineToMsgID: TPatternToMsgIDs;
167     fMissingFPCMsgItem: TFPCMsgItem;
168     fMsgID: Integer; // current message id given by ReadLine (-vq)
169     fMsgIsStdErr: boolean;
170     fMsgItemCantFindUnitUsedBy: TFPCMsgItem;
171     fMsgItemCompilationAborted: TFPCMsgItem;
172     fMsgItemErrorWhileCompilingResources: TFPCMsgItem;
173     fMsgItemErrorWhileLinking: TFPCMsgItem;
174     fMsgItemMethodIdentifierExpected: TFPCMsgItem;
175     fMsgItemIdentifierNotFound: TFPCMsgItem;
176     fMsgItemThereWereErrorsCompiling: TFPCMsgItem;
177     fMsgItemChecksumChanged: TFPCMsgItem;
178     fMsgItemUnitNotUsed: TFPCMsgItem;
179     fOutputIndex: integer; // current OutputIndex given by ReadLine
180     procedure FetchIncludePath(aPhase: TExtToolParserSyncPhase; MsgWorkerDir: String);
181     procedure FetchUnitPath(aPhase: TExtToolParserSyncPhase; MsgWorkerDir: String);
FileExistsnull182     function FileExists(const Filename: string; aSynchronized: boolean): boolean;
CheckForMsgIdnull183     function CheckForMsgId(p: PChar): boolean; // (MsgId) message
CheckFollowUpMessagenull184     function CheckFollowUpMessage(p: PChar): boolean;
CheckForFileLineColMessagenull185     function CheckForFileLineColMessage(p: PChar): boolean; // the normal messages: filename(y,x): Hint: ..
CheckForGeneralMessagenull186     function CheckForGeneralMessage(p: PChar): boolean; // Fatal: .., Error: ..., Panic: ..
CheckForInfosnull187     function CheckForInfos(p: PChar): boolean; // e.g. Free Pascal Compiler version 2.6.4 [2014/02/26] for i386
CheckForCompilingStatenull188     function CheckForCompilingState(p: PChar): boolean; // Compiling ..
CheckForAssemblingStatenull189     function CheckForAssemblingState(p: PChar): boolean; // Assembling ..
CheckForLinesCompilednull190     function CheckForLinesCompiled(p: PChar): boolean; // ..lines compiled..
CheckForExecutableInfonull191     function CheckForExecutableInfo(p: PChar): boolean;
CheckForLineProgressnull192     function CheckForLineProgress(p: PChar): boolean; // 600 206.521/231.648 Kb Used
CheckForLoadFromUnitnull193     function CheckForLoadFromUnit(p: PChar): Boolean;
CheckForWindresErrorsnull194     function CheckForWindresErrors(p: PChar): boolean;
CheckForLinkerErrorsnull195     function CheckForLinkerErrors(p: PChar): boolean;
CheckForAssemblerErrorsnull196     function CheckForAssemblerErrors(p: PChar): boolean;
CheckForUnspecificStdErrnull197     function CheckForUnspecificStdErr(p: PChar): boolean;
CreateMsgLinenull198     function CreateMsgLine: TMessageLine;
199     procedure AddLinkingMessages;
200     procedure AddResourceMessages;
NeedSourcenull201     function NeedSource(aPhase: TExtToolParserSyncPhase; SourceOk: boolean): boolean;
202     procedure ImproveMsgHiddenByIDEDirective(aPhase: TExtToolParserSyncPhase;
203       MsgLine: TMessageLine; SourceOK: Boolean);
204     procedure ImproveMsgSenderNotUsed(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
205     procedure ImproveMsgUnitNotUsed(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
206     procedure ImproveMsgUnitNotFound(aPhase: TExtToolParserSyncPhase;
207       MsgLine: TMessageLine);
208     procedure ImproveMsgLinkerUndefinedReference(aPhase: TExtToolParserSyncPhase;
209       MsgLine: TMessageLine);
210     procedure ImproveMsgIdentifierPosition(aPhase: TExtToolParserSyncPhase;
211       MsgLine: TMessageLine; SourceOK: boolean);
FindSrcViaPPUnull212     function FindSrcViaPPU(aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine;
213       const PPUFilename: string): boolean;
214     procedure Translate(p: PChar; MsgItem, TranslatedItem: TFPCMsgItem;
215       out TranslatedMsg: String; out MsgType: TMessageLineUrgency);
ReverseInstantFPCCacheDirnull216     function ReverseInstantFPCCacheDir(var aFilename: string; aSynchronized: boolean): boolean;
ReverseTestBuildDirnull217     function ReverseTestBuildDir(MsgLine: TMessageLine; var aFilename: string): boolean;
LongenFilenamenull218     function LongenFilename(MsgLine: TMessageLine; aFilename: string): string; // (worker thread)
219   protected
GetDefaultPCFullVersionnull220     function GetDefaultPCFullVersion: LongWord; virtual;
ToUTF8null221     function ToUTF8(const Line: string): string; virtual;
222   public
223     DirectoryStack: TStrings;
224     MsgFilename: string; // e.g. /path/to/fpcsrc/compiler/msg/errore.msg
225     MsgFile: TFPCMsgFilePoolItem;
226     TranslationFilename: string; // e.g. /path/to/fpcsrc/compiler/msg/errord.msg
227     TranslationFile: TFPCMsgFilePoolItem;
228     InstantFPCCache: string; // with trailing pathdelim
229     TestBuildDir: string; // with trailing pathdelim
230     VirtualProjectFiles: TFilenameToPointerTree;
231     PC_FullVersion: LongWord;
232     constructor Create(AOwner: TComponent); override;
233     destructor Destroy; override;
234     procedure Init; override; // called after macros resolved, before starting thread (main thread)
235     procedure InitReading; override; // called when process started, before first line (worker thread)
236     procedure Done; override; // called after process stopped (worker thread)
237     procedure ReadLine(Line: string; OutputIndex: integer; IsStdErr: boolean;
238       var Handled: boolean); override;
239     procedure AddMsgLine(MsgLine: TMessageLine); override;
240     procedure ImproveMessages(aPhase: TExtToolParserSyncPhase); override;
GetFPCMsgIDPatternnull241     function GetFPCMsgIDPattern(MsgID: integer): string; override;
IsMsgIDnull242     function IsMsgID(MsgLine: TMessageLine; MsgID: integer;
243       var Item: TFPCMsgItem): boolean;
CanParseSubToolnull244     class function CanParseSubTool(const SubTool: string): boolean; override;
DefaultSubToolnull245     class function DefaultSubTool: string; override;
GetMsgPatternnull246     class function GetMsgPattern(SubTool: string; MsgID: integer;
247       out Urgency: TMessageLineUrgency): string; override;
GetMsgHintnull248     class function GetMsgHint(SubTool: string; MsgID: integer): string;
249       override;
Prioritynull250     class function Priority: integer; override;
MsgLineIsIdnull251     class function MsgLineIsId(Msg: TMessageLine; MsgId: integer;
252       out Value1, Value2: string): boolean; override;
GetFPCMsgPatternnull253     class function GetFPCMsgPattern(Msg: TMessageLine): string; override;
GetFPCMsgValue1null254     class function GetFPCMsgValue1(Msg: TMessageLine): string; override;
GetFPCMsgValuesnull255     class function GetFPCMsgValues(Msg: TMessageLine; out Value1, Value2: string): boolean; override;
MsgFilePoolnull256     class function MsgFilePool: TFPCMsgFilePool; virtual;
257   end;
258 
259 var
260   FPCMsgFilePool: TFPCMsgFilePool = nil;
261 
262 // thread safe
FPCMsgToMsgUrgencynull263 function FPCMsgToMsgUrgency(Msg: TFPCMsgItem): TMessageLineUrgency;
FPCMsgTypeToUrgencynull264 function FPCMsgTypeToUrgency(const Typ: string): TMessageLineUrgency;
TranslateFPCMsgnull265 function TranslateFPCMsg(const Src, SrcPattern, TargetPattern: string): string;
FPCMsgFitsnull266 function FPCMsgFits(const Msg, Pattern: string;
267   VarStarts: PPChar = nil; VarEnds: PPChar = nil // 10 PChars
268   ): boolean;
GetFPCMsgValue1null269 function GetFPCMsgValue1(const Src, Pattern: string; out Value1: string): boolean;
GetFPCMsgValues2null270 function GetFPCMsgValues2(Src, Pattern: string; out Value1, Value2: string): boolean;
271 
272 // not thread safe
IsFileInIDESrcDirnull273 function IsFileInIDESrcDir(Filename: string): boolean; // (main thread)
274 
275 procedure RegisterFPCParser;
276 
277 implementation
278 
FPCMsgTypeToUrgencynull279 function FPCMsgTypeToUrgency(const Typ: string): TMessageLineUrgency;
280 begin
281   Result:=mluNone;
282   if (Typ='') or (length(Typ)<>1) then exit;
283   case UpChars[Typ[1]] of
284   'F': Result:=mluFatal;
285   'E': Result:=mluError;
286   'W': Result:=mluWarning;
287   'N': Result:=mluNote;
288   'H': Result:=mluHint;
289   'I': Result:=mluVerbose;  // info
290   'L': Result:=mluProgress; // line number
291   'C': Result:=mluVerbose;  // conditional: like IFDEFs
292   'U': Result:=mluVerbose2; // used: found files
293   'T': Result:=mluVerbose3; // tried: tried paths, general information
294   'D': Result:=mluDebug;
295   'X': Result:=mluProgress; // e.g. Size of Code
296   'O': Result:=mluProgress; // e.g., "press enter to continue"
297   else
298     Result:=mluNone;
299   end;
300 end;
301 
FPCMsgToMsgUrgencynull302 function FPCMsgToMsgUrgency(Msg: TFPCMsgItem): TMessageLineUrgency;
303 begin
304   Result:=mluNone;
305   if Msg=nil then exit;
306   Result:=FPCMsgTypeToUrgency(Msg.ShownTyp);
307   if Result<>mluNone then exit;
308   Result:=FPCMsgTypeToUrgency(Msg.Typ);
309   if Result=mluNone then begin
310     //debugln(['FPCMsgToMsgUrgency Msg.ShownTyp="',Msg.ShownTyp,'" Msg.Typ="',Msg.Typ,'"']);
311     Result:=mluVerbose3;
312   end;
313 end;
314 
IsFPCMsgVarnull315 function IsFPCMsgVar(p: PChar): boolean; inline;
316 begin
317   Result:=(p^='$') and (p[1] in ['0'..'9']);
318 end;
319 
IsFPCMsgEndOrVarnull320 function IsFPCMsgEndOrVar(p: PChar): boolean; inline;
321 begin
322   Result:=(p^=#0) or IsFPCMsgVar(p);
323 end;
324 
TranslateFPCMsgnull325 function TranslateFPCMsg(const Src, SrcPattern, TargetPattern: string): string;
326 { for example:
327   Src='A lines compiled, B sec C'
328   SrcPattern='$1 lines compiled, $2 sec $3'
329   TargetPattern='$1 Zeilen uebersetzt, $2 Sekunden $3'
330 
331   Result='A Zeilen uebersetzt, B Sekunden C'
332 }
333 var
334   SrcPos: PChar;
335   TargetPatPos: PChar;
336   TargetPos: PChar;
337   SrcVarStarts, SrcVarEnds: array[0..9] of PChar;
338   VarUsed: array[0..9] of integer;
339   i: Integer;
340 begin
341   Result:='';
342   {$IFDEF VerboseFPCTranslate}
343   debugln(['TranslateFPCMsg Src="',Src,'" SrcPattern="',SrcPattern,'" TargetPattern="',TargetPattern,'"']);
344   {$ENDIF}
345   if (Src='') or (SrcPattern='') or (TargetPattern='') then exit;
346 
347   if not FPCMsgFits(Src,SrcPattern,@SrcVarStarts[0],@SrcVarEnds[0]) then
348     exit;
349 
350   for i:=Low(SrcVarStarts) to high(SrcVarStarts) do
351     VarUsed[i]:=0;
352 
353   // create Target
354   SetLength(Result,length(TargetPattern)+length(Src));
355   TargetPatPos:=PChar(TargetPattern);
356   TargetPos:=PChar(Result);
357   while TargetPatPos^<>#0 do begin
358     //debugln(['TranslateFPCMsg Target ',dbgs(Pointer(TargetPatPos)),' ',ord(TargetPatPos^),' TargetPatPos="',TargetPatPos,'"']);
359     if IsFPCMsgVar(TargetPatPos) then begin
360       // insert variable
361       inc(TargetPatPos);
362       i:=ord(TargetPatPos^)-ord('0');
363       inc(TargetPatPos);
364       if SrcVarStarts[i]<>nil then begin
365         inc(VarUsed[i]);
366         if VarUsed[i]>1 then begin
367           // variable is used more than once => realloc result
368           dec(TargetPos,{%H-}PtrUInt(PChar(Result)));
369           SetLength(Result,length(Result)+SrcVarEnds[i]-SrcVarStarts[i]);
370           inc(TargetPos,{%H-}PtrUInt(PChar(Result)));
371         end;
372         SrcPos:=SrcVarStarts[i];
373         while SrcPos<SrcVarEnds[i] do begin
374           TargetPos^:=SrcPos^;
375           inc(TargetPos);
376           inc(SrcPos);
377         end;
378       end;
379     end else begin
380       // copy text from TargetPattern
381       TargetPos^:=TargetPatPos^;
382       inc(TargetPatPos);
383       inc(TargetPos);
384     end;
385   end;
386   SetLength(Result,TargetPos-PChar(Result));
387   if Result<>'' then
388     UTF8FixBroken(PChar(Result));
389 
390   {$IFDEF VerboseFPCTranslate}
391   debugln(['TranslateFPCMsg Result="',Result,'"']);
392   {$ENDIF}
393 end;
394 
FPCMsgFitsnull395 function FPCMsgFits(const Msg, Pattern: string; VarStarts: PPChar;
396   VarEnds: PPChar): boolean;
397 { for example:
398   Src='A lines compiled, B sec C'
399   SrcPattern='$1 lines compiled, $2 sec $3'
400 
401   VarStarts and VarEnds can be nil.
402   If you need the boundaries of the parameters allocate VarStarts and VarEnds as
403     VarStarts:=GetMem(SizeOf(PChar)*10);
404     VarEnds:=GetMem(SizeOf(PChar)*10);
405   VarStarts[0] will be $0, VarStarts[1] will be $1 and so forth
406 
407 }
408 var
409   MsgPos, PatPos: PChar;
410   MsgPos2, PatPos2: PChar;
411   i: Integer;
412 begin
413   Result:=false;
414   {$IFDEF VerboseFPCTranslate}
415   debugln(['FPCMsgFits Msg="',Msg,'" Pattern="',Pattern,'"']);
416   {$ENDIF}
417   if (Msg='') or (Pattern='') then exit;
418   MsgPos:=PChar(Msg);
419   PatPos:=PChar(Pattern);
420   // skip the characters of Msg copied from Pattern
421   while not IsFPCMsgEndOrVar(PatPos) do begin
422     if (MsgPos^<>PatPos^) then begin
423       // Pattern does not fit
424       {$IFDEF VerboseFPCTranslate}
425       debugln(['FPCMsgFits skipping start of Src and SrcPattern failed']);
426       {$ENDIF}
427       exit;
428     end;
429     inc(MsgPos);
430     inc(PatPos)
431   end;
432   {$IFDEF VerboseFPCTranslate}
433   debugln(['FPCMsgFits skipped start: SrcPos="',SrcPos,'" SrcPatPos="',SrcPatPos,'"']);
434   {$ENDIF}
435   if VarStarts<>nil then begin
436     FillByte(VarStarts^,SizeOf(PChar)*10,0);
437     FillByte(VarEnds^,SizeOf(PChar)*10,0);
438   end;
439   // find the parameters in Msg and store their boundaries in VarStarts, VarEnds
440   while (PatPos^<>#0) do begin
441     // read variable number
442     inc(PatPos);
443     i:=ord(PatPos^)-ord('0');
444     inc(PatPos);
445     if (VarEnds<>nil) and (VarEnds[i]=nil) then begin
446       VarStarts[i]:=MsgPos;
447       VarEnds[i]:=nil;
448     end;
449     // find the end of the parameter in Msg
450     // example:  Pattern='$1 found' Msg='Ha found found'
451     repeat
452       if MsgPos^=PatPos^ then begin
453         {$IFDEF VerboseFPCTranslate}
454         debugln(['FPCMsgFits candidate for param ',i,' end: SrcPos="',SrcPos,'" SrcPatPos="',SrcPatPos,'"']);
455         {$ENDIF}
456         MsgPos2:=MsgPos;
457         PatPos2:=PatPos;
458         while (MsgPos2^=PatPos2^) and not IsFPCMsgEndOrVar(PatPos2) do begin
459           inc(MsgPos2);
460           inc(PatPos2);
461         end;
462         if IsFPCMsgEndOrVar(PatPos2) then begin
463           {$IFDEF VerboseFPCTranslate}
464           debugln(['FPCMsgFits param ',i,' end found: SrcPos2="',SrcPos2,'" SrcPatPos2="',SrcPatPos2,'"']);
465           {$ENDIF}
466           if (VarEnds<>nil) and (VarEnds[i]=nil) then
467             VarEnds[i]:=MsgPos;
468           MsgPos:=MsgPos2;
469           PatPos:=PatPos2;
470           break;
471         end;
472         {$IFDEF VerboseFPCTranslate}
473         debugln(['FPCMsgFits searching further...']);
474         {$ENDIF}
475       end else if MsgPos^=#0 then begin
476         if IsFPCMsgEndOrVar(PatPos) then begin
477           // empty parameter at end
478           if (VarEnds<>nil) and (VarEnds[i]=nil) then
479             VarEnds[i]:=MsgPos;
480           break;
481         end else begin
482           // Pattern does not fit Msg
483           {$IFDEF VerboseFPCTranslate}
484           debugln(['FPCMsgFits finding end of parameter ',i,' failed']);
485           {$ENDIF}
486           exit;
487         end;
488       end;
489       inc(MsgPos);
490     until false;
491   end;
492   Result:=true;
493 end;
494 
GetFPCMsgValue1null495 function GetFPCMsgValue1(const Src, Pattern: string; out Value1: string
496   ): boolean;
497 { Pattern: 'Compiling $1'
498   Src:     'Compiling fcllaz.pas'
499   Value1:  'fcllaz.pas'
500 }
501 var
502   p: SizeInt;
503   l: SizeInt;
504 begin
505   Value1:='';
506   Result:=false;
507   if length(Src)<length(Pattern)-2 then exit;
508   p:=Pos('$1',Pattern);
509   if p<1 then exit;
510   // check start pattern
511   if (p>1) and (not CompareMem(Pointer(Src),Pointer(Pattern),p-1)) then exit;
512   // check end pattern
513   l:=length(Pattern)-p-2;
514   if (l>0)
515   and (not CompareMem(Pointer(Src)+length(Src)-l,Pointer(Pattern)+p+2,l)) then exit;
516   Value1:=copy(Src,p,length(Src)-length(Pattern)+2);
517   Result:=true;
518 end;
519 
GetFPCMsgValues2null520 function GetFPCMsgValues2(Src, Pattern: string; out Value1, Value2: string
521   ): boolean;
522 { Pattern: 'Unit $1 was not found but $2 exists'
523   Src:     'Unit dialogprocs was not found but dialogpr exists'
524   Value1:  'dialogprocs'
525   Value1:  'dialogpr'
526   Not supported: '$1$2'
527 }
528 var
529   p1: SizeInt;
530   LastPattern: String;
531   p2: SizeInt;
532   MiddlePattern: String;
533   SrcP1Behind: Integer;
534   SrcP2: Integer;
535 begin
536   Result:=false;
537   Value1:='';
538   Value2:='';
539   p1:=Pos('$1',Pattern);
540   if p1<1 then exit;
541   p2:=Pos('$2',Pattern);
542   if p2<=p1+2 then exit;
543   if LeftStr(Pattern,p1-1)<>LeftStr(Src,p1-1) then exit;
544   LastPattern:=RightStr(Pattern,length(Pattern)-p2-1);
545   if RightStr(Src,length(LastPattern))<>LastPattern then exit;
546   MiddlePattern:=copy(Pattern,p1+2,p2-p1-2);
547   SrcP1Behind:=PosEx(MiddlePattern,Src,p1+2);
548   if SrcP1Behind<1 then exit;
549   Value1:=copy(Src,p1,SrcP1Behind-p1);
550   SrcP2:=SrcP1Behind+length(MiddlePattern);
551   Value2:=copy(Src,SrcP2,length(Src)-SrcP2-length(LastPattern)+1);
552   Result:=true;
553 end;
554 
IsFileInIDESrcDirnull555 function IsFileInIDESrcDir(Filename: string): boolean;
556 var
557   LazDir: String;
558 begin
559   Filename:=TrimFilename(Filename);
560   if not FilenameIsAbsolute(Filename) then exit(false);
561   LazDir:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory);
562   Result:=FileIsInPath(Filename,LazDir+'ide')
563        or FileIsInPath(Filename,LazDir+'debugger')
564        or FileIsInPath(Filename,LazDir+'packager')
565        or FileIsInPath(Filename,LazDir+'converter')
566        or FileIsInPath(Filename,LazDir+'designer');
567 end;
568 
569 procedure RegisterFPCParser;
570 begin
571   ExternalToolList.RegisterParser(TIDEFPCParser);
572 end;
573 
574 { TPatternToMsgIDs }
575 
IndexOfnull576 function TPatternToMsgIDs.IndexOf(Pattern: PChar; Insert: boolean): integer;
577 var
578   l: Integer;
579   r: Integer;
580   m: Integer;
581   ItemP: PChar;
582   FindP: PChar;
583   cmp: Integer;
584 begin
585   Result:=-1;
586   l:=0;
587   r:=length(fItems)-1;
588   cmp:=0;
589   m:=0;
590   while (l<=r) do begin
591     m:=(l+r) div 2;
592     ItemP:=PChar(fItems[m].Pattern);
593     FindP:=Pattern;
594     while (ItemP^=FindP^) do begin
595       if ItemP^=#0 then
596         exit(m); // exact match
597       inc(ItemP);
598       inc(FindP);
599     end;
600     if ItemP^ in [#0,'$'] then begin
601       // Pattern longer than Item
602       if not Insert then begin
603         if (Result<0) or (length(fItems[m].Pattern)>length(fItems[Result].Pattern))
604         then
605           Result:=m;
606       end;
607     end;
608     cmp:=ord(ItemP^)-ord(FindP^);
609     if cmp<0 then
610       l:=m+1
611     else
612       r:=m-1;
613   end;
614   if Insert then begin
615     if cmp<0 then
616       Result:=m+1
617     else
618       Result:=m;
619   end;
620 end;
621 
622 constructor TPatternToMsgIDs.Create;
623 begin
624 
625 end;
626 
627 destructor TPatternToMsgIDs.Destroy;
628 begin
629   Clear;
630   inherited Destroy;
631 end;
632 
633 procedure TPatternToMsgIDs.Clear;
634 var
635   i: Integer;
636 begin
637   for i:=0 to length(fItems)-1 do
638     fItems[i].Free;
639   SetLength(fItems,0);
640 end;
641 
642 procedure TPatternToMsgIDs.Add(Pattern: string; MsgID: integer;
643   PatternLine: integer);
644 
645   procedure RaiseInvalidMsgID;
646   begin
647     raise Exception.Create('invalid MsgID: '+IntToStr(MsgID));
648   end;
649 
650 var
651   i: Integer;
652   Item: TPatternToMsgID;
653   Cnt: Integer;
654 begin
655   if MsgID=0 then
656     RaiseInvalidMsgID;
657   Pattern:=Trim(Pattern);
658   if (Pattern='') or (Pattern[1]='$') then exit;
659   i:=IndexOf(PChar(Pattern),true);
660   Cnt:=length(fItems);
661   SetLength(fItems,Cnt+1);
662   if Cnt-i>0 then
663     Move(fItems[i],fItems[i+1],SizeOf(TPatternToMsgID)*(Cnt-i));
664   Item:=TPatternToMsgID.Create;
665   fItems[i]:=Item;
666   Item.Pattern:=Pattern;
667   Item.MsgID:=MsgID;
668   Item.PatternLine:=PatternLine;
669 end;
670 
671 procedure TPatternToMsgIDs.AddLines(const Lines: string; MsgID: integer);
672 var
673   StartPos: PChar;
674   p: PChar;
675   PatternLine: Integer;
676 begin
677   PatternLine:=0;
678   p:=PChar(Lines);
679   while p^<>#0 do begin
680     StartPos:=p;
681     while not (p^ in [#0,#10,#13]) do inc(p);
682     if p>StartPos then begin
683       Add(copy(Lines,StartPos-PChar(Lines)+1,p-StartPos),MsgID,PatternLine);
684       inc(PatternLine);
685     end;
686     while p^ in [#10,#13] do inc(p);
687   end;
688 end;
689 
TPatternToMsgIDs.LineToMsgIDnull690 function TPatternToMsgIDs.LineToMsgID(p: PChar): integer;
691 var
692   Item: PPatternToMsgID;
693 begin
694   Item:=LineToPattern(p);
695   if Item=nil then
696     Result:=0
697   else
698     Result:=Item^.MsgID;
699 end;
700 
LineToPatternnull701 function TPatternToMsgIDs.LineToPattern(p: PChar): PPatternToMsgID;
702 var
703   i: Integer;
704 begin
705   while p^ in [' ',#9,#10,#13] do inc(p);
706   i:=IndexOf(p,false);
707   if i<0 then
708     Result:=nil
709   else
710     Result:=@fItems[i];
711 end;
712 
713 procedure TPatternToMsgIDs.WriteDebugReport;
714 var
715   i: Integer;
716 begin
717   debugln(['TLineStartToMsgIDs.WriteDebugReport Count=',length(fItems)]);
718   for i:=0 to Length(fItems)-1 do begin
719     debugln(['  ID=',fItems[i].MsgID,'="',fItems[i].Pattern,'"']);
720   end;
721   ConsistencyCheck;
722 end;
723 
724 procedure TPatternToMsgIDs.ConsistencyCheck;
725 
726   procedure E(Msg: string);
727   begin
728     raise Exception.Create(Msg);
729   end;
730 
731 var
732   i: Integer;
733   Item: TPatternToMsgID;
734 begin
735   for i:=0 to Length(fItems)-1 do begin
736     Item:=fItems[i];
737     if Item.MsgID<=0 then
738       E('Item.MsgID<=0');
739     if Item.Pattern='' then
740       E('Item.Pattern empty');
741     if IndexOf(PChar(Item.Pattern),false)<>i then
742       E('IndexOf '+dbgs(i)+' "'+Item.Pattern+'" IndexOf='+dbgs(IndexOf(PChar(Item.Pattern),false)));
743   end;
744 end;
745 
746 { TFPCMsgFilePool }
747 
748 procedure TFPCMsgFilePool.Log(Msg: string; AThread: TThread);
749 begin
750   EnterCriticalsection;
751   try
752     fPendingLog.Add(Msg);
753   finally
754     LeaveCriticalSection;
755   end;
756   if AThread<>nil then
757     LogSync
758   else
759     TThread.Synchronize(AThread,@LogSync);
760 end;
761 
762 procedure TFPCMsgFilePool.LogSync;
763 begin
764   EnterCriticalsection;
765   try
766     dbgout(fPendingLog.Text);
767   finally
768     LeaveCriticalSection;
769   end;
770 end;
771 
772 procedure TFPCMsgFilePool.SetDefaultEnglishFile(AValue: string);
773 begin
774   if FDefaultEnglishFile=AValue then Exit;
775   FDefaultEnglishFile:=AValue;
776   fMsgFileStamp:=-1;
777 end;
778 
779 procedure TFPCMsgFilePool.SetDefaultTranslationFile(AValue: string);
780 begin
781   if FDefaultTranslationFile=AValue then Exit;
782   FDefaultTranslationFile:=AValue;
783   fMsgFileStamp:=-1;
784 end;
785 
786 constructor TFPCMsgFilePool.Create(AOwner: TComponent);
787 begin
788   inherited Create(AOwner);
789   InitCriticalSection(fCritSec);
790   FFiles:=TFPList.Create;
791   fPendingLog:=TStringList.Create;
792   fMsgFileStamp:=-1;
793 end;
794 
795 destructor TFPCMsgFilePool.Destroy;
796 var
797   i: Integer;
798   Item: TFPCMsgFilePoolItem;
799 begin
800   EnterCriticalsection;
801   try
802     // free unused files
803     for i:=FFiles.Count-1 downto 0 do begin
804       Item:=TFPCMsgFilePoolItem(FFiles[i]);
805       if Item.fUseCount=0 then begin
806         Item.Free;
807         FFiles.Delete(i);
808       end else begin
809         if ExitCode=0 then
810           debugln(['TFPCMsgFilePool.Destroy file still used: ',Item.Filename]);
811       end;
812     end;
813     if FFiles.Count>0 then begin
814       if ExitCode<>0 then
815         exit;
816       raise Exception.Create('TFPCMsgFilePool.Destroy some files are still used');
817     end;
818     FreeAndNil(FFiles);
819     if FPCMsgFilePool=Self then
820       FPCMsgFilePool:=nil;
821     inherited Destroy;
822     FreeAndNil(fPendingLog);
823   finally
824     LeaveCriticalSection;
825   end;
826   DoneCriticalsection(fCritSec);
827 end;
828 
TFPCMsgFilePool.LoadCurrentEnglishFilenull829 function TFPCMsgFilePool.LoadCurrentEnglishFile(UpdateFromDisk: boolean;
830   AThread: TThread): TFPCMsgFilePoolItem;
831 var
832   anEnglishFile: string;
833   aTranslationFile: string;
834 begin
835   Result:=nil;
836   GetMsgFileNames(EnvironmentOptions.GetParsedCompilerFilename,'','',
837     anEnglishFile,aTranslationFile);
838   //writeln('TFPCMsgFilePool.LoadCurrentEnglishFile ',anEnglishFile);
839   if not FilenameIsAbsolute(anEnglishFile) then exit;
840   Result:=LoadFile(anEnglishFile,UpdateFromDisk,AThread);
841 end;
842 
LoadFilenull843 function TFPCMsgFilePool.LoadFile(aFilename: string; UpdateFromDisk: boolean;
844   AThread: TThread): TFPCMsgFilePoolItem;
845 var
846   IsMainThread: Boolean;
847 
848   procedure ResultOutdated;
849   begin
850     // cached file needs update
851     if Result.fUseCount=0 then begin
852       FFiles.Remove(Result);
853       Result.Free;
854     end;
855     Result:=nil;
856   end;
857 
FileExistsnull858   function FileExists: boolean;
859   begin
860     if IsMainThread then
861       Result:=FileExistsCached(aFilename)
862     else
863       Result:=FileExistsUTF8(aFilename);
864   end;
865 
FileAgenull866   function FileAge: longint;
867   begin
868     if IsMainThread then
869       Result:=FileAgeCached(aFilename)
870     else
871       Result:=FileAgeUTF8(aFilename);
872   end;
873 
874 var
875   Item: TFPCMsgFilePoolItem;
876   i: Integer;
877   NewItem: TFPCMsgFilePoolItem;
878   FileTxt: string;
879   ms: TMemoryStream;
880   Encoding: String;
881 begin
882   Result:=nil;
883   if aFilename='' then exit;
884   aFilename:=TrimAndExpandFilename(aFilename);
885   //Log('TFPCMsgFilePool.LoadFile '+aFilename,aThread);
886 
887   IsMainThread:=GetThreadID=MainThreadID;
888   if UpdateFromDisk then begin
889     if not FileExists then begin
890       Log('TFPCMsgFilePool.LoadFile file not found: '+aFilename,AThread);
891       exit;
892     end;
893   end;
894   NewItem:=nil;
895   ms:=nil;
896   EnterCriticalsection;
897   try
898     // search the newest version in cache
899     for i:=FFiles.Count-1 downto 0 do begin
900       Item:=TFPCMsgFilePoolItem(FFiles[i]);
901       if CompareFilenames(Item.Filename,aFilename)<>0 then continue;
902       Result:=Item;
903       break;
904     end;
905     if UpdateFromDisk then begin
906       if (Result<>nil)
907       and (FileAge<>Result.LoadedFileAge) then
908         ResultOutdated;
909     end else if Result=nil then begin
910       // not yet loaded, not yet checked if file exists -> check now
911       if not FileExists then
912         exit;
913     end;
914 
915     if Result<>nil then begin
916       // share
917       inc(Result.fUseCount);
918     end else begin
919       // load for the first time
920       NewItem:=TFPCMsgFilePoolItem.Create(Self,aFilename);
921       //Log('TFPCMsgFilePool.LoadFile '+dbgs(NewItem.FMsgFile<>nil)+' '+aFilename,aThread);
922       if Assigned(OnLoadFile) then begin
923         OnLoadFile(aFilename,FileTxt);
924       end else begin
925         ms:=TMemoryStream.Create;
926         ms.LoadFromFile(aFilename);
927         SetLength(FileTxt,ms.Size);
928         ms.Position:=0;
929         if FileTxt<>'' then
930           ms.Read(FileTxt[1],length(FileTxt));
931       end;
932       // convert encoding
933       Encoding:=GetDefaultFPCErrorMsgFileEncoding(aFilename);
934       FileTxt:=ConvertEncoding(FileTxt,Encoding,EncodingUTF8);
935       // parse
936       NewItem.FMsgFile.LoadFromText(FileTxt);
937       NewItem.FLoadedFileAge:=FileAge;
938       // load successful
939       Result:=NewItem;
940       NewItem:=nil;
941       FFiles.Add(Result);
942       inc(Result.fUseCount);
943       //log('TFPCMsgFilePool.LoadFile '+Result.Filename+' '+dbgs(Result.fUseCount),aThread);
944     end;
945   finally
946     ms.Free;
947     FreeAndNil(NewItem);
948     LeaveCriticalSection;
949   end;
950 end;
951 
952 procedure TFPCMsgFilePool.UnloadFile(var aFile: TFPCMsgFilePoolItem);
953 var
954   i: Integer;
955   Item: TFPCMsgFilePoolItem;
956   Keep: Boolean;
957 begin
958   EnterCriticalsection;
959   try
960     if aFile.fUseCount<=0 then
961       raise Exception.Create('TFPCMsgFilePool.UnloadFile already freed');
962     if FFiles.IndexOf(aFile)<0 then
963       raise Exception.Create('TFPCMsgFilePool.UnloadFile unknown, maybe already freed');
964     dec(aFile.fUseCount);
965     //log('TFPCMsgFilePool.UnloadFile '+aFile.Filename+' UseCount='+dbgs(aFile.fUseCount),aThread);
966     if aFile.fUseCount>0 then exit;
967     // not used anymore
968     if not FileExistsUTF8(aFile.Filename) then begin
969       Keep:=false;
970     end else begin
971       // file still exist on disk
972       // => check if it is the newest version
973       Keep:=true;
974       for i:=FFiles.Count-1 downto 0 do begin
975         Item:=TFPCMsgFilePoolItem(FFiles[i]);
976         if Item=aFile then break;
977         if CompareFilenames(Item.Filename,aFile.Filename)<>0 then continue;
978         // there is already a newer version
979         Keep:=false;
980         break;
981       end;
982     end;
983     if Keep then begin
984       // this file is the newest version => keep it in cache
985     end else begin
986       //log('TFPCMsgFilePool.UnloadFile free: '+aFile.Filename,aThread);
987       FFiles.Remove(aFile);
988       aFile.Free;
989     end;
990   finally
991     aFile:=nil;
992     LeaveCriticalSection;
993   end;
994 end;
995 
996 procedure TFPCMsgFilePool.EnterCriticalsection;
997 begin
998   System.EnterCriticalsection(fCritSec);
999 end;
1000 
1001 procedure TFPCMsgFilePool.LeaveCriticalSection;
1002 begin
1003   System.LeaveCriticalsection(fCritSec);
1004 end;
1005 
1006 procedure TFPCMsgFilePool.GetMsgFileNames(CompilerFilename, TargetOS,
1007   TargetCPU: string; out anEnglishFile, aTranslationFile: string);
1008 var
1009   FPCVer: String;
1010   FPCSrcDir: String;
1011   aFilename: String;
1012   CompilerKind: TPascalCompiler;
1013 begin
1014   if fMsgFileStamp<>CompilerParseStamp then begin
1015     fCurrentEnglishFile:=DefaultEnglishFile;
1016     fCurrentTranslationFile:=DefaulTranslationFile;
1017     // English msg file
1018     // => use fpcsrcdir/compiler/msg/errore.msg
1019     // the fpcsrcdir might depend on the FPC version
1020     FPCVer:=CodeToolBoss.CompilerDefinesCache.GetPCVersion(
1021               CompilerFilename,TargetOS,TargetCPU,false,CompilerKind);
1022     if CompilerKind<>pcFPC then
1023       ;// ToDo
1024     FPCSrcDir:=EnvironmentOptions.GetParsedFPCSourceDirectory(FPCVer);
1025     if FilenameIsAbsolute(FPCSrcDir) then begin
1026       // FPCSrcDir exists => use the errore.msg
1027       aFilename:=AppendPathDelim(FPCSrcDir)+GetForcedPathDelims('compiler/msg/errore.msg');
1028       if FileExistsCached(aFilename) then
1029         fCurrentEnglishFile:=aFilename;
1030     end;
1031     if not FileExistsCached(fCurrentEnglishFile) then begin
1032       // as fallback use the copy in the Codetools directory
1033       aFilename:=EnvironmentOptions.GetParsedLazarusDirectory;
1034       if FilenameIsAbsolute(aFilename) then begin
1035         aFilename:=AppendPathDelim(aFilename)+GetForcedPathDelims('components/codetools/fpc.errore.msg');
1036         if FileExistsCached(aFilename) then
1037           fCurrentEnglishFile:=aFilename;
1038       end;
1039     end;
1040     // translation msg file
1041     aFilename:=EnvironmentOptions.GetParsedCompilerMessagesFilename;
1042     if FilenameIsAbsolute(aFilename) and FileExistsCached(aFilename)
1043     and (CompareFilenames(aFilename,fCurrentEnglishFile)<>0) then
1044       fCurrentTranslationFile:=aFilename;
1045     fMsgFileStamp:=CompilerParseStamp;
1046   end;
1047   anEnglishFile:=fCurrentEnglishFile;
1048   aTranslationFile:=fCurrentTranslationFile;
1049 end;
1050 
1051 { TFPCMsgFilePoolItem }
1052 
1053 constructor TFPCMsgFilePoolItem.Create(aPool: TFPCMsgFilePool;
1054   const aFilename: string);
1055 begin
1056   inherited Create;
1057   FPool:=aPool;
1058   FFilename:=aFilename;
1059   FMsgFile:=TFPCMsgFile.Create;
1060 end;
1061 
1062 destructor TFPCMsgFilePoolItem.Destroy;
1063 begin
1064   FreeAndNil(FMsgFile);
1065   FFilename:='';
1066   inherited Destroy;
1067 end;
1068 
TFPCMsgFilePoolItem.GetMsgnull1069 function TFPCMsgFilePoolItem.GetMsg(ID: integer): TFPCMsgItem;
1070 begin
1071   Result:=FMsgFile.FindWithID(ID);
1072 end;
1073 
1074 { TIDEFPCParser }
1075 
1076 destructor TIDEFPCParser.Destroy;
1077 begin
1078   FreeAndNil(VirtualProjectFiles);
1079   FreeAndNil(FFilesToIgnoreUnitNotUsed);
1080   FreeAndNil(fFileExists);
1081   FreeAndNil(fCurSource);
1082   if TranslationFile<>nil then
1083     MsgFilePool.UnloadFile(TranslationFile);
1084   if MsgFile<>nil then
1085     MsgFilePool.UnloadFile(MsgFile);
1086   FreeAndNil(DirectoryStack);
1087   FreeAndNil(fLineToMsgID);
1088   inherited Destroy;
1089 end;
1090 
1091 procedure TIDEFPCParser.Init;
1092 
1093   procedure LoadMsgFile(aFilename: string; var List: TFPCMsgFilePoolItem);
1094   begin
1095     //debugln(['TFPCParser.Init load Msg filename=',aFilename]);
1096     if aFilename='' then
1097       debugln(['WARNING: TFPCParser.Init missing msg file'])
1098     else if (aFilename<>'') and (List=nil) then begin
1099       try
1100         List:=MsgFilePool.LoadFile(aFilename,true,nil);
1101         {$IFDEF VerboseExtToolThread}
1102         debugln(['LoadMsgFile successfully read ',aFilename]);
1103         {$ENDIF}
1104       except
1105         on E: Exception do begin
1106           debugln(['WARNING: TFPCParser.Init failed to load file '+aFilename+': '+E.Message]);
1107         end;
1108       end;
1109     end;
1110   end;
1111 
1112 var
1113   i: Integer;
1114   Param: String;
1115   p: PChar;
1116   aTargetOS: String;
1117   aTargetCPU: String;
1118   aProject: TLazProject;
1119   aProjFile: TLazProjectFile;
1120 begin
1121   inherited Init;
1122 
1123   PC_FullVersion:=GetDefaultPCFullVersion;
1124 
1125   if MsgFilePool<>nil then begin
1126     aTargetOS:='';
1127     aTargetCPU:='';
1128     for i:=0 to Tool.Process.Parameters.Count-1 do begin
1129       Param:=Tool.Process.Parameters[i];
1130       if Param='' then continue;
1131       p:=PChar(Param);
1132       if p^<>'-' then continue;
1133       if p[1]='T' then
1134         aTargetOS:=copy(Param,3,255)
1135       else if p[1]='P' then
1136         aTargetCPU:=copy(Param,3,255);
1137     end;
1138     MsgFilePool.GetMsgFileNames(Tool.Process.Executable,aTargetOS,aTargetCPU,
1139       MsgFilename,TranslationFilename);
1140   end;
1141 
1142   LoadMsgFile(MsgFilename,MsgFile);
1143   if TranslationFilename<>'' then
1144     LoadMsgFile(TranslationFilename,TranslationFile);
1145 
1146   // get include search path
1147   fIncludePathValidForWorkerDir:=Tool.WorkerDirectory;
1148   fIncludePath:=CodeToolBoss.GetIncludePathForDirectory(
1149                            ChompPathDelim(fIncludePathValidForWorkerDir));
1150   // get unit search path
1151   fUnitPathValidForWorkerDir:=Tool.WorkerDirectory;
1152   fUnitPath:=CodeToolBoss.GetUnitPathForDirectory(
1153                            ChompPathDelim(fUnitPathValidForWorkerDir));
1154 
1155   // get instantfpc cache directory
1156   InstantFPCCache:='$(InstantFPCCache)';
1157   if IDEMacros.SubstituteMacros(InstantFPCCache) then
1158     InstantFPCCache:=AppendPathDelim(InstantFPCCache)
1159   else
1160     InstantFPCCache:='';
1161 
1162   // get TestBuildDir
1163   if Tool.CurrentDirectoryIsTestDir then begin
1164     // source filenames in CurrentDirectory must be reversed back
1165     // -> store the list of virtual filenames (needed by worker thread)
1166     TestBuildDir:=AppendPathDelim(ResolveDots(Tool.Process.CurrentDirectory));
1167     if VirtualProjectFiles=nil then
1168       VirtualProjectFiles:=TFilenameToPointerTree.Create(true);
1169     aProject:=LazarusIDE.ActiveProject;
1170     for i:=0 to aProject.FileCount-1 do begin
1171       aProjFile:=aProject.Files[i];
1172       if aProjFile.IsPartOfProject and (not FilenameIsAbsolute(aProjFile.Filename)) then
1173         VirtualProjectFiles[aProjFile.Filename]:=Tool;
1174     end;
1175   end else
1176     TestBuildDir:='';
1177 end;
1178 
1179 procedure TIDEFPCParser.InitReading;
1180 
1181   procedure AddPatternItem(MsgID: integer);
1182   var
1183     Item: TFPCMsgItem;
1184   begin
1185     Item:=MsgFile.GetMsg(MsgID);
1186     if Item<>nil then
1187       fLineToMsgID.AddLines(Item.Pattern,Item.ID);
1188   end;
1189 
1190 var
1191   p: TExtToolParserSyncPhase;
1192 begin
1193   inherited InitReading;
1194 
1195   fLineToMsgID.Clear;
1196   AddPatternItem(FPCMsgIDLogo);
1197   AddPatternItem(FPCMsgIDLinking);
1198   AddPatternItem(FPCMsgIDCallingResourceCompiler);
1199   //fLineToMsgID.WriteDebugReport;
1200 
1201   for p:=low(fLastWorkerImprovedMessage) to high(fLastWorkerImprovedMessage) do
1202     fLastWorkerImprovedMessage[p]:=-1;
1203 
1204   FreeAndNil(DirectoryStack);
1205 end;
1206 
1207 procedure TIDEFPCParser.Done;
1208 begin
1209   FreeAndNil(fCurSource);
1210   inherited Done;
1211 end;
1212 
CheckForCompilingStatenull1213 function TIDEFPCParser.CheckForCompilingState(p: PChar): boolean;
1214 var
1215   OldP: PChar;
1216   AFilename: string;
1217   aDir: String;
1218   MsgLine: TMessageLine;
1219   NewFilename: String;
1220 begin
1221   OldP:=p;
1222   // for example 'Compiling ./subdir/unit1.pas'
1223   if fMsgID=0 then begin
1224     if not ReadString(p,'Compiling ') then exit(false);
1225     fMsgID:=FPCMsgIDCompiling;
1226     Result:=true;
1227   end else if fMsgID=FPCMsgIDCompiling then begin
1228     Result:=true;
1229     if not ReadString(p,'Compiling ') then exit;
1230   end else begin
1231     exit(false);
1232   end;
1233   // add path to history
1234   if (p^='.') and (p[1]=PathDelim) then
1235     inc(p,2); // skip ./
1236   AFilename:=TrimFilename(p);
1237   aDir:=ExtractFilePath(AFilename);
1238   if aDir<>'' then begin
1239     // make absolute
1240     if (not FilenameIsAbsolute(aDir)) and (Tool.WorkerDirectory<>'') then begin
1241       aDir:=TrimFilename(AppendPathDelim(Tool.WorkerDirectory)+aDir);
1242       AFilename:=aDir+ExtractFileName(AFilename);
1243     end;
1244     // reverse instantfpc cache
1245     if (InstantFPCCache<>'') and (Tool.WorkerDirectory<>'')
1246     and (FilenameIsAbsolute(aDir))
1247     and (CompareFilenames(InstantFPCCache,aDir)=0) then
1248     begin
1249       NewFilename:=AppendPathDelim(Tool.WorkerDirectory)+ExtractFileName(AFilename);
1250       if FileExists(NewFilename,false) then begin
1251         AFilename:=NewFilename;
1252         aDir:=Tool.WorkerDirectory;
1253       end;
1254     end;
1255     // store directory
1256     if DirectoryStack=nil then DirectoryStack:=TStringList.Create;
1257     if (DirectoryStack.Count=0)
1258     or (DirectoryStack[DirectoryStack.Count-1]<>aDir) then
1259       DirectoryStack.Add(aDir);
1260   end;
1261   MsgLine:=CreateMsgLine;
1262   MsgLine.Urgency:=mluProgress;
1263   MsgLine.SubTool:=DefaultSubTool;
1264   MsgLine.Filename:=AFilename;
1265   MsgLine.Msg:=OldP;
1266   inherited AddMsgLine(MsgLine);
1267   Result:=true;
1268 end;
1269 
CheckForAssemblingStatenull1270 function TIDEFPCParser.CheckForAssemblingState(p: PChar): boolean;
1271 var
1272   MsgLine: TMessageLine;
1273   OldP: PChar;
1274 begin
1275   Result:=fMsgID=9001;
1276   if (not Result) and (fMsgID>0) then exit;
1277   OldP:=p;
1278   if (not Result) and (not CompStr('Assembling ',p)) then exit;
1279   MsgLine:=CreateMsgLine;
1280   MsgLine.Urgency:=mluProgress;
1281   MsgLine.SubTool:=DefaultSubTool;
1282   MsgLine.Urgency:=mluProgress;
1283   MsgLine.Msg:=OldP;
1284   inherited AddMsgLine(MsgLine);
1285   Result:=true;
1286 end;
1287 
TIDEFPCParser.CheckForGeneralMessagenull1288 function TIDEFPCParser.CheckForGeneralMessage(p: PChar): boolean;
1289 { check for
1290   Fatal: message
1291   Hint: (11030) Start of reading config file /etc/fpc.cfg
1292   Error: /usr/bin/ppc386 returned an error exitcode
1293 }
1294 const
1295   FrontEndFPCExitCodeError = 'returned an error exitcode';
1296 var
1297   MsgLine: TMessageLine;
1298   MsgType: TMessageLineUrgency;
1299   p2: PChar;
1300   i: Integer;
1301   TranslatedItem: TFPCMsgItem;
1302   MsgItem: TFPCMsgItem;
1303   TranslatedMsg: String;
1304 
1305   procedure CheckFinalNote;
1306   // check if there was already an error message
1307   // if yes, then downgrade this message to a mluVerbose
1308   var
1309     u: TMessageLineUrgency;
1310   begin
1311     for u:=mluError to high(TMessageLineUrgency) do
1312       if Tool.WorkerMessages.UrgencyCounts[u]>0 then
1313       begin
1314         MsgType:=mluVerbose;
1315         exit;
1316       end;
1317   end;
1318 
1319 begin
1320   Result:=false;
1321   MsgType:=mluNone;
1322   if ReadString(p,'Fatal: ') then begin
1323     MsgType:=mluFatal;
1324     // check for "Fatal: compilation aborted"
1325     if fMsgItemCompilationAborted=nil then begin
1326       fMsgItemCompilationAborted:=MsgFile.GetMsg(FPCMsgIDCompilationAborted);
1327       if fMsgItemCompilationAborted=nil then
1328         fMsgItemCompilationAborted:=fMissingFPCMsgItem;
1329     end;
1330     p2:=p;
1331     if (fMsgItemCompilationAborted<>fMissingFPCMsgItem)
1332     and ReadString(p2,fMsgItemCompilationAborted.Pattern) then
1333       CheckFinalNote;
1334   end
1335   else if ReadString(p,'Panic') then
1336     MsgType:=mluPanic
1337   else if ReadString(p,'Error: ') then begin
1338     // check for fpc frontend message "Error: /usr/bin/ppc386 returned an error exitcode"
1339     TranslatedMsg:=p;
1340     MsgType:=mluError;
1341     if Pos(FrontEndFPCExitCodeError,TranslatedMsg)>0 then begin
1342       fMsgID:=FPCMsgIDCompilationAborted;
1343       CheckFinalNote;
1344     end;
1345   end
1346   else if ReadString(p,'Warn: ') or ReadString(p,'Warning:') then
1347     MsgType:=mluWarning
1348   else if ReadString(p,'Note: ') then
1349     MsgType:=mluNote
1350   else if ReadString(p,'Hint: ') then
1351     MsgType:=mluHint
1352   else if ReadString(p,'Debug: ') then
1353     MsgType:=mluDebug
1354   else begin
1355     exit;
1356   end;
1357   if MsgType=mluNone then exit;
1358 
1359   Result:=true;
1360   while p^ in [' ',#9] do inc(p);
1361   TranslatedMsg:='';
1362   if (p^='(') and (p[1] in ['0'..'9']) then begin
1363     p2:=p;
1364     inc(p2);
1365     i:=0;
1366     while (p2^ in ['0'..'9']) and (i<1000000) do begin
1367       i:=i*10+ord(p2^)-ord('0');
1368       inc(p2);
1369     end;
1370     if p2^=')' then begin
1371       fMsgID:=i;
1372       p:=p2+1;
1373       while p^ in [' ',#9] do inc(p);
1374       //if Pos('reading',String(p))>0 then
1375       //  debugln(['TFPCParser.CheckForGeneralMessage ID=',fMsgID,' Msg=',p]);
1376       if (fMsgID>0) then begin
1377         TranslatedItem:=nil;
1378         MsgItem:=nil;
1379         if (MsgFile<>nil) then
1380           MsgItem:=MsgFile.GetMsg(fMsgID);
1381         if (TranslationFile<>nil) then
1382           TranslatedItem:=TranslationFile.GetMsg(fMsgID);
1383         Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgType);
1384         if (TranslatedItem=nil) and (MsgItem=nil) then begin
1385           if ConsoleVerbosity>=1 then
1386             debugln(['TFPCParser.CheckForGeneralMessage msgid not found: ',fMsgID]);
1387         end;
1388       end;
1389 
1390     end;
1391   end;
1392   if (MsgType>=mluError) and (fMsgID=FPCMsgIDCompilationAborted) // fatal: Compilation aborted
1393   then begin
1394     CheckFinalNote;
1395   end;
1396   MsgLine:=CreateMsgLine;
1397   MsgLine.Urgency:=MsgType;
1398   MsgLine.SubTool:=DefaultSubTool;
1399   MsgLine.Msg:=p;
1400   MsgLine.TranslatedMsg:=TranslatedMsg;
1401   AddMsgLine(MsgLine);
1402 end;
1403 
CheckForLineProgressnull1404 function TIDEFPCParser.CheckForLineProgress(p: PChar): boolean;
1405 // for example:  600 206.521/231.648 Kb Used
1406 var
1407   OldP: PChar;
1408   MsgLine: TMessageLine;
1409 begin
1410   Result:=false;
1411   OldP:=p;
1412   if not ReadNumberWithThousandSep(p) then exit;
1413   if not ReadChar(p,' ') then exit;
1414   if not ReadNumberWithThousandSep(p) then exit;
1415   if not ReadChar(p,'/') then exit;
1416   if not ReadNumberWithThousandSep(p) then exit;
1417   if not ReadChar(p,' ') then exit;
1418   MsgLine:=CreateMsgLine;
1419   MsgLine.SubTool:=DefaultSubTool;
1420   MsgLine.Urgency:=mluProgress;
1421   MsgLine.Msg:=OldP;
1422   inherited AddMsgLine(MsgLine);
1423   Result:=true;
1424 end;
1425 
TIDEFPCParser.CheckForLinesCompilednull1426 function TIDEFPCParser.CheckForLinesCompiled(p: PChar): boolean;
1427 var
1428   OldStart: PChar;
1429   MsgLine: TMessageLine;
1430 begin
1431   Result:=fMsgID=FPCMsgIDLinesCompiled;
1432   if (not Result) and (fMsgID>0) then exit;
1433   OldStart:=p;
1434   if not Result then begin
1435     if not ReadNumberWithThousandSep(p) then exit;
1436     if not ReadString(p,' lines compiled, ') then exit;
1437     if not ReadNumberWithThousandSep(p) then exit;
1438   end;
1439   Result:=true;
1440   MsgLine:=CreateMsgLine;
1441   MsgLine.SubTool:=DefaultSubTool;
1442   if ShowLinesCompiled then
1443     MsgLine.Urgency:=mluImportant
1444   else
1445     MsgLine.Urgency:=mluVerbose;
1446   MsgLine.Msg:=OldStart;
1447   inherited AddMsgLine(MsgLine);
1448 end;
1449 
TIDEFPCParser.CheckForExecutableInfonull1450 function TIDEFPCParser.CheckForExecutableInfo(p: PChar): boolean;
1451 { For example:
1452 Size of Code: 1184256 bytes
1453 Size of initialized data: 519168 bytes
1454 Size of uninitialized data: 83968 bytes
1455 Stack space reserved: 262144 bytes
1456 Stack space commited: 4096 bytes
1457 }
1458 var
1459   OldStart: PChar;
1460   MsgLine: TMessageLine;
1461 begin
1462   Result:=(fMsgID>=9130) and (fMsgID<=9140);
1463   if (not Result) and (fMsgID>0) then exit;
1464   OldStart:=p;
1465   if (not Result) then begin
1466     if not (ReadString(p,'Size of Code: ') or
1467             ReadString(p,'Size of initialized data: ') or
1468             ReadString(p,'Size of uninitialized data: ') or
1469             ReadString(p,'Stack space reserved: ') or
1470             ReadString(p,'Stack space commited: ') or // message contains typo
1471             ReadString(p,'Stack space committed: ')) then exit;
1472     if not ReadNumberWithThousandSep(p) then exit;
1473     if not ReadString(p,' bytes') then exit;
1474   end;
1475   Result:=true;
1476   MsgLine:=CreateMsgLine;
1477   MsgLine.SubTool:=DefaultSubTool;
1478   MsgLine.Urgency:=mluProgress;
1479   MsgLine.Msg:=OldStart;
1480   inherited AddMsgLine(MsgLine);
1481 end;
1482 
CheckForWindresErrorsnull1483 function TIDEFPCParser.CheckForWindresErrors(p: PChar): boolean;
1484 // example: ...\windres.exe: warning: ...
1485 var
1486   MsgLine: TMessageLine;
1487   WPos: PChar;
1488 begin
1489   Result := false;
1490   WPos:=FindSubStrI('windres',p);
1491   if WPos=nil then exit;
1492   Result:=true;
1493   MsgLine:=CreateMsgLine;
1494   MsgLine.SubTool:=SubToolFPCWindRes;
1495   MsgLine.Urgency:=mluWarning;
1496   p := wPos + 7;
1497   if CompStr('.exe', p) then
1498     inc(p, 4);
1499   MsgLine.Msg:='windres' + p;
1500   AddMsgLine(MsgLine);
1501 end;
1502 
CheckForLinkerErrorsnull1503 function TIDEFPCParser.CheckForLinkerErrors(p: PChar): boolean;
1504 const
1505   patUndefinedSymbol: String = 'Undefined symbols for architecture';
1506   patLD: String = '/usr/bin/ld: ';
1507 var
1508   MsgLine: TMessageLine;
1509   Urgency: TMessageLineUrgency;
1510   s: string;
1511 begin
1512   if CompareMem(PChar(patUndefinedSymbol),p,length(patUndefinedSymbol)) then
1513   begin
1514     MsgLine:=CreateMsgLine;
1515     MsgLine.MsgID:=0;
1516     MsgLine.SubTool:=SubToolFPCLinker;
1517     MsgLine.Urgency:=mluError;
1518     MsgLine.Msg:='linker: '+p;
1519     inherited AddMsgLine(MsgLine);
1520     exit(true);
1521   end;
1522   if CompareMem(PChar(patLD),p,length(patLD)) then
1523   begin
1524     MsgLine:=CreateMsgLine;
1525     MsgLine.MsgID:=0;
1526     MsgLine.SubTool:=SubToolFPCLinker;
1527     s:=p;
1528     Urgency:=mluHint;
1529     if fMsgIsStdErr then
1530     begin
1531       Urgency:=mluWarning;
1532       if (Pos('link.res',s)>0) and (Pos(' -T',s)>0) then
1533         // /usr/bin/ld: warning: /path/link.res contains output sections; did you forget -T?
1534         Urgency:=mluVerbose;
1535     end;
1536     MsgLine.Urgency:=Urgency;
1537     MsgLine.Msg:='linker: '+s;
1538     inherited AddMsgLine(MsgLine);
1539     exit(true);
1540   end;
1541   Result:=false;
1542 end;
1543 
TIDEFPCParser.CheckForAssemblerErrorsnull1544 function TIDEFPCParser.CheckForAssemblerErrors(p: PChar): boolean;
1545 // example:
1546 //   <stdin>:227:9: error: unsupported directive '.stabs'
1547 var
1548   APos: PChar;
1549   s: string;
1550   MsgLine: TMessageLine;
1551 begin
1552   Result:=false;
1553   APos:=FindSubStrI('error: unsupported directive',p);
1554   if APos=nil then exit;
1555   Result:=true;
1556   MsgLine:=CreateMsgLine;
1557   MsgLine.SubTool:=SubToolFPCWindRes;
1558   MsgLine.Urgency:=mluError;
1559   s:=APos;
1560   if Pos('.stabs',s)>0 then
1561     s+='. Hint: Use another type of debug info.';
1562   MsgLine.Msg:='assembler: '+s;
1563   AddMsgLine(MsgLine);
1564 end;
1565 
CheckForUnspecificStdErrnull1566 function TIDEFPCParser.CheckForUnspecificStdErr(p: PChar): boolean;
1567 var
1568   MsgLine: TMessageLine;
1569 begin
1570   if not fMsgIsStdErr then exit(false);
1571   Result:=true;
1572   MsgLine:=CreateMsgLine;
1573   MsgLine.SubTool:=SubToolFPC;
1574   MsgLine.Urgency:=mluError;
1575   MsgLine.Msg:=p;
1576   AddMsgLine(MsgLine);
1577 end;
1578 
TIDEFPCParser.CheckForInfosnull1579 function TIDEFPCParser.CheckForInfos(p: PChar): boolean;
1580 
ReadFPCLogonull1581   function ReadFPCLogo(PatternItem: PPatternToMsgID;
1582     out FPCVersionAsInt: LongWord): boolean;
1583   var
1584     Line: string;
1585     Ranges: TFPCMsgRanges;
1586     aRange: PFPCMsgRange;
1587     i: SizeInt;
1588     aFPCFullVersion: String;
1589     FPCVersion: integer;
1590     FPCRelease: integer;
1591     FPCPatch: integer;
1592   begin
1593     Result:=false;
1594     FPCVersionAsInt:=0;
1595     i:=Pos('$FPCFULLVERSION',PatternItem^.Pattern);
1596     if i<1 then exit;
1597     Line:=p;
1598     Ranges:=nil;
1599     try
1600       ExtractFPCMsgParameters(PatternItem^.Pattern,Line,Ranges);
1601       if Ranges.Count>0 then begin
1602         // first is $FPCFULLVERSION
1603         aRange:=@Ranges.Ranges[0];
1604         aFPCFullVersion:=copy(Line,aRange^.StartPos+1,aRange^.EndPos-aRange^.StartPos);
1605         SplitFPCVersion(aFPCFullVersion,FPCVersion,FPCRelease,FPCPatch);
1606         FPCVersionAsInt:=FPCVersion*10000+FPCRelease*100+FPCPatch;
1607         Result:=FPCVersionAsInt>0;
1608       end;
1609       // second is $FPCDATE
1610       // third is $FPCCPU
1611     finally
1612       Ranges.Free;
1613     end;
1614   end;
1615 
1616 var
1617   MsgItem: TFPCMsgItem;
1618   MsgLine: TMessageLine;
1619   MsgType: TMessageLineUrgency;
1620   PatternItem: PPatternToMsgID;
1621   aFPCVersion: LongWord;
1622 begin
1623   Result:=false;
1624   PatternItem:=fLineToMsgID.LineToPattern(p);
1625   if PatternItem=nil then exit;
1626   fMsgID:=PatternItem^.MsgID;
1627   if (fMsgID=FPCMsgIDLogo) and (DirectoryStack<>nil) then begin
1628     // a new call of the compiler (e.g. when compiling via make)
1629     // => clear stack
1630     FreeAndNil(DirectoryStack);
1631   end;
1632   MsgItem:=MsgFile.GetMsg(fMsgID);
1633   if MsgItem=nil then exit;
1634   Result:=true;
1635   MsgType:=FPCMsgToMsgUrgency(MsgItem);
1636   if MsgType=mluNone then
1637     MsgType:=mluVerbose;
1638   MsgLine:=CreateMsgLine;
1639   MsgLine.SubTool:=DefaultSubTool;
1640   MsgLine.Urgency:=MsgType;
1641   if (fMsgID=FPCMsgIDLogo) and ReadFPCLogo(PatternItem,aFPCVersion) then begin
1642     if aFPCVersion<>PC_FullVersion then begin
1643       // unexpected FPC version => always show
1644       MsgLine.Urgency:=mluImportant;
1645       PC_FullVersion:=aFPCVersion;
1646     end;
1647   end;
1648   AddMsgLine(MsgLine);
1649 end;
1650 
CreateMsgLinenull1651 function TIDEFPCParser.CreateMsgLine: TMessageLine;
1652 begin
1653   Result:=inherited CreateMsgLine(fOutputIndex);
1654   Result.MsgID:=fMsgID;
1655   if fMsgIsStdErr then
1656     Result.Flags:=Result.Flags+[mlfStdErr];
1657 end;
1658 
1659 procedure TIDEFPCParser.AddLinkingMessages;
1660 { Add messages for all output between "Linking ..." and the
1661   current line "Error while linking"
1662 
1663 For example:
1664   Linking /home/user/project1
1665   /usr/bin/ld: warning: /home/user/link.res contains output sections; did you forget -T?
1666   /usr/bin/ld: cannot find -la52
1667   project1.lpr(20,1) Error: Error while linking
1668 
1669   Examples for linking errors:
1670   linkerror.o(.text$_main+0x9):linkerror.pas: undefined reference to `NonExistingFunction'
1671 
1672   /path/lib/x86_64-linux/blaunit.o: In function `FORMCREATE':
1673   /path//blaunit.pas:45: undefined reference to `BLAUNIT_BLABLA'
1674 
1675   Closing script ppas.sh
1676 
1677   Mac OS X linker example:
1678   ld: framework not found Cocoas
1679   Note: this comes in stderr, so it might be some lines after corresponding stdout
1680 
1681   Multiline Mac OS X linker example:
1682   Undefined symbols:
1683     "_exterfunc", referenced from:
1684         _PASCALMAIN in testld.o
1685     "_exterfunc2", referenced from:
1686         _PASCALMAIN in testld.o
1687   ld: symbol(s) not found
1688 
1689   Linking project1
1690   Undefined symbols for architecture x86_64:
1691     "_GetCurrentEventButtonState", referenced from:
1692         _COCOAINT_TCOCOAWIDGETSET_$__GETKEYSTATE$LONGINT$$SMALLINT in cocoaint.o
1693   ld: symbol(s) not found for architecture x86_64
1694   An error occurred while linking
1695 
1696   Linking IDE:
1697   (9015) Linking ../lazarus
1698   /usr/bin/ld: cannot find -lGL
1699   make[2]: *** [lazarus] Error 1
1700   make[1]: *** [idepkg] Error 2
1701   make: *** [idepkg] Error 2
1702   /home/mattias/pascal/wichtig/lazarus/ide/lazarus.pp(161,1) Error: (9013) Error while linking
1703 
1704 }
1705 var
1706   i: Integer;
1707   MsgLine: TMessageLine;
1708 begin
1709   // change all low urgency messages in front of the last message to Important
1710   i:=Tool.WorkerMessages.Count-1;
1711   while i>=0 do begin
1712     MsgLine:=Tool.WorkerMessages[i];
1713     //debugln(['TIDEFPCParser.AddLinkingMessages ',i,' ',dbgs(MsgLine.Urgency),' ',MsgLine.Msg]);
1714     if MsgLine.Urgency<mluHint then
1715       MsgLine.Urgency:=mluImportant
1716     else
1717       break;
1718     dec(i);
1719   end;
1720 
1721   // add all skipped lines in front of the linking error
1722   i:=Tool.WorkerMessages.Count-1;
1723   if i<0 then exit;
1724   MsgLine:=Tool.WorkerMessages[i];
1725   //debugln(['TIDEFPCParser.AddLinkingMessages MsgLine.OutputIndex=',MsgLine.OutputIndex,' fOutputIndex=',fOutputIndex]);
1726   for i:=MsgLine.OutputIndex+1 to fOutputIndex-1 do begin
1727     MsgLine:=inherited CreateMsgLine(i);
1728     MsgLine.MsgID:=0;
1729     MsgLine.SubTool:=SubToolFPCLinker;
1730     if MsgLine.Msg<>'' then
1731       MsgLine.Urgency:=mluImportant
1732     else
1733       MsgLine.Urgency:=mluVerbose2;
1734     inherited AddMsgLine(MsgLine);
1735   end;
1736 end;
1737 
1738 procedure TIDEFPCParser.AddResourceMessages;
1739 {  Add messages for all output between "Calling resource compiler " and the
1740   current line "Error while compiling resources"
1741 
1742 For example:
1743   Calling resource compiler "/usr/bin/fpcres" with "-o /home/user/project1.or -a x86_64 -of elf -v "@/home/user/project1.reslst"" as command line
1744   Debug: parsing command line parameters
1745   ...
1746   Error: Error while compiling resources
1747 }
1748 var
1749   i: Integer;
1750   MsgLine: TMessageLine;
1751 begin
1752   // find message "Calling resource compiler ..."
1753   i:=Tool.WorkerMessages.Count-1;
1754   while (i>=0) and (Tool.WorkerMessages[i].MsgID<>FPCMsgIDCallingResourceCompiler) do
1755     dec(i);
1756   if i<0 then exit;
1757   MsgLine:=Tool.WorkerMessages[i];
1758   for i:=MsgLine.OutputIndex+1 to fOutputIndex-1 do begin
1759     MsgLine:=inherited CreateMsgLine(i);
1760     MsgLine.MsgID:=0;
1761     MsgLine.SubTool:=SubToolFPCRes;
1762     if MsgLine.Msg<>'' then
1763       MsgLine.Urgency:=mluHint
1764     else
1765       MsgLine.Urgency:=mluVerbose2;
1766     inherited AddMsgLine(MsgLine);
1767   end;
1768 end;
1769 
NeedSourcenull1770 function TIDEFPCParser.NeedSource(aPhase: TExtToolParserSyncPhase;
1771   SourceOk: boolean): boolean;
1772 begin
1773   if SourceOk then exit(false);
1774   case aPhase of
1775   etpspAfterReadLine: NeedSynchronize:=true;
1776   etpspSynchronized: NeedAfterSync:=true;
1777   end;
1778   Result:=true;
1779 end;
1780 
IsMsgIDnull1781 function TIDEFPCParser.IsMsgID(MsgLine: TMessageLine; MsgID: integer;
1782   var Item: TFPCMsgItem): boolean;
1783 begin
1784   if MsgLine.MsgID=MsgID then exit(true);
1785   Result:=false;
1786   if MsgLine.MsgID<>0 then exit;
1787   if MsgLine.SubTool<>DefaultSubTool then exit;
1788   if Item=nil then begin
1789     Item:=MsgFile.GetMsg(MsgID);
1790     if Item=nil then
1791       Item:=fMissingFPCMsgItem;
1792   end;
1793   if Item=fMissingFPCMsgItem then exit;
1794   if Item.PatternFits(MsgLine.Msg)<0 then exit;
1795   MsgLine.MsgID:=MsgID;
1796   Result:=true;
1797 end;
1798 
1799 procedure TIDEFPCParser.ImproveMsgHiddenByIDEDirective(
1800   aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine; SourceOK: Boolean);
1801 // check for {%H-}
1802 
IsHnull1803   function IsH(p: PChar): boolean; inline;
1804   begin
1805     Result:=(p^='{') and (p[1]='%') and (p[2]='H') and (p[3]='-');
1806   end;
1807 
1808 var
1809   p: PChar;
1810   X: Integer;
1811   Y: Integer;
1812   HasDirective: Boolean;
1813   AbsPos: Integer; // 0-based
1814   OtherPos: Integer;
1815   AtomEnd: integer;
1816 begin
1817   if MsgLine.Urgency>=mluError then exit;
1818   if mlfHiddenByIDEDirectiveValid in MsgLine.Flags then exit;
1819   if NeedSource(aPhase,SourceOK) then
1820     exit;
1821 
1822   X:=MsgLine.Column;
1823   Y:=MsgLine.Line;
1824   if (y<=fCurSource.LineCount) and (x-1<=fCurSource.GetLineLength(y-1))
1825   then begin
1826     HasDirective:=false;
1827     AbsPos:=fCurSource.GetLineStart(y-1)+x-2; // 0-based
1828     p:=PChar(fCurSource.Source)+AbsPos;
1829     //debugln(['TFPCParser.ImproveMsgHiddenByIDEDirective ',MsgLine.Filename,' ',Y,',',X,' ',copy(fCurSource.GetLine(y-1),1,x-1),'|',copy(fCurSource.GetLine(y-1),x,100),' p=',p[0],p[1],p[2]]);
1830     if IsH(p) then
1831       // directive beginning at cursor
1832       HasDirective:=true
1833     else if (x>5) and IsH(p-5) then
1834       // directive ending at cursor
1835       HasDirective:=true
1836     else begin
1837       // different compiler versions report some message positions differently.
1838       // They changed some message positions from start to end of token.
1839       // => check other end of token
1840       //debugln(['TIDEFPCParser.ImproveMsgHiddenByIDEDirective mlfLeftToken=',mlfLeftToken in MsgLine.Flags]);
1841       if mlfLeftToken in MsgLine.Flags then begin
1842         if IsIdentChar[p[-1]] then begin
1843           OtherPos:=AbsPos+1;
1844           ReadPriorPascalAtom(fCurSource.Source,OtherPos,AtomEnd);
1845           if (OtherPos>5) and (AtomEnd=AbsPos+1)
1846           and IsH(@fCurSource.Source[OtherPos-5]) then begin
1847             // for example: {%H-}identifier|
1848             HasDirective:=true;
1849           end;
1850         end;
1851       end else begin
1852         if IsIdentStartChar[p^] then begin
1853           inc(p,GetIdentLen(p));
1854           if IsH(p) then
1855             // for example: |identifier{%H-}
1856             HasDirective:=true;
1857         end;
1858       end;
1859     end;
1860     if HasDirective then begin
1861       MsgLine.Flags:=MsgLine.Flags+[mlfHiddenByIDEDirective,
1862         mlfHiddenByIDEDirectiveValid];
1863       exit;
1864     end;
1865   end;
1866   MsgLine.Flags:=MsgLine.Flags+[mlfHiddenByIDEDirectiveValid];
1867 end;
1868 
1869 procedure TIDEFPCParser.ImproveMsgSenderNotUsed(
1870   aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
1871 // FPCMsgIDParameterNotUsed = 5024;  Parameter "$1" not used
1872 begin
1873   if aPhase<>etpspAfterReadLine then exit;
1874   if (MsgLine.Urgency<=mluVerbose) then exit;
1875   // check for Sender not used
1876   if HideHintsSenderNotUsed
1877   and (MsgLine.Msg='Parameter "Sender" not used') then begin
1878     MsgLine.Urgency:=mluVerbose;
1879   end;
1880 end;
1881 
1882 procedure TIDEFPCParser.ImproveMsgUnitNotUsed(aPhase: TExtToolParserSyncPhase;
1883   MsgLine: TMessageLine);
1884 // check for Unit not used message in main sources
1885 // and change urgency to merely 'verbose'
1886 begin
1887   if aPhase<>etpspAfterReadLine then exit;
1888   if (MsgLine.Urgency<=mluVerbose) then exit;
1889   if not IsMsgID(MsgLine,FPCMsgIDUnitNotUsed,fMsgItemUnitNotUsed) then exit;
1890 
1891   //debugln(['TIDEFPCParser.ImproveMsgUnitNotUsed ',aPhase=etpspSynchronized,' ',MsgLine.Msg]);
1892   // unit not used
1893   if IndexInStringList(FilesToIgnoreUnitNotUsed,cstFilename,MsgLine.Filename)>=0 then
1894   begin
1895     MsgLine.Urgency:=mluVerbose;
1896   end else if HideHintsUnitNotUsedInMainSource then begin
1897     if FilenameExtIs(MsgLine.Filename, 'lpr', true) then
1898       // a lpr does not use a unit => not important
1899       MsgLine.Urgency:=mluVerbose
1900     else if FilenameIsAbsolute(MsgLine.Filename)
1901     and FileExists(ChangeFileExt(MsgLine.Filename, '.lpk'), aPhase=etpspSynchronized)
1902     then begin
1903       // a lpk does not use a unit => not important
1904       MsgLine.Urgency:=mluVerbose;
1905     end;
1906   end;
1907 end;
1908 
1909 procedure TIDEFPCParser.ImproveMsgUnitNotFound(aPhase: TExtToolParserSyncPhase;
1910   MsgLine: TMessageLine);
1911 
1912   procedure FixSourcePos(CodeBuf: TCodeBuffer; MissingUnitname: string);
1913   var
1914     InPos: Integer;
1915     NamePos: Integer;
1916     Tool: TCodeTool;
1917     Caret: TCodeXYPosition;
1918     NewFilename: String;
1919   begin
1920     {$IFDEF VerboseFPCMsgUnitNotFound}
1921     debugln(['TIDEFPCParser.ImproveMsgUnitNotFound File=',CodeBuf.Filename]);
1922     {$ENDIF}
1923     LazarusIDE.SaveSourceEditorChangesToCodeCache(nil);
1924     if not CodeToolBoss.FindUnitInAllUsesSections(CodeBuf,MissingUnitname,NamePos,InPos)
1925     then begin
1926       DebugLn('TIDEFPCParser.ImproveMsgUnitNotFound FindUnitInAllUsesSections failed due to syntax errors or '+MissingUnitname+' is not used in '+CodeBuf.Filename);
1927       exit;
1928     end;
1929     Tool:=CodeToolBoss.CurCodeTool;
1930     if Tool=nil then exit;
1931     if not Tool.CleanPosToCaret(NamePos,Caret) then exit;
1932     if (Caret.X>0) and (Caret.Y>0) then begin
1933       //DebugLn('QuickFixUnitNotFoundPosition Line=',dbgs(Line),' Col=',dbgs(Col));
1934       NewFilename:=Caret.Code.Filename;
1935       MsgLine.SetSourcePosition(NewFilename,Caret.Y,Caret.X);
1936     end;
1937   end;
1938 
1939   procedure FindPPUFiles(MissingUnitname: string; PkgList: TFPList;
1940     PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
1941     );
1942   var
1943     i: Integer;
1944     Pkg: TIDEPackage;
1945     DirCache: TCTDirectoryCache;
1946     PPUFilename: String;
1947     UnitOutDir: String;
1948   begin
1949     if PkgList=nil then exit;
1950     for i:=0 to PkgList.Count-1 do begin
1951       Pkg:=TIDEPackage(PkgList[i]);
1952       UnitOutDir:=Pkg.LazCompilerOptions.GetUnitOutputDirectory(false);
1953       //debugln(['TQuickFixUnitNotFoundPosition.Execute ',Pkg.Name,' UnitOutDir=',UnitOutDir]);
1954       if not FilenameIsAbsolute(UnitOutDir) then continue;
1955       DirCache:=CodeToolBoss.DirectoryCachePool.GetCache(UnitOutDir,true,false);
1956       PPUFilename:=DirCache.FindFile(MissingUnitname+'.ppu',ctsfcLoUpCase);
1957       //debugln(['TQuickFixUnitNotFoundPosition.Execute ShortPPU=',PPUFilename]);
1958       if PPUFilename='' then continue;
1959       PPUFilename:=AppendPathDelim(DirCache.Directory)+PPUFilename;
1960       PPUFiles.AddObject(PPUFilename,Pkg);
1961     end;
1962   end;
1963 
1964   procedure FindPPUInInstalledPkgs(MissingUnitname: string;
1965     PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
1966     );
1967   var
1968     i: Integer;
1969     Pkg: TIDEPackage;
1970     PkgList: TFPList;
1971   begin
1972     // search ppu in installed packages
1973     PkgList:=TFPList.Create;
1974     try
1975       for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin
1976         Pkg:=PackageEditingInterface.GetPackages(i);
1977         if Pkg.AutoInstall=pitNope then continue;
1978         PkgList.Add(Pkg);
1979       end;
1980       FindPPUFiles(MissingUnitname,PkgList,PPUFiles);
1981     finally
1982       PkgList.Free;
1983     end;
1984   end;
1985 
1986   procedure FindPPUInModuleAndDeps(MissingUnitname: string; Module: TObject;
1987     PPUFiles: TStringList // Strings:PPUFilename, Objects:TIDEPackage
1988     );
1989   var
1990     PkgList: TFPList;
1991   begin
1992     PkgList:=nil;
1993     try
1994       PackageEditingInterface.GetRequiredPackages(Module,PkgList);
1995       if (Module is TIDEPackage) then begin
1996         if PkgList=nil then
1997           PkgList:=TFPList.Create;
1998         if PkgList.IndexOf(Module)<0 then
1999           PkgList.Add(Module);
2000       end;
2001       FindPPUFiles(MissingUnitname,PkgList,PPUFiles);
2002     finally
2003       PkgList.Free;
2004     end;
2005   end;
2006 
2007   procedure FindPackage(MissingUnitname: string; OnlyInstalled: boolean;
2008     out Pkg: TIDEPackage; out PkgName: string; out PkgFile: TLazPackageFile);
2009   var
2010     i: Integer;
2011     j: Integer;
2012     aFile: TLazPackageFile;
2013     CurPkg: TIDEPackage;
2014   begin
2015     PkgName:='';
2016     PkgFile:=nil;
2017     Pkg:=nil;
2018     // search unit in packages
2019     for i:=0 to PackageEditingInterface.GetPackageCount-1 do begin
2020       CurPkg:=PackageEditingInterface.GetPackages(i);
2021       if OnlyInstalled and (CurPkg.AutoInstall=pitNope) then
2022         continue;
2023       if CompareTextCT(CurPkg.Name,MissingUnitname)=0 then begin
2024         PkgName:=CurPkg.Name;
2025         Pkg:=CurPkg;
2026         break;
2027       end;
2028       for j:=0 to CurPkg.FileCount-1 do begin
2029         aFile:=CurPkg.Files[j];
2030         if not (aFile.FileType in PkgFileRealUnitTypes) then
2031           continue;
2032         if CompareTextCT(ExtractFileNameOnly(aFile.Filename),MissingUnitname)<>0
2033         then continue;
2034         if (PkgFile=nil) or (aFile.InUses and not PkgFile.InUses) then
2035         begin
2036           // a better file was found
2037           PkgFile:=aFile;
2038           PkgName:=CurPkg.Name;
2039           Pkg:=CurPkg;
2040         end;
2041       end;
2042     end;
2043   end;
2044 
2045 var
2046   MissingUnitName: string;
2047   UsedByUnit: string;
2048   Filename: String;
2049   NewFilename: String;
2050   CodeBuf: TCodeBuffer;
2051   Owners: TFPList;
2052   UsedByOwner: TObject;
2053   UsedByPkg: TIDEPackage;
2054   PPUFilename: String;
2055   OnlyInstalled: Boolean;
2056   s: String;
2057   PPUFiles: TStringList; // Strings:PPUFilename, Objects:TIDEPackage
2058   i: Integer;
2059   DepOwner: TObject;
2060   TheOwner: TObject;
2061   MissingPkg: TIDEPackage;
2062   MissingPkgName: String;
2063   MissingPkgFile: TLazPackageFile;
2064   FPCUnitFilename: String;
2065 begin
2066   if MsgLine.Urgency<mluError then exit;
2067   if not IsMsgID(MsgLine,FPCMsgIDCantFindUnitUsedBy,fMsgItemCantFindUnitUsedBy)
2068   then // Can't find unit $1 used by $2
2069     exit;
2070   case aPhase of
2071   etpspAfterReadLine:
2072     begin
2073       NeedSynchronize:=true;
2074       exit;
2075     end;
2076   etpspSynchronized: ;
2077   etpspAfterSync: exit;
2078   end;
2079 
2080   // in main thread
2081 
2082   if not GetFPCMsgValues(MsgLine,MissingUnitName,UsedByUnit) then
2083     exit;
2084   MsgLine.Attribute[FPCMsgAttrMissingUnit]:=MissingUnitName;
2085   MsgLine.Attribute[FPCMsgAttrUsedByUnit]:=UsedByUnit;
2086 
2087   {$IFDEF VerboseFPCMsgUnitNotFound}
2088   debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Missing="',MissingUnitname,'" used by "',UsedByUnit,'"']);
2089   {$ENDIF}
2090 
2091   CodeBuf:=nil;
2092   Filename:=MsgLine.GetFullFilename;
2093   if (CompareFilenames(ExtractFileName(Filename),'staticpackages.inc')=0)
2094   and ((ExtractFilePath(Filename)='')
2095     or (CompareFilenames(ExtractFilePath(Filename),AppendPathDelim(GetPrimaryConfigPath))=0))
2096   then begin
2097     // common case: when building the IDE a package unit is missing
2098     // staticpackages.inc(1,1) Fatal: Can't find unit sqldblaz used by Lazarus
2099     // change to lazarus.pp(1,1)
2100     Filename:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory)+'ide'+PathDelim+'lazarus.pp';
2101     MsgLine.SetSourcePosition(Filename,1,1);
2102     MsgLine.Msg:=Format(lisCanTFindAValidPpu, [MissingUnitname]);
2103   end else if SysUtils.CompareText(ExtractFileNameOnly(Filename),UsedByUnit)<>0
2104   then begin
2105     // the message belongs to another unit
2106     NewFilename:='';
2107     if FilenameIsAbsolute(Filename) then
2108     begin
2109       // For example: /path/laz/main.pp(1,1) Fatal: Can't find unit lazreport used by lazarus
2110       // => search source 'lazarus' in directory
2111       NewFilename:=CodeToolBoss.DirectoryCachePool.FindUnitInDirectory(
2112                                      ExtractFilePath(Filename),UsedByUnit,true);
2113     end;
2114     if NewFilename='' then begin
2115       TheOwner:=nil;
2116       if Tool.Data is TIDEExternalToolData then begin
2117         TheOwner:=ExternalToolList.GetIDEObject(TIDEExternalToolData(Tool.Data));
2118       end else if Tool.Data=nil then begin
2119         {$IFDEF VerboseFPCMsgUnitNotFound}
2120         debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Tool.Data=nil, ProcDir=',Tool.Process.CurrentDirectory]);
2121         {$ENDIF}
2122       end;
2123       NewFilename:=LazarusIDE.FindUnitFile(UsedByUnit,TheOwner);
2124       if NewFilename='' then begin
2125         {$IFDEF VerboseFPCMsgUnitNotFound}
2126         debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unit not found: ',UsedByUnit]);
2127         {$ENDIF}
2128       end;
2129     end;
2130     if NewFilename<>'' then
2131       Filename:=NewFilename;
2132   end;
2133 
2134   if FilenameIsAbsolute(Filename) or (mlfTestBuildFile in MsgLine.Flags) then begin
2135     CodeBuf:=CodeToolBoss.LoadFile(Filename,false,false);
2136     if CodeBuf=nil then begin
2137       {$IFDEF VerboseFPCMsgUnitNotFound}
2138       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unable to load unit: ',Filename]);
2139       {$ENDIF}
2140     end;
2141   end else begin
2142     {$IFDEF VerboseFPCMsgUnitNotFound}
2143     debugln(['TIDEFPCParser.ImproveMsgUnitNotFound unable to locate UsedByUnit: ',UsedByUnit,' Filename="',MsgLine.Filename,'" Attr[',FPCMsgAttrWorkerDirectory,']=',MsgLine.Attribute[FPCMsgAttrWorkerDirectory],' Tool.WorkerDirectory=',Tool.WorkerDirectory]);
2144     {$ENDIF}
2145   end;
2146 
2147   // fix line and column
2148   Owners:=nil;
2149   PPUFiles:=TStringList.Create;
2150   try
2151     UsedByOwner:=nil;
2152     UsedByPkg:=nil;
2153     if CodeBuf<>nil then begin
2154       FixSourcePos(CodeBuf,MissingUnitname);
2155       Owners:=PackageEditingInterface.GetOwnersOfUnit(CodeBuf.Filename);
2156       if (Owners<>nil) and (Owners.Count>0) then begin
2157         UsedByOwner:=TObject(Owners[0]);
2158         if UsedByOwner is TIDEPackage then
2159           UsedByPkg:=TIDEPackage(UsedByOwner);
2160       end;
2161     end;
2162 
2163     // if the ppu exists then improve the message
2164     if (CodeBuf<>nil) and FilenameIsAbsolute(CodeBuf.Filename) then begin
2165       {$IFDEF VerboseFPCMsgUnitNotFound}
2166       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Filename=',CodeBuf.Filename]);
2167       {$ENDIF}
2168       PPUFilename:=CodeToolBoss.DirectoryCachePool.FindCompiledUnitInCompletePath(
2169                         ExtractFilePath(CodeBuf.Filename),MissingUnitname);
2170       if (PPUFilename<>'') then begin
2171         FPCUnitFilename:=CodeToolBoss.DirectoryCachePool.FindUnitInUnitSet(
2172           ExtractFilePath(CodeBuf.Filename),MissingUnitName);
2173       end else
2174         FPCUnitFilename:='';
2175       {$IFDEF VerboseFPCMsgUnitNotFound}
2176       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound PPUFilename=',PPUFilename,' IsFileInIDESrcDir=',IsFileInIDESrcDir(CodeBuf.Filename)]);
2177       {$ENDIF}
2178       OnlyInstalled:=IsFileInIDESrcDir(CodeBuf.Filename);
2179       if OnlyInstalled then begin
2180         FindPPUInInstalledPkgs(MissingUnitname,PPUFiles);
2181       end else if UsedByOwner<>nil then
2182         FindPPUInModuleAndDeps(MissingUnitName,UsedByOwner,PPUFiles);
2183       {$IFDEF VerboseFPCMsgUnitNotFound}
2184       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound PPUFiles in PPU path=',PPUFiles.Count]);
2185       {$ENDIF}
2186       FindPackage(MissingUnitname,OnlyInstalled,MissingPkg,MissingPkgName,MissingPkgFile);
2187       {$IFDEF VerboseFPCMsgUnitNotFound}
2188       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound MissingUnitPkg=',MissingPkgName]);
2189       {$ENDIF}
2190       s:=Format(lisCannotFind, [MissingUnitname]);
2191       if UsedByUnit<>'' then
2192         s+=Format(lisUsedBy, [UsedByUnit]);
2193       if PPUFiles.Count>0 then begin
2194         // there is a ppu file in a package output directory, but the compiler
2195         // didn't like it => change message
2196         if PPUFilename='' then
2197           PPUFilename:=PPUFiles[0];
2198         s+=Format(lisIncompatiblePpu, [PPUFilename]);
2199         if PPUFiles.Count=1 then
2200           s+=Format(lisPackage3, [TIDEPackage(PPUFiles.Objects[0]).Name])
2201         else begin
2202           s+=lisMultiplePack;
2203           for i:=0 to PPUFiles.Count-1 do begin
2204             if i>0 then
2205               s+=', ';
2206             s+=TIDEPackage(PPUFiles.Objects[i]).Name;
2207           end;
2208         end;
2209       end else if PPUFilename<>'' then begin
2210         if CompareFilenames(PPUFilename,FPCUnitFilename)=0 then begin
2211           // there is ppu in the FPC units, but the compiler does not like it
2212           // => a) using a wrong compiler version (wrong fpc.cfg)
2213           //    b) user units in fpc.cfg
2214           //    c) fpc units not compiled with -Ur
2215           //    d) wrong target platform
2216           s+=', ppu='+PPUFilename+', check your fpc.cfg';
2217         end else begin
2218           // there is a ppu file in the source path
2219           if (MissingPkg<>nil) and (MissingPkg.LazCompilerOptions.UnitOutputDirectory='')
2220           then
2221             s+='. '+lisPackageNeedsAnOutputDirectory
2222           else
2223             s+='. '+lisMakeSureAllPpuFilesOfAPackageAreInItsOutputDirecto;
2224           s+=' '+Format(lisPpuInWrongDirectory, [PPUFilename]);
2225           if MissingPkgName<>'' then
2226             s+=' '+Format(lisCleanUpPackage, [MissingPkgName]);
2227           s+='.';
2228         end;
2229       end
2230       else if (UsedByPkg<>nil) and (CompareTextCT(UsedByPkg.Name,MissingPkgName)=0)
2231       then begin
2232         // two units of a package cannot find each other
2233         s+=Format(lisCheckSearchPathPackageTryACleanRebuildCheckImpleme, [
2234           UsedByPkg.Name]);
2235         s+='.';
2236       end else if (MissingPkgName<>'')
2237       and (OnlyInstalled
2238         or ((UsedByOwner<>nil)
2239            and PackageEditingInterface.IsOwnerDependingOnPkg(UsedByOwner,MissingPkgName,DepOwner)))
2240       then begin
2241         // ppu file of an used package is missing
2242         if (MissingPkgFile<>nil) and (not MissingPkgFile.InUses) then
2243           s+=Format(lisEnableFlagUseUnitOfUnitInPackage, [MissingUnitName, MissingPkgName])
2244         else
2245           s+=Format(lisCheckIfPackageCreatesPpuCheckNothingDeletesThisFil, [
2246             MissingPkgName, MissingUnitName]);
2247         s+='.';
2248       end else begin
2249         if MissingPkgName<>'' then
2250           s+=Format(lisCheckIfPackageIsInTheDependencies, [MissingPkgName]);
2251         if UsedByOwner is TLazProject then
2252           s+=lisOfTheProjectInspector
2253         else if UsedByPkg<>nil then
2254           s+=Format(lisOfPackage, [UsedByPkg.Name]);
2255         s+='.';
2256       end;
2257       MsgLine.Msg:=s;
2258       {$IFDEF VerboseFPCMsgUnitNotFound}
2259       debugln(['TIDEFPCParser.ImproveMsgUnitNotFound Msg.Msg="',MsgLine.Msg,'"']);
2260       {$ENDIF}
2261     end;
2262   finally
2263     PPUFiles.Free;
2264     Owners.Free;
2265   end;
2266 end;
2267 
2268 procedure TIDEFPCParser.ImproveMsgLinkerUndefinedReference(
2269   aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine);
2270 
CheckForLinuxLDFileAndLineNumbernull2271   function CheckForLinuxLDFileAndLineNumber: boolean;
2272   { For example:
2273     /path/lib/x86_64-linux/blaunit.o: In function `FORMCREATE':
2274     /path//blaunit.pas:45: undefined reference to `BLAUNIT_BLABLA'
2275   }
2276   var
2277     p: PChar;
2278     Msg: String;
2279     aFilename: String;
2280     LineNumber: Integer;
2281     i: SizeInt;
2282   begin
2283     Result:=false;
2284     if aPhase<>etpspAfterReadLine then exit;
2285     if MsgLine.HasSourcePosition then exit;
2286     Msg:=MsgLine.Msg;
2287     p:=PChar(Msg);
2288     // check for "filename:decimals: message"
2289     //  or unit1.o(.text+0x3a):unit1.pas:48: undefined reference to `DoesNotExist'
2290 
2291     // read filename
2292     repeat
2293       if p^=#0 then exit;
2294       inc(p);
2295     until (p^=':') and (p[1] in ['0'..'9']);
2296     aFilename:=LeftStr(Msg,p-PChar(Msg));
2297     // check for something):filename
2298     i:=Pos('):',aFilename);
2299     if i>0 then
2300       Delete(aFilename,1,i+1);
2301     aFilename:=TrimFilename(aFilename);
2302 
2303     // read line number
2304     inc(p);
2305     LineNumber:=0;
2306     while p^ in ['0'..'9'] do begin
2307       LineNumber:=LineNumber*10+ord(p^)-ord('0');
2308       if LineNumber>9999999 then exit;
2309       inc(p);
2310     end;
2311     if p^<>':' then exit;
2312     inc(p);
2313     while p^ in [' '] do inc(p);
2314 
2315     Result:=true;
2316     MsgLine.Msg:=copy(Msg,p-PChar(Msg)+1,length(Msg));
2317     MsgLine.SetSourcePosition(aFilename,LineNumber,1);
2318     MsgLine.Urgency:=mluError;
2319   end;
2320 
2321   function CheckForDarwinLDReferencedFrom: boolean;
2322   { For example:
2323      "_UNIT1_GIBTESNICHT", referenced from:
2324   }
2325   var
2326     MangledName: string;
2327     aComplete: boolean;
2328     aErrorMsg: string;
2329     NewCode: TCodeBuffer;
2330     NewX: integer;
2331     NewY: integer;
2332     NewTopLine: integer;
2333   begin
2334     Result:=false;
2335     if MsgLine.HasSourcePosition then exit;
2336     // check for '  "_FPC-Mangled-Identifier", referenced from:
2337     if not etFPCMsgParser.GetFPCMsgValue1(MsgLine.Msg,'  "_$1", referenced from:',
2338       MangledName)
2339     then exit;
2340     Result:=true;
2341     case aPhase of
2342     etpspAfterReadLine:
2343       begin
2344         NeedSynchronize:=true;
2345         exit;
2346       end;
2347     etpspAfterSync: exit;
2348     end;
2349     // in main thread
2350     CodeToolBoss.FindFPCMangledIdentifier(MangledName,aComplete,aErrorMsg,
2351       nil,NewCode,NewX,NewY,NewTopLine);
2352     if NewCode=nil then exit;
2353     Result:=true;
2354     MsgLine.SetSourcePosition(NewCode.Filename,NewY,NewX);
2355     MsgLine.Urgency:=mluError;
2356   end;
2357 
CheckForDarwinLDMangledInOnull2358   function CheckForDarwinLDMangledInO: boolean;
2359   { For example:
2360            _UNIT1_TFORM1_$__FORMCREATE$TOBJECT in unit1.o
2361   }
2362   var
2363     MangledName: string;
2364     aUnitName: string;
2365     aComplete: boolean;
2366     aErrorMsg: string;
2367     NewCode: TCodeBuffer;
2368     NewX: integer;
2369     NewY: integer;
2370     NewTopLine: integer;
2371   begin
2372     Result:=false;
2373     if MsgLine.HasSourcePosition then exit;
2374     if not etFPCMsgParser.GetFPCMsgValues2(MsgLine.Msg,'      _$1 in $2.o',
2375       MangledName,aUnitName)
2376     then exit;
2377     Result:=true;
2378     case aPhase of
2379     etpspAfterReadLine:
2380       begin
2381         NeedSynchronize:=true;
2382         exit;
2383       end;
2384     etpspAfterSync: exit;
2385     end;
2386     // in main thread
2387     CodeToolBoss.FindFPCMangledIdentifier(MangledName,aComplete,aErrorMsg,
2388       nil,NewCode,NewX,NewY,NewTopLine);
2389     if NewCode=nil then exit;
2390     Result:=true;
2391     MsgLine.SetSourcePosition(NewCode.Filename,NewY,NewX);
2392     MsgLine.Urgency:=mluError;
2393   end;
2394 
2395 begin
2396   if MsgLine.SubTool<>SubToolFPCLinker then exit;
2397 
2398   if CheckForLinuxLDFileAndLineNumber then exit;
2399   if CheckForDarwinLDReferencedFrom then exit;
2400   if CheckForDarwinLDMangledInO then exit;
2401 end;
2402 
2403 procedure TIDEFPCParser.ImproveMsgIdentifierPosition(
2404   aPhase: TExtToolParserSyncPhase; MsgLine: TMessageLine; SourceOK: boolean);
2405 { FPC sometimes reports the token after the identifier
2406   => fix the position
2407   Examples:
2408     "  i :="
2409     unit1.pas(42,5) Error: (5000) Identifier not found "i"
2410 
2411     "procedure TMyClass.DoIt  ;"
2412     test.pas(7,26) Error: (3047) method identifier expected
2413 }
2414 const
2415   AttrPosChecked = 'PosChecked';
2416 var
2417   LineRange: TLineRange;
2418   Line, Col: Integer;
2419   p, AtomEnd: integer;
2420   Src: String;
2421   Identifier: String;
2422   NewP: Integer;
2423 begin
2424   Col:=MsgLine.Column;
2425   Line:=MsgLine.Line;
2426   if (Col<1) or (Line<1) then
2427     exit;
2428   if (Line=1) and (Col=1) then exit;
2429   if MsgLine.SubTool<>SubToolFPC then exit;
2430   if MsgLine.MsgID=0 then begin
2431     // maybe not compiled with -vq: search patterns of common messages
2432     if (not IsMsgID(MsgLine,FPCMsgIDIdentifierNotFound,fMsgItemIdentifierNotFound))
2433     and (not IsMsgID(MsgLine,FPCMsgIDMethodIdentifierExpected,fMsgItemMethodIdentifierExpected))
2434     then
2435       exit;
2436   end;
2437   if MsgLine.MsgID=FPCMsgIDMethodIdentifierExpected then
2438     Identifier:=''
2439   else begin
2440     Identifier:=GetFPCMsgValue1(MsgLine);
2441     if not IsValidIdent(Identifier) then exit;
2442   end;
2443 
2444   if MsgLine.Attribute[AttrPosChecked]<>'' then exit;
2445   if NeedSource(aPhase,SourceOK) then
2446     exit;
2447   MsgLine.Attribute[AttrPosChecked]:=ClassName;
2448 
2449   //DebuglnThreadLog(['Old Line=',Line,' ',MsgLine.Column]);
2450   if Line>=fCurSource.LineCount then exit;
2451   fCurSource.GetLineRange(Line-1,LineRange);
2452   //DebuglnThreadLog(['Old Range=',LineRange.StartPos,'-',LineRange.EndPos,' Str="',copy(fCurSource.Source,LineRange.StartPos,LineRange.EndPos-LineRange.StartPos),'"']);
2453   Col:=Min(Col,LineRange.EndPos-LineRange.StartPos+1);
2454   p:=LineRange.StartPos+Col-1;
2455   Src:=fCurSource.Source;
2456   if Identifier<>'' then begin
2457     // message is about a specific identifier
2458     if CompareIdentifiers(PChar(Identifier),@Src[p])=0 then begin
2459       // already pointing at the start of the identifier
2460       exit;
2461     end;
2462   end else begin
2463     // message is about any one identifier
2464     if IsIdentStartChar[Src[p]] then begin
2465       // already pointing at an identifier
2466       exit;
2467     end;
2468   end;
2469   // go to prior token
2470   //DebuglnThreadLog(['New Line=',Line,' Col=',Col,' p=',p]);
2471   NewP:=p;
2472   ReadPriorPascalAtom(Src,NewP,AtomEnd,false);
2473   if NewP<1 then exit;
2474   if Identifier<>'' then begin
2475     // message is about a specific identifier
2476     if CompareIdentifiers(PChar(Identifier),@Src[NewP])<>0 then begin
2477       // the prior token is not the identifier neither
2478       // => don't know
2479       exit;
2480     end;
2481   end else begin
2482     // message is about any one identifier
2483     if not IsIdentStartChar[Src[NewP]] then begin
2484       // the prior token is not an identifier neither
2485       // => don't know
2486       exit;
2487     end;
2488   end;
2489   fCurSource.AbsoluteToLineCol(NewP,Line,Col);
2490   //DebuglnThreadLog(['New Line=',Line,' Col=',Col,' p=',NewP]);
2491   if (Line<1) or (Col<1) then exit;
2492   if MsgLine.Urgency>=mluError then begin
2493     // position errors at start of wrong identifier, nicer for identifier completion
2494     MsgLine.SetSourcePosition(MsgLine.Filename,Line,Col);
2495     MsgLine.Flags:=MsgLine.Flags-[mlfLeftToken];
2496   end else begin
2497     // position hints at end of identifier, nicer for {%H-}
2498     MsgLine.SetSourcePosition(MsgLine.Filename,Line,Col+length(Identifier));
2499     MsgLine.Flags:=MsgLine.Flags+[mlfLeftToken];
2500   end;
2501 end;
2502 
TIDEFPCParser.FindSrcViaPPUnull2503 function TIDEFPCParser.FindSrcViaPPU(aPhase: TExtToolParserSyncPhase;
2504   MsgLine: TMessageLine; const PPUFilename: string): boolean;
2505 { in main thread
2506  for example:
2507    /usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
2508    PPUFilename=/usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu
2509    Filename=filutil.inc
2510 }
2511 var
2512   i: Integer;
2513   PrevMsgLine: TMessageLine;
2514   aFilename: String;
2515   MsgWorkerDir: String;
2516   UnitSrcFilename: String;
2517   IncPath: String;
2518   Dir: String;
2519   ShortFilename: String;
2520   IncFilename: String;
2521   AnUnitName: String;
2522   InFilename: String;
2523 begin
2524   case aPhase of
2525   etpspAfterReadLine: exit(false);
2526   etpspSynchronized: ;
2527   etpspAfterSync: exit(true);
2528   end;
2529   Result:=true;
2530 
2531   // in main thread
2532   i:=MsgLine.Index;
2533   aFilename:=MsgLine.Filename;
2534   //debugln(['TIDEFPCParser.FindSrcViaPPU i=',i,' PPUFilename="',PPUFilename,'" Filename="',aFilename,'"']);
2535   if (i>0) then begin
2536     PrevMsgLine:=Tool.WorkerMessages[i-1];
2537     if (PrevMsgLine.SubTool=DefaultSubTool)
2538     and (CompareFilenames(PPUFilename,PrevMsgLine.Attribute['PPU'])=0)
2539     and FilenameIsAbsolute(PrevMsgLine.Filename)
2540     and (CompareFilenames(ExtractFilename(PrevMsgLine.Filename),ExtractFilename(aFilename))=0)
2541     then begin
2542       // same file as previous message => use it
2543       MsgLine.Filename:=PrevMsgLine.Filename;
2544       exit;
2545     end;
2546   end;
2547 
2548   if not FilenameIsAbsolute(PPUFilename) then
2549   begin
2550     exit;
2551   end;
2552 
2553   ShortFilename:=ExtractFilename(aFilename);
2554   MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
2555   AnUnitName:=ExtractFilenameOnly(PPUFilename);
2556   InFilename:='';
2557   UnitSrcFilename:=CodeToolBoss.DirectoryCachePool.FindUnitSourceInCompletePath(
2558                               MsgWorkerDir,AnUnitName,InFilename);
2559   //debugln(['TIDEFPCParser.FindSrcViaPPU MsgWorkerDir="',MsgWorkerDir,'" UnitSrcFilename="',UnitSrcFilename,'"']);
2560   if UnitSrcFilename<>'' then begin
2561     if CompareFilenames(ExtractFilename(UnitSrcFilename),ShortFilename)=0 then
2562     begin
2563       MsgLine.Filename:=UnitSrcFilename;
2564       exit;
2565     end;
2566     Dir:=ChompPathDelim(TrimFilename(ExtractFilePath(UnitSrcFilename)));
2567     IncPath:=CodeToolBoss.GetIncludePathForDirectory(Dir);
2568     IncFilename:=SearchFileInPath(ShortFilename,Dir,IncPath,';',ctsfcDefault);
2569     //debugln(['TIDEFPCParser.FindSrcViaPPU Dir="',Dir,'" IncPath="',IncPath,'" ShortFilename="',ShortFilename,'" IncFilename="',IncFilename,'"']);
2570     if IncFilename<>'' then begin
2571       MsgLine.Filename:=IncFilename;
2572       exit;
2573     end;
2574   end;
2575 end;
2576 
2577 procedure TIDEFPCParser.Translate(p: PChar; MsgItem, TranslatedItem: TFPCMsgItem;
2578   out TranslatedMsg: String; out MsgType: TMessageLineUrgency);
2579 begin
2580   TranslatedMsg:='';
2581   MsgType:=mluNone;
2582   if TranslatedItem<>nil then
2583     MsgType:=FPCMsgToMsgUrgency(TranslatedItem);
2584   if (MsgType=mluNone) and (MsgItem<>nil) then
2585     MsgType:=FPCMsgToMsgUrgency(MsgItem);
2586   if TranslatedItem<>nil then begin
2587     if System.Pos('$',TranslatedItem.Pattern)<1 then begin
2588       TranslatedMsg:=TranslatedItem.Pattern;
2589       UTF8FixBroken(TranslatedMsg);
2590     end
2591     else if MsgItem<>nil then
2592       TranslatedMsg:=TranslateFPCMsg(p,MsgItem.Pattern,TranslatedItem.Pattern);
2593     //debugln(['TFPCParser.Translate Translation="',TranslatedMsg,'"']);
2594   end;
2595 end;
2596 
ReverseInstantFPCCacheDirnull2597 function TIDEFPCParser.ReverseInstantFPCCacheDir(var aFilename: string;
2598   aSynchronized: boolean): boolean;
2599 var
2600   Reversed: String;
2601 begin
2602   Result:=false;
2603   if (InstantFPCCache='') then exit;
2604   if (CompareFilenames(ExtractFilePath(aFilename),InstantFPCCache)=0) then begin
2605     Reversed:=AppendPathDelim(Tool.WorkerDirectory)+ExtractFilename(aFilename);
2606     if FileExists(Reversed,aSynchronized) then begin
2607       aFilename:=Reversed;
2608       Result:=true;
2609     end;
2610   end;
2611 end;
2612 
ReverseTestBuildDirnull2613 function TIDEFPCParser.ReverseTestBuildDir(MsgLine: TMessageLine;
2614   var aFilename: string): boolean;
2615 var
2616   Reversed: String;
2617   l: Integer;
2618 begin
2619   Result:=false;
2620   if not Tool.CurrentDirectoryIsTestDir then exit;
2621   l:=length(TestBuildDir); // Note: TestBuildDir includes trailing PathDelim
2622   if (length(aFilename)>l) and (aFilename[l]=PathDelim)
2623   and (CompareFilenames(LeftStr(aFilename,l),TestBuildDir)=0) then begin
2624     Reversed:=copy(aFilename,l+1,length(aFilename));
2625     if VirtualProjectFiles.Contains(Reversed) then begin
2626       MsgLine.Flags:=MsgLine.Flags+[mlfTestBuildFile];
2627       MsgLine.Attribute[MsgAttrDiskFilename]:=aFilename;
2628       aFilename:=Reversed;
2629       Result:=true;
2630     end
2631   end;
2632 end;
2633 
2634 constructor TIDEFPCParser.Create(AOwner: TComponent);
2635 begin
2636   inherited Create(AOwner);
2637   fMissingFPCMsgItem:=TFPCMsgItem(Pointer(1));
2638   fLineToMsgID:=TPatternToMsgIDs.Create;
2639   fFileExists:=TFilenameToPointerTree.Create(false);
2640   FFilesToIgnoreUnitNotUsed:=TStringList.Create;
2641   HideHintsSenderNotUsed:=true;
2642   HideHintsUnitNotUsedInMainSource:=true;
2643   PC_FullVersion:=GetCompiledFPCVersion;
2644 end;
2645 
TIDEFPCParser.FileExistsnull2646 function TIDEFPCParser.FileExists(const Filename: string; aSynchronized: boolean
2647   ): boolean;
2648 var
2649   p: Pointer;
2650 begin
2651   // check internal cache
2652   p:=fFileExists[Filename];
2653   if p=Pointer(Self) then
2654     Result:=true
2655   else if p=Pointer(fFileExists) then
2656     Result:=false
2657   else begin
2658     // check disk
2659     if aSynchronized then
2660       Result:=FileExistsCached(Filename)
2661     else
2662       Result:=FileExistsUTF8(Filename);
2663     // save result
2664     if Result then
2665       fFileExists[Filename]:=Pointer(Self)
2666     else
2667       fFileExists[Filename]:=Pointer(fFileExists);
2668   end;
2669 end;
2670 
2671 procedure TIDEFPCParser.FetchIncludePath(aPhase: TExtToolParserSyncPhase;
2672   MsgWorkerDir: String);
2673 begin
2674   if MsgWorkerDir='' then
2675     MsgWorkerDir:=Tool.WorkerDirectory;
2676   if fIncludePathValidForWorkerDir<>MsgWorkerDir then begin
2677     // fetch include path from IDE
2678     case aPhase of
2679     etpspAfterReadLine:
2680       NeedSynchronize:=true;
2681     etpspSynchronized:
2682       begin
2683         fIncludePathValidForWorkerDir:=MsgWorkerDir;
2684         fIncludePath:=CodeToolBoss.GetIncludePathForDirectory(
2685                                  ChompPathDelim(MsgWorkerDir));
2686         {$IFDEF VerboseFPCMsgUnitNotFound}
2687         debugln(['TIDEFPCParser.FetchIncludePath ',fIncludePath]);
2688         {$ENDIF}
2689         NeedAfterSync:=true;
2690       end;
2691     end;
2692   end;
2693 end;
2694 
2695 procedure TIDEFPCParser.FetchUnitPath(aPhase: TExtToolParserSyncPhase;
2696   MsgWorkerDir: String);
2697 begin
2698   if MsgWorkerDir='' then
2699     MsgWorkerDir:=Tool.WorkerDirectory;
2700   if fUnitPathValidForWorkerDir<>MsgWorkerDir then begin
2701     // fetch unit path from IDE
2702     case aPhase of
2703     etpspAfterReadLine:
2704       NeedSynchronize:=true;
2705     etpspSynchronized:
2706       begin
2707         fUnitPathValidForWorkerDir:=MsgWorkerDir;
2708         fUnitPath:=CodeToolBoss.GetUnitPathForDirectory(
2709                                  ChompPathDelim(MsgWorkerDir));
2710         NeedAfterSync:=true;
2711       end;
2712     end;
2713   end;
2714 end;
2715 
TIDEFPCParser.CheckForMsgIdnull2716 function TIDEFPCParser.CheckForMsgId(p: PChar): boolean;
2717 var
2718   MsgItem: TFPCMsgItem;
2719   TranslatedItem: TFPCMsgItem;
2720   MsgLine: TMessageLine;
2721   TranslatedMsg: String;
2722   MsgUrgency: TMessageLineUrgency;
2723   Msg: string;
2724 begin
2725   Result:=false;
2726   if (fMsgID<1) or (MsgFile=nil) then exit;
2727   MsgItem:=MsgFile.GetMsg(fMsgID);
2728   if MsgItem=nil then exit;
2729   Result:=true;
2730   TranslatedItem:=nil;
2731   if (TranslationFile<>nil) then
2732     TranslatedItem:=TranslationFile.GetMsg(fMsgID);
2733   Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgUrgency);
2734   Msg:=p;
2735   case fMsgID of
2736   FPCMsgIDThereWereErrorsCompiling: // There were $1 errors compiling module, stopping
2737     MsgUrgency:=mluVerbose;
2738   FPCMsgIDLinesCompiled: // n lines compiled, m sec
2739     if ShowLinesCompiled then MsgUrgency:=mluImportant;
2740   end;
2741   MsgLine:=CreateMsgLine;
2742   MsgLine.SubTool:=DefaultSubTool;
2743   MsgLine.Urgency:=MsgUrgency;
2744   MsgLine.Msg:=Msg;
2745   MsgLine.TranslatedMsg:=TranslatedMsg;
2746   AddMsgLine(MsgLine);
2747 end;
2748 
TIDEFPCParser.CheckFollowUpMessagenull2749 function TIDEFPCParser.CheckFollowUpMessage(p: PChar): boolean;
2750 var
2751   i: Integer;
2752   LastMsgLine, MsgLine: TMessageLine;
2753 begin
2754   Result:=false;
2755   if (p^=' ') then begin
2756     i:=Tool.WorkerMessages.Count-1;
2757     if i<0 then exit;
2758     LastMsgLine:=Tool.WorkerMessages[i];
2759     if LastMsgLine.SubTool=SubToolFPCLinker then begin
2760       // a follow up line of the linker output
2761       Result:=true;
2762       MsgLine:=CreateMsgLine;
2763       MsgLine.MsgID:=0;
2764       MsgLine.SubTool:=SubToolFPCLinker;
2765       MsgLine.Urgency:=LastMsgLine.Urgency;
2766       MsgLine.Msg:='linker: '+p;
2767       inherited AddMsgLine(MsgLine);
2768     end;
2769   end;
2770 end;
2771 
CheckForFileLineColMessagenull2772 function TIDEFPCParser.CheckForFileLineColMessage(p: PChar): boolean;
2773 { filename(line,column) Hint: message
2774   filename(line,column) Hint: (msgid) message
2775   filename(line) Hint: (msgid) message
2776   B:\file(3)name(line,column) Hint: (msgid) message
2777   /usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
2778 }
2779 var
2780   FileStartPos: PChar;
2781   FileEndPos: PChar;
2782   LineStartPos: PChar;
2783   ColStartPos: PChar;
2784   MsgType: TMessageLineUrgency;
2785   MsgLine: TMessageLine;
2786   p2: PChar;
2787   i: Integer;
2788   TranslatedItem: TFPCMsgItem;
2789   MsgItem: TFPCMsgItem;
2790   TranslatedMsg: String;
2791   aFilename: String;
2792   Column: Integer;
2793   PPUFileStartPos: PChar;
2794   PPUFileEndPos: PChar;
2795 begin
2796   Result:=false;
2797   FileStartPos:=p;
2798   FileEndPos:=nil;
2799   PPUFileStartPos:=nil;
2800   PPUFileEndPos:=nil;
2801   // search colon and last ( in front of colon
2802   while true do begin
2803     case p^ of
2804     #0: exit;
2805     '(': FileEndPos:=p;
2806     ':':
2807       if (p-FileStartPos>5) and (p[-4]='.') and (p[-3] in ['p','P'])
2808       and (p[-2] in ['p','P']) and (p[-1] in ['u','U']) then begin
2809         // e.g. /usr/lib/fpc/3.1.1/units/x86_64-linux/rtl/sysutils.ppu:filutil.inc(481,10) Error: (5088) ...
2810         if PPUFileStartPos<>nil then exit;
2811         PPUFileStartPos:=FileStartPos;
2812         PPUFileEndPos:=p;
2813         FileStartPos:=p+1;
2814       end
2815       else if (DriveSeparator='') or (p-FileStartPos>1) then
2816         break;
2817     end;
2818     inc(p);
2819   end;
2820   if (FileEndPos=nil) or (FileEndPos-FileStartPos=0) or (FileEndPos[-1]=' ') then exit;
2821   p:=FileEndPos;
2822   inc(p); // skip bracket
2823   LineStartPos:=p;
2824   if not ReadDecimal(p) then exit;
2825   if p^=',' then begin
2826     if not ReadChar(p,',') then exit;
2827     ColStartPos:=p;
2828     if not ReadDecimal(p) then exit;
2829   end else
2830     ColStartPos:=nil;
2831   if not ReadChar(p,')') then exit;
2832   if not ReadChar(p,' ') then exit;
2833   MsgType:=mluNote;
2834   if ReadString(p,'Info:') then begin
2835     MsgType:=mluVerbose;
2836   end else if ReadString(p,'Hint:') then begin
2837     MsgType:=mluHint;
2838   end else if ReadString(p,'Note:') then begin
2839     MsgType:=mluNote;
2840   end else if ReadString(p,'Warn:') or ReadString(p,'Warning: ') then begin
2841     MsgType:=mluWarning;
2842   end else if ReadString(p,'Error:') then begin
2843     MsgType:=mluError;
2844   end else if ReadString(p,'Fatal:') then begin
2845     MsgType:=mluError;
2846   end else begin
2847     p2:=p;
2848     while not (p2^ in [':',#0,' ']) do inc(p2);
2849     if p2^=':' then begin
2850       // unknown type (maybe a translation?)
2851       p:=p2+1;
2852     end;
2853   end;
2854   while p^ in [' ',#9] do inc(p);
2855   Result:=true;
2856   TranslatedMsg:='';
2857   if (p^='(') and (p[1] in ['0'..'9']) then begin
2858     // (msgid)
2859     p2:=p;
2860     inc(p2);
2861     i:=0;
2862     while (p2^ in ['0'..'9']) and (i<1000000) do begin
2863       i:=i*10+ord(p2^)-ord('0');
2864       inc(p2);
2865     end;
2866     if p2^=')' then begin
2867       fMsgID:=i;
2868       p:=p2+1;
2869       while p^ in [' ',#9] do inc(p);
2870       //debugln(['TFPCParser.CheckForFileLineColMessage ID=',fMsgID,' Msg=',FileStartPos]);
2871       if (fMsgID>0) then begin
2872         TranslatedItem:=nil;
2873         MsgItem:=nil;
2874         if (TranslationFile<>nil) then
2875           TranslatedItem:=TranslationFile.GetMsg(fMsgID);
2876         if (MsgFile<>nil) then
2877           MsgItem:=MsgFile.GetMsg(fMsgID);
2878         if (TranslatedItem=nil) and (MsgItem=nil) then begin
2879           if ConsoleVerbosity>=1 then
2880             debugln(['TFPCParser.CheckForFileLineColMessage msgid not found: ',fMsgID]);
2881         end else begin
2882           Translate(p,MsgItem,TranslatedItem,TranslatedMsg,MsgType);
2883           if MsgType=mluNone then begin
2884             if ConsoleVerbosity>=1 then
2885               debugln(['TFPCParser.CheckForFileLineColMessage msgid has no type: ',fMsgID]);
2886           end;
2887         end;
2888       end;
2889     end;
2890   end;
2891   if ColStartPos<>nil then
2892     Column:=Str2Integer(ColStartPos,0)
2893   else
2894     Column:=0;
2895 
2896   MsgLine:=CreateMsgLine;
2897   MsgLine.SubTool:=DefaultSubTool;
2898   MsgLine.Urgency:=MsgType;
2899   aFilename:=GetString(FileStartPos,FileEndPos-FileStartPos);
2900   if PPUFileStartPos<>nil then
2901     MsgLine.Attribute['PPU']:=GetString(PPUFileStartPos,PPUFileEndPos-PPUFileStartPos);
2902   MsgLine.Filename:=LongenFilename(MsgLine,aFilename);
2903   MsgLine.Line:=Str2Integer(LineStartPos,0);
2904   MsgLine.Column:=Column;
2905   MsgLine.Msg:=p;
2906   MsgLine.TranslatedMsg:=TranslatedMsg;
2907   //debugln(['TFPCParser.CheckForFileLineColMessage ',dbgs(MsgLine.Urgency)]);
2908 
2909   AddMsgLine(MsgLine);
2910 end;
2911 
CheckForLoadFromUnitnull2912 function TIDEFPCParser.CheckForLoadFromUnit(p: PChar): Boolean;
2913 var
2914   OldP: PChar;
2915   MsgLine: TMessageLine;
2916 begin
2917   Result:=fMsgID=10027;
2918   if (not Result) and (fMsgID>0) then exit;
2919   OldP:=p;
2920   if not Result then begin
2921     if not ReadString(p,'Load from ') then exit;
2922     while not (p^ in ['(',#0]) do inc(p);
2923     if p^<>'(' then exit;
2924     while not (p^ in [')',#0]) do inc(p);
2925     if p^<>')' then exit;
2926     if not ReadString(p,') unit ') then exit;
2927   end;
2928   MsgLine:=CreateMsgLine;
2929   MsgLine.SubTool:=DefaultSubTool;
2930   MsgLine.Urgency:=mluProgress;
2931   MsgLine.Msg:=OldP;
2932   AddMsgLine(MsgLine);
2933   Result:=true;
2934 end;
2935 
2936 procedure TIDEFPCParser.ReadLine(Line: string; OutputIndex: integer;
2937   IsStdErr: boolean; var Handled: boolean);
2938 { returns true, if it is a compiler message
2939    Examples for freepascal compiler messages:
2940      Compiling <filename>
2941      Assembling <filename>
2942      Fatal: <some text>
2943      Fatal: (message id) <some text>
2944      (message id) <some text>
2945      <filename>(123,45) <ErrorType>: <some text>
2946      <filename>(123) <ErrorType>: <some text>
2947      <filename>(456) <ErrorType>: <some text> in line (123)
2948      [0.000] (3101) Macro defined: CPUAMD64
2949      <filename>(12,34) <ErrorType>: (5024) <some text>
2950 }
2951 var
2952   p: PChar;
2953 begin
2954   if Line='' then exit;
2955   Line:=ToUTF8(Line);
2956   p:=PChar(Line);
2957   fOutputIndex:=OutputIndex;
2958   fMsgID:=0;
2959   fMsgIsStdErr:=IsStdErr;
2960 
2961   // skip time [0.000]
2962   if (p^='[') and (p[1] in ['0'..'9']) then begin
2963     inc(p,2);
2964     while p^ in ['0'..'9','.'] do inc(p);
2965     if p^<>']' then exit; // not a fpc message
2966     inc(p);
2967     while p^ in [' '] do inc(p);
2968   end;
2969 
2970   // read message ID (000)
2971   if (p^='(') and (p[1] in ['0'..'9']) then begin
2972     inc(p);
2973     while p^ in ['0'..'9','.'] do begin
2974       if fMsgID>1000000 then exit; // not a fpc message
2975       fMsgID:=fMsgID*10+ord(p^)-ord('0');
2976       inc(p);
2977     end;
2978     if p^<>')' then exit; // not a fpc message
2979     inc(p);
2980     while p^=' ' do inc(p);
2981   end;
2982 
2983   if p^ in [#0..#31,' '] then begin
2984     CheckFollowUpMessage(p);
2985     exit; // not a fpc message
2986   end;
2987 
2988   Handled:=true;
2989 
2990   //debugln(['TIDEFPCParser.ReadLine ',IsStdErr,' ',Line]);
2991 
2992   // check for (msgid) message
2993   if CheckForMsgId(p) then exit;
2994   // check for 'filename(line,column) Error: message'
2995   if CheckForFileLineColMessage(p) then exit;
2996   // check for 'Compiling <filename>'
2997   if CheckForCompilingState(p) then exit;
2998   // check for 'Assembling <filename>'
2999   if CheckForAssemblingState(p) then exit;
3000   // check for 'Fatal: ', 'Panic: ', 'Error: ', ...
3001   if CheckForGeneralMessage(p) then exit;
3002   // check for '<line> <kb>/<kb>'...
3003   if CheckForLineProgress(p) then exit;
3004   // check for '<int> Lines compiled, <int>.<int> sec'
3005   if CheckForLinesCompiled(p) then exit;
3006   // check for infos (logo, Linking <Progname>)
3007   if CheckForInfos(p) then exit;
3008   // check for -vx output
3009   if CheckForExecutableInfo(p) then exit;
3010   // check for Load from unit
3011   if CheckForLoadFromUnit(p) then exit;
3012   // check for windres errors
3013   if CheckForWindresErrors(p) then exit;
3014   // check for linker errors
3015   if CheckForLinkerErrors(p) then exit;
3016   // check for assembler errors
3017   if CheckForAssemblerErrors(p) then exit;
3018 
3019   // last: check for unknown std error
3020   if CheckForUnspecificStdErr(p) then exit;
3021 
3022   {$IFDEF VerboseFPCParser}
3023   debugln('TFPCParser.ReadLine UNKNOWN: ',Line);
3024   {$ENDIF}
3025   Handled:=false;
3026 end;
3027 
3028 procedure TIDEFPCParser.AddMsgLine(MsgLine: TMessageLine);
3029 begin
3030   if IsMsgID(MsgLine,FPCMsgIDErrorWhileCompilingResources,
3031     fMsgItemErrorWhileCompilingResources)
3032   then begin
3033     // Error while compiling resources
3034     AddResourceMessages;
3035     MsgLine.Msg:=MsgLine.Msg+' -> '+'Compile with -vd for more details. Check for duplicates.';
3036     MsgLine.TranslatedMsg:=MsgLine.TranslatedMsg+' -> '+lisCompileWithVdForMoreDetailsCheckForDuplicates;
3037   end
3038   else if IsMsgID(MsgLine,FPCMsgIDErrorWhileLinking,fMsgItemErrorWhileLinking) then
3039     AddLinkingMessages
3040   else if IsMsgID(MsgLine,FPCMsgIDChecksumChanged,fMsgItemChecksumChanged) then
3041     MsgLine.Urgency:=mluWarning
3042   else if IsMsgID(MsgLine,FPCMsgIDThereWereErrorsCompiling,
3043     fMsgItemThereWereErrorsCompiling)
3044   then
3045     MsgLine.Urgency:=mluVerbose;
3046   inherited AddMsgLine(MsgLine);
3047 end;
3048 
TIDEFPCParser.LongenFilenamenull3049 function TIDEFPCParser.LongenFilename(MsgLine: TMessageLine; aFilename: string
3050   ): string;
3051 var
3052   ShortFilename: String;
3053   i: Integer;
3054   LastMsgLine: TMessageLine;
3055   LastFilename: String;
3056 begin
3057   Result:=TrimFilename(aFilename);
3058   if FilenameIsAbsolute(Result) then begin
3059     if ReverseInstantFPCCacheDir(Result,false) then exit;
3060     if ReverseTestBuildDir(MsgLine,Result) then exit;
3061     exit;
3062   end;
3063   if MsgLine.Attribute['PPU']<>'' then begin
3064     MsgLine.Attribute[FPCMsgAttrWorkerDirectory]:=Tool.WorkerDirectory;
3065     exit;
3066   end;
3067 
3068   ShortFilename:=Result;
3069   // check last message line
3070   LastMsgLine:=Tool.WorkerMessages.GetLastLine;
3071   if (LastMsgLine<>nil) then begin
3072     if mlfTestBuildFile in LastMsgLine.Flags then
3073       LastFilename:=LastMsgLine.Attribute[MsgAttrDiskFilename]
3074     else
3075       LastFilename:=LastMsgLine.Filename;
3076     if FilenameIsAbsolute(LastFilename) then begin
3077       if (length(LastFilename)>length(ShortFilename))
3078       and (LastFilename[length(LastFilename)-length(ShortFilename)] in AllowDirectorySeparators)
3079       and (CompareFilenames(RightStr(LastFilename,length(ShortFilename)),ShortFilename)=0)
3080       then begin
3081         if mlfTestBuildFile in LastMsgLine.Flags then begin
3082           MsgLine.Attribute[MsgAttrDiskFilename]:=LastFilename;
3083           MsgLine.Flags:=MsgLine.Flags+[mlfTestBuildFile];
3084           Result:=LastMsgLine.Filename;
3085         end else begin
3086           Result:=LastFilename;
3087           ReverseTestBuildDir(MsgLine,Result);
3088         end;
3089         exit;
3090       end;
3091     end;
3092   end;
3093   // search file in the last compiling directories
3094   if DirectoryStack<>nil then begin
3095     for i:=DirectoryStack.Count-1 downto 0 do begin
3096       Result:=AppendPathDelim(DirectoryStack[i])+ShortFilename;
3097       if FileExists(Result,false) then begin
3098         ReverseTestBuildDir(MsgLine,Result);
3099         exit;
3100       end;
3101     end;
3102   end;
3103   // search file in worker directory
3104   if Tool.WorkerDirectory<>'' then begin
3105     Result:=AppendPathDelim(Tool.WorkerDirectory)+ShortFilename;
3106     if FileExists(Result,false) then begin
3107       ReverseTestBuildDir(MsgLine,Result);
3108       exit;
3109     end;
3110   end;
3111 
3112   // file not found
3113   Result:=ShortFilename;
3114 
3115   // save Tool.WorkerDirectory for ImproveMessage
3116   MsgLine.Attribute[FPCMsgAttrWorkerDirectory]:=Tool.WorkerDirectory;
3117 end;
3118 
GetDefaultPCFullVersionnull3119 function TIDEFPCParser.GetDefaultPCFullVersion: LongWord;
3120 var
3121   Kind: TPascalCompiler;
3122 begin
3123   // get compiler version
3124   Result:=LongWord(CodeToolBoss.GetPCVersionForDirectory(Tool.WorkerDirectory,Kind));
3125   if Kind=pcFPC then ;
3126 end;
3127 
ToUTF8null3128 function TIDEFPCParser.ToUTF8(const Line: string): string;
3129 begin
3130   if PC_FullVersion>=20701 then
3131     Result:=ConsoleToUTF8(Line)
3132   else begin
3133     {$IFDEF MSWINDOWS}
3134     Result:=WinCPToUTF8(Line);
3135     {$ELSE}
3136     Result:=SysToUTF8(Line);
3137     {$ENDIF}
3138   end;
3139 end;
3140 
3141 procedure TIDEFPCParser.ImproveMessages(aPhase: TExtToolParserSyncPhase);
3142 var
3143   i: Integer;
3144   MsgLine: TMessageLine;
3145   aFilename: String;
3146   Y: Integer;
3147   X: Integer;
3148   Code: TCodeBuffer;
3149   SourceOK: Boolean;
3150   MsgWorkerDir: String;
3151   PrevMsgLine: TMessageLine;
3152   CmdLineParams: String;
3153   SrcFilename: String;
3154   PPUFilename: String;
3155 begin
3156   //debugln(['TIDEFPCParser.ImproveMessages START ',aSynchronized,' Last=',fLastWorkerImprovedMessage[aSynchronized],' Now=',Tool.WorkerMessages.Count]);
3157   for i:=fLastWorkerImprovedMessage[aPhase]+1 to Tool.WorkerMessages.Count-1 do
3158   begin
3159     MsgLine:=Tool.WorkerMessages[i];
3160     Y:=MsgLine.Line;
3161     X:=MsgLine.Column;
3162     if (Y>0) and (X>0)
3163     and (MsgLine.SubTool=DefaultSubTool) and (MsgLine.Filename<>'')
3164     then begin
3165       if mlfTestBuildFile in MsgLine.Flags then
3166         aFilename:=MsgLine.Attribute[MsgAttrDiskFilename]
3167       else
3168         aFilename:=MsgLine.Filename;
3169       PPUFilename:='';
3170       if (not FilenameIsAbsolute(aFilename)) then begin
3171         PPUFilename:=MsgLine.Attribute['PPU'];
3172         if PPUFilename<>'' then begin
3173           // compiler gave ppu file and relative source file
3174           if not FindSrcViaPPU(aPhase,MsgLine,PPUFilename) then continue;
3175         end;
3176       end;
3177       if (not FilenameIsAbsolute(aFilename)) then begin
3178         // short file name => 1. search the full file name in previous message
3179         if i>0 then begin
3180           PrevMsgLine:=Tool.WorkerMessages[i-1];
3181           if (PrevMsgLine.SubTool=DefaultSubTool)
3182           and FilenameIsAbsolute(PrevMsgLine.Filename)
3183           and (CompareFilenames(ExtractFilename(PrevMsgLine.Filename),ExtractFilename(aFilename))=0)
3184           then begin
3185             // same file as previous message => use it
3186             aFilename:=PrevMsgLine.Filename;
3187             MsgLine.Filename:=aFilename;
3188           end;
3189         end;
3190       end;
3191       if (not FilenameIsAbsolute(aFilename)) then begin
3192         // short file name => 2. search in include path
3193         MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
3194         FetchIncludePath(aPhase,MsgWorkerDir); // needs Phase etpspAfterReadLine+etpspSynchronized
3195         {$IFDEF VerboseFPCMsgUnitNotFound}
3196         if aPhase=etpspSynchronized then
3197           debugln(['TIDEFPCParser.ImproveMessages IncPath="',fIncludePath,'" aFilename="',aFilename,'" MsgWorkerDir="',MsgWorkerDir,'"']);
3198         {$ENDIF}
3199         if (aPhase in [etpspAfterReadLine,etpspAfterSync])
3200         and (fIncludePathValidForWorkerDir=MsgWorkerDir) then begin
3201           // include path is valid and in worker thread
3202           // -> search file
3203           aFilename:=FileUtil.SearchFileInPath(aFilename,MsgWorkerDir,fIncludePath,';',
3204                                  [FileUtil.sffSearchLoUpCase,sffFile]);
3205           if aFilename<>'' then
3206             MsgLine.Filename:=aFilename;
3207         end;
3208       end;
3209       if (not FilenameIsAbsolute(aFilename)) and (aPhase=etpspAfterReadLine)
3210       then begin
3211         CmdLineParams:=Tool.CmdLineParams;
3212         if Pos(CmdLineParams,PathDelim+'fpc'+ExeExt+' ')>0 then begin
3213           // short file name => 3. check the cmd line param source file
3214           SrcFilename:=GetFPCParameterSrcFile(Tool.CmdLineParams);
3215           if (SrcFilename<>'')
3216           and ((CompareFilenames(ExtractFilename(SrcFilename),aFilename)=0)
3217           or (CompareFilenames(ExtractFileNameOnly(SrcFilename),aFilename)=0))
3218           then begin
3219             if not FilenameIsAbsolute(SrcFilename) then begin
3220               MsgWorkerDir:=MsgLine.Attribute[FPCMsgAttrWorkerDirectory];
3221               SrcFilename:=ResolveDots(AppendPathDelim(MsgWorkerDir)+SrcFilename);
3222             end;
3223             if FilenameIsAbsolute(SrcFilename) then
3224               MsgLine.Filename:=SrcFilename;
3225           end;
3226         end;
3227       end;
3228 
3229       // get source
3230       SourceOK:=false;
3231       aFilename:=MsgLine.Filename;
3232       if FilenameIsAbsolute(aFilename) or (mlfTestBuildFile in MsgLine.Flags)
3233       then begin
3234         if (fCurSource<>nil)
3235         and (CompareFilenames(aFilename,fCurSource.Filename)=0) then begin
3236           SourceOK:=true;
3237         end else begin
3238           // need source
3239           case aPhase of
3240           etpspAfterReadLine:
3241             NeedSynchronize:=true;
3242           etpspSynchronized:
3243             begin
3244               // load source file
3245               //debugln(['TFPCParser.ImproveMessages loading ',aFilename]);
3246               Code:=CodeToolBoss.LoadFile(aFilename,true,false);
3247               if Code<>nil then begin
3248                 if fCurSource=nil then
3249                   fCurSource:=TCodeBuffer.Create;
3250                 fCurSource.Filename:=aFilename;
3251                 if Code.FileOnDiskNeedsUpdate then begin
3252                   // IDE buffer contains changes that are not yet saved to disk
3253                   // The compiler messages are about the disk file
3254                   // => load the file
3255                   fCurSource.LoadFromFile(aFilename);
3256                 end else begin
3257                   // IDE buffer valid => just copy
3258                   fCurSource.Source:=Code.Source;
3259                 end;
3260                 SourceOK:=true;
3261                 NeedAfterSync:=true;
3262               end;
3263             end;
3264           end;
3265         end;
3266       end;
3267 
3268       ImproveMsgIdentifierPosition(aPhase, MsgLine, SourceOK);
3269       ImproveMsgHiddenByIDEDirective(aPhase, MsgLine, SourceOK);
3270       ImproveMsgUnitNotUsed(aPhase, MsgLine);
3271       ImproveMsgSenderNotUsed(aPhase, MsgLine);
3272     end else if MsgLine.SubTool=SubToolFPCLinker then begin
3273       ImproveMsgLinkerUndefinedReference(aPhase, MsgLine);
3274     end;
3275     ImproveMsgUnitNotFound(aPhase, MsgLine);
3276   end;
3277   fLastWorkerImprovedMessage[aPhase]:=Tool.WorkerMessages.Count-1;
3278 end;
3279 
TIDEFPCParser.CanParseSubToolnull3280 class function TIDEFPCParser.CanParseSubTool(const SubTool: string): boolean;
3281 begin
3282   Result:=(CompareText(SubTool,SubToolFPC)=0)
3283        or (CompareText(SubTool,SubToolFPCLinker)=0)
3284        or (CompareText(SubTool,SubToolFPCRes)=0);
3285 end;
3286 
TIDEFPCParser.DefaultSubToolnull3287 class function TIDEFPCParser.DefaultSubTool: string;
3288 begin
3289   Result:=SubToolFPC;
3290 end;
3291 
TIDEFPCParser.GetMsgHintnull3292 class function TIDEFPCParser.GetMsgHint(SubTool: string; MsgID: integer): string;
3293 var
3294   CurMsgFile: TFPCMsgFilePoolItem;
3295   MsgItem: TFPCMsgItem;
3296 begin
3297   Result:='';
3298   if CompareText(SubTool,DefaultSubTool)=0 then begin
3299     CurMsgFile:=MsgFilePool.LoadCurrentEnglishFile(false,nil);
3300     if CurMsgFile=nil then exit;
3301     try
3302       MsgItem:=CurMsgFile.GetMsg(MsgID);
3303       if MsgItem=nil then exit;
3304       Result:=MsgItem.GetTrimmedComment(false,true);
3305     finally
3306       MsgFilePool.UnloadFile(CurMsgFile);
3307     end;
3308   end;
3309 end;
3310 
TIDEFPCParser.GetMsgPatternnull3311 class function TIDEFPCParser.GetMsgPattern(SubTool: string; MsgID: integer; out
3312   Urgency: TMessageLineUrgency): string;
3313 var
3314   CurMsgFile: TFPCMsgFilePoolItem;
3315   MsgItem: TFPCMsgItem;
3316 begin
3317   Result:='';
3318   Urgency:=mluNone;
3319   if CompareText(SubTool,DefaultSubTool)=0 then begin
3320     if MsgFilePool=nil then exit;
3321     CurMsgFile:=MsgFilePool.LoadCurrentEnglishFile(false,nil);
3322     if CurMsgFile=nil then exit;
3323     try
3324       MsgItem:=CurMsgFile.GetMsg(MsgID);
3325       if MsgItem=nil then exit;
3326       Result:=MsgItem.Pattern;
3327       Urgency:=FPCMsgToMsgUrgency(MsgItem);
3328     finally
3329       MsgFilePool.UnloadFile(CurMsgFile);
3330     end;
3331   end;
3332 end;
3333 
TIDEFPCParser.Prioritynull3334 class function TIDEFPCParser.Priority: integer;
3335 begin
3336   Result:=SubToolFPCPriority;
3337 end;
3338 
TIDEFPCParser.MsgLineIsIdnull3339 class function TIDEFPCParser.MsgLineIsId(Msg: TMessageLine; MsgId: integer; out
3340   Value1, Value2: string): boolean;
3341 
GetStrnull3342   function GetStr(FromPos, ToPos: PChar): string;
3343   begin
3344     if (FromPos=nil) or (FromPos=ToPos) then
3345       Result:=''
3346     else begin
3347       SetLength(Result,ToPos-FromPos);
3348       Move(FromPos^,Result[1],ToPos-FromPos);
3349     end;
3350   end;
3351 
3352 var
3353   aFPCParser: TFPCParser;
3354   Pattern: String;
3355   VarStarts: PPChar;
3356   VarEnds: PPChar;
3357   s: String;
3358 begin
3359   Value1:='';
3360   Value2:='';
3361   if Msg=nil then exit(false);
3362   if Msg.SubTool<>DefaultSubTool then exit(false);
3363   if (Msg.MsgID<>MsgId)
3364   and (Msg.MsgID<>0) then exit(false);
3365   Result:=true;
3366   aFPCParser:=GetFPCParser(Msg);
3367   if aFPCParser=nil then exit;
3368   Pattern:=aFPCParser.GetFPCMsgIDPattern(MsgId);
3369   VarStarts:=GetMem(SizeOf(PChar)*10);
3370   VarEnds:=GetMem(SizeOf(PChar)*10);
3371   s:=Msg.Msg;
3372   Result:=FPCMsgFits(s,Pattern,VarStarts,VarEnds);
3373   if Result then begin
3374     Value1:=GetStr(VarStarts[1],VarEnds[1]);
3375     Value2:=GetStr(VarStarts[2],VarEnds[2]);
3376   end;
3377   Freemem(VarStarts);
3378   Freemem(VarEnds);
3379 end;
3380 
TIDEFPCParser.GetFPCMsgIDPatternnull3381 function TIDEFPCParser.GetFPCMsgIDPattern(MsgID: integer): string;
3382 var
3383   MsgItem: TFPCMsgItem;
3384 begin
3385   Result:='';
3386   if MsgID<=0 then exit;
3387   if MsgFile=nil then exit;
3388   MsgItem:=MsgFile.GetMsg(MsgID);
3389   if MsgItem=nil then exit;
3390   Result:=MsgItem.Pattern;
3391 end;
3392 
TIDEFPCParser.GetFPCMsgPatternnull3393 class function TIDEFPCParser.GetFPCMsgPattern(Msg: TMessageLine): string;
3394 var
3395   aFPCParser: TFPCParser;
3396 begin
3397   Result:='';
3398   if Msg.MsgID<=0 then exit;
3399   aFPCParser:=GetFPCParser(Msg);
3400   if aFPCParser=nil then exit;
3401   Result:=aFPCParser.GetFPCMsgIDPattern(Msg.MsgID);
3402 end;
3403 
TIDEFPCParser.GetFPCMsgValue1null3404 class function TIDEFPCParser.GetFPCMsgValue1(Msg: TMessageLine): string;
3405 begin
3406   Result:='';
3407   if Msg.MsgID<=0 then exit;
3408   if Msg.SubTool<>DefaultSubTool then exit;
3409   if not etFPCMsgParser.GetFPCMsgValue1(Msg.Msg,GetFPCMsgPattern(Msg),Result) then
3410     Result:='';
3411 end;
3412 
TIDEFPCParser.GetFPCMsgValuesnull3413 class function TIDEFPCParser.GetFPCMsgValues(Msg: TMessageLine; out Value1,
3414   Value2: string): boolean;
3415 begin
3416   Result:=false;
3417   if Msg.MsgID<=0 then exit;
3418   if Msg.SubTool<>DefaultSubTool then exit;
3419   Result:=etFPCMsgParser.GetFPCMsgValues2(Msg.Msg,GetFPCMsgPattern(Msg),Value1,Value2);
3420 end;
3421 
TIDEFPCParser.MsgFilePoolnull3422 class function TIDEFPCParser.MsgFilePool: TFPCMsgFilePool;
3423 begin
3424   Result:=FPCMsgFilePool;
3425 end;
3426 
3427 initialization
3428   IDEFPCParser:=TIDEFPCParser;
3429 finalization
3430   FreeAndNil(FPCMsgFilePool);
3431 
3432 end.
3433 
3434