2
***************************************************************************
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. *
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. *
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19
***************************************************************************
21
Author: Mattias Gaertner
24
Parsing fpc message files.
25
FPC prints message IDs with -vq
29
general_t_compilername=01000_T_Compiler: $1
30
% When the \var{-vt} switch is used, this line tells you what compiler
33
<part>_<type>_<txtidentifier>=<id>_<idtype>_<message with plcaeholders>
36
unit CodeToolsFPCMsgs;
40
{off $DEFINE VerboseFPCMsgFile}
45
Classes, SysUtils, FileProcs, AVL_Tree;
56
TfmiSpecialItems = set of TfmiSpecialItem;
62
Part: string; // e.g. 'general', 'unit', 'link'
63
Typ: string; // e.g. 'f','e','w','n','h','i','l','u','t','c','d','x','o'
64
TxtIdentifier: string; // identifier
65
ID: integer; // positive number
66
ShownTyp: string; // e.g. shown Typ, can be different from Typ
67
Pattern: string; // Text with placeholders $1 .. $9
68
PatternEndSpace: string;
69
Comment: string; // multi line
71
Index: integer; // index in list
72
function GetName(WithID: boolean = true): string;
73
function PatternFits(aMsg: string): integer; // >=0 fits
74
function GetTrimmedComment(NoLineBreaks, NoLatex: boolean): string;
81
fSpecialItems: array[TfmiSpecialItem] of TFPCMsgItem;
82
FItems: TFPList; // list of TFPCMsgItem
83
fSortedForID: TAVLTree; // tree of TFPCMsgItem sorted for ID
84
fItemById: array of TFPCMsgItem;
85
fNodeMgr: TAVLTreeNodeMemManager;
86
function GetItems(Index: integer): TFPCMsgItem;
87
procedure CreateArray;
88
function GetSpecialItems(Index: TfmiSpecialItem): TFPCMsgItem;
91
destructor Destroy; override;
92
procedure LoadFromFile(const Filename: string);
93
procedure LoadFromList(List: TStrings); virtual;
94
procedure LoadFromText(s: string); virtual;
95
procedure Clear; virtual;
96
function Count: integer;
97
property Items[Index: integer]: TFPCMsgItem read GetItems; default;
98
function FindWithID(ID: integer): TFPCMsgItem;
99
function FindWithMessage(Msg: string): TFPCMsgItem;
100
function GetMsgText(Item: TFPCMsgItem): string; // prepends msg type (e.g. Error:)
101
function PatternFits(Item: TFPCMsgItem; aMsg: string): integer; // >=0 fits
102
property SpecialItems[Index: TfmiSpecialItem]: TFPCMsgItem read GetSpecialItems;
103
function MsgTypToSpecialItem(const Typ: string): TFPCMsgItem;
106
function CompareFPCMsgId(item1, item2: Pointer): integer;
107
function CompareIDWithFPCMsgId(PtrID, Item: Pointer): integer;
110
TFPCMsgRange = record
114
PFPCMsgRange = ^TFPCMsgRange;
118
TFPCMsgRanges = class
123
Ranges: PFPCMsgRange;
124
property Count: integer read FCount;
125
property Capacity: integer read FCapacity;
126
procedure Add(StartPos, EndPos: integer);
127
procedure Clear(FreeMemory: boolean = false);
128
destructor Destroy; override;
131
procedure ExtractFPCMsgParameters(const Mask, Txt: string; var Ranges: TFPCMsgRanges);
133
function dbgs(i: TfmiSpecialItem): string; overload;
137
function CompareFPCMsgId(item1, item2: Pointer): integer;
139
Msg1: TFPCMsgItem absolute item1;
140
Msg2: TFPCMsgItem absolute item2;
142
if Msg1.ID<Msg2.ID then
144
else if Msg1.ID>Msg2.ID then
150
function CompareIDWithFPCMsgId(PtrID, Item: Pointer): integer;
152
Msg: TFPCMsgItem absolute Item;
155
ID:=PInteger(PtrID)^;
158
else if ID>Msg.ID then
164
procedure ExtractFPCMsgParameters(const Mask, Txt: string;
165
var Ranges: TFPCMsgRanges);
172
function FindEndOfNextMatch(MaskStartPos, MaskEndPos, TxtStartPos: PChar): PChar;
177
while TxtStartPos^<>#0 do begin
179
MaskPos:=MaskStartPos;
180
while (MaskPos<MaskEndPos) and (MaskPos^=TxtPos^) do begin
184
if MaskPos=MaskEndPos then begin
202
Ranges:=TFPCMsgRanges.Create;
204
if Mask='' then exit;
205
BaseMaskPos:=PChar(Mask);
209
BaseTxtPos:=PChar(Txt);
211
MaskPos:=BaseMaskPos;
213
while (MaskPos^=TxtPos^) do begin
214
if MaskPos^=#0 then exit;
215
if (MaskPos^='$') and (MaskPos[1]<>'$') then break;
219
while MaskPos^='$' do begin
220
// skip variable in mask
222
while MaskPos^ in ['0'..'9','A'..'Z','a'..'z','_'] do inc(MaskPos);
223
// get next pattern in mask
224
MaskStartPos:=MaskPos;
225
while (MaskPos^<>#0) and (MaskPos^<>'$') do inc(MaskPos);
226
if MaskPos^=#0 then begin
227
// variable at end of mask
228
Ranges.Add(TxtPos-BaseTxtPos,length(Txt)+1);
231
// search pattern in txt
232
TxtEndPos:=FindEndOfNextMatch(MaskStartPos,MaskPos,TxtPos);
233
if TxtEndPos=nil then exit;
234
Ranges.Add(TxtPos-BaseTxtPos,TxtEndPos-BaseTxtPos);
239
function dbgs(i: TfmiSpecialItem): string;
242
fmisiFatal: Result:='fatal';
243
fmisiError: Result:='error';
244
fmisiWarning: Result:='warning';
245
fmisiNote: Result:='note';
246
fmisiHint: Result:='hint';
253
function TFPCMsgItem.GetName(WithID: boolean): string;
256
if Typ<>'' then Result:=Result+Typ+'_';
257
Result:=Result+TxtIdentifier;
259
Result:=Result+'='+IntToStr(ID);
262
function TFPCMsgItem.PatternFits(aMsg: string): integer;
274
// Pattern is for example "$1 lines compiled, $2 sec$3"
275
if (aMsg='') or (Pattern='') then exit;
277
// aMsg can start with a filename => hard to tell where the message starts
278
// the Pattern is always at the end of aMsg => quick check the end
279
PatLen:=length(Pattern);
281
and ((Pattern[PatLen-1]<>'$') or (not (Pattern[PatLen] in ['0'..'9'])))
283
// the pattern does not have a placeholder at the end
284
// => the tail must be the pattern => check tail
285
PatStartPos:=PChar(Pattern);
286
PatEndPos:=@Pattern[PatLen];
287
MsgPos:=@aMsg[length(aMsg)];
288
MsgStartPos:=PChar(aMsg);
289
while (PatEndPos^=MsgPos^) do begin
290
if PatEndPos=PatStartPos then begin
291
// pattern has no placeholders and whole pattern fits
296
if (PatEndPos^ in ['0'..'9']) and (PatEndPos[-1]='$') then begin
297
// pattern behind last placeholder fits
298
// => a full check is needed
301
if MsgPos=MsgStartPos then begin
302
// pattern does not fit
309
PatEndPos:=PChar(Pattern);
310
MsgFitPos:=PChar(aMsg);
313
PatStartPos:=PatEndPos;
314
// get next pattern between placeholders
316
if PatEndPos^=#0 then break;
317
if (PatEndPos^='$') and (PatEndPos[1] in ['0'..'9']) then break;
320
if PatEndPos<>PatStartPos then begin
321
// search pattern in Pattern
325
while (MsgPos^=PatPos^) and (PatPos<PatEndPos) do begin
329
if PatPos=PatEndPos then
331
// does not fit => check next
334
if PatPos<PatEndPos then
335
exit(-1); // pattern not found => does not fit
336
inc(MatchLen,PatEndPos-PatStartPos);
337
// pattern fits, search the rest of the patterns behind this position
340
if PatEndPos^=#0 then begin
341
// whole pattern fits
345
// skip placeholder $d
350
function TFPCMsgItem.GetTrimmedComment(NoLineBreaks, NoLatex: boolean): string;
356
if NoLatex then begin
359
while i<length(Result) do begin
360
if Result[i]='\' then begin
363
if Result[i] in ['a'..'z','A'..'Z'] then begin
365
while (i<=length(Result))
366
and (Result[i] in ['a'..'z','A'..'Z','0'..'9','_']) do
368
System.Delete(Result,StartPos,i-StartPos);
372
System.Delete(Result,StartPos,1);
374
end else if Result[i] in ['{','}'] then begin
375
System.Delete(Result,i,1);
381
for i:=length(Result) downto 1 do begin
382
if NoLineBreaks and (Result[i] in [#10,#13]) then
384
else if Result[i]=#9 then
386
if Result[i]=' ' then begin
387
if (i=1) or (i=length(Result)) or (Result[i+1] in [' ',#10,#13]) then
388
system.Delete(Result,i,1);
395
function TFPCMsgFile.GetItems(Index: integer): TFPCMsgItem;
397
Result:=TFPCMsgItem(FItems[Index]);
400
procedure TFPCMsgFile.CreateArray;
407
//debugln(['TFPCMsgFile.CreateArray START']);
408
SetLength(fItemById,0);
409
if fSortedForID.Count=0 then
411
Item:=TFPCMsgItem(fSortedForID.FindLowest.Data);
413
if MinID<0 then begin
414
debugln(['TFPCMsgFile.CreateArray WARNING: MinID ',MinID,' too low: ',Item.Pattern]);
417
Item:=TFPCMsgItem(fSortedForID.FindHighest.Data);
419
if MaxID>100000 then begin
420
debugln(['TFPCMsgFile.CreateArray WARNING: MaxID ',MaxID,' too high: ',Item.Pattern]);
423
//debugln(['TFPCMsgFile.CreateArray Max=',MaxID]);
424
SetLength(fItemById,MaxID+1);
425
for i:=0 to length(fItemById)-1 do fItemById[i]:=nil;
426
for i:=0 to FItems.Count-1 do begin
427
Item:=TFPCMsgItem(FItems[i]);
428
//debugln(['TFPCMsgFile.CreateArray ',Item.ID,' ',copy(Item.Pattern,1,20),'..',copy(Item.Pattern,length(Item.Pattern)-19,20)]);
429
fItemById[Item.ID]:=Item;
433
function TFPCMsgFile.GetSpecialItems(Index: TfmiSpecialItem): TFPCMsgItem;
435
Result:=fSpecialItems[Index];
438
constructor TFPCMsgFile.Create;
441
FItems:=TFPList.Create;
442
fSortedForID:=TAVLTree.Create(@CompareFPCMsgId);
443
fNodeMgr:=TAVLTreeNodeMemManager.Create;
444
fSortedForID.SetNodeManager(fNodeMgr);
447
destructor TFPCMsgFile.Destroy;
451
FreeAndNil(fSortedForID);
452
FreeAndNil(fNodeMgr);
456
procedure TFPCMsgFile.LoadFromFile(const Filename: string);
460
{$IFDEF VerboseFPCMsgFile}
461
debugln(['TFPCMsgFile.LoadFromFile START ',Filename]);
463
sl:=TStringList.Create;
465
sl.LoadFromFile(UTF8ToSys(Filename));
472
procedure TFPCMsgFile.LoadFromList(List: TStrings);
474
function ReadTilChar(var p: PChar; EndChar: char; out s: string): boolean;
482
if c=#0 then exit(false);
483
if c=EndChar then begin
488
if p=StartPos then exit(false);
489
SetLength(s,p-StartPos);
490
System.Move(StartPos^,s[1],length(s));
495
function ReadItem(var Line: integer; const s: string): TFPCMsgItem;
496
// <part>_<typ>_<txtidentifier>=<id>_<idtype>_<message with placeholders>
497
// option and wpo are different:
498
// <part>_<txtidentifier>=<id>_<idtype>_<message with placeholders>
500
// <part>_<txtidentifier>=<id>_[<multi line message with placeholders>
518
if not ReadTilChar(p,'_',Part) then begin
519
{$IFDEF VerboseFPCMsgFile}
520
debugln(['TFPCMsgFile.LoadFromList invalid <part>, line ',Line,': "',s,'"']);
524
if (Part='option') or (Part='wpo') then
526
else if not ReadTilChar(p,'_',Typ) then begin
527
{$IFDEF VerboseFPCMsgFile}
528
debugln(['TFPCMsgFile.LoadFromList invalid <type>, line ',Line,': "',s,'"']);
531
end else if (length(Typ)<>1)
532
or (not (Typ[1] in ['f','e','w','n','h','i','l','u','t','c','d','x','o']))
534
{$IFDEF VerboseFPCMsgFile}
535
debugln(['TFPCMsgFile.LoadFromList invalid <type>, line ',Line,': "',s,'"']);
539
if not ReadTilChar(p,'=',TxtID) then begin
540
{$IFDEF VerboseFPCMsgFile}
541
debugln(['TFPCMsgFile.LoadFromList invalid <textidentifier>, line ',Line,': "',s,'"']);
545
if not ReadTilChar(p,'_',IDStr) then begin
546
{$IFDEF VerboseFPCMsgFile}
547
debugln(['TFPCMsgFile.LoadFromList invalid id, line ',Line,': "',s,'"']);
551
ID:=StrToIntDef(IDStr,-1);
553
{$IFDEF VerboseFPCMsgFile}
554
debugln(['TFPCMsgFile.LoadFromList invalid id, line ',Line,': "',s,'"']);
560
if not ReadTilChar(p,'_',ShownTyp) then begin
561
{$IFDEF VerboseFPCMsgFile}
562
debugln(['TFPCMsgFile.LoadFromList invalid urgency, line ',Line,': "',s,'"']);
568
// multi line message
572
if Line>=List.Count then exit;
574
//debugln(['ReadItem ID=',ID,' h=',h]);
575
if (h<>'') and (h[1]=']') then break;
576
Msg:=Msg+h+LineEnding;
581
while (i>=1) and (Msg[i] in [' ',#9,#10,#13]) do dec(i);
582
if i<length(Msg) then begin
583
MsgEndSpace:=copy(Msg,i+1,length(Msg));
584
System.Delete(Msg,i+1,length(Msg));
588
Result:=TFPCMsgItem.Create;
591
Result.TxtIdentifier:=TxtID;
593
Result.ShownTyp:=ShownTyp;
595
Result.PatternEndSpace:=MsgEndSpace;
596
//debugln(['ReadItem Part=',Part,' Typ=',Typ,' TxtID=',TxtID,' ID=',ID,' IdTyp=',ShownTyp,' Msg="',copy(Result.Pattern,1,20),'"']);
604
//debugln(['TFPCMsgFile.LoadFromList START']);
608
while Line<List.Count do begin
613
end else if s[1]='#' then begin
615
end else if s[1]='%' then begin
617
if Item<>nil then begin
618
if Item.Comment<>'' then
619
Item.Comment:=Item.Comment+LineEnding;
620
Item.Comment:=Item.Comment+copy(s,2,length(s));
623
Item:=ReadItem(Line,s);
624
if Item<>nil then begin
625
//debugln(['TFPCMsgFile.LoadFromList ',Item.ID,' ',Item.Pattern]);
626
Item.Index:=FItems.Count;
628
fSortedForID.Add(Item);
630
1012: fSpecialItems[fmisiFatal]:=Item;
631
1013: fSpecialItems[fmisiError]:=Item;
632
1014: fSpecialItems[fmisiWarning]:=Item;
633
1015: fSpecialItems[fmisiNote]:=Item;
634
1016: fSpecialItems[fmisiHint]:=Item;
643
procedure TFPCMsgFile.LoadFromText(s: string);
647
//debugln(['TFPCMsgFile.LoadFromText START']);
648
sl:=TStringList.Create;
657
procedure TFPCMsgFile.Clear;
662
for s:=Low(fSpecialItems) to high(fSpecialItems) do
663
fSpecialItems[s]:=nil;
664
SetLength(fItemById,0);
666
for i:=0 to FItems.Count-1 do
667
TObject(FItems[i]).Free;
671
function TFPCMsgFile.Count: integer;
673
Result:=FItems.Count;
676
function TFPCMsgFile.FindWithID(ID: integer): TFPCMsgItem;
680
//debugln(['TFPCMsgFile.FindWithID ',ID,' Max=',length(fItemById)]);
681
if (ID>=0) and (ID<length(fItemById)) then begin
682
Result:=fItemById[ID];
685
Node:=fSortedForID.FindKey(@ID,@CompareIDWithFPCMsgId);
687
Result:=TFPCMsgItem(Node.Data)
692
function TFPCMsgFile.FindWithMessage(Msg: string): TFPCMsgItem;
698
BestMatchLen: Integer;
708
if (p^='[') and (p[1] in ['0'..'9']) then begin
710
while p^ in ['0'..'9','.'] do inc(p);
711
if p^<>']' then exit; // not a fpc message
713
while p^ in [' '] do inc(p);
716
// read message ID (000)
718
if (p^='(') and (p[1] in ['0'..'9']) then begin
720
while p^ in ['0'..'9','.'] do begin
721
if MsgID>1000000 then exit; // not a fpc message
722
MsgID:=MsgID*10+ord(p^)-ord('0');
725
if p^<>')' then exit; // not a fpc message
727
while p^ in [' '] do inc(p);
728
Result:=FindWithID(MsgID);
732
// search a message pattern that fits the Msg
734
for i:=0 to Count-1 do begin
736
if Item.Pattern='' then continue;
737
MatchLen:=PatternFits(Item,Msg);
738
if MatchLen>BestMatchLen then begin
739
BestMatchLen:=MatchLen;
745
function TFPCMsgFile.GetMsgText(Item: TFPCMsgItem): string;
749
if Item=nil then exit('');
750
Result:=Item.Pattern;
751
si:=MsgTypToSpecialItem(Item.Typ);
753
Result:=si.Pattern+' '+Result;
756
function TFPCMsgFile.PatternFits(Item: TFPCMsgItem; aMsg: string): integer;
760
Result:=Item.PatternFits(aMsg);
761
if Result<0 then exit;
762
// some messages have two types
764
si:=MsgTypToSpecialItem(Item.Typ);
765
if si<>nil then begin
766
if System.Pos(si.Pattern,aMsg)>0 then
767
inc(Result,length(si.Pattern));
771
function TFPCMsgFile.MsgTypToSpecialItem(const Typ: string): TFPCMsgItem;
774
if length(Typ)<>1 then exit;
776
'f': Result:=fSpecialItems[fmisiFatal];
777
'e': Result:=fSpecialItems[fmisiError];
778
'w': Result:=fSpecialItems[fmisiWarning];
779
'n': Result:=fSpecialItems[fmisiNote];
780
'h': Result:=fSpecialItems[fmisiHint];
786
procedure TFPCMsgRanges.Add(StartPos, EndPos: integer);
788
if Count=Capacity then begin
792
fCapacity:=Capacity*2;
793
ReAllocMem(Ranges,Capacity*SizeOf(TFPCMsgRange));
795
Ranges[FCount].StartPos:=StartPos;
796
Ranges[FCount].EndPos:=EndPos;
800
procedure TFPCMsgRanges.Clear(FreeMemory: boolean);
803
if not FreeMemory then begin
804
ReAllocMem(Ranges,0);
809
destructor TFPCMsgRanges.Destroy;