Repository URL to install this package:
|
Version:
3.2.0 ▾
|
{
This file is part of the Free Component Library (FCL)
Copyright (c) 2018 Mattias Gaertner mattias@freepascal.org
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:
Logging to stdout or file.
Filtering messages by number and type.
Registering messages with number, pattern and type (error, warning, note, etc).
}
unit Pas2jsLogger;
{$mode objfpc}{$H+}
{$i pas2js_defines.inc}
interface
uses
{$IFDEF Pas2JS}
JS,
{$IFDEF NodeJS}
NodeJSFS,
{$ENDIF}
{$ENDIF}
pas2jsutils,
{$IFDEF HASFILESYSTEM}
pas2jsfileutils,
{$ENDIF}
Types, Classes, SysUtils,
PasTree, PScanner,
jstree, jsbase, jswriter, fpjson;
const
ExitCodeErrorInternal = 1; // internal error
ExitCodeErrorInParams = 2; // error in command line parameters
ExitCodeErrorInConfig = 3; // error in config file
ExitCodeFileNotFound = 4;
ExitCodeWriteError = 5;
ExitCodeSyntaxError = 6;
ExitCodeConverterError = 7;
ExitCodePCUError = 8;
ExitCodeToolError = 9;
const
DefaultLogMsgTypes = [mtFatal..mtDebug]; // by default show everything
type
{$IFDEF Pas2JS}
{ TPas2jsStream }
TPas2jsStream = class
public
procedure Write(const s: string); virtual; abstract;
end;
{ TPas2jsFileStream }
TPas2jsFileStream = class(TPas2JSStream)
public
constructor Create(Filename: string; Mode: cardinal);
destructor Destroy; override;
procedure Write(const s: string); override;
end;
const
fmCreate = $FF00;
fmOpenRead = 0;
//fmOpenWrite = 1;
//fmOpenReadWrite = 2;
{ Share modes}
//fmShareCompat = $0000;
//fmShareExclusive = $0010;
//fmShareDenyWrite = $0020;
//fmShareDenyRead = $0030;
fmShareDenyNone = $0040;
{$ELSE}
TPas2jsStream = TStream;
TPas2jsFileStream = TFileStream;
{$ENDIF}
type
{ TPas2jsMessage }
TPas2jsMessage = class
public
Number: integer;
Typ: TMessageType;
Pattern: string;
end;
TPas2jsLogEvent = Procedure (Sender : TObject; Const Msg : String) Of Object;
{ TConsoleFileWriter }
TConsoleFileWriter = Class(TTextWriter)
Public
Constructor Create(aFileName : String); reintroduce;
Function DoWrite(Const S : TJSWriterString) : Integer; override;
Procedure Flush;
end;
{ TPas2jsLogger }
TPas2jsLogger = class
private
FDebugLog: TPas2JSStream;
FEncoding: string;
FLastMsgCol: integer;
FLastMsgFile: string;
FLastMsgLine: integer;
FLastMsgNumber: integer;
FLastMsgTxt: string;
FLastMsgType: TMessageType;
FMsgNumberDisabled: TIntegerDynArray;// sorted ascending
FMsg: TFPList; // list of TPas2jsMessage
FOnFormatPath: TPScannerFormatPathEvent;
FOnLog: TPas2jsLogEvent;
FOutputFile: TTextWriter; // TFileWriter;
FOutputFilename: string;
FShowMsgNumbers: boolean;
FShowMsgTypes: TMessageTypes;
FSorted: boolean;
{$IFDEF HasStdErr}
FWriteMsgToStdErr: boolean;
{$ENDIF}
function GetMsgCount: integer;
function GetMsgNumberDisabled(MsgNumber: integer): boolean;
function GetMsgs(Index: integer): TPas2jsMessage; inline;
function FindMsgNumberDisabled(MsgNumber: integer; FindInsertPos: boolean): integer;
procedure SetEncoding(const AValue: string);
procedure SetMsgNumberDisabled(MsgNumber: integer; AValue: boolean);
procedure SetOutputFilename(AValue: string);
procedure SetSorted(AValue: boolean);
procedure DoLogRaw(const Msg: string; SkipEncoding : Boolean);
Protected
// so it can be overridden
function CreateTextWriter(const aFileName: string): TTextWriter; virtual;
public
{$IFDEF EnableLogFile}
LogFile: TStringList;
procedure LogF(args: array of const);
{$ENDIF}
constructor Create;
destructor Destroy; override;
procedure RegisterMsg(MsgType: TMessageType; MsgNumber: integer; Pattern: string);
function FindMsg(MsgNumber: integer; ExceptionOnNotFound: boolean): TPas2jsMessage;
procedure Sort;
procedure LogRaw(const Msg: string); overload;
procedure LogRaw(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}); overload;
procedure LogLn;
procedure LogPlain(const Msg: string); overload;
procedure LogPlain(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}); overload;
procedure LogMsg(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF};
const Filename: string = ''; Line: integer = 0; Col: integer = 0;
UseFilter: boolean = true);
procedure Log(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
const Filename: string = ''; Line: integer = 0; Col: integer = 0;
UseFilter: boolean = true);
procedure LogMsgIgnoreFilter(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
procedure LogExceptionBackTrace(E: Exception);
function MsgTypeToStr(MsgType: TMessageType): string;
function GetMsgText(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
function FormatMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
const Filename: string = ''; Line: integer = 0; Col: integer = 0): string;
function FormatJSONMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer = 0;
const Filename: string = ''; Line: integer = 0; Col: integer = 0): string;
procedure OpenOutputFile;
procedure Flush;
procedure CloseOutputFile;
procedure Reset;
procedure ClearLastMsg;
procedure OpenDebugLog;
procedure CloseDebugLog;
procedure DebugLogWriteLn(Msg: string); overload;
function GetEncodingCaption: string;
class function Concatenate(Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
public
property Encoding: string read FEncoding write SetEncoding; // normalized
property MsgCount: integer read GetMsgCount;
property Msgs[Index: integer]: TPas2jsMessage read GetMsgs;
property MsgNumberDisabled[MsgNumber: integer]: boolean read GetMsgNumberDisabled write SetMsgNumberDisabled;
property OnFormatPath: TPScannerFormatPathEvent read FOnFormatPath write FOnFormatPath;
property OutputFilename: string read FOutputFilename write SetOutputFilename;
property ShowMsgNumbers: boolean read FShowMsgNumbers write FShowMsgNumbers;
property ShowMsgTypes: TMessageTypes read FShowMsgTypes write FShowMsgTypes;
{$IFDEF HasStdErr}
property WriteMsgToStdErr: boolean read FWriteMsgToStdErr write FWriteMsgToStdErr;
{$ENDIF}
property Sorted: boolean read FSorted write SetSorted;
property OnLog: TPas2jsLogEvent read FOnLog write FOnLog;
property LastMsgType: TMessageType read FLastMsgType write FLastMsgType;
property LastMsgFile: string read FLastMsgFile write FLastMsgFile;
property LastMsgLine: integer read FLastMsgLine write FLastMsgLine;
property LastMsgCol: integer read FLastMsgCol write FLastMsgCol;
property LastMsgTxt: string read FLastMsgTxt write FLastMsgTxt;
property LastMsgNumber: integer read FLastMsgNumber write FLastMsgNumber;
property DebugLog: TPas2jsStream read FDebugLog write FDebugLog;
end;
function CompareP2JMessage(Item1, Item2: {$IFDEF Pas2JS}JSValue{$ELSE}Pointer{$ENDIF}): Integer;
function QuoteStr(const s: string; Quote: char = '"'): string;
function DeQuoteStr(const s: string; Quote: char = '"'): string;
function AsString(Element: TPasElement; Full: boolean = true): string; overload;
function AsString(Element: TJSElement): string; overload;
function DbgString(Element: TJSElement; Indent: integer): string; overload;
function DbgAsString(Element: TJSValue; Indent: integer): string; overload;
function DbgAsString(Element: TJSArrayLiteralElements; Indent: integer): string; overload;
function DbgAsString(Element: TJSObjectLiteralElements; Indent: integer): string; overload;
function DbgAsString(Element: TJSObjectLiteralElement; Indent: integer): string; overload;
{$IFDEF UsePChar}
function DbgHexMem(p: Pointer; Count: integer): string;
{$ENDIF}
function DbgStr(const s: string): string;
implementation
function CompareP2JMessage(Item1, Item2: {$IFDEF Pas2JS}JSValue{$ELSE}Pointer{$ENDIF}): Integer;
var
Msg1: TPas2jsMessage absolute Item1;
Msg2: TPas2jsMessage absolute Item2;
begin
Result:=Msg1.Number-Msg2.Number;
end;
function QuoteStr(const s: string; Quote: char): string;
begin
Result:={$IFDEF Pas2JS}SysUtils.QuotedStr{$ELSE}AnsiQuotedStr{$ENDIF}(S,Quote);
end;
function DeQuoteStr(const s: string; Quote: char): string;
begin
Result:={$IFDEF Pas2JS}SysUtils.DeQuoteString{$ELSE}AnsiDequotedStr{$ENDIF}(S,Quote);
end;
function AsString(Element: TPasElement; Full: boolean): string;
begin
if Element=nil then
Result:='(no element)'
else begin
Result:=Element.GetDeclaration(Full);
end;
end;
function AsString(Element: TJSElement): string;
var
aTextWriter: TBufferWriter;
aWriter: TJSWriter;
begin
aTextWriter:=TBufferWriter.Create(120);
aWriter:=TJSWriter.Create(aTextWriter);
aWriter.WriteJS(Element);
Result:=aTextWriter.AsString;
aWriter.Free;
aTextWriter.Free;
end;
function DbgString(Element: TJSElement; Indent: integer): string;
begin
if Element=nil then
Result:='(*no element*)'
else if Element is TJSLiteral then
begin
Result:=DbgAsString(TJSLiteral(Element).Value,Indent+2);
end else if Element is TJSPrimaryExpressionIdent then
begin
Result:=String(TJSPrimaryExpressionIdent(Element).Name);
// array literal
end else if Element is TJSArrayLiteral then
begin
Result:='['+DbgAsString(TJSArrayLiteral(Element).Elements,Indent+2)+']';
// object literal
end else if Element is TJSObjectLiteral then
begin
Result:='['+DbgAsString(TJSObjectLiteral(Element).Elements,Indent+2)+']';
// arguments
end else if Element is TJSArguments then
begin
Result:='('+DbgAsString(TJSArguments(Element).Elements,Indent+2)+')';
// member
end else if Element is TJSMemberExpression then
begin
Result:='('+DbgString(TJSMemberExpression(Element).MExpr,Indent+2)+')';
// ToDo: TJSNewMemberExpression
// ToDo: TJSDotMemberExpression
// ToDo: TJSBracketMemberExpression
// call
end else if Element is TJSCallExpression then
begin
Result:=DbgString(TJSCallExpression(Element).Expr,Indent+2)
+DbgString(TJSCallExpression(Element).Args,Indent+2);
// unary
end else if Element is TJSUnary then
begin
Result:=TJSUnary(Element).PrefixOperator
+DbgString(TJSUnary(Element).A,Indent+2)
+TJSUnary(Element).PostFixOperator;
// binary
end else if Element is TJSBinary then
begin
if Element is TJSStatementList then
begin
Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2)+';'+LineEnding
+StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent);
end else if Element is TJSVariableDeclarationList then
begin
Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2)+';'+LineEnding
+StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent);
end else if Element is TJSWithStatement then
begin
Result:='with ('+DbgString(TJSBinaryExpression(Element).A,Indent+2)+'){'+LineEnding
+StringOfChar(' ',Indent)+DbgString(TJSBinaryExpression(Element).B,Indent+2)+LineEnding
+StringOfChar(' ',Indent)+'}';
end else if Element is TJSBinaryExpression then
begin
Result:=DbgString(TJSBinaryExpression(Element).A,Indent+2);
if TJSBinaryExpression(Element).AllowCompact then
Result+=TJSBinaryExpression(Element).OperatorString
else
Result+=' '+TJSBinaryExpression(Element).OperatorString+' ';
Result+=DbgString(TJSBinaryExpression(Element).B,Indent+2);
end else begin
Result:='{: unknown binary Element: '+Element.Classname+':}';
end;
// ? :
end else if Element is TJSConditionalExpression then
begin
Result:=DbgString(TJSConditionalExpression(Element).A,Indent+2)
+'?'+DbgString(TJSConditionalExpression(Element).B,Indent+2)
+':'+DbgString(TJSConditionalExpression(Element).C,Indent+2);
// assignment
end else if Element is TJSAssignStatement then
begin
Result:=DbgString(TJSAssignStatement(Element).LHS,Indent+2)
+TJSAssignStatement(Element).OperatorString
+DbgString(TJSAssignStatement(Element).Expr,Indent+2);
// var
end else if Element is TJSVarDeclaration then
begin
Result:=TJSVarDeclaration(Element).Name;
if TJSVarDeclaration(Element).Init<>nil then
Result+='='+DbgString(TJSVarDeclaration(Element).Init,Indent+2);
// if(){} else {}
end else if Element is TJSIfStatement then
begin
Result:='if('+DbgString(TJSIfStatement(Element).Cond,Indent+2)+'){'+LineEnding
+StringOfChar(' ',Indent+2)+DbgString(TJSIfStatement(Element).BTrue,Indent+2)+LineEnding
+StringOfChar(' ',Indent);
if TJSIfStatement(Element).BFalse<>nil then
Result+=' else {'+LineEnding
+StringOfChar(' ',Indent+2)+DbgString(TJSIfStatement(Element).BFalse,Indent+2)+LineEnding
+StringOfChar(' ',Indent)+'}';
// body
end else if Element is TJSBodyStatement then
begin
// while(){}
if Element is TJSWhileStatement then
begin
Result:='while('+DbgString(TJSWhileStatement(Element).Cond,Indent+2)+')';
if TJSWhileStatement(Element).Body<>nil then
Result+=DbgString(TJSWhileStatement(Element).Body,Indent)
else
Result+='{}';
// do{}while()
end else if Element is TJSDoWhileStatement then
begin
Result:='do';
if TJSDoWhileStatement(Element).Body<>nil then
Result+=DbgString(TJSDoWhileStatement(Element).Body,Indent)
else
Result+='{}';
Result+='('+DbgString(TJSDoWhileStatement(Element).Cond,Indent+2)+')';
// for(Init;Incr;Cond)Body
end else if Element is TJSForStatement then
begin
Result:='for(';
if TJSForStatement(Element).Init<>nil then
Result+=DbgString(TJSForStatement(Element).Init,Indent+2);
Result+=';';
if TJSForStatement(Element).Cond<>nil then
Result+=DbgString(TJSForStatement(Element).Cond,Indent+2);
Result+=';';
if TJSForStatement(Element).Incr<>nil then
Result+=DbgString(TJSForStatement(Element).Incr,Indent+2);
Result+=')';
if TJSForStatement(Element).Body<>nil then
Result+=DbgString(TJSForStatement(Element).Body,Indent)
else
Result+='{}';
// {}
end else begin
if TJSBodyStatement(Element).Body<>nil then
Result+='{'+LineEnding
+StringOfChar(' ',Indent+2)+DbgString(TJSBodyStatement(Element).Body,Indent+2)+LineEnding
+StringOfChar(' ',Indent)+'}'
else
Result+='{}';
end;
end else begin
Result:='{: unknown Element: '+Element.Classname+':}';
end;
end;
function DbgAsString(Element: TJSValue; Indent: integer): string;
begin
if Element=nil then
Result:='(no value)'
else begin
case Element.ValueType of
jstUNDEFINED: Result:='undefined';
jstNull: Result:='null';
jstBoolean: Result:=BoolToStr(Element.AsBoolean,'true','false');
jstNumber: str(Element.AsNumber,Result);
jstString: Result:=QuoteStr(String(Element.AsString),'''');
jstObject: Result:='{:OBJECT:}';
jstReference: Result:='{:REFERENCE:}';
JSTCompletion: Result:='{:COMPLETION:}';
else Result:='{:Unknown ValueType '+IntToStr(ord(Element.ValueType))+':}';
end;
end;
Result:=StringOfChar(' ',Indent)+Result;
end;
function DbgAsString(Element: TJSArrayLiteralElements; Indent: integer): string;
var
i: Integer;
begin
Result:='';
for i:=0 to TJSArrayLiteralElements(Element).Count-1 do begin
if i>0 then Result+=',';
Result+=DbgString(TJSArrayLiteralElements(Element).Elements[i].Expr,Indent+2);
end;
end;
function DbgAsString(Element: TJSObjectLiteralElements; Indent: integer): string;
var
i: Integer;
begin
Result:='';
for i:=0 to TJSObjectLiteralElements(Element).Count-1 do begin
if i>0 then Result+=',';
Result+=DbgString(TJSObjectLiteralElements(Element).Elements[i].Expr,Indent+2);
end;
end;
function DbgAsString(Element: TJSObjectLiteralElement; Indent: integer): string;
begin
Result:=String(TJSObjectLiteralElement(Element).Name)
+':'+DbgString(TJSObjectLiteralElement(Element).Expr,Indent+2);
end;
{$IFDEF UsePChar}
function DbgHexMem(p: Pointer; Count: integer): string;
var
i: Integer;
begin
Result:='';
for i:=0 to Count-1 do
Result:=Result+HexStr(ord(PChar(p)[i]),2);
end;
{$ENDIF}
function DbgStr(const s: string): string;
var
i: Integer;
c: Char;
begin
Result:='';
for i:=1 to length(s) do begin
c:=s[i];
case c of
#0..#31,#127..#255: Result+='$'+HexStr(ord(c),2);
else Result+=c;
end;
end;
end;
{ TConsoleFileWriter }
constructor TConsoleFileWriter.Create(aFileName: String);
begin
Inherited Create;
Write('Opening console log: '+aFileName);
end;
Function TConsoleFileWriter.DoWrite(Const S : TJSWriterString) : Integer;
begin
Result:=Length(S);
{AllowWriteln}
Writeln(S);
{AllowWriteln-}
end;
procedure TConsoleFileWriter.FLush;
begin
end;
{$IFDEF Pas2JS}
{ TPas2jsFileStream }
constructor TPas2jsFileStream.Create(Filename: string; Mode: cardinal);
begin
{AllowWriteln}
writeln('TPas2JSFileStream.Create TODO ',Filename,' Mode=',Mode);
{AllowWriteln-}
raise Exception.Create('TPas2JSFileStream.Create');
end;
destructor TPas2jsFileStream.Destroy;
begin
{AllowWriteln}
writeln('TPas2JSFileStream.Destroy TODO');
{AllowWriteln-}
raise Exception.Create('TPas2JSFileStream.Destroy');
inherited Destroy;
end;
procedure TPas2jsFileStream.Write(const s: string);
begin
{AllowWriteln}
writeln('TPas2JSFileStream.Write TODO s="',s,'"');
{AllowWriteln-}
raise Exception.Create('TPas2JSFileStream.Write');
end;
{$ENDIF}
{ TPas2jsLogger }
function TPas2jsLogger.GetMsgs(Index: integer): TPas2jsMessage;
begin
Result:=TPas2jsMessage(FMsg[Index]);
end;
function TPas2jsLogger.FindMsgNumberDisabled(MsgNumber: integer;
FindInsertPos: boolean): integer;
var
l, r, m, CurMsgNumber: Integer;
begin
l:=0;
r:=length(FMsgNumberDisabled)-1;
m:=0;
while l<=r do begin
m:=(l+r) div 2;
CurMsgNumber:=FMsgNumberDisabled[m];
if MsgNumber<CurMsgNumber then
r:=m-1
else if MsgNumber>CurMsgNumber then
l:=m+1
else
exit(m);
end;
if FindInsertPos then
begin
Result:=m;
if l>m then inc(Result);
end else begin
Result:=-1;
end;
end;
procedure TPas2jsLogger.SetEncoding(const AValue: string);
var
NewValue: String;
begin
{$IFDEF Pas2JS}
NewValue:=Trim(lowercase(AValue));
{$ELSE}
NewValue:=NormalizeEncoding(AValue);
{$ENDIF}
if FEncoding=NewValue then Exit;
//LogPlain(ClassName+': Encoding changed from "'+FEncoding+'" to "'+NewValue+'"');
FEncoding:=NewValue;
end;
function TPas2jsLogger.GetMsgNumberDisabled(MsgNumber: integer): boolean;
begin
Result:=FindMsgNumberDisabled(MsgNumber,false)>=0;
end;
procedure TPas2jsLogger.SetMsgNumberDisabled(MsgNumber: integer; AValue: boolean
);
{$IF defined(FPC) and (FPC_FULLVERSION<30101)}
procedure Delete(var A: TIntegerDynArray; Index, Count: integer); overload;
var
i: Integer;
begin
for i:=Index+Count to length(A)-1 do
A[i-Count]:=A[i];
SetLength(A,length(A)-Count);
end;
procedure Insert(Item: integer; var A: TIntegerDynArray; Index: integer); overload;
var
i: Integer;
begin
SetLength(A,length(A)+1);
for i:=length(A)-1 downto Index+1 do
A[i]:=A[i-1];
A[Index]:=Item;
end;
{$ENDIF}
var
InsertPos, OldCount: Integer;
begin
OldCount:=length(FMsgNumberDisabled);
if AValue then
begin
// enable
InsertPos:=FindMsgNumberDisabled(MsgNumber,true);
if (InsertPos<OldCount) and (FMsgNumberDisabled[InsertPos]=MsgNumber) then
exit; // already disabled
// insert into array
Insert(MsgNumber,FMsgNumberDisabled,InsertPos);
end else begin
// disable
InsertPos:=FindMsgNumberDisabled(MsgNumber,false);
if InsertPos<0 then exit;
// delete from array
Delete(FMsgNumberDisabled,InsertPos,1);
end;
end;
procedure TPas2jsLogger.SetOutputFilename(AValue: string);
begin
if FOutputFilename=AValue then Exit;
CloseOutputFile;
FOutputFilename:=AValue;
if OutputFilename<>'' then
OpenOutputFile;
end;
procedure TPas2jsLogger.SetSorted(AValue: boolean);
begin
if FSorted=AValue then Exit;
FSorted:=AValue;
if FSorted then Sort;
end;
procedure TPas2jsLogger.DoLogRaw(const Msg: string; SkipEncoding : Boolean);
var
S: String;
begin
if SkipEncoding then
S:=Msg
else begin
{$IFDEF FPC_HAS_CPSTRING}
if (Encoding='utf8') or (Encoding='json') then
S:=Msg
else if Encoding='console' then
S:=UTF8ToConsole(Msg)
else if Encoding='system' then
S:=UTF8ToSystemCP(Msg)
else begin
// default: write UTF-8 to outputfile and console codepage to console
if FOutputFile=nil then
S:=UTF8ToConsole(Msg);
end;
{$ELSE}
S:=Msg;
{$ENDIF}
end;
//writeln('TPas2jsLogger.LogPlain "',Encoding,'" "',DbgStr(S),'"');
if DebugLog<>nil then
DebugLogWriteLn(S);
if FOnLog<>Nil then
FOnLog(Self,S)
else if FOutputFile<>nil then
FOutputFile.Write(S+LineEnding)
else begin
{$IFDEF FPC_HAS_CPSTRING}
// prevent codepage conversion magic
SetCodePage(RawByteString(S), CP_OEMCP, False);
{$ENDIF}
{AllowWriteln}
{$IFDEF HasStdErr}
if WriteMsgToStdErr then
writeln(StdErr,S)
else
{$ENDIF}
writeln(S);
{AllowWriteln-}
end;
end;
constructor TPas2jsLogger.Create;
begin
FMsg:=TFPList.Create;
FShowMsgTypes:=DefaultLogMsgTypes;
end;
destructor TPas2jsLogger.Destroy;
var
i: Integer;
begin
CloseOutputFile;
CloseDebugLog;
for i:=0 to FMsg.Count-1 do
TObject(FMsg[i]).{$IFDEF Pas2JS}Destroy{$ELSE}Free{$ENDIF};
FreeAndNil(FMsg);
FMsgNumberDisabled:=nil;
inherited Destroy;
end;
procedure TPas2jsLogger.RegisterMsg(MsgType: TMessageType; MsgNumber: integer;
Pattern: string);
var
Msg: TPas2jsMessage;
begin
if MsgNumber=0 then
raise Exception.Create('internal error: TPas2jsLogger.RegisterMsg MsgNumber=0');
Msg:=TPas2jsMessage.Create;
Msg.Number:=MsgNumber;
Msg.Typ:=MsgType;
Msg.Pattern:=Pattern;
FMsg.Add(Msg);
FSorted:=false;
end;
function TPas2jsLogger.GetMsgCount: integer;
begin
Result:=FMsg.Count;
end;
function TPas2jsLogger.FindMsg(MsgNumber: integer; ExceptionOnNotFound: boolean
): TPas2jsMessage;
var
l, r, m: Integer;
Msg: TPas2jsMessage;
begin
if not FSorted then Sort;
l:=0;
r:=GetMsgCount-1;
while l<=r do begin
m:=(l+r) div 2;
Msg:=Msgs[m];
if MsgNumber<Msg.Number then
r:=m-1
else if MsgNumber>Msg.Number then
l:=m+1
else
exit(Msg);
end;
Result:=nil;
if ExceptionOnNotFound then
raise Exception.Create('invalid message number '+IntToStr(MsgNumber));
end;
procedure TPas2jsLogger.Sort;
var
i: Integer;
LastMsg, CurMsg: TPas2jsMessage;
begin
if FMsg.Count>1 then
begin;
FMsg.Sort(@CompareP2JMessage);
// check for duplicates
LastMsg:=TPas2jsMessage(FMsg[0]);
for i:=1 to FMsg.Count-1 do begin
CurMsg:=TPas2jsMessage(FMsg[i]);
if LastMsg.Number=CurMsg.Number then
raise Exception.Create('duplicate message number '+IntToStr(CurMsg.Number)+'. 1st="'+LastMsg.Pattern+'" 2nd="'+CurMsg.Pattern+'"');
LastMsg:=CurMsg;
end;
end;
FSorted:=true;
end;
function TPas2jsLogger.GetMsgText(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
var
Msg: TPas2jsMessage;
begin
Msg:=FindMsg(MsgNumber,true);
Result:=MsgTypeToStr(Msg.Typ)+': '+Format(Msg.Pattern,Args);
end;
procedure TPas2jsLogger.LogRaw(const Msg: string);
begin
ClearLastMsg;
DoLogRaw(Msg,False);
end;
procedure TPas2jsLogger.LogRaw(
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
begin
LogRaw(Concatenate(Args));
end;
procedure TPas2jsLogger.LogLn;
begin
LogRaw('');
end;
procedure TPas2jsLogger.DebugLogWriteLn(Msg: string);
begin
if FDebugLog=nil then exit;
Msg:=Msg+LineEnding;
{$IFDEF Pas2JS}
FDebugLog.Write(Msg);
{$ELSE}
FDebugLog.Write(Msg[1],length(Msg));
{$ENDIF}
end;
function TPas2jsLogger.GetEncodingCaption: string;
begin
Result:=Encoding;
if Result='' then
begin
{$IFDEF FPC_HAS_CPSTRING}
if FOutputFile=nil then
Result:='console'
else
{$ENDIF}
Result:='utf-8';
end;
if Result='console' then
begin
{$IFDEF Unix}
if not IsNonUTF8System then
Result:='utf-8';
{$ENDIF}
end;
if Result='utf8' then
Result:='utf-8';
end;
class function TPas2jsLogger.Concatenate(
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF}): string;
var
s: String;
i: Integer;
{$IFDEF Pas2JS}
V: JSValue;
{$ELSE}
V: TVarRec;
{$ENDIF}
begin
s:='';
for i:=Low(Args) to High(Args) do
begin
V:=Args[i];
{$IFDEF Pas2JS}
case jsTypeOf(V) of
'boolean':
if V then s+='true' else s+='false';
'number':
if isInteger(V) then
s+=str(NativeInt(V))
else
s+=str(Double(V));
'string':
s+=String(V);
else continue;
end;
{$ELSE}
case V.VType of
vtInteger: s += IntToStr(V.VInteger);
vtBoolean: s += BoolToStr(V.VBoolean);
vtChar: s += V.VChar;
{$ifndef FPUNONE}
vtExtended: ; // V.VExtended^;
{$ENDIF}
vtString: s += V.VString^;
vtPointer: ; // V.VPointer;
vtPChar: s += V.VPChar;
vtObject: ; // V.VObject;
vtClass: ; // V.VClass;
vtWideChar: s += AnsiString(V.VWideChar);
vtPWideChar: s += AnsiString(V.VPWideChar);
vtAnsiString: s += AnsiString(V.VAnsiString);
vtCurrency: ; // V.VCurrency^);
vtVariant: ; // V.VVariant^);
vtInterface: ; // V.VInterface^);
vtWidestring: s += AnsiString(WideString(V.VWideString));
vtInt64: s += IntToStr(V.VInt64^);
vtQWord: s += IntToStr(V.VQWord^);
vtUnicodeString:s += AnsiString(UnicodeString(V.VUnicodeString));
end;
{$ENDIF}
end;
Result:=s;
end;
procedure TPas2jsLogger.LogPlain(const Msg: string);
var
s: String;
begin
ClearLastMsg;
if Encoding='json' then
begin
s:=FormatJSONMsg(mtInfo,Msg,0,'',0,0);
DoLogRaw(s,True);
end else
DoLogRaw(Msg,False);
end;
procedure TPas2jsLogger.LogPlain(
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
begin
LogPlain(Concatenate(Args));
end;
procedure TPas2jsLogger.LogMsg(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF};
const Filename: string; Line: integer; Col: integer; UseFilter: boolean);
var
Msg: TPas2jsMessage;
begin
Msg:=FindMsg(MsgNumber,true);
Log(Msg.Typ,SafeFormat(Msg.Pattern,Args),MsgNumber,Filename,Line,Col,UseFilter);
end;
procedure TPas2jsLogger.Log(MsgType: TMessageType; Msg: string;
MsgNumber: integer; const Filename: string; Line: integer; Col: integer;
UseFilter: boolean);
var
s: String;
begin
if UseFilter and not (MsgType in FShowMsgTypes) then exit;
if MsgNumberDisabled[MsgNumber] then exit;
if encoding='json' then
s:=FormatJSONMsg(MsgType,Msg,MsgNumber,Filename,Line,Col)
else
s:=FormatMsg(MsgType,Msg,MsgNumber,Filename,Line,Col);
FLastMsgType:=MsgType;
FLastMsgNumber:=MsgNumber;
FLastMsgTxt:=Msg;
FLastMsgFile:=Filename;
FLastMsgLine:=Line;
FLastMsgCol:=Col;
DoLogRaw(s,False);
end;
procedure TPas2jsLogger.LogMsgIgnoreFilter(MsgNumber: integer;
Args: array of {$IFDEF Pas2JS}jsvalue{$ELSE}const{$ENDIF});
begin
LogMsg(MsgNumber,Args,'',0,0,false);
end;
procedure TPas2jsLogger.LogExceptionBackTrace(E: Exception);
{$IFDEF Pas2js}
begin
{$IFDEF NodeJS}
if (E<>nil) and (E.NodeJSError<>nil) then
{AllowWriteln}
writeln(E.NodeJSError.Stack);
{AllowWriteln-}
{$ENDIF}
end;
{$ELSE}
var
lErrorAddr: CodePointer;
FrameCount: LongInt;
Frames: PCodePointer;
FrameNumber: Integer;
begin
lErrorAddr:=ExceptAddr;
FrameCount:=ExceptFrameCount;
Frames:=ExceptFrames;
Log(mtDebug,BackTraceStrFunc(lErrorAddr));
for FrameNumber := 0 to FrameCount-1 do
Log(mtDebug,BackTraceStrFunc(Frames[FrameNumber]));
if E=nil then ;
end;
{$ENDIF}
function TPas2jsLogger.MsgTypeToStr(MsgType: TMessageType): string;
begin
case MsgType of
mtFatal: Result:='Fatal';
mtError: Result:='Error';
mtWarning: Result:='Warning';
mtNote: Result:='Note';
mtHint: Result:='Hint';
mtInfo: Result:='Info';
mtDebug: Result:='Debug';
else Result:='Verbose';
end;
end;
function TPas2jsLogger.FormatMsg(MsgType: TMessageType; Msg: string;
MsgNumber: integer; const Filename: string; Line: integer; Col: integer
): string;
// e.g. file(line,col) type: (number) msg
var
s: String;
begin
s:='';
if Filename<>'' then
begin
if Assigned(OnFormatPath) then
s+=OnFormatPath(Filename)
else
s+=Filename;
if Line>0 then
begin
s+='('+IntToStr(Line);
if Col>0 then s+=','+IntToStr(Col);
s+=')';
end;
if s<>'' then s+=' ';
end;
s+=MsgTypeToStr(MsgType)+': ';
if ShowMsgNumbers and (MsgNumber<>0) then
s+='('+IntToStr(MsgNumber)+') ';
s+=Msg;
Result:=s;
end;
function TPas2jsLogger.FormatJSONMsg(MsgType: TMessageType; Msg: string; MsgNumber: integer; const Filename: string; Line: integer;
Col: integer): string;
Var
J : TJSONObject;
FN : String;
begin
if Assigned(OnFormatPath) then
FN:=OnFormatPath(Filename)
else
FN:=Filename;
J:=TJSONObject.Create([
'message',Msg,
'line',Line,
'col',Col,
'number',MsgNumber,
'filename',FN,
'type',MsgTypeToStr(MsgType)
]);
try
Result:=J.AsJSON;
finally
J.Free;
end;
end;
function TPas2jsLogger.CreateTextWriter(const aFileName: string): TTextWriter;
begin
{$IFDEF HASFILESYSTEM}
Result:=TFileWriter.Create(aFilename);
{$ELSE}
Result:=TConsoleFileWriter.Create(aFileName);
{$ENDIF}
end;
{$IFDEF EnableLogFile}
procedure TPas2jsLogger.LogF(args: array of const);
begin
if LogFile=nil then
LogFile:=TStringList.Create;
LogFile.Add(TPas2jsLogger.Concatenate(args));
LogFile.SaveToFile('c:\tmp\libpas2jsparams.txt');
end;
{$ENDIF}
procedure TPas2jsLogger.OpenOutputFile;
begin
{$IFDEF HASFILESYSTEM}
if FOutputFile<>nil then exit;
if OutputFilename='' then
raise Exception.Create('Log has empty OutputFilename');
if DirectoryExists(OutputFilename) then
raise Exception.Create('Log is directory: "'+OutputFilename+'"');
{$ENDIF}
FOutputFile:=CreateTextWriter(OutputFileName);
{$IFDEF FPC_HAS_CPSTRING}
if (Encoding='') or (Encoding='utf8') then
FOutputFile.Write(UTF8BOM);
{$ENDIF}
end;
procedure TPas2jsLogger.Flush;
begin
{$IFDEF HASFILESYSTEM}
if Assigned(FOutputFile) and (FoutputFile is TFileWriter) then
TFileWriter(FOutputFile).Flush;
{$ENDIF}
end;
procedure TPas2jsLogger.CloseOutputFile;
begin
if FOutputFile=nil then exit;
Flush;
FreeAndNil(FOutputFile);
end;
procedure TPas2jsLogger.Reset;
begin
OutputFilename:='';
FMsgNumberDisabled:=nil;
ShowMsgNumbers:=false;
FShowMsgTypes:=DefaultLogMsgTypes;
end;
procedure TPas2jsLogger.ClearLastMsg;
begin
FLastMsgType:=mtInfo;
FLastMsgNumber:=0;
FLastMsgTxt:='';
FLastMsgFile:='';
FLastMsgLine:=0;
FLastMsgCol:=0;
end;
procedure TPas2jsLogger.OpenDebugLog;
const
DbgLogFilename = 'pas2jsdebug.log';
begin
FDebugLog:=TPas2jsFileStream.Create(DbgLogFilename,fmCreate or fmShareDenyNone);
end;
procedure TPas2jsLogger.CloseDebugLog;
begin
FreeAndNil(FDebugLog);
end;
end.