Repository URL to install this package:
|
Version:
3.2.0 ▾
|
{
This file is part of the Free Component Library (FCL)
Copyright (c) 2018 Michael Van Canneyt
Pascal to Javascript converter class.
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.
**********************************************************************
Abstract:
FileSystem aware compiler descendent with support for PCU files.
}
unit Pas2JSPCUCompiler;
{$mode objfpc}{$H+}
{$I pas2js_defines.inc}
{$IF defined(VerboseUnitQueue) or defined(VerbosePCUFiler)}
{$DEFINE ReallyVerbose}
{$ENDIF}
interface
uses
SysUtils, Classes,
jstree,
PasTree, PScanner, PasResolveEval,
FPPas2Js,
Pas2jsCompiler, Pas2JSFS, Pas2JSFSCompiler, Pas2JsFiler,
Pas2jsLogger, Pas2jsFileUtils;
Type
{ TFilerPCUSupport }
TFilerPCUSupport = Class(TPCUSupport)
Private
FPCUFormat: TPas2JSPrecompileFormat;
FPrecompileInitialFlags: TPCUInitialFlags;
FPCUReader: TPCUCustomReader;
FPCUReaderStream: TStream;
function OnPCUConverterIsElementUsed(Sender: TObject; El: TPasElement): boolean;
function OnPCUConverterIsTypeInfoUsed(Sender: TObject; El: TPasElement): boolean;
function OnWriterIsElementUsed(Sender: TObject; El: TPasElement): boolean;
procedure OnFilerGetSrc(Sender: TObject; aFilename: string; out p: PChar; out Count: integer);
Public
constructor Create(aCompilerFile: TPas2JSCompilerFile; aFormat: TPas2JSPrecompileFormat); reintroduce;
destructor Destroy; override;
function Compiler: TPas2JSCompiler;
function HandleException(E: Exception): Boolean; override;
function FindPCU(const UseUnitName: string): string; override;
function FindPCU(const UseUnitName: string; out aFormat: TPas2JSPrecompileFormat): string;
function HasReader: Boolean; override;
function ReadContinue: Boolean; override;
function ReadCanContinue: Boolean; override;
procedure SetInitialCompileFlags; override;
procedure WritePCU; override;
procedure CreatePCUReader; override;
procedure ReadUnit; override;
property PrecompileInitialFlags: TPCUInitialFlags read FPrecompileInitialFlags;
end;
{ TPas2jsPCUCompilerFile }
TPas2jsPCUCompilerFile = Class(TPas2jsCompilerFile)
function CreatePCUSupport: TPCUSupport; override;
end;
{ TPas2jsPCUCompiler }
TPas2jsPCUCompiler = Class(TPas2JSFSCompiler)
Private
FPrecompileFormat: TPas2JSPrecompileFormat;
Protected
procedure WritePrecompiledFormats; override;
function CreateCompilerFile(const PasFileName, PCUFilename: String): TPas2jsCompilerFile; override;
procedure HandleOptionPCUFormat(Value: string); override;
property PrecompileFormat: TPas2JSPrecompileFormat read FPrecompileFormat;
end;
implementation
{$IFDEF HASPAS2JSFILER}
{ ---------------------------------------------------------------------
TFilerPCUSupport
---------------------------------------------------------------------}
{ TFilerPCUSupport }
constructor TFilerPCUSupport.Create(aCompilerFile: TPas2JSCompilerFile; aFormat: TPas2JSPrecompileFormat);
begin
Inherited Create(aCompilerFile);
FPCUFormat:=AFormat;
if FPCUFormat=nil then
RaiseInternalError(20181207143653,aCompilerFile.UnitFilename);
FPrecompileInitialFlags:=TPCUInitialFlags.Create;
end;
destructor TFilerPCUSupport.Destroy;
begin
FreeAndNil(FPrecompileInitialFlags);
FreeAndNil(FPCUReader);
FreeAndNil(FPCUReaderStream);
inherited Destroy;
end;
function TFilerPCUSupport.Compiler: TPas2JSCompiler;
begin
Result:=MyFile.Compiler;
end;
function TFilerPCUSupport.HandleException(E: Exception): Boolean;
begin
Result:=False;
if E is EPas2JsReadError then
begin
Result:=True;
if EPas2JsReadError(E).Owner is TPCUCustomReader then
MyFile.Log.Log(mtError,E.Message,0,MyFile.PCUFilename)
else
MyFile.Log.Log(mtError,E.Message);
Compiler.Terminate(ExitCodePCUError);
end
else if (E is EPas2JsWriteError) then
begin
MyFile.Log.Log(mtFatal,E.ClassName+':'+E.Message);
Compiler.Terminate(ExitCodeErrorInternal);
Result:=True;
end
end;
function TFilerPCUSupport.FindPCU(const UseUnitName: string): string;
var
aPCUFormat: TPas2JSPrecompileFormat;
begin
Result:=FindPCU(UseUnitName,aPCUFormat);
if (Result<>'') and (FPCUFormat<>aPCUFormat) then
RaiseInternalError(20181207143826,UseUnitName);
end;
function TFilerPCUSupport.HasReader: Boolean;
begin
Result:=Assigned(FPCUReader);
end;
function TFilerPCUSupport.ReadContinue: Boolean;
begin
Result:=FPCUReader.ReadContinue;
end;
function TFilerPCUSupport.ReadCanContinue: Boolean;
begin
Result:=FPCUReader.ReadCanContinue;
end;
procedure TFilerPCUSupport.SetInitialCompileFlags;
begin
PrecompileInitialFlags.ParserOptions:=MyFile.Parser.Options;
PrecompileInitialFlags.ModeSwitches:=MyFile.Scanner.CurrentModeSwitches;
PrecompileInitialFlags.BoolSwitches:=MyFile.Scanner.CurrentBoolSwitches;
PrecompileInitialFlags.ConverterOptions:=MyFile.GetInitialConverterOptions;
PrecompileInitialFlags.TargetPlatform:=Compiler.TargetPlatform;
PrecompileInitialFlags.TargetProcessor:=Compiler.TargetProcessor;
end;
procedure TFilerPCUSupport.CreatePCUReader;
var
aFile: TPas2jsFile;
s: String;
begin
if MyFile.PCUFilename='' then
RaiseInternalError(20180312144742,MyFile.PCUFilename);
if FPCUReader<>nil then
RaiseInternalError(20180312142938,GetObjName(FPCUReader));
if FPCUFormat=nil then
RaiseInternalError(20180312142954,'');
FPCUReader:=FPCUFormat.ReaderClass.Create;
FPCUReader.SourceFilename:=ExtractFileName(MyFile.PCUFilename);
if MyFile.ShowDebug then
MyFile.Log.LogMsg(nParsingFile,[QuoteStr(MyFile.PCUFilename)]);
aFile:=Compiler.FS.LoadFile(MyFile.PCUFilename,true);
if aFile=nil then
RaiseInternalError(20180312145941,MyFile.PCUFilename);
FPCUReaderStream:=TMemoryStream.Create;
s:=aFile.Source;
//writeln('TPas2jsCompilerFile.CreatePCUReader ',PCUFilename,'-----START-----');
//writeln(s);
//writeln('TPas2jsCompilerFile.CreatePCUReader ',PCUFilename,'-----END-------');
if s<>'' then
begin
FPCUReaderStream.Write(s[1],length(s));
FPCUReaderStream.Position:=0;
end;
end;
procedure TFilerPCUSupport.ReadUnit;
begin
FPCUReader.ReadPCU(MyFile.PascalResolver,FPCUReaderStream);
SetPasModule(MyFile.PascalResolver.RootElement);
SetReaderState(prsCanContinue);
end;
function TFilerPCUSupport.FindPCU(const UseUnitName: string;
out aFormat: TPas2JSPrecompileFormat): string;
function SearchInDir(DirPath: string): boolean;
var
i: Integer;
CurFormat: TPas2JSPrecompileFormat;
Filename: String;
begin
if DirPath='' then exit(false);
DirPath:=IncludeTrailingPathDelimiter(DirPath);
for i:=0 to PrecompileFormats.Count-1 do
begin
CurFormat:=PrecompileFormats[i];
if not CurFormat.Enabled then continue;
Filename:=DirPath+UseUnitName+'.'+CurFormat.Ext;
if Compiler.FS.PCUExists(Filename) then
begin
FindPCU:=Filename;
aFormat:=CurFormat;
exit(true);
end;
end;
Result:=false;
end;
var
L: TstringList;
i: Integer;
begin
Result:='';
aFormat:=nil;
L:=TStringList.Create;
try
Compiler.FS.GetPCUDirs(L,MyFile.FileResolver.BaseDirectory);
for i:=0 to L.Count-1 do
if SearchInDir(L[i]) then exit;
finally
L.Free;
end;
end;
function TFilerPCUSupport.OnWriterIsElementUsed(Sender: TObject;
El: TPasElement): boolean;
begin
Result:=MyFile.UseAnalyzer.IsUsed(El);
end;
procedure TFilerPCUSupport.WritePCU;
Const
AllowCompressed =
{$IFDEF DisablePCUCompressed}false{$ELSE}true{$ENDIF};
var
Writer: TPCUWriter;
ms: TMemoryStream;
DestDir: String;
JS: TJSElement;
FN: String;
begin
if FPCUFormat=Nil then
exit; // Don't write
if MyFile.PasModule.ClassType<>TPasModule then
begin
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU not a unit: ',MyFile.PasFilename,' skip');
{$ENDIF}
exit;
end;
if (MyFile.PCUFilename<>'') or (FPCUReader<>nil) then
begin
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU already precompiled "',MyFile.PCUFilename,'" Reader=',GetObjName(FPCUReader));
{$ENDIF}
exit;
end;
// Determine output filename
FN:=ExtractFilenameOnly(MyFile.PasFilename)+'.'+FPCUFormat.Ext;
if Compiler.FS.UnitOutputPath<>'' then
FN:=Compiler.FS.UnitOutputPath+FN
else
FN:=ExtractFilePath(MyFile.PasFilename)+FN;
// Set as our filename
SetPCUFilename(FN);
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU precompiling ',MyFile.PCUFilename);
{$ENDIF}
JS:=nil;
ms:=TMemoryStream.Create;
Writer:=FPCUFormat.WriterClass.Create;
try
Writer.GUID:=Compiler.PrecompileGUID;
Writer.OnGetSrc:=@OnFilerGetSrc;
Writer.OnIsElementUsed:=@OnWriterIsElementUsed;
// create JavaScript for procs, initialization, finalization
MyFile.CreateConverter;
MyFile.Converter.Options:=MyFile.Converter.Options+[coStoreImplJS];
MyFile.Converter.OnIsElementUsed:=@OnPCUConverterIsElementUsed;
MyFile.Converter.OnIsTypeInfoUsed:=@OnPCUConverterIsTypeInfoUsed;
JS:=MyFile.Converter.ConvertPasElement(MyFile.PasModule,MyFile.PascalResolver);
MyFile.Converter.Options:=MyFile.Converter.Options-[coStoreImplJS];
MyFile.PCUSupport.SetInitialCompileFlags;
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU create pcu ... ',MyFile.PCUFilename);
{$ENDIF}
Writer.WritePCU(MyFile.PascalResolver,MyFile.Converter,
PrecompileInitialFlags,ms,AllowCompressed);
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU precompiled ',MyFile.PCUFilename);
{$ENDIF}
MyFile.Log.LogMsg(nWritingFile,[QuoteStr(Compiler.FS.FormatPath(MyFile.PCUFilename))],'',0,0,
not (coShowLineNumbers in Compiler.Options));
// check output directory
DestDir:=ChompPathDelim(ExtractFilePath(MyFile.PCUFilename));
if (DestDir<>'') and not Compiler.FS.DirectoryExists(DestDir) then
begin
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU output dir not found "',DestDir,'"');
{$ENDIF}
MyFile.Log.LogMsg(nOutputDirectoryNotFound,[QuoteStr(Compiler.FS.FormatPath(DestDir))]);
Compiler.Terminate(ExitCodeFileNotFound);
end;
if Compiler.FS.DirectoryExists(MyFile.PCUFilename) then
begin
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU file is folder "',DestDir,'"');
{$ENDIF}
MyFile.Log.LogMsg(nFileIsFolder,[QuoteStr(Compiler.FS.FormatPath(MyFile.PCUFilename))]);
Compiler.Terminate(ExitCodeWriteError);
end;
ms.Position:=0;
Compiler.FS.SaveToFile(ms,MyFile.PCUFilename);
{$IFDEF REALLYVERBOSE}
writeln('TPas2jsCompilerFile.WritePCU written ',MyFile.PCUFilename);
{$ENDIF}
finally
JS.Free;
Writer.Free;
ms.Free;
end;
end;
procedure TFilerPCUSupport.OnFilerGetSrc(Sender: TObject; aFilename: string;
out p: PChar; out Count: integer);
var
SrcFile: TPas2jsFile;
begin
if Sender=nil then
RaiseInternalError(20180311135558,aFilename);
SrcFile:=MyFile.Compiler.FS.LoadFile(aFilename);
if SrcFile=nil then
RaiseInternalError(20180311135329,aFilename);
p:=PChar(SrcFile.Source);
Count:=length(SrcFile.Source);
end;
function TFilerPCUSupport.OnPCUConverterIsElementUsed(Sender: TObject;
El: TPasElement): boolean;
begin
if (coKeepNotUsedPrivates in MyFile.Compiler.Options) then
Result:=true
else
Result:=MyFile.UseAnalyzer.IsUsed(El);
end;
function TFilerPCUSupport.OnPCUConverterIsTypeInfoUsed(Sender: TObject;
El: TPasElement): boolean;
begin
if Sender=nil then ;
if El=nil then ;
// PCU does not need precompiled typeinfo
Result:=false;
end;
{ TPas2jsPCUCompiler }
procedure TPas2jsPCUCompiler.WritePrecompiledFormats;
Var
I: Integer;
begin
if PrecompileFormats.Count>0 then
begin
if PrecompileFormats.Count>1 then
begin
writeHelpLine(' -JU<x>: Create precompiled units in format x.');
for i:=0 to PrecompileFormats.Count-1 do
with PrecompileFormats[i] do
writeHelpLine(' -JU'+Ext+': '+Description);
writeHelpLine(' -JU-: Disable prior -JU<x> option. Do not create precompiled units.');
end else
begin
with PrecompileFormats[0] do
writeHelpLine(' -JU'+Ext+': Create precompiled units using '+Description);
writeHelpLine(' -JU- : Disable prior -JU<x> option. Do not create precompiled units.');
end;
end;
end;
function TPas2jsPCUCompiler.CreateCompilerFile(const PasFileName,
PCUFilename: String): TPas2jsCompilerFile;
begin
Result:=TPas2JSPCUCompilerFile.Create(Self,PasFileName,PCUFilename);
end;
procedure TPas2jsPCUCompiler.HandleOptionPCUFormat(Value: string);
Var
Found: Boolean;
I: integer;
PF: TPas2JSPrecompileFormat;
begin
Found:=false;
for i:=0 to PrecompileFormats.Count-1 do
begin
PF:=PrecompileFormats[i];
if not SameText(Value,PF.Ext) then continue;
FPrecompileFormat:=PrecompileFormats[i];
Options:=Options+[coPrecompile];
Found:=true;
end;
if not Found then
ParamFatal('invalid precompile output format (-JU) "'+Value+'"');
end;
{ TPas2jsPCUCompilerFile }
function TPas2jsPCUCompilerFile.CreatePCUSupport: TPCUSupport;
Var
PF: TPas2JSPrecompileFormat;
begin
// Note that if no format was preset, no files will be written
PF:=(Compiler as TPas2jsPCUCompiler).FPrecompileFormat;
if (PF=nil) and (PrecompileFormats.Count>0) then
PF:=PrecompileFormats[0];
if PF<>Nil then
Result:=TFilerPCUSupport.Create(Self,PF)
else
Result:=Nil;
end;
{$ENDIF}
end.