unit uPSPreProcessor; {$I PascalScript.inc} interface uses Classes, SysUtils, uPSCompiler, uPSUtils; type EPSPreProcessor = class(Exception); //- jgv TPSPreProcessor = class; TPSPascalPreProcessorParser = class; TPSOnNeedFile = function (Sender: TPSPreProcessor; const callingfilename: tbtstring; var FileName, Output: tbtstring): Boolean; TPSOnProcessDirective = procedure ( Sender: TPSPreProcessor; Parser: TPSPascalPreProcessorParser; const Active: Boolean; const DirectiveName, DirectiveParam: tbtString; Var Continue: Boolean); //- jgv - application set continue to false to stop the normal directive processing TPSLineInfo = class(TObject) private function GetLineOffset(I: Integer): Cardinal; function GetLineOffsetCount: Longint; protected FEndPos: Cardinal; FStartPos: Cardinal; FFileName: tbtstring; FLineOffsets: TIfList; public property FileName: tbtstring read FFileName; property StartPos: Cardinal read FStartPos; property EndPos: Cardinal read FEndPos; property LineOffsetCount: Longint read GetLineOffsetCount; property LineOffset[I: Longint]: Cardinal read GetLineOffset; constructor Create; destructor Destroy; override; end; TPSLineInfoResults = record Row, Col, Pos: Cardinal; Name: tbtstring; end; TPSLineInfoList = class(TObject) private FItems: TIfList; FCurrent: Longint; function GetCount: Longint; function GetItem(I: Integer): TPSLineInfo; protected function Add: TPSLineInfo; public property Count: Longint read GetCount; property Items[I: Longint]: TPSLineInfo read GetItem; default; procedure Clear; function GetLineInfo(const ModuleName: tbtstring; Pos: Cardinal; var Res: TPSLineInfoResults): Boolean; property Current: Longint read FCurrent write FCurrent; constructor Create; destructor Destroy; override; end; TPSDefineStates = class; TPSPreProcessor = class(TObject) private FID: Pointer; FCurrentDefines, FDefines: TStringList; FCurrentLineInfo: TPSLineInfoList; FOnNeedFile: TPSOnNeedFile; FAddedPosition: Cardinal; FDefineState: TPSDefineStates; FMaxLevel: Longint; FMainFileName: tbtstring; FMainFile: tbtstring; FOnProcessDirective: TPSOnProcessDirective; FOnProcessUnknowDirective: TPSOnProcessDirective; procedure ParserNewLine(Sender: TPSPascalPreProcessorParser; Row, Col, Pos: Cardinal); procedure IntPreProcess(Level: Integer; const OrgFileName: tbtstring; FileName: tbtstring; Dest: TStream); protected procedure doAddStdPredefines; virtual; // jgv public {The maximum number of levels deep the parser will go, defaults to 20} property MaxLevel: Longint read FMaxLevel write FMaxLevel; property CurrentLineInfo: TPSLineInfoList read FCurrentLineInfo; property OnNeedFile: TPSOnNeedFile read FOnNeedFile write FOnNeedFile; property Defines: TStringList read FDefines write FDefines; property MainFile: tbtstring read FMainFile write FMainFile; property MainFileName: tbtstring read FMainFileName write FMainFileName; property ID: Pointer read FID write FID; procedure AdjustMessages(Comp: TPSPascalCompiler); procedure AdjustMessage(Msg: TPSPascalCompilerMessage); //-jgv procedure PreProcess(const Filename: tbtstring; var Output: tbtstring); procedure Clear; constructor Create; destructor Destroy; override; property OnProcessDirective: TPSOnProcessDirective read fOnProcessDirective write fOnProcessDirective; property OnProcessUnknowDirective: TPSOnProcessDirective read fOnProcessUnknowDirective write fOnProcessUnknowDirective; end; TPSPascalPreProcessorType = (ptEOF, ptOther, ptDefine); TPSOnNewLine = procedure (Sender: TPSPascalPreProcessorParser; Row, Col, Pos: Cardinal) of object; TPSPascalPreProcessorParser = class(TObject) private FData: tbtstring; FText: PAnsichar; FToken: tbtstring; FTokenId: TPSPascalPreProcessorType; FLastEnterPos, FLen, FRow, FCol, FPos: Cardinal; FOnNewLine: TPSOnNewLine; public procedure SetText(const dta: tbtstring); procedure Next; property Token: tbtstring read FToken; property TokenId: TPSPascalPreProcessorType read FTokenId; property Row: Cardinal read FRow; property Col: Cardinal read FCol; property Pos: Cardinal read FPos; property OnNewLine: TPSOnNewLine read FOnNewLine write FOnNewLine; end; TPSDefineState = class(TObject) private FInElse: Boolean; FDoWrite: Boolean; public property InElse: Boolean read FInElse write FInElse; property DoWrite: Boolean read FDoWrite write FDoWrite; end; TPSDefineStates = class(TObject) private FItems: TIfList; function GetCount: Longint; function GetItem(I: Integer): TPSDefineState; function GetWrite: Boolean; function GetPrevWrite: Boolean; //JeromeWelsh - nesting fix public property Count: Longint read GetCount; property Item[I: Longint]: TPSDefineState read GetItem; default; function Add: TPSDefineState; procedure Delete(I: Longint); constructor Create; destructor Destroy; override; procedure Clear; property DoWrite: Boolean read GetWrite; property DoPrevWrite: Boolean read GetPrevWrite; //JeromeWelsh - nesting fix end; implementation {$IFDEF DELPHI3UP } resourceString {$ELSE } const {$ENDIF } RPS_TooManyNestedInclude = 'Too many nested include files while processing ''%s'' from ''%s'''; RPS_IncludeNotFound = 'Unable to find file ''%s'' used from ''%s'''; RPS_DefineTooManyParameters = 'Too many parameters at %d:%d'; RPS_NoIfdefForEndif = 'No IFDEF for ENDIF at %d:%d'; RPS_NoIfdefForElse = 'No IFDEF for ELSE at %d:%d'; RPS_ElseTwice = 'Can''t use ELSE twice at %d:%d'; RPS_UnknownCompilerDirective = 'Unknown compiler directives at %d:%d'; RPs_DefineNotClosed = 'Define not closed'; { TPSLineInfoList } function TPSLineInfoList.Add: TPSLineInfo; begin Result := TPSLineInfo.Create; FItems.Add(Result); end; procedure TPSLineInfoList.Clear; var i: Longint; begin for i := FItems.count -1 downto 0 do TPSLineInfo(FItems[i]).Free; FItems.Clear; end; constructor TPSLineInfoList.Create; begin inherited Create; FItems := TIfList.Create; end; destructor TPSLineInfoList.Destroy; begin Clear; FItems.Free; inherited Destroy; end; function TPSLineInfoList.GetCount: Longint; begin Result := FItems.Count; end; function TPSLineInfoList.GetItem(I: Integer): TPSLineInfo; begin Result := TPSLineInfo(FItems[i]); end; function TPSLineInfoList.GetLineInfo(const ModuleName: tbtstring; Pos: Cardinal; var Res: TPSLineInfoResults): Boolean; var i,j: Longint; linepos: Cardinal; Item: TPSLineInfo; lModuleName: tbtstring; begin lModuleName := FastUpperCase(ModuleName); for i := FItems.Count -1 downto 0 do begin Item := FItems[i]; if (Pos >= Item.StartPos) and (Pos < Item.EndPos) and (lModuleName = '') or (lModuleName = Item.FileName) then begin Res.Name := Item.FileName; Pos := Pos - Item.StartPos; Res.Pos := Pos; Res.Col := 1; Res.Row := 1; LinePos := 0; for j := 0 to Item.LineOffsetCount -1 do begin if Pos >= Item.LineOffset[j] then begin linepos := Item.LineOffset[j]; end else begin Res.Row := j; // j -1, but line counting starts at 1 Res.Col := pos - linepos + 1; Break; end; end; Result := True; exit; end; end; Result := False; end; { TPSLineInfo } constructor TPSLineInfo.Create; begin inherited Create; FLineOffsets := TIfList.Create; end; destructor TPSLineInfo.Destroy; begin FLineOffsets.Free; inherited Destroy; end; function TPSLineInfo.GetLineOffset(I: Integer): Cardinal; begin Result := Longint(FLineOffsets[I]); end; function TPSLineInfo.GetLineOffsetCount: Longint; begin result := FLineOffsets.Count; end; { TPSPascalPreProcessorParser } procedure TPSPascalPreProcessorParser.Next; var ci: Cardinal; begin FPos := FPos + FLen; case FText[FPos] of #0: begin FLen := 0; FTokenId := ptEof; end; '''': begin ci := FPos; while (FText[ci] <> #0) do begin Inc(ci); while FText[ci] = '''' do begin if FText[ci+1] <> '''' then Break; inc(ci); inc(ci); end; if FText[ci] = '''' then Break; if FText[ci] = #13 then begin inc(FRow); if FText[ci] = #10 then inc(ci); FLastEnterPos := ci -1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end else if FText[ci] = #10 then begin inc(FRow); FLastEnterPos := ci -1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end; end; FLen := ci - FPos + 1; FTokenId := ptOther; end; '(': begin if FText[FPos + 1] = '*' then begin ci := FPos + 1; while (FText[ci] <> #0) do begin if (FText[ci] = '*') and (FText[ci + 1] = ')') then Break; if FText[ci] = #13 then begin inc(FRow); if FText[ci+1] = #10 then inc(ci); FLastEnterPos := ci -1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end else if FText[ci] = #10 then begin inc(FRow); FLastEnterPos := ci -1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end; Inc(ci); end; FTokenId := ptOther; if (FText[ci] <> #0) then Inc(ci, 2); FLen := ci - FPos; end else begin FTokenId := ptOther; FLen := 1; end; end; '/': begin if FText[FPos + 1] = '/' then begin ci := FPos + 1; while (FText[ci] <> #0) and (FText[ci] <> #13) and (FText[ci] <> #10) do begin Inc(ci); end; FTokenId := ptOther; FLen := ci - FPos; end else begin FTokenId := ptOther; FLen := 1; end; end; '{': begin ci := FPos + 1; while (FText[ci] <> #0) and (FText[ci] <> '}') do begin if FText[ci] = #13 then begin inc(FRow); if FText[ci+1] = #10 then inc(ci); FLastEnterPos := ci - 1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end else if FText[ci] = #10 then begin inc(FRow); FLastEnterPos := ci - 1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end; Inc(ci); end; if (FText[FPos + 1] = '$') or (FText[FPos + 1] = '.') then FTokenId := ptDefine else FTokenId := ptOther; FLen := ci - FPos + 1; end; else begin ci := FPos + 1; while not (FText[ci] in [#0,'{', '(', '''', '/']) do begin if FText[ci] = #13 then begin inc(FRow); if FText[ci+1] = #10 then inc(ci); FLastEnterPos := ci - 1; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end else if FText[ci] = #10 then begin inc(FRow); FLastEnterPos := ci -1 ; if @FOnNewLine <> nil then FOnNewLine(Self, FRow, FPos - FLastEnterPos + 1, ci+1); end; Inc(Ci); end; FTokenId := ptOther; FLen := ci - FPos; end; end; FCol := FPos - FLastEnterPos + 1; FToken := Copy(FData, FPos +1, FLen); end; procedure TPSPascalPreProcessorParser.SetText(const dta: tbtstring); begin FData := dta; FText := pAnsichar(FData); FLen := 0; FPos := 0; FCol := 1; FLastEnterPos := 0; FRow := 1; if @FOnNewLine <> nil then FOnNewLine(Self, 1, 1, 0); Next; end; { TPSPreProcessor } procedure TPSPreProcessor.AdjustMessage(Msg: TPSPascalCompilerMessage); var Res: TPSLineInfoResults; begin if CurrentLineInfo.GetLineInfo(Msg.ModuleName, Msg.Pos, Res) then begin Msg.SetCustomPos(res.Pos, Res.Row, Res.Col); Msg.ModuleName := Res.Name; end; end; procedure TPSPreProcessor.AdjustMessages(Comp: TPSPascalCompiler); var i: Longint; begin for i := 0 to Comp.MsgCount -1 do AdjustMessage (Comp.Msg[i]); end; procedure TPSPreProcessor.Clear; begin FDefineState.Clear; FDefines.Clear; FCurrentDefines.Clear; FCurrentLineInfo.Clear; FMainFile := ''; end; constructor TPSPreProcessor.Create; begin inherited Create; FDefines := TStringList.Create; FCurrentLineInfo := TPSLineInfoList.Create; FCurrentDefines := TStringList.Create; FDefines.Duplicates := dupIgnore; FCurrentDefines.Duplicates := dupIgnore; FDefineState := TPSDefineStates.Create; FMaxLevel := 20; doAddStdPredefines; end; destructor TPSPreProcessor.Destroy; begin FDefineState.Free; FCurrentDefines.Free; FDefines.Free; FCurrentLineInfo.Free; inherited Destroy; end; procedure TPSPreProcessor.doAddStdPredefines; begin //--- 20050708_jgv FCurrentDefines.Add (Format ('VER%d', [PSCurrentBuildNo])); {$IFDEF CPU386 } FCurrentDefines.Add ('CPU386'); {$ENDIF } {$IFDEF MSWINDOWS } FCurrentDefines.Add ('MSWINDOWS'); FCurrentDefines.Add ('WIN32'); {$ENDIF } {$IFDEF LINUX } FCurrentDefines.Add ('LINUX'); {$ENDIF } end; procedure TPSPreProcessor.IntPreProcess(Level: Integer; const OrgFileName: tbtstring; FileName: tbtstring; Dest: TStream); var Parser: TPSPascalPreProcessorParser; dta: tbtstring; item: TPSLineInfo; s, name: tbtstring; current, i: Longint; ds: TPSDefineState; AppContinue: Boolean; begin if Level > MaxLevel then raise EPSPreProcessor.CreateFmt(RPS_TooManyNestedInclude, [FileName, OrgFileName]); Parser := TPSPascalPreProcessorParser.Create; try Parser.OnNewLine := ParserNewLine; if FileName = MainFileName then begin dta := MainFile; end else if (@OnNeedFile = nil) or (not OnNeedFile(Self, OrgFileName, FileName, dta)) then raise EPSPreProcessor.CreateFmt(RPS_IncludeNotFound, [FileName, OrgFileName]); Item := FCurrentLineInfo.Add; current := FCurrentLineInfo.Count -1; FCurrentLineInfo.Current := current; Item.FStartPos := Dest.Position; Item.FFileName := FileName; Parser.SetText(dta); while Parser.TokenId <> ptEOF do begin s := Parser.Token; if Parser.TokenId = ptDefine then begin Delete(s,1,2); // delete the {$ Delete(s,length(s), 1); // delete the } //-- 20050707_jgv trim right i := length (s); while (i > 0) and (s[i] = ' ') do begin Delete (s, i, 1); Dec (i); end; //-- end_jgv if pos(tbtChar(' '), s) = 0 then begin name := uppercase(s); s := ''; end else begin Name := uppercase(copy(s,1,pos(' ', s)-1)); Delete(s, 1, pos(' ', s)); end; //-- 20050707_jgv - ask the application AppContinue := True; If @OnProcessDirective <> Nil then OnProcessDirective (Self, Parser, FDefineState.DoWrite, name, s, AppContinue); If AppContinue then //-- end jgv if (Name = 'I') or (Name = 'INCLUDE') or (Name = '.INCLUDE') then begin if FDefineState.DoWrite then begin FAddedPosition := 0; IntPreProcess(Level +1, FileName, s, Dest); FCurrentLineInfo.Current := current; FAddedPosition := Cardinal(Dest.Position) - Item.StartPos - Parser.Pos; end; end else if (Name = 'DEFINE') then begin if FDefineState.DoWrite then begin if pos(' ', s) <> 0 then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); FCurrentDefines.Add(Uppercase(S)); end; end else if (Name = 'UNDEF') then begin if FDefineState.DoWrite then begin if pos(' ', s) <> 0 then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); i := FCurrentDefines.IndexOf(Uppercase(s)); if i <> -1 then FCurrentDefines.Delete(i); end; end else if (Name = 'IFDEF') then begin if pos(' ', s) <> 0 then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); //JeromeWelsh - nesting fix if (FDefineState.DoWrite and (FCurrentDefines.IndexOf(Uppercase(s)) <> -1)) then FDefineState.Add.DoWrite := True else FDefineState.Add.DoWrite := False; end else if (Name = 'IFNDEF') then begin if pos(' ', s) <> 0 then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); //JeromeWelsh - nesting fix if (FCurrentDefines.IndexOf(Uppercase(s)) = -1) and FDefineState.DoWrite then FDefineState.Add.DoWrite := True else FDefineState.Add.DoWrite := False; end else if (Name = 'ENDIF') then begin //- jgv remove - borland use it (sysutils.pas) //- if s <> '' then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); if FDefineState.Count = 0 then raise EPSPreProcessor.CreateFmt(RPS_NoIfdefForEndif, [Parser.Row, Parser.Col]); FDefineState.Delete(FDefineState.Count -1); // remove define from list end else if (Name = 'ELSE') then begin if s<> '' then raise EPSPreProcessor.CreateFmt(RPS_DefineTooManyParameters, [Parser.Row, Parser.Col]); if FDefineState.Count = 0 then raise EPSPreProcessor.CreateFmt(RPS_NoIfdefForElse, [Parser.Row, Parser.Col]); ds := FDefineState[FDefineState.Count -1]; if ds.InElse then raise EPSPreProcessor.CreateFmt(RPS_ElseTwice, [Parser.Row, Parser.Col]); ds.FInElse := True; //JeromeWelsh - nesting fix ds.DoWrite := not ds.DoWrite and FDefineState.DoPrevWrite; end //-- 20050710_jgv custom application error process else if Parser.Token[2] <> '.' then begin If @OnProcessUnknowDirective <> Nil then begin OnProcessUnknowDirective (Self, Parser, FDefineState.DoWrite, name, s, AppContinue); end; If AppContinue then //-- end jgv raise EPSPreProcessor.CreateFmt(RPS_UnknownCompilerDirective, [Parser.Row, Parser.Col]); end; end; if (not FDefineState.DoWrite) or (Parser.TokenId = ptDefine) then begin SetLength(s, Length(Parser.Token)); for i := length(s) downto 1 do s[i] := #32; // space end; Dest.Write(s[1], length(s)); Parser.Next; end; Item.FEndPos := Dest.Position; finally Parser.Free; end; end; procedure TPSPreProcessor.ParserNewLine(Sender: TPSPascalPreProcessorParser; Row, Col, Pos: Cardinal); begin if FCurrentLineInfo.Current >= FCurrentLineInfo.Count then exit; //errr ??? with FCurrentLineInfo.Items[FCurrentLineInfo.Current] do begin Pos := Pos + FAddedPosition; FLineOffsets.Add(Pointer(Pos)); end; end; procedure TPSPreProcessor.PreProcess(const Filename: tbtstring; var Output: tbtstring); var Stream: TMemoryStream; begin FAddedPosition := 0; FCurrentDefines.Assign(FDefines); Stream := TMemoryStream.Create; try IntPreProcess(0, '', FileName, Stream); Stream.Position := 0; SetLength(Output, Stream.Size); Stream.Read(Output[1], Length(Output)); finally Stream.Free; end; if FDefineState.Count <> 0 then raise EPSPreProcessor.Create(RPs_DefineNotClosed); end; { TPSDefineStates } function TPSDefineStates.Add: TPSDefineState; begin Result := TPSDefineState.Create; FItems.Add(Result); end; procedure TPSDefineStates.Clear; var i: Longint; begin for i := Longint(FItems.Count) -1 downto 0 do TPSDefineState(FItems[i]).Free; FItems.Clear; end; constructor TPSDefineStates.Create; begin inherited Create; FItems := TIfList.Create; end; procedure TPSDefineStates.Delete(I: Integer); begin TPSDefineState(FItems[i]).Free; FItems.Delete(i); end; destructor TPSDefineStates.Destroy; var i: Longint; begin for i := Longint(FItems.Count) -1 downto 0 do TPSDefineState(FItems[i]).Free; FItems.Free; inherited Destroy; end; function TPSDefineStates.GetCount: Longint; begin Result := FItems.Count; end; function TPSDefineStates.GetItem(I: Integer): TPSDefineState; begin Result := FItems[i]; end; function TPSDefineStates.GetWrite: Boolean; begin if FItems.Count = 0 then result := true else Result := TPSDefineState(FItems[FItems.Count -1]).DoWrite; end; //JeromeWelsh - nesting fix function TPSDefineStates.GetPrevWrite: Boolean; begin if FItems.Count < 2 then result := true else Result := TPSDefineState(FItems[FItems.Count -2]).DoWrite; end; end.