{ Copyright (C) 2017 - 2020 by Michael Van Canneyt michael@freepascal.org pas2js Delphi stub generator - component See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. } unit stubcreator; {$mode objfpc}{$H+} interface uses Classes, SysUtils, strutils, inifiles, pscanner, pparser, pastree, iostream, paswrite; type { We have to override abstract TPasTreeContainer methods } TSimpleEngine = class(TPasTreeContainer) public function CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility: TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; override; function FindElement(const AName: String): TPasElement; override; end; TWriteCallBack = Procedure (Data : Pointer; AFileData : PAnsiChar; AFileDataLen: Int32); stdcall; TWriteEvent = Procedure(AFileData : String) of object; TUnitAliasCallBack = Function (Data: Pointer; AUnitName: PAnsiChar; var AUnitNameMaxLen: Int32): boolean; {$IFDEF UseCDecl}cdecl{$ELSE}stdcall{$ENDIF}; { TStubCreator } TStubCreator = Class(TComponent) private FConfigFile: String; FHeaderStream: TStream; FIncludePaths: TStrings; FInputFile: String; FOnUnitAliasData: Pointer; FOnWrite: TWriteEvent; FOnWriteCallBack: TWriteCallBack; FOutputFile: String; FDefines : TStrings; FOptions: TPasWriterOptions; FLineNumberWidth, FIndentSize : Integer; FExtraUnits : String; FForwardClasses : String; FHeaderFile : String; FOutputStream: TStream; FWriteStream : TStringStream; FCallBackData : Pointer; FLastErrorClass : String; FLastError : String; FOnUnitAlias : TUnitAliasCallBack; procedure SetDefines(AValue: TStrings); procedure SetIncludePaths(AValue: TStrings); procedure SetOnWrite(AValue: TWriteEvent); procedure SetWriteCallback(AValue: TWriteCallBack); function CheckUnitAlias(const AUnitName: String): String; Protected procedure DoExecute;virtual; Procedure DoWriteEvent; virtual; procedure ReadConfig(const aFileName: String); virtual; procedure ReadConfig(const aIni: TIniFile); virtual; procedure WriteModule(M: TPasModule); virtual; function GetModule: TPasModule; virtual; Function MaybeGetFileStream(AStream : TStream; Const AFileName : String; aFileMode : Word) : TStream; virtual; Public Constructor Create(AOwner : TComponent); override; Destructor Destroy; override; Function Execute: Boolean; Procedure GetLastError(Out AError,AErrorClass : String); // Streams take precedence over filenames. They will be freed on destroy! // OutputStream can be used combined with write callbacks. Property OutputStream : TStream Read FOutputStream Write FOutputStream; Property HeaderStream : TStream Read FHeaderStream Write FHeaderStream; Property OnUnitAlias: TUnitAliasCallBack read FOnUnitAlias Write FOnUnitAlias; Property OnUnitAliasData : Pointer Read FOnUnitAliasData Write FOnUnitAliasData; Property OnWriteCallBack : TWriteCallBack Read FOnWriteCallBack Write SetWriteCallback; Property CallbackData : Pointer Read FCallBackData Write FCallBackData; Property ExtraUnits : String Read FExtraUnits write FExtraUnits; Published Property Defines : TStrings Read FDefines Write SetDefines; Property ConfigFileName : String Read FConfigFile Write FConfigFile; Property InputFileName : String Read FInputFile write FInputFile; Property OutputFileName : String Read FOutputFile write FOutputFile; Property HeaderFileName : String Read FHeaderFile write FHeaderFile; Property ForwardClasses : String Read FForwardClasses write FForwardClasses; Property IncludePaths : TStrings Read FIncludePaths Write SetIncludePaths; Property OnWrite : TWriteEvent Read FOnWrite Write SetOnWrite; end; Implementation uses Math; ResourceString SErrNoDestGiven = 'No destination file specified.'; SErrNoSourceParsed = 'Parsing produced no file.'; procedure TStubCreator.SetDefines(AValue: TStrings); begin if FDefines=AValue then Exit; FDefines.Assign(AValue); end; procedure TStubCreator.SetIncludePaths(AValue: TStrings); begin if FIncludePaths=AValue then Exit; FIncludePaths.Assign(AValue); end; procedure TStubCreator.SetOnWrite(AValue: TWriteEvent); begin if FOnWrite=AValue then Exit; FOnWrite:=AValue; FreeAndNil(FWriteStream); if Assigned(AValue) then FWriteStream:=TStringStream.Create(''); end; procedure TStubCreator.SetWriteCallback(AValue: TWriteCallBack); begin if FOnWriteCallBack=AValue then Exit; FOnWriteCallBack:=AValue; FreeAndNil(FWriteStream); if Assigned(AValue) then FWriteStream:=TStringStream.Create(''); end; function TStubCreator.CheckUnitAlias(const AUnitName: String): String; const MAX_UNIT_NAME_LENGTH = 255; var UnitMaxLenthName: Integer; begin Result := AUnitName; UnitMaxLenthName := Max(MAX_UNIT_NAME_LENGTH, Result.Length); SetLength(Result, UnitMaxLenthName); if FOnUnitAlias(OnUnitAliasData, @Result[1], UnitMaxLenthName) then Result := LeftStr(PChar(Result), UnitMaxLenthName); end; procedure TStubCreator.DoWriteEvent; Var S : String; begin If Assigned(FOnWrite) then FOnWrite(FWriteStream.DataString); if Assigned(FOnWriteCallBack) then begin S:=FWriteStream.DataString; FOnWriteCallBack(FCallBackData,PChar(S),Length(S)); end; end; { TStubCreator } procedure TStubCreator.ReadConfig(const aFileName: String); Var ini : TMemIniFile; begin ini:=TMemIniFile.Create(AFileName); try ReadConfig(Ini); finally Ini.Free; end; end; procedure TStubCreator.ReadConfig(const aIni: TIniFile); Const DelChars = [',',' ']; Var O : TPaswriterOptions; S : String; I : Integer; begin O:=[]; With aIni do begin if ReadBool('Config','addlinenumber',False) then Include(O,woAddLineNumber); if ReadBool('Config','addsourcelinenumber',False) then Include(O,woAddLineNumber); FOptions:=FOptions+O; InputFilename:=ReadString('config','input',InputFilename); OutputFilename:=ReadString('config','output',OutputFilename); HeaderFilename:=ReadString('config','header',HeaderFilename); FIndentSize:=ReadInteger('config','indentsize',FIndentSize); FLineNumberWidth:=ReadInteger('config','linenumberwidth',FLineNumberWidth); FExtraUnits:=ReadString('config','extra',FExtraUnits); FForwardClasses:=ReadString('config','forwardclasses',FForwardClasses); S:=ReadString('config','defines',''); if (S<>'') then For I:=1 to WordCount(S,DelChars) do FDefines.Add(UpperCase(ExtractWord(I,S,DelChars))); S:=ReadString('config','includepaths',''); if (S<>'') then For I:=1 to WordCount(S,[',',';']) do FIncludePaths.Add(ExtractWord(I,S,[',',';'])); end; if (FForwardClasses<>'') or (FForwardClasses='all') then Include(O,woForwardClasses); end; function TStubCreator.Execute: Boolean; begin FLastErrorClass:=''; FLastError:=''; Result := False; if Defines.IndexOf('MakeStub')=-1 then Try DoExecute; Result := True; except On E : Exception do begin FLastErrorClass:=E.Classname; FLastError:=E.Message; end; end; end; procedure TStubCreator.GetLastError(out AError, AErrorClass: String); begin AError:=FLastError; AErrorClass:=FLastErrorClass; end; procedure TStubCreator.DoExecute; Var M : TPasModule; begin If (ConfigFileName<>'') then ReadConfig(ConfigFileName); if InputFilename = '' then raise Exception.Create(SErrNoSourceGiven); if (OutputFilename = '') and (FoutputStream=Nil) and (FWriteStream=Nil) then raise Exception.Create(SErrNoDestGiven); if CompareText(ForwardClasses,'all')=0 then begin Include(Foptions,woForwardClasses); ForwardClasses:=''; end else if (ForwardClasses<>'') then Include(Foptions,woForwardClasses); Include(Foptions,woForceOverload); M:=GetModule; if M=Nil then raise Exception.Create(SErrNoSourceParsed); try WriteModule(M); finally M.Free; end; end; { TSimpleEngine } function TSimpleEngine.CreateElement(AClass: TPTreeElement; const AName: String; AParent: TPasElement; AVisibility: TPasMemberVisibility; const ASourceFilename: String; ASourceLinenumber: Integer): TPasElement; begin Result := AClass.Create(AName, AParent); Result.Visibility := AVisibility; Result.SourceFilename := ASourceFilename; Result.SourceLinenumber := ASourceLinenumber; end; function TSimpleEngine.FindElement(const AName: String): TPasElement; begin { dummy implementation, see TFPDocEngine.FindElement for a real example } Result := nil; if AName<>'' then ; // Keep compiler happy end; function TStubCreator.GetModule: TPasModule; Var SE : TSimpleEngine; FileResolver: TFileResolver; Parser: TPasParser; Scanner: TPascalScanner; var s: String; begin Result := nil; FileResolver := nil; Scanner := nil; Parser := nil; SE:=TSimpleEngine.Create; try // File resolver FileResolver := TFileResolver.Create; FileResolver.UseStreams:=True; FileResolver.AddIncludePath(ExtractFilePath(InputFileName)); For S in FIncludePaths do FileResolver.AddIncludePath(S); // Scanner Scanner := TPascalScanner.Create(FileResolver); Scanner.Options:=[po_AsmWhole,po_KeepClassForward,po_ExtConstWithoutExpr]; SCanner.LogEvents:=SE.ScannerLogEvents; SCanner.OnLog:=SE.Onlog; For S in FDefines do Scanner.AddDefine(S); if FDefines.IndexOf('MAKESTUB')=-1 then Scanner.AddDefine('MAKESTUB'); Scanner.OpenFile(InputFilename); // Parser Parser:=TPasParser.Create(Scanner, FileResolver, SE); Parser.LogEvents:=SE.ParserLogEvents; Parser.OnLog:=SE.Onlog; Parser.Options:=Parser.Options+[po_AsmWhole,po_delphi,po_KeepClassForward,po_ExtConstWithoutExpr,po_AsyncProcs]; Parser.ParseMain(Result); finally Parser.Free; Scanner.Free; FileResolver.Free; SE.Free; end; end; function TStubCreator.MaybeGetFileStream(AStream: TStream; const AFileName: String; aFileMode: Word): TStream; begin If Assigned(AStream) then Result:=AStream else if (AFileName<>'') then Result:=TFileStream.Create(AFileName,aFileMode) else Result:=Nil; end; constructor TStubCreator.Create(AOwner: TComponent); begin inherited Create(AOwner); FDefines:=TStringList.Create; FIncludePaths:=TStringList.Create; FLineNumberWidth:=4; FIndentSize:=2; FExtraUnits:=''; FOptions:=[woNoImplementation,woNoExternalClass,woNoExternalVar,woNoExternalFunc,woNoAsm,woSkipPrivateExternals,woAlwaysRecordHelper,woSkipHints]; end; destructor TStubCreator.Destroy; begin FreeAndNil(FWriteStream); FreeAndNil(FOutputStream); FreeAndNil(FHeaderStream); FreeAndNil(FIncludePaths); FreeAndNil(FDefines); inherited Destroy; end; procedure TStubCreator.WriteModule(M: TPasModule); Var F,H : TStream; W : TPasWriter; begin W:=Nil; F:=MaybeGetFileStream(OutputStream,FOutputFile,fmCreate); if (F=Nil) then if FWriteStream<>nil then F:=FWriteStream else F:=TIOStream.Create(iosOutPut); try H:=MaybeGetFileStream(HeaderStream,FHeaderFile,fmOpenRead or fmShareDenyWrite); if Assigned(h) then try F.CopyFrom(H,H.Size); finally if H<>HeaderStream then H.Free; end; W:=TPasWriter.Create(F); W.Options:=FOptions; W.ExtraUnits:=FExtraUnits; if Assigned(FOnUnitAlias) then W.OnUnitAlias:=@CheckUnitAlias; if FIndentSize<>-1 then W.IndentSize:=FIndentSize; if FLineNumberWidth>0 then W.LineNumberWidth:=FLineNumberWidth; W.ForwardClasses.CommaText:=FForwardClasses; W.WriteModule(M); if Assigned(FWriteStream) then DoWriteEvent; finally W.Free; if (F<>OutputStream) and (F<>FWriteStream) then F.Free; end; end; end.